ZonedDateTime change behavior jdk 8/11 - java

I am migrating an application from jdk 8 to 11 and I can see ZonedDateTime change is behavior about daylight saving time.
JDK8
ZonedDateTime parse = ZonedDateTime.parse("2037-05-10T19:15:00.000+01:00[Europe/Paris]");
System.out.println(parse);
output:
2037-05-10T19:15+02:00[Europe/Paris]
JDK11/12
ZonedDateTime parse = ZonedDateTime.parse("2037-05-10T19:15:00.000+01:00[Europe/Paris]");
System.out.println(parse);
2037-05-10T20:15+02:00[Europe/Paris]
Can someone explain to me why did they change this behavior ?
Best regards,

It’s a known bug in Java 8: JDK-8066982
I believe that what you are experiencing in Java 8 is really this bug: ZonedDateTime.parse() returns wrong ZoneOffset around DST fall transition. The bug title doesn’t tell the whole story. The real issue is that in Java 8 DateTimeFormatter.ISO_ZONED_DATE_TIME (which is implicitly used by the one-arg ZonedDateTime.parse that you use) ignores the offset if a time zone ID is included in the parsed string. This in combination with a time zone database that disagrees with your string about the offset used in Paris in October 2037 causes a moment in time to be parsed that conflicts with the offset in the string.
The bug is fixed in Java 9. So in Java 9, 10 and 11, since the same disagreement about offset is still there, the moment parsed is based on the offset of the string. It is then converted to the time zone from the string using the rules from the time zone database. This causes the offset to be changed from +01:00 to +02:00 and the hour of day correspondingly from 19:15 to 20:15. I agree with Java 9+ that this is the correct behaviour.
Don’t use ZonedDateTime for far-future dates
Your problem is also partly caused by using ZonedDateTime for a future date. This is only recommended for the very near future where we assume that no zone rules are changed. For a date and time in 2037, you should either use an Instant if you know the moment in time, or a LocalDateTime if you know just the date and time of day. Only when the time draws near and you trust that your Java installation has got the last time zone updates, convert to a ZonedDateTime.
As has been discussed in the comments, we probably don’t know the correct UTC offset for Paris in October 2037 yet. It seems that EU is likely to abandon summer time (DST) from 2021, and as far as I know, the French politicians have not yet decided what the time will be in France after that.
What if we wanted the time of day from the string?
To get the time from the string (19:15), parse into a LocalDateTime:
String zdtString = "2037-05-10T19:15:00.000+01:00[Europe/Paris]";
LocalDateTime dateTime
= LocalDateTime.parse(zdtString, DateTimeFormatter.ISO_ZONED_DATE_TIME);
System.out.println("Date and time from string: " + dateTime);
Output is (run on Java 11):
Date and time from string: 2037-05-10T19:15
In case you wanted the full Java 8 behaviour on a later Java version — as I mentioned, it’s not recommended, you shouldn’t use ZonedDateTime here:
TemporalAccessor parsed = DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(zdtString);
LocalDateTime dateTime = LocalDateTime.from(parsed);
ZoneId zone = ZoneId.from(parsed);
ZonedDateTime java8Zdt = dateTime.atZone(zone);
System.out.println("Time from string in zone from string: " + java8Zdt);
Time from string in zone from string: 2037-05-10T19:15+02:00[Europe/Paris]

Related

ZonedDateTime to Instant is right in Java 8 but off by 1h in Java 9+ [duplicate]

I am migrating an application from jdk 8 to 11 and I can see ZonedDateTime change is behavior about daylight saving time.
JDK8
ZonedDateTime parse = ZonedDateTime.parse("2037-05-10T19:15:00.000+01:00[Europe/Paris]");
System.out.println(parse);
output:
2037-05-10T19:15+02:00[Europe/Paris]
JDK11/12
ZonedDateTime parse = ZonedDateTime.parse("2037-05-10T19:15:00.000+01:00[Europe/Paris]");
System.out.println(parse);
2037-05-10T20:15+02:00[Europe/Paris]
Can someone explain to me why did they change this behavior ?
Best regards,
It’s a known bug in Java 8: JDK-8066982
I believe that what you are experiencing in Java 8 is really this bug: ZonedDateTime.parse() returns wrong ZoneOffset around DST fall transition. The bug title doesn’t tell the whole story. The real issue is that in Java 8 DateTimeFormatter.ISO_ZONED_DATE_TIME (which is implicitly used by the one-arg ZonedDateTime.parse that you use) ignores the offset if a time zone ID is included in the parsed string. This in combination with a time zone database that disagrees with your string about the offset used in Paris in October 2037 causes a moment in time to be parsed that conflicts with the offset in the string.
The bug is fixed in Java 9. So in Java 9, 10 and 11, since the same disagreement about offset is still there, the moment parsed is based on the offset of the string. It is then converted to the time zone from the string using the rules from the time zone database. This causes the offset to be changed from +01:00 to +02:00 and the hour of day correspondingly from 19:15 to 20:15. I agree with Java 9+ that this is the correct behaviour.
Don’t use ZonedDateTime for far-future dates
Your problem is also partly caused by using ZonedDateTime for a future date. This is only recommended for the very near future where we assume that no zone rules are changed. For a date and time in 2037, you should either use an Instant if you know the moment in time, or a LocalDateTime if you know just the date and time of day. Only when the time draws near and you trust that your Java installation has got the last time zone updates, convert to a ZonedDateTime.
As has been discussed in the comments, we probably don’t know the correct UTC offset for Paris in October 2037 yet. It seems that EU is likely to abandon summer time (DST) from 2021, and as far as I know, the French politicians have not yet decided what the time will be in France after that.
What if we wanted the time of day from the string?
To get the time from the string (19:15), parse into a LocalDateTime:
String zdtString = "2037-05-10T19:15:00.000+01:00[Europe/Paris]";
LocalDateTime dateTime
= LocalDateTime.parse(zdtString, DateTimeFormatter.ISO_ZONED_DATE_TIME);
System.out.println("Date and time from string: " + dateTime);
Output is (run on Java 11):
Date and time from string: 2037-05-10T19:15
In case you wanted the full Java 8 behaviour on a later Java version — as I mentioned, it’s not recommended, you shouldn’t use ZonedDateTime here:
TemporalAccessor parsed = DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(zdtString);
LocalDateTime dateTime = LocalDateTime.from(parsed);
ZoneId zone = ZoneId.from(parsed);
ZonedDateTime java8Zdt = dateTime.atZone(zone);
System.out.println("Time from string in zone from string: " + java8Zdt);
Time from string in zone from string: 2037-05-10T19:15+02:00[Europe/Paris]

How to parse offset it is not specified?

I have time 12:00:00 in format HH:mm:ss.
I know that this time comes from server witch is setup with +3 offset.
If i use SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");, it parses time with regard to device, which can be in a different timezone.
Is there another way to parse it with regard to +3 offset except adding it to the original string?
First, should your server rather send the time in UTC? If clients are everywhere, this would seem more time zone neutral and standardized. However, the way to handle it in code wouldn’t be much different. In any case the server offset form UTC could be constant:
private static final ZoneOffset serverOffset = ZoneOffset.ofHours(3);
In real code you will probably want to make it configurable somehow, though. To parse:
OffsetTime serverTime = LocalTime.parse("12:00:00").atOffset(serverOffset);
System.out.println(serverTime);
This prints
12:00+03:00
Since your time format agrees with LocalTime’s default (ISO 8601), we need no explicit formatter. If a representation of the time with offset is all you need, we’re done. If you need to convert to the user’s local time, to do that reliably you need to decide both a time zone and a date:
LocalTime clientTime = serverTime.atDate(LocalDate.of(2018, Month.JANUARY, 25))
.atZoneSameInstant(ZoneId.of("Indian/Maldives"))
.toLocalTime();
System.out.println(clientTime);
With the chosen day and zone we get
14:00
Please substitute your desired time zone and date.
Just hypothetically, if you knew the user’s offset from UTC, you could use just that:
LocalTime clientTime = serverTime.withOffsetSameInstant(ZoneOffset.of("-08:45"))
.toLocalTime();
The example yields 00:15. However, no one knows when the politicians introduce summer time (DST) or other anomalies in the user’s time zone, so I discourage relying on an offset alone.
And yes, I too am using java.time. SimpleDateFormat is not only long outdated, it is also notoriously troublesome, so java.time is what I warmly recommend.
Set the timezone on your SimpleDateFormat object:
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("GMT+03:00"));
I recommend you use the Java 8 date and time API (package java.time) instead of the old API, of which SimpleDateFormat is a part.
Using the Java 8 DateTime API:
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("HH:mm:ss");
LocalTime clientLocalTime = LocalTime
.parse("12:00:00", formatter)
// Create an OffsetTime object set to the server's +3 offset zone
.atOffset(ZoneOffset.ofHours(3))
// Convert the time from the server timezone to the client's local timezone.
// This expects the time value to be from the same day,
// otherwise the local timezone offset may be incorrect.
.withOffsetSameInstant(ZoneId.systemDefault().getRules().getOffset(Instant.now()))
// Drop the timezone info - not necessary
.toLocalTime();

FastDateFormat parser outputs incorrect time

I've been scratching my head trying to understand why the FastDateFormat parser is returning a very incorrect time. The string timestamp I'm trying to convert is in GMT/UTC, and I'm trying to insert it into a Timestamp column in DB2.
Here's the code:
String gmtTimestamp = "2017-03-12 02:38:30.417000000";
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSSSSSSS", TimeZone.getTimeZone("GMT"));
java.util.Date d = fdf.parse(gmtTimestamp);
Timestamp ts1 = new Timestamp(d.getTime());
System.out.println(ts1);
The time that's printed is: "2017-03-16 17:28:30.0", 4 days and nearly 15 hours off. What's happening here?
TL;DR
String gmtTimestamp = "2017-03-12 02:38:30.417000000";
DateTimeFormatter dtf
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSSSSS");
Instant i1 = LocalDateTime.parse(gmtTimestamp, dtf)
.atOffset(ZoneOffset.UTC)
.toInstant();
System.out.println(i1);
This prints
2017-03-12T02:38:30.417Z
The implicit call to Instant.toString() produces date and time in UTC (for our purpose the same as GMT), so you recognize the date and time from your GMT string.
java.time
I recommend you drop the java.sql.Timestamp class. It is long outdated, and today we have so much better in java.time, the modern Java date and time API. The original purpose of Timestamp was to store and retrieve date-time values to and from SQL databases. With a sufficiently new JDBC driver (JDBC 4.2), you can and will want to take advantage of two classes from java.time for that purpose: Instant for a point on the timeline and LocalDateTime for date and time without time zone.
In case you do need a Timestamp (for instance for a legacy API or an older JDBC driver you don’t want to upgrade just now), convert the Instant from above to Timestamp just before handing it to the legacy API or the database:
Timestamp ts1 = Timestamp.from(i1);
System.out.println(ts1);
Running in America/Chicago time zone this prints:
2017-03-11 20:38:30.417
Timestamp.toString() grabs the JVM’s time zone setting and outputs the date and time in this time zone (which may be confusing).
What was happening in your code snippet?
FastDateFormat uses SimpleDateFormat format patterns. In SimpleDateFormat and FastDateFormat capital S means milliseconds. so 417000000 was taken as milliseconds (where you intended 417 milliseconds), it is rpughly the same as 4 days 20 hours, which were added to the date-time value up to the seconds. I reproduced your result using a SimpleDateFormat and setting my JVM to America/Chicago time zone. Other time zones that are -5 hours from UTC in March will produce the same result. Since the Timestamp was printed at offset -5:00, the apparent difference in the output was a bit less than the real difference of 4 days 20 hours, “only” 4 days 15 hours.
By contrast, though the modern DateTimeFormatter mostly uses the same format pattern letters, to it capital S means fraction of second, which is why we get 30.417 seconds as expected and desired.
Quotes
All patterns are compatible with SimpleDateFormat (except time zones
and some year patterns - see below).
(FastDateFormat documentation)
S Millisecond Number 978
(SimpleDateFormat documentation)

Jboss Java Date daylight saving time

Have an issue where, when clocks are moved due to a Daylight savings time (twice a year), dates are not correct in Java (I am based in Central Europe: GMT+2 in summer, GMT+1 in winter)
If time is moved 1 hour ahead, new Date() still returns old time (1 hour behind of current time).
In Java 7, can this be solved, without restarting the Jboss application servers?
If I change the time manually in Windows, reproduce the problem: Date is not updated to the system date unless jboss is restarted.
Calendar c = Calendar.getInstance();
c.setTime(new Date());
In Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes.
With this, you can handle DST changes easily.
First, you can use the org.threeten.bp.DateTimeUtils to convert from and to Calendar.
The following code converts the Calendar to org.threeten.bp.Instant, which is a class that represents an "UTC instant" (a timestamp independent of timezone: right now, at this moment, everybody in the world are in the same instant, although their local date and time might be different, depending on where they are).
Then, the Instant is converted to a org.threeten.bp.ZonedDateTime (which means: at this instant, what is the date and time at this timezone?). I also used the org.threeten.bp.ZoneId to get the timezone:
Calendar c = Calendar.getInstance();
c.setTime(new Date());
// get the current instant in UTC timestamp
Instant now = DateTimeUtils.toInstant(c);
// convert to some timezone
ZonedDateTime z = now.atZone(ZoneId.of("Europe/Berlin"));
// today is 08/06/2017, so Berlin is in DST (GMT+2)
System.out.println(z); // 2017-06-08T14:11:58.608+02:00[Europe/Berlin]
// testing with a date in January (not in DST, GMT+1)
System.out.println(z.withMonth(1)); // 2017-01-08T14:11:58.608+01:00[Europe/Berlin]
I've just picked some timezone that uses Central Europe timezone (Europe/Berlin): you can't use those 3-letter abbreviations, because they are ambiguous and not standard. You can change the code to the timezone that suits best for your system (you can get a list of all available timezones with ZoneId.getAvailableZoneIds()).
I prefer this solution because it's explicit what timezone we're using to display to the user (Date and Calendar's toString() methods use the default timezone behind the scenes and you never know what they're doing).
And internally, we can keep using the Instant, which is in UTC, so it's not affected by timezones (and you can always convert to and from timezones whenever you need) - if you want to convert the ZonedDateTime back to an Instant, just use the toInstant() method.
Actually, if you want to get the current date/time, just forget the old classes (Date and Calendar) and use just the Instant:
// get the current instant in UTC timestamp
Instant now = Instant.now();
But if you still need to use the old classes, just use DateTimeUtils to do the conversions.
The output of the examples above are the result of the ZonedDateTime.toString() method. If you want to change the format, use the org.threeten.bp.format.DateTimeFormatter class (take a look at the javadoc for more details about all the possible formats):
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss z X");
// DST (GMT+02)
System.out.println(formatter.format(z)); // 08/06/2017 14:11:58 CEST +02
// not DST (GMT+01)
System.out.println(formatter.format(z.withMonth(1))); // 08/01/2017 14:11:58 CET +01
Use ZonedDateTime class from JDK 8 java.time. It accommodates the Daylight Saving Time changes.
Refer the details at : https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html

UTC to Local date conversion

Hi I have a table from which I get a date like "2017-03-24 06:01:33" which is in UTC. In my java program I have to receive it as a string. How to convert it to a local date string using client's offset hour and minute?
I would do:
OffsetDateTime odt = LocalDateTime.parse(dateFromDb, DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"))
.atOffset(ZoneOffset.UTC);
String localDateString = odt.atZoneSameInstant(ZoneOffset.ofHoursMinutes(5, 30))
.toLocalDateTime()
.toString();
I am assuming you have got the client’s time zone as an offset in hours and minutes. Then you can pass those into from ZoneOffset.ofHoursMinutes(). Beware that if the client time zone uses summer time (daylight savings time) some of the year (as is the case in greater parts of the USA), you need to be sure you get the offset for the time of year where the date falls. Other ways of specifying the client time zone would be ZoneId.systemDefault() or ZoneId.of("Asia/Kolkata") — such would know the summer time rules and hence may be safer.
The code prints:
2017-03-24T11:31:33
You notice it’s five and a half hours ahead of the input string as expected.
If you only require the date, not the time, use toLocalDate() instead of toLocalDateTime().
All of the above requires Java 8. Or the backport of the Java 8 date and time classes to Java 6 and 7, see ThreeTen Backport. If you cannot use Java 8 and do not want to depend on the backport, find your inspiration in this question: Not able to parse UTC date time to EST local time.
Use this
simpleDateFormat.setTimeZone("Your-Client's-Timezone");

Categories

Resources