Create ISO 8601 date String with GWT - java

Here's my code:
public static String getStringFormat(Date inputDate, String timeZone){
String strFormat = null;
try{
final TimeZone computedTimeZone = TimeZone.createTimeZone(TimeZoneInfo.buildTimeZoneData(timeZone));
DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.ISO_8601);
strFormat = dateTimeFormat.format(inputDate, computedTimeZone);
Date d = new Date(strFormat);
strFormat = dateTimeFormat.format(d, TimeZone.createTimeZone(0));
String[] s = strFormat.split("\\+");
strFormat = s[0];
}catch(Exception e){
Console.log(e.getMessage());
}
return strFormat;
}
For input, new Date() and Etc/GMT+3 this function returns null. What could be wrong?
Error
Error: NullPointerException: undefined
at NBF_g$.QBF_g$ [as createError_0_g$] (NullPointerException.java:40)
at NBF_g$.ub_g$ [as initializeBackingError_0_g$] (Throwable.java:113)
at NBF_g$.bb_g$ (Throwable.java:61)
at NBF_g$.Ib_g$ (Exception.java:25)
at NBF_g$.avq_g$ (RuntimeException.java:25)
at NBF_g$.gfs_g$ (JsException.java:34)
at new NBF_g$ (NullPointerException.java:27)
at new wou_g$ (JSONString.java:43)

The method TimeZoneInfo.buildTimeZoneData(String tzJSON) doesn't accept the name of the zone, but needs a JSON string full of the details of how that zone works. It turns out that the browser doesn't come to you with all of the details of how all time zones work, so your app has to already be prepared to handle them.
GWT ships with all of the timezones (though they are currently a little out of date, and should be updated in this next release), but you have to tell the compiler which ones you want, or it will compile them out. The full list of all possible timezones and their offsets, etc is not small, so I would encourage you to limit the list.
These are stored in the constants interface TimeZoneConstants. Here is how you might use it:
TimeZoneConstants constants = GWT.create(TimeZoneConstants.class);
// This is the shorthand for TimeZone.createTimeZone(TimeZoneInfo.buildTimeZoneData(...))
TimeZone computedTimeZone = TimeZone.createTimeZone(constants.americaAdak());
//...
If you want to use the timezone string instead, say, passed from the server, you could build a map of the possible timezones that are supported. Be aware though that the full map is very large (200KB just for the timezones in the "America/..." group).
computedTimeZone = TimeZone.createTimeZone(constants.americaAdak());
zones.put(computedTimeZone.getID(), computedTimeZone);
computedTimeZone = TimeZone.createTimeZone(constants.americaAnchorage());
zones.put(computedTimeZone.getID(), computedTimeZone);
computedTimeZone = TimeZone.createTimeZone(constants.americaAnguilla());
zones.put(computedTimeZone.getID(), computedTimeZone);
//...
Then you can read out a specific item from the map as needed:
String tzName = Window.prompt("Enter a timezone name", "America/Chicago");
DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.ISO_8601);
String strFormat = dateTimeFormat.format(inputDate, zones.get(tzName));
//...
In your comment, you clarified the question, that you only need to deal with offsets, not the full TimeZone string format, i.e. Etc/GMT+3, meaning "Offset of +3 hours from GMT". This is easier to handle - simply parse out the +3 into a number, and use the TimeZone.createTimeZone(int timeZoneOffsetInMinutes) method. This will not understand daylight savings time, but that wouldn't be possible without the full name of the timezone or list of offsets, etc (which gets to why that JSON is so large).
//TODO, implement parse(), don't forget about negative numbers
int offsetInHours = parse(timeZone);
TimeZone computedTimeZone = TimeZone.createTimeZone(60 * offsetInHours);
//...

Related

Java: Formatting a Date inside of a string

I know I'm probably doing something wrong, but I am trying to format a Date that is currently stored inside of a string but it won't let me parse it to a String because it doesn't recognize it as a Date (because it's in a String variable) and won't let me format it because it cannot format it in its current state. For reference, I am making a time clock application.
I apologize if I'm doing something stupid but I am fairly new to this and have never used SimpleDateFormat before. I put some snippets of code below:
ArrayList<String> punchHistoryTimes = new ArrayList<String>();
SimpleDateFormat sdf =new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
public void updatePunchHistory(Sheet sheet){
for(int rowNum:rowNumbers){
punchHistoryTimes.add(sheet.getRow(rowNum).getCell(1).getStringCellValue());
punchHistory.add(new JLabel(sheet.getRow(rowNum).getCell(0).getRichStringCellValue().getString()+ " " + sheet.getRow(rowNum).getCell(1).getRichStringCellValue().getString()+ " " + sheet.getRow(rowNum).getCell(2).getRichStringCellValue().getString()));
}
}
//other code is above this but not relevant to the issue
currentEmployee.setEndTime(sdf.format(currentDate));
if(punchHistory.get(punchHistory.size()-1).getText().contains("Clocked In")){
calcTimeWorked(punchHistory.get(punchHistoryTimes.size()-1).getText(),currentEmployee.getEndTime());
}else{
//This line below is where the error is happening
//value of currentEmployee.getStartTime() at error: 1654653731536
//value of currentEmployee.getEndTime() at error: 07-06-2022 21:02:12
//Both currentEmployee.getStartTime() and currentEmployee.getEndTime() are stored as Strings
calcTimeWorked(currentEmployee.getStartTime(),currentEmployee.getEndTime());
}
currentEmployee.setHoursWorked(differenceInTime);
I tried using the debugger and it shows the error is that it cannot parse 1654653731536. I understand the issue but cannot get a solution. I believe the issue is because when it stores the value in the excel file it is storing the date as a string but then when it pulls the date back out of the excel later (the application would have been closed between these events) it views it as a string and does not recognize that there is a Date inside of the String. Is there any way to cast the String 1654653731536 to a Date?

Formatting UTC date-time's partial seconds

After parsing JSON UTC date-time data from a server, I was presented with
2017-03-27 16:27:45.567
... is there any way to format this without using tedious amount of String manipulation so that the seconds part is rounded up to 46 prior to passing it in as a DateTimeFormat pattern of say, "yyyy-MM-dd HH:mm:ss"?
You can round the second up like this:
DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
.withZoneUTC()
.parseDateTime("2017-03-27 16:27:45.567")
.secondOfMinute()
.roundCeilingCopy();
System.out.println(dateTime);
// 2017-03-27T16:27:46.000Z
Have you looked at (and could you use) the MomentJS library? I had issues with reading various date formats from the server and making sense of them in JavaScript code (which led me here). Since then, I've used MomentJS and working with dates/times in JavaScript has been much easier.
Here is an example:
<script>
try
{
var myDateString = "2017-03-27 16:27:45.567";
var d = moment(myDateString);
var result = d.format('YYYY/MM/DD HH:mm:ss');
alert("Simple Format: " + result);
// If we have millliseconds, increment to the next second so that
// we can then get its 'floor' by using the startOf() function.
if(d.millisecond() > 0)
d = d.add(1, 'second');
result = d.startOf('second').format('YYYY/MM/DD HH:mm:ss');
alert("Rounded Format: " + result);
}
catch(er)
{
console.log(er);
}
</script>
But of course, you'll probably want to wrap this logic into a function.

DBunit - Unable to typecast <1997/02/14> to TimeStamp

I'm doing an integration testing with DBUnit (2.49) + Hibernate (4.1.3) following this tutorial.
Production database : Oracle 10
Test database : Hsqldb 2.3.3
Context
My data contains the current format of date : yyyy/MM/dd. However,according to DBUnit faq, DBUnit only supports this format yyyy-mm-dd hh:mm:ss.fffffffff, so I had to create a new format for TimeStamp.
How I tried to fix it
I created a CustomTimeStampDataType based on this tutorial. I changed this part:
String formats[] = {"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm a", "yyyy-MM-dd HH:mm:ss.fffffffff"};
into this one:
String formats[] = {"yyyy/MM/dd"};
I created a CustomeDataTypeFactory following the same tutorial. I only make it extend Oracle10DataTypeFactory rather than DefaultDatatTypeFactory.
In HibernateDBUnitTestCase, I override setDatabaseConfig() with the following:
#Override
protected void setUpDatabaseConfig(DatabaseConfig config){
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new CustomDataTypeFactory());
}
But I got new errors
I ran a unit test and got this error.
org.dbunit.dataset.datatype.TypeCastException: Unable to typecast value <1997/02/14> of type <java.lang.String> to TIMESTAMP
at org.dbunit.dataset.datatype.TimestampDataType.typeCast(TimestampDataType.java:120)
at org.dbunit.dataset.datatype.TimestampDataType.setSqlValue(TimestampDataType.java:176)
at org.dbunit.database.statement.SimplePreparedStatement.addValue(SimplePreparedStatement.java:73)
at org.dbunit.operation.RefreshOperation$RowOperation.execute(RefreshOperation.java:189)
at org.dbunit.operation.RefreshOperation.execute(RefreshOperation.java:113)
at org.dbunit.AbstractDatabaseTester.executeOperation(AbstractDatabaseTester.java:190)
at org.dbunit.AbstractDatabaseTester.onSetup(AbstractDatabaseTester.java:103)
at org.dbunit.DatabaseTestCase.setUp(DatabaseTestCase.java:156)
at test.HibernateDbUnitTestCase.setUp(HibernateDbUnitTestCase.java:85)
at test.PlayerTest.setUp(PlayerTest.java:117)
Caused by: java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
at java.sql.Timestamp.valueOf(Unknown Source)
at org.dbunit.dataset.datatype.TimestampDataType.typeCast(TimestampDataType.java:116)
... 20 more
That was weird, it seemed like my CustomTimeStamp was not called, so I changed the date in the dataset using the default format : 1997-02-14 00:00:00.0, and ran the unit test again. Then I got:
org.dbunit.dataset.datatype.TypeCastException: Unable to typecast value <1997-02-14 00:00:00.0> of type <java.lang.String> to TIMESTAMP
at test.CustomTimestampDataType.typeCast(CustomTimestampDataType.java:69)
at test.CustomTimestampDataType.setSqlValue(CustomTimestampDataType.java:84)
at org.dbunit.database.statement.SimplePreparedStatement.addValue(SimplePreparedStatement.java:73)
at org.dbunit.operation.RefreshOperation$RowOperation.execute(RefreshOperation.java:189)
at org.dbunit.operation.RefreshOperation.execute(RefreshOperation.java:113)
at org.dbunit.AbstractDatabaseTester.executeOperation(AbstractDatabaseTester.java:190)
at org.dbunit.AbstractDatabaseTester.onSetup(AbstractDatabaseTester.java:103)
at org.dbunit.DatabaseTestCase.setUp(DatabaseTestCase.java:156)
at test.HibernateDbUnitTestCase.setUp(HibernateDbUnitTestCase.java:85)
at test.PlayerTest.setUp(PlayerTest.java:117)
That means CustomTimeStamp was actually called. Seems like, the problem stemed from DatabaseTestCase.setUp which somehow called the wrong TimeStampDataType.
How could I fix this issue?
My first option was to replace every yyyy/MM/dd into yyyy-mm-dd in the dataset using regular expressions. This worked fine, until I had to test a method that selected a date based on a request (so the format is yyyy-mm-dd) and compared it to the current date. ( so the format is yyyy / mm / dd). Hsqldb can't compare two dates with different format.
My second option was to decompile dbunit.jar, rewrite TimeStampDataType based on the tutorial. I'm unfamiliar with bytecode writing so before entering uncharted waters, I wanted to know if you had another solution.
Thank you in advance
Fixed it!
So I ended up using my second option.
This is the detailed path for those who need it.
Download dbUnit.2.2.source.jar
Unzip the jar
Go to Eclipse, File > New > Java Project
Uncheck "Use default location"
In Location : specify the path to the new folder created from the jar
Click on Finish
Modify the TimestampDataType.java (if needed)
Instead of ts = java.sql.Timestamp.valueOf(stringValue); use the code below
String formats[] =
{"dd/MM/yyyy HH:mm:ss.SS"}; //and more depending on your need
Timestamp ts = null;
for (int i = 0; i < formats.length; i++)
{
SimpleDateFormat sdf = new SimpleDateFormat(formats[i]);
try {
java.util.Date date = sdf.parse(stringValue);
ts = new Timestamp(date.getTime());
return ts;
}
catch( ParseException e) {
}
Modify the DateDataType.java (if needed)
Instead of return java.sql.Date.valueOf(stringValue); , use the code below
String formats[] =
{"dd/MM/yyyy"}; //and more depending on your need
for (int i = 0; i < formats.length; i++)
{
SimpleDateFormat sdf = new SimpleDateFormat(formats[i]);
try {
java.util.Date date = sdf.parse(stringValue);
java.sql.Date datesql = new java.sql.Date(date.getTime());
return datesql;
}
catch( ParseException e) {
}
}
Right-click on your project, then Export
Select JAR file, then Next
Fill the export destination then Finish.
You just have to add this new jar to the library to make it work.

How to insert date in mongo db from java

There are many similar questions asked. But not exactly similar to the issue i am facing. I have seen almost all the questions and answers around it
So the problem is
I got to insert a date field in my mongo collection
But I can't access the collection directly. I got to use a service. The service takes a string and returns me oid.
So once i construct the BasicDBObject I call toString on it and pass it on to my service.. I even tried inserting it directly in a test collection and mongo is complaining.
BasicDBObject document = new BasicDBObject();
long createdAtSinceEpoch = 0;
long expiresAtSinceEpoch = 0;
createdAtSinceEpoch = System.nanoTime();
Date createdAt = new Date(TimeUnit.NANOSECONDS.toMillis(createdAtSinceEpoch));
document.append("createdAt", createdAt);
expiresAtSinceEpoch = createdAtSinceEpoch + +TimeUnit.SECONDS.toNanos(30);
Date expiresAt = new Date(TimeUnit.NANOSECONDS.toMillis(expiresAtSinceEpoch));
document.append("expiresAt", expiresAt);
service.storeRecord(document.toString());
and the generated JSON String looks like
{
"createdAt": {
"$date": "2015-09-01T20:05:21.641Z"
},
"expiresAt": {
"$date": "2015-09-01T20:05:51.641Z"
}
and Mongo complains that
Unable to parse JSON : Date expecting integer milliseconds, at (3,17)
So If i pass milliseconds alone instead of date object in the document.append() method then it DOES NOT recognize this field as date and considers it as String but inserts into the collection
I need 2 things
1) I want the data to be inserted
2) I am planning to expire that row by adding an index to the expiresAt field. So I want mongo to recognize that its a date field
JSON makes a difference between a numeric field and a text field containing a number. The latter one is only recognized as a String; I assume that this is what you did when you thought you were giving your service the date as an integer. Unfortunately you didn’t show us the relevant code.
When I save the Date info as a non String format, I annotate the field in my DTO as below. This helps the MongoDB know that the field is to be treated as an ISO date which then would be useful for making range search etc.,
#DateTimeFormat(iso = ISO.DATE_TIME) private Date date;
Date date = new Date();
BasicDBObject date= new BasicDBObject("date", date);
Data.insert(date);

How to display common era ("CE") in Java-8?

Following code does not print "CE" or "Current Era":
System.out.println(IsoEra.CE.getDisplayName(TextStyle.SHORT, Locale.UK)); // output: AD
System.out.println(IsoEra.CE.getDisplayName(TextStyle.FULL, Locale.UK)); // output: Anno Domini
Of course, IsoEra.CE.name() helps but not if the full display name like "common era" or "current era" is required. I consider this a little bit strange because the javadoc of IsoEra explicitly mentions the term "Current era" in its class description. It does not even work for root locale. The use-case here is to serve clients with a non-religious background.
This does not help, too:
LocalDate date = LocalDate.now();
String year = date.format(DateTimeFormatter.ofPattern("G yyyy", Locale.UK)); // AD 2015
System.out.println(year);
The only way I found was:
TextStyle style = ...;
Map<Long,String> eras = new HashMap<>();
long bce = (long) IsoEra.BCE.getValue(); // 0L
long ce = (long) IsoEra.CE.getValue(); // 1L
if (style == TextStyle.FULL) {
eras.put(bce, "Before current era");
eras.put(ce, "Current era");
} else {
eras.put(bce, "BCE");
eras.put(ce, "CE");
}
DateTimeFormatter dtf =
new DateTimeFormatterBuilder()
.appendText(ChronoField.ERA, eras)
.appendPattern(" yyyy").toFormatter();
System.out.println(LocalDate.now().format(dtf)); // CE 2015
Is there any better or shorter way?
No, there is no better way to do it!
Explanation: "Current era" (and accoridngly "before current era") is the "name of a field"(abstract/meta) of the ISO standard. Of course there is also no (standardized) country specific translation for these fields and no pattern which prints this output.(By the standard they are referenced in english only, and by jdk respectively only as CE, BCE). So what the original output shows:
AD
Anno Domini
is correct, and the ISO-conform (english) translation of the era (of a date which is "in the current era").
To solve this, I absolutely agree with your approach (of custom date formatting), and going deeper into details: I wouldn't dare to change a single line of it!
The only savings potential I see is in "initialization"(maybe use a EnumMap for the TextStyles...and ... how many languages do you want to support?) ..and "by refactoring".
Thank you for the interesting "problem", and providing a solution to it!

Categories

Resources