Woo*_*ody 0 java timezone dst java-time
使用Java我有一个SimpleTimeZone具有GMT偏移和来自遗留系统的夏令时信息的实例.
我想检索ZoneId能够使用Java 8时间API.
实际上,toZoneId返回ZoneId没有夏令时的信息
SimpleTimeZone stz = new SimpleTimeZone( 2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY,1,1,1, Calendar.FEBRUARY,1,1,1, 1 * 60 * 60 * 1000);
stz.toZoneId();
Run Code Online (Sandbox Code Playgroud)
小智 5
首先,当你这样做时:
SimpleTimeZone stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, 1, 1, Calendar.FEBRUARY, 1, 1, 1, 1 * 60 * 60 * 1000);
Run Code Online (Sandbox Code Playgroud)
您正在创建ID等于的时区"GMT".当你打电话时toZoneId(),它只是调用ZoneId.of("GMT")(它使用与参数相同的ID,如@Ole VV的回答中所述).然后ZoneId类加载JVM中配置的任何夏令时信息(它不保留与原始SimpleTimeZone对象相同的规则).
并且根据ZoneIdjavadoc:如果区域ID等于'GMT','UTC'或'UT',则结果是具有相同ID的ZoneId,并且规则等同于ZoneOffset.UTC.并且ZoneOffset.UTC根本没有DST规则.
所以,如果你想拥有一个ZoneId具有相同DST规则的实例,你必须手工创建它们(我不知道它是可能的,但实际上是,请查看下面的内容).
查看SimpleTimeZonejavadoc,您创建的实例具有以下规则(根据我的测试):
+02:00(提前2小时UTC/GMT)1作为开始和结束时间通过)+03:00+02:00)实际上,根据javadoc,你应该在dayOfWeek参数中传递一个负数以这种方式工作,所以应该像这样创建时区:
stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, -Calendar.SUNDAY, 1, Calendar.FEBRUARY, 1, -Calendar.SUNDAY, 1, 1 * 60 * 60 * 1000);
Run Code Online (Sandbox Code Playgroud)
但在我的测试中,两者都以相同的方式工作(也许它修复了非负值).无论如何,我做了一些测试只是为了检查这些规则.首先,我SimpleDateFormat使用您的自定义时区创建了一个:
TimeZone t = TimeZone.getTimeZone("America/Sao_Paulo");
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss Z");
sdf.setTimeZone(t);
Run Code Online (Sandbox Code Playgroud)
然后我测试了边界日期(在夏令时开始和结束之前):
// 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())));
Run Code Online (Sandbox Code Playgroud)
输出是:
01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
05/02/2017 00:00:00 +0300
04/02/2017 23:01:00 +0200
因此,它遵循上述规则(在午夜01/01/2017偏移是+0200,一分钟之后它在DST中(现在是偏移+0300;相反发生在05/02(DST结束并且偏移返回到+0200)).
ZoneId使用上面的规则创建一个可惜的是,你不能扩展ZoneId和ZoneOffset,你不能同时更改它们,因为两者都是不可变的.但是可以创建自定义规则并将它们分配给新的规则ZoneId.
它似乎并没有有办法直接从出口的规则SimpleTimeZone来ZoneId,所以你必须手动创建它们.
首先,我们需要创建一个ZoneRules类,该类包含偏移何时以及如何更改的所有规则.为了创建它,我们需要构建一个包含2个类的列表:
ZoneOffsetTransition:定义偏移量更改的特定日期.必须至少有一个使其工作(使用空列表失败)ZoneOffsetTransitionRule:定义一般规则,不限于特定日期(例如"1月的第一个星期日,偏移从X变为Y").我们必须有2个规则(一个用于DST启动,另一个用于DST结束)那么,让我们创建它们:
// 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);
Run Code Online (Sandbox Code Playgroud)
我无法创建一个ZoneOffsetTransition在午夜后的第一个毫秒开始的时间(它们实际上是从午夜开始),因为秒的分数必须为零(如果不是,则ZoneOffsetTransition.of()抛出异常).所以,我决定在过去(LocalDateTime.MIN)设置一个日期,以免干扰规则.
但ZoneOffsetTransitionRule实例的工作方式与预期完全一样(DST在午夜后开始和结束1毫秒,就像SimpleTimeZone实例一样).
现在我们必须将其设置ZoneRules为时区.正如我所说,ZoneId不能扩展(构造函数不是公共的),也不是ZoneOffset(它是一个 final类).我最初以为设置规则的唯一方法是创建一个实例,并将其设置使用反射,但实际上该API提供一种方式来创建自定义ZoneId的通过扩展java.time.zone.ZoneRulesProvider类:
// new provider for my custom zone id's
public class CustomZoneRulesProvider extends ZoneRulesProvider {
@Override
protected Set<String> provideZoneIds() {
// returns only one ID
return Collections.singleton("MyNewTimezone");
}
@Override
protected ZoneRules provideRules(String zoneId, boolean forCaching) {
// returns the ZoneRules for the custom timezone
if ("MyNewTimezone".equals(zoneId)) {
ZoneRules rules = // create the ZoneRules as above
return rules;
}
return null;
}
// returns a map with the ZoneRules, check javadoc for more details
@Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
TreeMap<String, ZoneRules> map = new TreeMap<>();
ZoneRules rules = getRules(zoneId, false);
if (rules != null) {
map.put(zoneId, rules);
}
return map;
}
}
Run Code Online (Sandbox Code Playgroud)
请记住,您不应将ID设置为"GMT","UTC"或任何有效ID(您可以检查所有现有ID ZoneId.getAvailableZoneIds())."GMT"和"UTC"是API内部使用的特殊名称,可能会导致意外行为.所以选择一个不存在的名称 - 我选择了MyNewTimezone(没有空格,否则它会失败,因为ZoneRegion如果名称中有空格则抛出异常).
让我们来测试这个新的时区.必须使用以下ZoneRulesProvider.registerProvider方法注册新类:
// 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));
Run Code Online (Sandbox Code Playgroud)
输出是相同的(所以规则是相同的SimpleTimeZone):
01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
05/02/2017 00:00:00 +0300
04/02/2017 23:01:00 +0200
笔记:
CustomZoneRulesProvider创建只有一个新的ZoneId,但当然,你可以扩展它来创建更多.检查javadoc以获取有关如何更正实现自己的规则提供程序的更多详细信息.ZoneRules.一种方法是使用SimpleTimeZone.toString()方法(返回对象的内部状态)并读取javadoc以了解参数如何影响规则.SimpleTimeZone和ZoneId规则以不同的方式运行.我已经测试了不同年份的一些日期,似乎工作正常.