First of all, when you do this:
SimpleTimeZone stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, 1, 1, Calendar.FEBRUARY, 1, 1, 1, 1 * 60 * 60 * 1000);
You create a time zone with an identifier equal to "GMT" . When you call toZoneId() , it just calls ZoneId.of("GMT") (it uses the same identifier as the parameter, as already said in @Ole VV answer ). And then the ZoneId class loads any ZoneId data configured in the JVM (it doesn't save the same rules from the original SimpleTimeZone object).
And according to ZoneId javadoc : If the zone identifier is 'GMT' , 'UTC' or 'UT', then the result will be ZoneId with the same identifier and the rules equivalent to ZoneOffset.UTC. And ZoneOffset.UTC has no DST rules at all.
So, if you want to have an instance of ZoneId with the same DST rules, you will have to create them manually (I did not know that this is possible, but actually, check below).
Your DST rules
Looking at SimpleTimeZone javadoc , the instance you created has the following rules (according to my tests):
- standard offset
+02:00 (2 hours ahead of UTC / GMT) - DST starts on the first Sunday of January (see javadoc for more details), 1 millisecond after midnight (you have passed
1 as the start and end time) - when in DST the offset changes to
+03:00 - DST ends on the first Sunday of February, 1 millisecond after midnight (then the offset returns to
+02:00 )
In fact, according to javadoc, you had to pass a negative number in the dayOfWeek parameters to work this way, so the time zone should be created as follows:
stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, -Calendar.SUNDAY, 1, Calendar.FEBRUARY, 1, -Calendar.SUNDAY, 1, 1 * 60 * 60 * 1000);
But in my tests both worked the same way (maybe it captures non-negative values). Anyway, I checked some tests to check these rules. First I created SimpleDateFormat with your custom timezone:
TimeZone t = TimeZone.getTimeZone("America/Sao_Paulo"); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss Z"); sdf.setTimeZone(t);
Then I tested with border dates (before the start and end of the DST):
// starts at 01/01/2017 (first Sunday of January) ZonedDateTime z = ZonedDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(2)); // 01/01/2017 00:00:00 +0200 (not in DST yet, offset is still +02) System.out.println(sdf.format(new Date(z.toInstant().toEpochMilli()))); // 01/01/2017 01:01:00 +0300 (DST starts, offset changed to +03) System.out.println(sdf.format(new Date(z.plusMinutes(1).toInstant().toEpochMilli()))); // ends at 05/02/2017 (first Sunday of February) z = ZonedDateTime.of(2017, 2, 5, 0, 0, 0, 0, ZoneOffset.ofHours(3)); // 05/02/2017 00:00:00 +0300 (in DST yet, offset is still +03) System.out.println(sdf.format(new Date(z.toInstant().toEpochMilli()))); // 04/02/2017 23:01:00 +0200 (DST ends, offset changed to +02 - clock moves back 1 hour: from midnight to 11 PM of previous day) System.out.println(sdf.format(new Date(z.plusMinutes(1).toInstant().toEpochMilli())));
Output:
01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
02/05/2017 00:00:00 +0300
02/02/2017 23:01:00 +0200
So, this follows the rules described above (on 01/01/2017 midnight, the offset is +0200 , after a minute it is in DST (now the offset is +0300 ), the opposite happens on 05/02 (the end of the DST and the offset returns to +0200 )).
Create a ZoneId with the rules above
Unfortunately, you cannot extend ZoneId and ZoneOffset , and you cannot change them either, because both of them are immutable. But itβs possible to create custom rules and assign them to the new ZoneId .
And it has no way to directly export rules from SimpleTimeZone to ZoneId , so you have to create them manually.
First we need to create ZoneRules , a class that contains all the rules when and how the offset changes. To create it, we need to build a list of 2 classes:
ZoneOffsetTransition : Defines a specific offset change date. There must be at least one to make it work (with an empty list, it failed)ZoneOffsetTransitionRule : defines a general rule that is not limited to a specific date (for example, "on the first Sunday of January, the offset changes from X to Y" ). We must have 2 rules (one to start DST and one to complete DST).
So, create them:
// offsets (standard and DST) ZoneOffset standardOffset = ZoneOffset.ofHours(2); ZoneOffset dstOffset = ZoneOffset.ofHours(3); // you need to create at least one transition (using a date in the very past to not interfere with the transition rules) LocalDateTime startDate = LocalDateTime.MIN; LocalDateTime endDate = LocalDateTime.MIN.plusDays(1); // DST transitions (date when it happens, offset before and offset after) - you need to create at least one ZoneOffsetTransition start = ZoneOffsetTransition.of(startDate, standardOffset, dstOffset); ZoneOffsetTransition end = ZoneOffsetTransition.of(endDate, dstOffset, standardOffset); // create list of transitions (to be used in ZoneRules creation) List<ZoneOffsetTransition> transitions = Arrays.asList(start, end); // a time to represent the first millisecond after midnight LocalTime firstMillisecond = LocalTime.of(0, 0, 0, 1000000); // DST start rule: first Sunday of January, 1 millisecond after midnight ZoneOffsetTransitionRule startRule = ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.SUNDAY, firstMillisecond, false, TimeDefinition.WALL, standardOffset, standardOffset, dstOffset); // DST end rule: first Sunday of February, 1 millisecond after midnight ZoneOffsetTransitionRule endRule = ZoneOffsetTransitionRule.of(Month.FEBRUARY, 1, DayOfWeek.SUNDAY, firstMillisecond, false, TimeDefinition.WALL, standardOffset, dstOffset, standardOffset); // list of transition rules List<ZoneOffsetTransitionRule> transitionRules = Arrays.asList(startRule, endRule); // create the ZoneRules instance (it'll be set on the timezone) ZoneRules rules = ZoneRules.of(start.getOffsetAfter(), end.getOffsetAfter(), transitions, transitions, transitionRules);
I could not create a ZoneOffsetTransition that starts the first millisecond after midnight (they actually start at exactly midnight), because the fraction of seconds should be zero (if not, ZoneOffsetTransition.of() throws an exception). So, I decided to set the date in the past ( LocalDateTime.MIN ) so as not to interfere with the rules.
But ZoneOffsetTransitionRule instances work exactly as expected (DST starts and ends 1 millisecond after midnight, just like the SimpleTimeZone instance).
Now we need to set this ZoneRules in the time zone. As I said, ZoneId cannot be extended (the constructor is not public), and not one ZoneOffset (this is the final class). Initially, I thought the only way to set the rules is to instantiate and set it using reflection, but the API actually provides a way to create custom ZoneId by extending the java.time.zone.ZoneRulesProvider class:
Please keep in mind that you should not set the identifier "GMT", "UTC" or any valid identifier (you can check all existing identifiers with ZoneId.getAvailableZoneIds() ). "GMT" and "UTC" are special names used inside the API, and this can lead to unexpected behavior. Therefore, select a name that does not exist. I chose MyNewTimezone (without spaces, otherwise it will fail because ZoneRegion throws an exception if there is a space in the name).
Check out this new time zone. The new class must be registered using the ZoneRulesProvider.registerProvider method:
// register the new zonerules provider ZoneRulesProvider.registerProvider(new CustomZoneRulesProvider()); // create my custom zone ZoneId customZone = ZoneId.of("MyNewTimezone"); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss Z"); // starts at 01/01/2017 (first Sunday of January) ZonedDateTime z = ZonedDateTime.of(2017, 1, 1, 0, 0, 0, 0, customZone); // 01/01/2017 00:00:00 +0200 (not in DST yet, offset is still +02) System.out.println(z.format(fmt)); // 01/01/2017 01:01:00 +0300 (DST starts, offset changed to +03) System.out.println(z.plusMinutes(1).format(fmt)); // ends at 05/02/2017 (first Sunday of February) z = ZonedDateTime.of(2017, 2, 5, 0, 0, 0, 0, customZone); // 05/02/2017 00:00:00 +0300 (in DST yet, offset is still +03) System.out.println(z.format(fmt)); // 04/02/2017 23:01:00 +0200 (DST ends, offset changed to +02 - clock moves back 1 hour: from midnight to 11 PM of previous day) System.out.println(z.plusMinutes(1).format(fmt));
The output is the same (therefore, the rules are the same as those used by SimpleTimeZone ):
01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
02/05/2017 00:00:00 +0300
02/02/2017 23:01:00 +0200
Notes:
CustomZoneRulesProvider creates only one new ZoneId , but of course you can expand it to create more. Check out javadoc for more details on how to fix the implementation of your own rules provider.- Before creating
ZoneRules , you need to check exactly what rules are used in custom time zones. One way is to use the SimpleTimeZone.toString() method (which returns the internal state of the object) and read javadoc to find out how the parameters affect the rules. - I have not tested enough cases to find out if there is a specific date when the
SimpleTimeZone and ZoneId behave differently. I tested several dates in different years and it seems to work fine.