问题
I'm attempting to model an iCalendar VTIMEZONE
object using Java's ZoneId
and ZoneOffsetTransitionRule
.
My VTIMEZONE
object looks like
BEGIN:VTIMEZONE
TZID:Central European Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=MO
END:DAYLIGHT
END:VTIMEZONE
I need to create my own ZoneId
to model this because, as far as I know, there isn't a ZoneId
available with these offsets and in which DST starts on the first Monday of January (as opposed to some Sunday of March).
I have the following for creating a ZoneOffsetTransitionRule
ZoneOffsetTransitionRule of =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofHours(1),
ZoneOffset.ofHours(1), ZoneOffset.ofHours(2));
But I'm not sure if it's correct or how to create a ZoneId
from this.
- Is that transition rule accurate to model the
DAYLIGHT
component of myVTIMEZONE
? - How can I create a
ZoneId
from this so I can eventually create aZonedDateTime
?
回答1:
The only way to get a ZoneId
(at least if we’re not very hacky) is through the factory methods of ZoneId
and its subclass ZoneOffset
. It might seem at first that this leaves of with the built-in ZoneId
s. However, there’s a backdoor for specifying additional ZoneId
s that ZoneId.of
can then produce. It’s called ZoneRulesProvider
. We need to specify an new and unique ID and we need to specify the zone rules (hence the name ZoneRulesProvider
).
So with your ZoneOffsetTransitionRule
you are already on the way. We need two of them, though, yours for transitioning to DST (which would normally have happened in the spring) and one for going the other way in the fall.
The following listing isn’t production code, of course, but is just to demonstrate that it is doable to develop and register your own ZoneRulesProvider
.
final String customZoneId = "Custom-CEST-1";
final ZoneOffset standardOffset = ZoneOffset.ofHours(1);
final ZoneOffset summerTimeOffset = ZoneOffset.ofHours(2);
// At least one transistion is required
ZoneOffsetTransition initialTransition = ZoneOffsetTransition.of(
LocalDateTime.of(1601, 1, 1, 3, 0), summerTimeOffset, standardOffset);
List<ZoneOffsetTransition> transitionList = List.of(initialTransition);
// Rules for going to and from summer time (DST)
ZoneOffsetTransitionRule springRule =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
standardOffset, summerTimeOffset);
ZoneOffsetTransitionRule fallRule =
ZoneOffsetTransitionRule.of(Month.OCTOBER, -1, DayOfWeek.SUNDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
summerTimeOffset, standardOffset);
ZoneRules rules = ZoneRules.of(standardOffset, standardOffset,
transitionList, transitionList, List.of(springRule, fallRule));
// The heart of the magic: the ZoneRulesProvider
ZoneRulesProvider customProvider = new ZoneRulesProvider() {
@Override
protected Set<String> provideZoneIds() {
return Set.of(customZoneId);
}
@Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
return new TreeMap<>(Map.of(customZoneId, rules));
}
@Override
protected ZoneRules provideRules(String zoneId, boolean forCaching) {
return rules;
}
};
// Registering the ZoneRulesProvider is the key to ZoneId using it
ZoneRulesProvider.registerProvider(customProvider);
// Get an instance of our custom ZoneId
ZoneId customZone = ZoneId.of(customZoneId);
// Transition to standard time was Sunday, October 29, 2017,
// so try the day before and the day after
System.out.println(LocalDate.of(2017, Month.OCTOBER, 28).atStartOfDay(customZone));
System.out.println(LocalDate.of(2017, Month.OCTOBER, 30).atStartOfDay(customZone));
// The special thing about our custom ZoneID is that transition to DST
// happened on Monday, January 1. Try the day before and the day after.
System.out.println(LocalDate.of(2017, Month.DECEMBER, 31).atStartOfDay(customZone));
System.out.println(LocalDate.of(2018, Month.JANUARY, 2).atStartOfDay(customZone));
The code prints:
2017-10-28T00:00+02:00[Custom-CEST-1]
2017-10-30T00:00+01:00[Custom-CEST-1]
2017-12-31T00:00+01:00[Custom-CEST-1]
2018-01-02T00:00+02:00[Custom-CEST-1]
We see that we get the expected DST offset of +02:00 exactly before the transition to standard time and again after the transition to summer time.
来源:https://stackoverflow.com/questions/51406787/custom-zoneids-time-zones-in-java