java timezone id_java – 从SimpleTimeZone获取ZoneId

首先,当你这样做时:

SimpleTimeZone stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, 1, 1, Calendar.FEBRUARY, 1, 1, 1, 1 * 60 * 60 * 1000);

您正在创建ID等于“GMT”的时区.当你调用ZoneId()时,它只调用ZoneId.of(“GMT”)(它使用与参数相同的ID,如@Ole V.V.’s answer中已经说明的那样).然后,ZoneId类加载在JVM中配置的任何夏令时信息(它不保留与原始SimpleTimeZone对象相同的规则).

并且根据ZoneId javadoc:如果区域ID等于’GMT’,’UTC’或’UT’,则结果是具有相同ID的ZoneId,并且规则等同于ZoneOffset.UTC.而ZoneOffset.UTC根本没有DST规则.

因此,如果你想拥有一个具有相同DST规则的ZoneId实例,你必须手工创建它们(我不知道它是可能的,但实际上是,请查看下面的内容).

您的DST规则

查看SimpleTimeZone javadoc,您创建的实例具有以下规则(根据我的测试):

>标准偏差为02:00(提前2小时UTC / GMT)

> DST从1月的第一个星期日开始(请查看javadoc以获取更多详细信息),午夜后1毫秒(您将1作为开始和结束时间)

>在DST时,偏移更改为03:00

> DST在2月的第一个星期日结束,午夜后1毫秒(然后偏移回到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);

但在我的测试中,两者都以相同的方式工作(也许它修复了非负值).无论如何,我做了一些测试只是为了检查这些规则.首先,我使用您的自定义时区创建了一个SimpleDateFormat:

TimeZone t = TimeZone.getTimeZone("America/Sao_Paulo");

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss Z");

sdf.setTimeZone(t);

然后我测试了边界日期(在夏令时开始和结束之前):

// 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())));

输出是:

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 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 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);

我无法创建一个在午夜后的第一个毫秒开始的ZoneOffsetTransition(它们实际上恰好在午夜开始),因为秒的分数必须为零(如果不是,则ZoneOffsetTransition.of()抛出异常).所以,我决定在过去设置一个日期(LocalDateTime.MIN),以免干扰规则.

但是ZoneOffsetTransitionRule实例的工作方式与预期完全一样(DST在午夜之后开始和结束1毫秒,就像SimpleTimeZone实例一样).

现在我们必须将此ZoneRules设置为时区.正如我所说,ZoneId无法扩展(构造函数不公开),ZoneOffset也不是(最终类).我最初认为设置规则的唯一方法是创建一个实例并使用反射设置它,但实际上API提供了一种通过扩展java.time.zone.ZoneRulesProvider类来创建自定义ZoneId的方法:

// new provider for my custom zone id's

public class CustomZoneRulesProvider extends ZoneRulesProvider {

@Override

protected Set 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 provideVersions(String zoneId) {

TreeMap map = new TreeMap<>();

ZoneRules rules = getRules(zoneId, false);

if (rules != null) {

map.put(zoneId, rules);

}

return map;

}

}

请记住,您不应将ID设置为“GMT”,“UTC”或任何有效ID(您可以使用ZoneId.getAvailableZoneIds()检查所有现有ID). “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));

输出是相同的(因此规则与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,但当然您可以扩展它以创建更多. Check the javadoc了解有关如何更正实施您自己的规则提供程序的更多详细信息.

>在创建ZoneRules之前,您必须确切地检查自定义时区的规则.一种方法是使用SimpleTimeZone.toString()方法(返回对象的内部状态)并读取javadoc以了解参数如何影响规则.

>我没有测试足够的案例来了解SimpleTimeZone和ZoneId规则是否有某种特定日期以不同的方式表现.我已经测试了不同年份的一些日期,似乎工作正常.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值