我正在尝试使用 Java 的 ZoneId
和 ZoneOffsetTransitionRule
为 iCalendar VTIMEZONE
对象建模。
我的 VTIMEZONE
对象看起来像
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
我需要创建自己的 ZoneId
来对此进行建模,因为据我所知,没有可用于这些偏移量且 DST 开始的 ZoneId
在一月的第一个星期一(而不是三月的某个星期日)。
我有以下用于创建 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));
但我不确定它是否正确或如何从中创建一个 ZoneId
。
- 该转换规则对我的
VTIMEZONE
的DAYLIGHT
组件建模是否准确? - 如何从中创建一个
ZoneId
,以便最终创建一个ZonedDateTime
?
最佳答案
获得 ZoneId
的唯一方法(至少如果我们不是非常 hacky)是通过 ZoneId
的工厂方法和它的子类 ZoneOffset
。乍一看,这似乎与内置的 ZoneId
无关。但是,有一个后门用于指定 ZoneId.of
可以生成的其他 ZoneId
。它称为 ZoneRulesProvider
。我们需要指定一个新的唯一 ID,并且需要指定区域规则(因此得名 ZoneRulesProvider
)。
因此,通过您的 ZoneOffsetTransitionRule
,您已经在路上了。不过,我们需要其中两个,一个用于过渡到夏令时(通常在 Spring 发生),另一个用于在秋季过渡到夏令时。
当然,以下 list 不是生产代码,只是为了证明开发和注册您自己的 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));
代码打印:
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]
我们看到我们在转换到标准时间之前和转换到夏令时之后再次获得了 +02:00 的预期 DST 偏移。
关于java - Java 中的自定义 ZoneIds/时区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51406787/