Summary
-------
Support the "vanguard" format of the TZ Database.
Problem
-------
The [IANA time zone database][1] has been producing three formats of the same release, i.e., vanguard, main, and rearguard. JDK relies on the rearguard format which does not use negative DST and start/end time within the transition day. It is anticipated that rearguard format is going to be obsolete.
Solution
--------
Support negative DST offset and transition time beyond the transition day, so that JDK can use vanguard/main format files. Although the savings are expressed in negative in the vanguard files, JDK translates them into positive savings by offsetting the GMT (standard) offset by that negative savings value. For example for the "Eire" rule (excerpt only relevant portion:
# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
:
Rule Eire 1981 max - Mar lastSun 1:00u 0 -
Rule Eire 1996 max - Oct lastSun 1:00u -1:00 -
# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Europe/Dublin -0:25:00 - LMT 1880 Aug 2
:
# The next line is for when negative SAVE values are used.
1:00 Eire IST/GMT
# These three lines are for when SAVE values are always nonnegative.
# 1:00 - IST 1971 Oct 31 2:00u
# 0:00 GB-Eire GMT/IST 1996
# 0:00 EU GMT/IST
In the vanguard format, the GMT offset is one hour and one hour negative savings in winter, JDK interprets it as GMT offset is Z and no savigns in winter (same behavior as before).
The serialized form of `ZoneOffsetTransitionRule` needs to be modified to accommodate the `time` of the cutover beyond 0:00-24:00 so that it can differentiate old and new forms.
Specification
-------------
Add the following two methods in `java.time.zone.ZoneOffsetTransitionRule` class:
/**
* Obtains an instance defining the yearly rule to create transitions between two offsets.
* <p>
* Applications should normally obtain an instance from {@link ZoneRules}.
* This factory is only intended for use when creating {@link ZoneRules}.
*
* @param month the month of the month-day of the first day of the cutover week, not null
* @param dayOfMonthIndicator the day of the month-day of the cutover week, positive if the week is that
* day or later, negative if the week is that day or earlier, counting from the last day of the month,
* from -28 to 31 excluding 0
* @param dayOfWeek the required day-of-week, null if the month-day should not be changed
* @param time the cutover time in the 'before' offset expressed using a duration to allow values outside the range 00:00 to 23:59, not null
* @param timeDefnition how to interpret the cutover
* @param standardOffset the standard offset in force at the cutover, not null
* @param offsetBefore the offset before the cutover, not null
* @param offsetAfter the offset after the cutover, not null
* @return the rule, not null
* @throws IllegalArgumentException if the day of month indicator is invalid
* @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
* @throws IllegalArgumentException if {@code time.getNano()} returns non-zero value
* @since 14
*/
public static ZoneOffsetTransitionRule of(
Month month,
int dayOfMonthIndicator,
DayOfWeek dayOfWeek,
Duration time,
TimeDefinition timeDefnition,
ZoneOffset standardOffset,
ZoneOffset offsetBefore,
ZoneOffset offsetAfter)
/**
* Gets the cutover time in the 'before' offset expressed using a duration to allow values outside the range 00:00 to 23:59
* <p>
* The time is interpreted using the time definition.
*
* @return the local time of the transition, expressed in a duration, not null
* @since 14
*/
public Duration getLocalTimeDuration()
Mark these methods as deprecated, they are superseded by the above methods.
/**
* Obtains an instance defining the yearly rule to create transitions between two offsets.
* <p>
* Applications should normally obtain an instance from {@link ZoneRules}.
* This factory is only intended for use when creating {@link ZoneRules}.
*
* @param month the month of the month-day of the first day of the cutover week, not null
* @param dayOfMonthIndicator the day of the month-day of the cutover week, positive if the week is that
* day or later, negative if the week is that day or earlier, counting from the last day of the month,
* from -28 to 31 excluding 0
* @deprecated This method cannot handle transition ocurring outside of 00:00-24:00 time of
* the transition day. Use {@code of()} method that takes {@link java.time.Duration}.
* @param dayOfWeek the required day-of-week, null if the month-day should not be changed
* @param time the cutover time in the 'before' offset, not null
* @param timeEndOfDay whether the time is midnight at the end of day
* @param timeDefnition how to interpret the cutover
* @param standardOffset the standard offset in force at the cutover, not null
* @param offsetBefore the offset before the cutover, not null
* @param offsetAfter the offset after the cutover, not null
* @return the rule, not null
* @throws IllegalArgumentException if the day of month indicator is invalid
* @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
* @throws IllegalArgumentException if {@code time.getNano()} returns non-zero value
*/
@Deprecated(since="14", forRemoval=true)
public static ZoneOffsetTransitionRule of(
Month month,
int dayOfMonthIndicator,
DayOfWeek dayOfWeek,
LocalTime time,
boolean timeEndOfDay,
TimeDefinition timeDefnition,
ZoneOffset standardOffset,
ZoneOffset offsetBefore,
ZoneOffset offsetAfter)
/**
* Gets the local time of day of the transition which must be checked with
* {@link #isMidnightEndOfDay()}.
* <p>
* The time is converted into an instant using the time definition.
*
* @deprecated This method cannot handle transition ocurring outside of 00:00-24:00 time of
* the transition day. Use {@link getLocalTimeDuration()} method instead.
* @throws DateTimeException if the cut over time is outside of the transition day.
* @return the local time of day of the transition, not null
*/
@Deprecated(since="14", forRemoval=true)
public LocalTime getLocalTime()
/**
* Is the transition local time midnight at the end of day.
* <p>
* The transition may be represented as occurring at 24:00.
*
* @deprecated This method is superseded by {@link getLocalTimeDuration()}.
* @return whether a local time of midnight is at the start or end of the day
*/
@Deprecated(since="14", forRemoval=true)
public boolean isMidnightEndOfDay()
Change the method description of `writeReplace()` method related to `timeByte` and `timeSecs` from:
/**
* Writes the object using a
* <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
* @serialData
* Refer to the serialized form of
* <a href="{@docRoot}/serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>
* for the encoding of epoch seconds and offsets.
* <pre style="font-size:1.0em">{@code
*
* out.writeByte(3); // identifies a ZoneOffsetTransition
* final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
* final int stdOffset = standardOffset.getTotalSeconds();
* final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
* final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
* final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
* final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
* final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
* final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
* final int dowByte = (dow == null ? 0 : dow.getValue());
* int b = (month.getValue() << 28) + // 4 bits
* ((dom + 32) << 22) + // 6 bits
* (dowByte << 19) + // 3 bits
* (timeByte << 14) + // 5 bits
* (timeDefinition.ordinal() << 12) + // 2 bits
* (stdOffsetByte << 4) + // 8 bits
* (beforeByte << 2) + // 2 bits
* afterByte; // 2 bits
* out.writeInt(b);
* if (timeByte == 31) {
* out.writeInt(timeSecs);
* }
* if (stdOffsetByte == 255) {
* out.writeInt(stdOffset);
* }
* if (beforeByte == 3) {
* out.writeInt(offsetBefore.getTotalSeconds());
* }
* if (afterByte == 3) {
* out.writeInt(offsetAfter.getTotalSeconds());
* }
* }
* </pre>
*
* @return the replacing object, not null
*/
to:
/**
* Writes the object using a
* <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
* @serialData
* Refer to the serialized form of
* <a href="{@docRoot}/serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>
* for the encoding of epoch seconds and offsets.
* <pre style="font-size:1.0em">{@code
*
* out.writeByte(4); // identifies a ZoneOffsetTransitionRule, version 2.
* final long timeSecs = time.toSeconds();
* final int stdOffset = standardOffset.getTotalSeconds();
* final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
* final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
* final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
* final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
* final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
* final int dowByte = (dow == null ? 0 : dow.getValue());
* int b = (month.getValue() << 28) + // 4 bits
* ((dom + 32) << 22) + // 6 bits
* (dowByte << 19) + // 3 bits
* (timeByte << 14) + // 5 bits
* (timeDefinition.ordinal() << 12) + // 2 bits
* (stdOffsetByte << 4) + // 8 bits
* (beforeByte << 2) + // 2 bits
* afterByte; // 2 bits
* out.writeInt(b);
* out.writeLong(timeSecs);
* if (stdOffsetByte == 255) {
* out.writeInt(stdOffset);
* }
* if (beforeByte == 3) {
* out.writeInt(offsetBefore.getTotalSeconds());
* }
* if (afterByte == 3) {
* out.writeInt(offsetAfter.getTotalSeconds());
* }
* }
* </pre>
*
* @return the replacing object, not null
*/
Make the following changes in `java.util.SimpleTimeZone` class:
Parameter description of `startTime` in its constructors, and in `setStartRule()` methods from:
The daylight saving time starting time in local wall clock
time, which is local standard time in this case.
to:
The daylight saving time starting time in local wall clock
time (in milliseconds, can be outside 00:00-24:00), which is local
standard time in this case.
And similar changes should be made to the methods that take `endTime`, i.e. from:
The daylight saving time ending time in local wall clock
time, which is local standard time in this case.
to:
The daylight saving time ending time in local wall clock (in milliseconds, can be outside of 00:00-24:00), which is local
standard time in this case.
[1]: https://www.iana.org/time-zones