Preserving the Timestamp details in java - java

I've an input string as
2020-01-21T02:16:51.8320Z
I need to parse this string into a java Date object.
I tried using following code.
LocalDate localDate = LocalDate.parse(date, flexUtcDtf);
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
Output:
Wed Jan 22 00:00:00 EST 2020
Is it possible to get the output as following Date object instead?(i.e. preserving the time details as well)
Wed Jan 21 02:46:51.8320 EST 2020
Thanks.

java.util.Date
.from(
Instant.parse(
"2020-01-21T02:16:51.8320Z"
)
)
.toString()
Beware of data loss. Your input has a fourth digit of decimal fraction of a second. That means microseconds. The Instant class can handle that. But the legacy Date class you asked for cannot, and is limited to milliseconds. So any microseconds will be lopped off, truncated to milliseconds.
The terrible legacy classes such as java.util.Date have been given new methods to facilitate converting back and forth between the modern java.time classes. Here we are using Date.from( Instant ) to produce a legacy date from the modern Instant parsed from your input.
Beware that Date has many flaws and problems. Among those is the behavior of its toString method. That method takes the value of the Date which is a moment in UTC, and then applies the JVM’s current default time zone while generating the text. This creates the illusion of that time zone being part of the Date.
I suggest you avoid Date entirely, and use only the java.time classes. But my code here answers the Question as asked.
Also, your desired output format is a terrible one. Instead, use ISO 8601 standard formats for data exchange. For presentation to the user, use DateTimeFormatter.ofLocalizedDateTime. Both of these topics have been addressed many times on Stack Overflow, so search to learn more.

First - congratulations on using the Java 8 time functions - wise choice!
Per your question:
This is the way to convert "LocalDate" to "java.util.Date":
Date myDate = Date.from(localDate.atZone(ZoneId.systemDefault()).toInstant());
... or ...
Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
Per the documentation:
https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
LocalDate is an immutable date-time object that represents a date,
often viewed as year-month-day. Other date fields, such as
day-of-year, day-of-week and week-of-year, can also be accessed. For
example, the value "2nd October 2007" can be stored in a LocalDate.
This class does not store or represent a time or time-zone. Instead,
it is a description of the date, as used for birthdays. It cannot
represent an instant on the time-line without additional information
such as an offset or time-zone.
So a better choice might be LocalDateTime
In either case, "java.util.Date" automatically has "everything you need". It is a "date/time" object. It stores date and time, irrespective of any time zone.

Related

Why is the following date conversion in Java 8 not appropriate?

I have seen a lot of debates on the following date conversion:
timeStamp.toLocalDateTime().toLocalDate();
Some people say that it is not appropriate because the timezone has to be specified for proper conversion, otherwise the result may be unexpected. My requirement is that I have an object that contains Timestamp fields and another object that contains LocalDate fields. I have to take the date difference between both so I think that the best common type to use is LocalDate. I don't see why the timezone has to be specified as either timestamp or LocalDate just represent dates. The timezone is already implied. Can someone give an example when this conversion fails?.
It’s more complicated than that. While it’s true that a Timestamp is a point in time, it also tends to have a dual nature where it sometimes pretends to be a date and time of day instead.
BTW, you probably already know, the Timestamp class is poorly designed and long outdated. Best if you can avoid it completely. If you are getting a Timestamp from a legacy API, you are doing the right thing: immediately converting it to a type from java.time, the modern Java date and time API.
Timestamp is a point in time
To convert a point in time (however represented) to a date you need to decide on a time zone. It is never the same date in all time zones. So the choice of time zone will always make a difference. So one correct conversion would be:
ZoneId zone = ZoneId.of("Africa/Cairo");
LocalDate date = timestamp.toInstant().atZone(zone).toLocalDate();
The Timestamp class was designed for use with your SQL database. If your datatype in SQL is timestamp with time zone, then it unambiguously denotes a point in time, and you need to see it as a point in time as just described. Even when to most database engines timestamp with time zone really just means “timestamp in UTC”, it’s still a point in time.
And then again: sometimes to be thought of as date and time of day
From the documentation of Timestamp:
A Timestamp also provides formatting and parsing operations to support
the JDBC escape syntax for timestamp values.
The JDBC escape syntax is defined as
yyyy-mm-dd hh:mm:ss.fffffffff, where fffffffff indicates
nanoseconds.
This doesn’t define any point in time. It’s a mere date and time of day. What the documentation doesn’t even tell you is that the date and time of day is understood in the default time zone of the JVM.
I suppose that the reason for seeing a Timestamp in this way comes from the SQL Timestamp datatype. In most database engines this is a date and time without time zone. It’s not a timestamp, despite the name! It doesn’t define a point in time, which is the purpose of and is in the definition of timestamp.
I have seen a number of cases where the Timestamp prints the same date and time as in the database, but doesn’t represent the point in time implied in the database. For example, there may be a decision that “timestamps” in the database are in UTC, while the JVM uses the time zone of the place where it’s running. It’s a bad practice, but it is not one that will go away within a few years.
This must also have been the reason why Timestamp was fitted with the toLocalDateTime method that you used in the question. It gives you that date and time that were in the database, right? So in this case your conversion in the question ought to be correct, or…?
Where this can fail miserably without us having a chance to notice is, as others have mentioned already, when the default time zone of the JVM is changed. The JVM’s default time zone can be changed at any time from any place in your program or any other program running in the same JVM. When this happens, your Timestamp objects don’t change their point in time, but they do tacitly change their time of day, sometimes also their date. I’ve read horror stories — in Stack Overflow questions and elsewhere — about the wrong results and the confusion coming out of this.
Solution: don’t use Timestamp
Since JDBC 4.2 you can retrieve java.time types out of your SQL database. If your SQL datatype is timestamp with time zone (recommended for timestamps), fetch an OffsetDateTime. Some JDBC drivers also let you fetch an Instant, that’s fine too. In both cases no time zone change will play any trick on you. If the SQL type is timestamp without time zone (discouraged and all too common), fetch a LocalDateTime. Again you can be sure that your object doesn’t change its date and time no matter if the JVM time zone setting changes. Only your LocalDateTime never defined a point in time. Conversion to LocalDate is trivial, as you have already demonstrated in the question.
Links
java.sql.Timestamp documentation
Wikipedia article: Timestamp
Question: Getting the date from a ResultSet for use with java.time classes
Question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
As you can see here(taken from https://stackoverflow.com/a/32443004/1398418):
Timestamp represents a moment in UTC and is the equivalent of the modern Instant.
When you do:
timeStamp.toLocalDateTime().toLocalDate();
the timeStamp is converted from UTC to the system timezone. It's the same as doing:
timeStamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
For example:
Timestamp stamp = new Timestamp(TimeUnit.HOURS.toMillis(-1)); // UTC 1969-12-31
System.setProperty("user.timezone", "EET"); // Set system time zone to Eastern European EET - UTC+2
stamp.toLocalDateTime().toLocalDate(); // represents EET 1970-01-01
stamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // represents EET 1970-01-01
That result (getting the date in the system time zone) is expected and if that's what you want, doing timeStamp.toLocalDateTime().toLocalDate() is appropriate and correct.
You're saying that you have a LocalDate field in some object and you want to get a period between it and a Timestamp, well that's just not possible without aditional information. LocalDate just represents a date, it has no time zone information, you need to know how it was created and what time zone was used.
If it represent a date in the system time zone then getting the period by using timeStamp.toLocalDateTime().toLocalDate() would be correct, if it represents a date in UTC or any other time zone then you might get a wrong result.
For example if the LocalDate field represents a date in UTC you will need to use:
timeStamp.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
Example: the 23rd of January becomes the 24th
You asked:
Can someone give an example when this conversion fails?.
Yes, I can.
Start with the 23rd of January.
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.of( 23 , 0 );
ZoneId zMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , zMontreal );
Instant instant = zdt.toInstant();
zdt.toString() = 2020-01-23T23:00-05:00[America/Montreal]
instant.toString() = 2020-01-24T04:00:00Z
The Instant class represents a moment as seen in UTC. Let's convert to the terribly legacy class java.sql.Timestamp using the new conversion method added to that old class.
// Convert from modern class to troubled legacy class `Timestamp`.
java.sql.Timestamp ts = Timestamp.from( instant );
ts.toString() = 2020-01-23 20:00:00.0
Unfortunately, the Timestamp::toString method dynamically applies the JVM’s current default time zone while generating text.
ZoneOffset defaultOffset = ZoneId.systemDefault().getRules().getOffset( ts.toInstant() );
System.out.println( "JVM’s current default time zone: " + ZoneId.systemDefault() + " had an offset then of: " + defaultOffset );
JVM’s current default time zone: America/Los_Angeles had an offset then of: -08:00
So Timestamp::toString misreports the object’s UTC value after adjusting back eight hours from 4 AM to 8 PM. This anti-feature is one of several severe problems with this poorly designed class. For more discussion of the screwy behavior of Timestamp, see the correct Answer by Ole V.V.
Let's run your code. Imagine at runtime the JVM’s current default time zone is Asia/Tokyo.
TimeZone.setDefault( TimeZone.getTimeZone( "Asia/Tokyo" ) );
LocalDate localDate = ts.toLocalDateTime().toLocalDate();
Test for equality. Oops! We ended up with the 24th rather than the 23rd.
boolean sameDate = ld.isEqual( localDate );
System.out.println( "sameDate = " + sameDate + " | ld: " + ld + " localDate: " + localDate );
sameDate = false | ld: 2020-01-23 localDate: 2020-01-24
See this code run live at IdeOne.com.
So what is wrong with your code?
Never use java.sql.Timestamp. It is one of several terrible date-time classes shipped with the earliest versions of Java. Never use these legacy classes. They have been supplanted entirely by the modern java.time classes defined in JSR 310.
You called toLocalDateTime which strips away vital information. Any time zone or offset-from-UTC is removed, leaving only a date and a time-of-day. So this class cannot be used to represent a moment, is not a point on the timeline. Ex: 2020-12-25 at noon — is that noon in Delhi, noon in Düsseldorf, or noon in Detroit, three different moments several hours apart? A LocalDateTime is inherently ambiguous.
You ignored the crucial issue of time zone in determining a date. For any given moment, the date varies around the globe. At one moment it may be “tomorrow” in Australia while simultaneously “yesterday” in Mexico.
The problem lies in what is being represented by these objects. Your question forgets a crucial aspect, which is: What is the type of timeStamp?
I'm guessing it's a java.sql.Timestamp object.
Timestamp, just like java.util.Date, is old API equivalent to Instant.
It represents an instant in time, in the sense that it is milliseconds since jan 1st 1970 UTC. The system has no idea which timezone that was supposed to be in. You're supposed to know; the error, if an error is going to occur here, already occurred before you get to this code. Here's a trivial explanation of how it COULD go wrong:
you start off with a user entering a date in a date field on a webform; it's 2020-04-01.
Your server, running in Amsterdam, saves it to a DB column that is internally represented as UTC, no zone. This is a mistake (you're not saving an instant in time, you're saving a date, these two are not the same thing). What is actually stored in the DB is the exact moment in time that it is midnight, 2020-04-01 in amsterdam (in UTC, that'd be 22:00 the previous day!).
Later, you query this moment in time back into a java.sql.Timestamp object, and you're doing this when the server's tz is elsewhere (say, London time). You then convert this to a localdatetime, and from there to a localdate, and.... you get 2020-03-31 out.
Whoops.
Dates should remain dates. Never convert LocalX (be it Time, Date, or DateTime) to Instant (or anything that effectively is an instant, including j.s.Timestamp, or j.u.Date - yes, j.u.Date does NOT represent a date, it is very badly named), or vice versa, or pain will ensue. If you must because of backward APIs take extreme care; it's hard to test that 'moving the server's timezone around' breaks stuff!

Is there a format string for SimpleDateFormat to get milliseconds date-time value instead of human-readable form?

By means of classes like SimpleDateFormat it is possible to format time and date in a suitable format.
Examples here
https://developer.android.com/reference/java/text/SimpleDateFormat#examples
In Java it starts with milliseconds value and then that value gets translated into human readable format.
Sometime it is useful to have that value instead of the human readable form.
Example:
If I am not wrong the 1578738100000 value just means the UTC value Sat Jan 11 2020 10:21:40.
Is it possible to have a format string that yields a string with milliseconds instead of the human readable form?
I know that it is possible to get the milliseconds value directly from the Date class but what I am asking here is whether milliseconds are one of the possible format string to feed SimpleDateFormat (or similar classes) with.
Be clear in understanding that date-time value objects and formatter objects play different roles.
A date-time object has no format, it represents a date and/or time-of-day with or without the context of a time zone or offset-from-UTC.
A formatter has no value, no date nor time-of-day. A formatter’s job is to work with a date-time object to produce text in a certain format representing that date-time object’s value.
So tracking a count of milliseconds since the epoch reference is the job of the date-time object, not the formatter. Producing human-readable text is the job of the formatter. So, no, the formatter does not produce a count of milliseconds.
And, no, you should not be using a count of milliseconds to communicate date-time values. Such numbers have no meaning to a human reader which leads to easily missing erroneous data. And such data does not readily identify itself - is it a number of whole seconds, milliseconds, microseconds, or nanoseconds? And what is the epoch reference date, which of the couple dozen commonly used epochs?
Instead communicate date-time values as text using the ISO 8601 standard formats.
Another problem: you are using terrible date-time classes that were supplanted years ago by the modern java.time classes defined in JSR 310.
If your number is a count of milliseconds since the epoch reference of first moment of 1970 in UTC, parse as a Instant.
Instant instant = Instant.ofEpochMilli( 1_578_738_100_000L ) ;
If you insist on working with a count-from-epoch against my advice, you can interrogate the Instant.
long milliseconds = instant.toEpochMilli() ;
Generate text in standard ISO 8601 format.
String output = instant.toString() ;
For other formats, adjust the Instant into an OffsetDateTime or ZonedDateTime object, and generate text with a DateTimeFormatter. All this has been covered many many times already. So search Stack Overflow to learn more.
instant
.atZone(
ZoneId.of( "America/Montreal" )
)
.format(
DateTimeFormatter
.ofLocalizedDateTime( FormatStyle.FULL )
.withLocale( Locale.CANADA_FRENCH )
)
Lastly, be aware that while the legacy classes were limited to a resolution of milliseconds, the java.time classes revolve to the much finer nanoseconds. So beware of possible data loss when calling Instant::toEpochMilli as any microseconds or nanoseconds are ignored,
The old and outdated SimpleDateFormat class cannot do that. Its replacement, the modern DateTimeFormatter, can.
DateTimeFormatter epochMilliFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
Instant sampleInstant = OffsetDateTime
.of(2020, 1, 11, 10, 21, 40, 0, ZoneOffset.UTC)
.toInstant();
String formattedValue = epochMilliFormatter.format(sampleInstant);
System.out.println(formattedValue);
Output from this snippet is the number you mentioned:
1578738100000
Using ChronoField.INSTANT_SECONDS in the formatter gives us the seconds since the epoch. We wanted milliseconds, so we need to append ChronoField.MILLI_OF_SECOND immediately and make sure that they are printed in exact 3 positions, zero padded. This is what the 3 as 2nd argument to appendValue() does.
Is it possible to have a format string that yields a string with
milliseconds …?
No, with a format pattern string it is not possible, neither with SimpleDateFormat nor with DateTimeFormatter. You can go through the possible pattern letter of each and see that there is no pattern letter for neither seconds nor milliseconds since the epoch.
Are you sure that you want it, though? Even if this is for storing or for data interchange between systems, using milliseoncds since the epoch is not generally recommended exactly because they not human readable and therefore troublesome in debugging and in ad hoc queries. For most purposes you will be better off using a string in ISO 8601 format, like 2020-01-11T10:21:40Z. See the other answer by Basil Bourque for details. ISO 8601 format has been designed to be readable by both humans and computers.
You should not have wanted to use SimpleDateFormat anyway
The SimpleDateFormat class is notoriously troublesome (though even more for parsing than for formatting). It is also long outdated. The Date class that you mentioned is poorly designed and long outdated too. I recommend that you use java.time, the modern Java date and time API, as I do above.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of DateTimeFormatter with the format pattern letters that it accepts.
Wikipedia article: ISO 8601

Add a Java 8 ISO 8601 time duration expression to java.util.Date

I have an expression like "PT20.345S", "P2DT3H4M" etc as described here https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-
How can I parse this, add it to the current time and get a java.util.Date object?
Neither works:
Date d1 = Date.from(LocalDateTime.now().plus(Duration.parse(_expression)));
Date d2 = Date.from(Duration.parse(_expression).addTo(LocalDateTime.now()));
Duration amountToAdd = Duration.parse("PT20.345S"); // Represent a span of time. Here, about twenty and a third seconds.
Instant now = Instant.now() ; // Capture the current moment in UTC.
Instant otherMoment = now.plus(amountToAdd); // Add the span-of-time to the current moment, for a moment in the future (or in the past if the duration is negative).
String output = otherMoment.toString(): // Generate a String in standard ISO 8601 format.
2018-06-30T19:34:47Z
Convert from modern java.time class to legacy class.
Date date1 = Date.from(otherMoment);
System.out.println(date1);
Running just now in Europe/Copenhagen time zone I got:
Sat Jun 30 21:34:47 CEST 2018
If I use your other example duration string, P2DT3H4M, I got:
Tue Jul 03 00:38:26 CEST 2018
Or if you’re into one-liners:
Date date1 = Date.from(Instant.now().plus(Duration.parse("PT20.345S")));
The java.util.Date class is long outdated, so ideally you shouldn’t want to have one. If you need one anyway, typically for a legacy API that you cannot change or don’t want to change just now, you are thinking correctly when doing as much of the logic as possible using java.time, the modern Java date and time API, and converting to Date only in the end. Date’s closest cousin in the modern world is Instant, and direct conversions between Instant and Date exist, which is why I am using this class. An Instant is also lovely independent of zone offsets and time zones.
In your code, the first solution should work if you convert the LocalDateTime to Instant with ZoneOffset (example UTC, or default of your system using ZoneOffset.systemDefault()) like below:
Date d1 = Date.from(LocalDateTime.now().plus(Duration.parse(_expression)).toInstant(OffsetDateTime.now().getOffset());
However, LocalDateTime is wrongly used in this case, because it does not represent a moment, is not a point on the timeline
From javadoc:
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
But, an Instant is a moment on the timeline in UTC
This class models a single instantaneous point on the time-line. This might be used to record event time-stamps in the application.
So, if you use an Instant, you know exactly what moment in time is being referred to, regardless of time zones. Since you are going to handle the business logic like adding amount of time to current time and convert to Date, this is a handy class to be used.
Date date1 = Date.from(Instant.now().plus(Duration.parse("PT20.345S")));

Trying to parse a datetime in PDT to a ZonedDateTime representation

How should I parse this datetime value that is in the PDT timezone?
06/24/2017 07:00 AM (PDT)
I want to maintain the timezone so that I can then represent the time in other timezones depending on the website visitors preferences.
I tried using ZonedDateTime but I get a parse error:
java.time.ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)")
The error is:
java.time.format.DateTimeParseException: Text '06/24/2017 07:00 AM (PDT)' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:582) ... 29 elided
Also, do you agree that I should be using a ZonedDateTime?
Since your format is non-standard, you need to specify it to the parser:
ZonedDateTime.parse(
"06/24/2017 07:00 AM (PDT)",
DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm a (zzz)")
);
The parse method expects a String in a specific format, like 2007-12-03T10:15:30+01:00[Europe/Paris]. As your input is in a different format, you need a DateTimeFormatter.
One detail to notice is that the API uses IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
The API makes some exceptions with specific IDs and provides some defaults for them. For PDT, it defaults to America/Los_Angeles.
Another detail is that in the example below I used lowercase hh in the pattern: the format has AM/PM indication, so I think that hh is the correct pattern, as its value is from 1 to 12 (the common values when there's the AM/PM indicator).
If you use uppercase HH, it allows values from 0 to 23 (and it's not common to use this with AM/PM), and it will throw an exception if the input contains an hour like 07:00 PM.
So the code will be like:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a (zzz)");
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
System.out.println(z);
The output is:
2017-06-24T07:00-07:00[America/Los_Angeles]
But not all the 3-letter timezone names will be recognized by the API and will throw an exception.
Anyway, there are other timezones that also are in PDT (like America/Vancouver) - you can get a list of all by calling ZoneId.getAvailableZoneIds(). If you want to use a different timezone as the default, you can create a set of preferred zones and build a formatter with this set:
Set<ZoneId> preferredZones = new HashSet<>();
// set America/Vancouver as preferred zone
preferredZones.add(ZoneId.of("America/Vancouver"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// pattern
.appendPattern("MM/dd/yyyy hh:mm a (")
// append timezone with set of prefered zones
.appendZoneText(TextStyle.SHORT, preferredZones)
// finish the pattern
.appendPattern(")")
// create formatter
.toFormatter();
System.out.println(ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt));
The API will use the preferred zones set (in this case, America/Vancouver) instead of the default (America/Los_Angeles). The output will be:
2017-06-24T07:00-07:00[America/Vancouver]
It's not clear where the input String's come from. If you can't control their format, then you have no choice: they need to be parsed this way. Then you can convert it to another timezone using the withZoneSameInstant method:
// parse the input string
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
// convert to another timezone
ZonedDateTime other = z.withZoneSameInstant(ZoneId.of("America/Sao_Paulo")); // 2017-06-24T11:00-03:00[America/Sao_Paulo]
The value of other will be 2017-06-24T11:00-03:00[America/Sao_Paulo].
But if you can control the output, it's always better (IMO) to internally work with UTC (java.time.Instant), and convert to some timezone only when displaying to users:
// convert ZonedDateTime to instant
ZonedDateTime z = // parse input
// convert to UTC (Instant is always in UTC)
Instant instant = z.toInstant();
// internally work with instant (as it's always in UTC)
// convert instant to some timezone only when necessary (like displaying to users)
ZonedDateTime converted = instant.atZone(ZoneId.of("Europe/London"));
The error you get is well covered in the other answers already.
Also, do you agree that I should be using a ZonedDateTime?
Yes and no. Your string should definitely be parsed into a ZonedDateTime. I recommend you convert it to an Instant and store this. Then when you need to present it to a user according to his/her time zone preference, you may either convert the Instant to a ZonedDateTime again or just format it using a DateTimeFormatter with the desired default time zone.
Why do it this way? First, common practice is to store Instants. Some prefer to store just milliseconds since the epoch, I think this some (often misunderstood) performance measure. Certainly such milliseconds I quite unreadable while Instants can be deciphered on eye-sight, at least roughly. The only other alternative I respect is when you know for certain that your application will never need to be concerned with a time zone (does this ever happen?), then sometimes LocalDateTime is used for storage.
If I understand your situation correctly, you need to store the point in time for display into multiple time zones. You don’t need to store the time zone in which the time was originally entered (like PDT, except PDT is not really a full time zone). Instant is time zone neutral, which is one reason I prefer it over storing the time in some time zone, as ZonedDateTime would. Also an Instant is simpler conceptually, and my guess is that it is also simpler implementation-wise.
There are a couple of much better answers here: Best practices with saving datetime & timezone info in database when data is dependant on datetime.

Java Date confusion

I am really very confused about dates.
I have a web application which has a backend on Java. I have a date field which has no time information. I write this info to DB using hibernate. Its definition is
#Column
#Temporal(TemporalType.DATE)
private Date startDate;
Actually these are basic infos. Here is my problem.
I run java on UTC(GMT+0) timezone but client timezone is GMT+3.
A user picks a day (let's say 10.12.2016 (dd.MM.yyyy)). On client side this day represented as 10.12.2016 00:00 GMT+3. After this user wants to save this date.
This date comes to my backend as 09.12.2016 21:00 GMT+0. I want to save this date without timeinfo. So java code saves data as 09.12.2016. (which corresponds to 09.12.2016 00:00 GMT+0)
Now user wants to see what s/he saved. Java reads date as 09.12.2016 00:00 GMT+0 and client gets this date as 09.12.2016 03:00 GMT+3. At the end, the user sees date as 09.12.2016 but s/he saved this date as 10.12.2016.
So the user sees the day, one day before s/he actually saved.
I want to keep data as TemporalType.DATE and want to show user the correct date.
How can I solve this problem? I keep digging for 2 days on Google but I could not find a reasonable explanation.
NOTE:** My users can be different timezones not only on GMT+3. Please consider this situation.
I suppose that, if your application is aware of client's GMT you are receiving the GMT position from request so I won't go deep into this point because it would be needed further information about what your application does.
Once you have request's GMT info, you only have to do this:
Date databaseDate = database's date (you said you were working in GMT and it was also stored in GMT 0 so no further transformation is required)
//As you said you are working in GMT 0 this will be the GMT of your calendar.
Calendar calendar = new GregorianCalendar();
//As you know your GMT you only have to set it through this method
TimeZone clientTimezone = TimeZone.getTimeZone("GMT-1:00");
Date originalDate = inputCalendar.getTime();
calendar.setTimeZone(clientTimezone);
//This will return you the offset -positive or negative- of calendars time zone respect to UTC 0 - GMT 0 - in milliseconds
int offset = inputCalendar.getTimeZone().getOffset(originalDate.getTime());
//You get the database's date in milliseconds and add your offset to it. It can be positive or negative and the final result will the date transformed to your request's GMT
Date clientDate= (new Date(originalDate.getTime() + offset));
And it would be enough for your requirements.
Several options available for you.
If you are working with Java 8 (or can switch to java 8) then new
package java.time provides much more flexible date model and class
LocalDate will give you precisely what you need.
If you work with java 7 or less I would sudjest to store your Date
in DB as a String field. I.e. format your Date to String and save it
that way to DB into varchar field. When you read it just parse it
back to Date. This way your "10.12.2016" will remain such in any
time zone without any additional trickery.
Finally, if you insist on keeping it as Date you might want to
inforce that all your Dates are always stored in GMT+0 regardless of
actiual location. This way user in the same location will always see
the same date. But two users in different locations still might see
the same field as a different dates
I would sugjest the 1st option if possible and if not then 2d. Third is would become more and more complex if your app is used by users in dfferent time zones.
Also you might find this article interesting: It is about converting a String of unknown format to Date: Parsing any string to Date
Some general info about your date-time issues, not Hibernate specific…
What time zone is the current default on your server should be irrelevant. Always specify your desired/expected time zone in your code by always passing the optional time zone argument. Discussed many times on Stack Overflow, so search for more info.
LocalDate todayMontreal = LocalDate.now( ZoneId.of( "America/Montreal" ) ) ;
A date-only value without time zone is indeed imprecise. For any given moment, the date varies around the world by zone. A few minutes after midnight in Paris France is a new day while still ‘yesterday’ in Montréal Canada. Discussed many times on Stack Overflow, so search for more info.
If you want precision use a date-time with time zone. Discussed many times on Stack Overflow, so search for more info.
ZonedDateTime todayMontrealStart = todayMontreal.atStartOfDay( ZoneId.of( "America/Montreal" ) ) ;
…or…
ZonedDateTime todayMontrealStart = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
For storing data, make that time zone UTC. The Instant class is always in UTC by default.
Instant instant = todayMontrealStart.toInstant();
Your JDBC driver and/or database may do this for you. With JDBC 4.2 and later, pass java.time objects via getObject and setObject, otherwise fall back to java.sql types for exchanging data with database. Discussed many times on Stack Overflow, so search for more info.
Avoid the troublesome old date-time classes such as java.util.Date and .Calendar. Now legacy, supplanted by the java.time classes. Discussed many times on Stack Overflow, so search for more info.
Specifically, you should search Stack Overflow for the classes LocalDate, Instant, OffsetDateTime, and ZonedDateTime.
Bonus tip: when exchanging date-time values as text such as between your client and server, use ISO 8601 standard formats. Discussed many times on Stack Overflow, so search for more info.

Categories