Java 从SimpleTimeZone获取ZoneId

Java 从SimpleTimeZone获取ZoneId,java,timezone,dst,java-time,Java,Timezone,Dst,Java Time,使用Java,我有一个SimpleTimeZone实例,其中包含来自遗留系统的GMT偏移量和夏令时信息 我想检索ZoneId,以便能够使用Java8时间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); s

使用Java,我有一个
SimpleTimeZone
实例,其中包含来自遗留系统的GMT偏移量和夏令时信息

我想检索
ZoneId
,以便能够使用Java8时间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();

我认为这是不可能的。人们也可能会问这有什么意义。如果一个时区不在奥尔森数据库中,它在现实世界中几乎不被使用,那么它在您的程序中有什么好的用途呢?当然,我理解这样一种情况,即您的遗留程序创建了一个确实表示在野外使用的时区的
SimpleTimeZone
,但只是给了它一个不正确的ID,因此
TimeZone.toZoneId()
无法进行正确的转换

我检查了
TimeZone.toZoneId()的源代码。它完全依赖于从该方法获得的值。它不查看偏移或分区规则。重要的是,
SimpleTimeZone
不会覆盖该方法。因此,如果您的
SimpleTimeZone
具有已知ID(包括EST或ACT等不受欢迎的缩写),您将获得正确的
ZoneId
。否则你就不会

因此,我认为您最好的选择是找出您的旧代码试图提供给您的时区的正确时区ID,然后从
ZoneId.of(String)
获取您的
ZoneId
。或者稍微好一点,构建自己的别名映射,其中包含旧代码中的一个或多个ID以及相应的现代ID,然后使用
ZoneId.of(String,map)

自动化翻译的一种可能尝试是迭代可用时区(通过
时区.getAvailableIDs()
时区.getTimeZone(String)
)获得),并使用
hasSameRules()
对每个时区进行比较。然后从ID字符串或从中获得的
时区创建您的
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”
的时区。当您调用
toZoneId()
时,它只调用
ZoneId.of(“GMT”)
(它使用与参数相同的ID,如中所述)。然后
ZoneId
类加载JVM中配置的任何夏时制信息(它不保留原始
SimpleTimeZone
对象的相同规则)

根据:如果区域ID等于“GMT”、“UTC”或“UT”,则结果是具有相同ID和与ZoneOffset.UTC等效的规则的区域ID。UTC根本没有DST规则

因此,如果您想要有一个具有相同DST规则的
ZoneId
实例,您必须手动创建它们(我不知道这是可能的,但实际上是,请检查下面)


你的DST规则 请看,您创建的实例具有以下规则(根据我的测试):

  • 标准偏移量为
    +02:00
    (UTC/GMT前2小时)
  • 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);
然后,我使用边界日期(DST开始和结束之前)进行测试:

输出为:

2017年1月1日00:00:00+0200
2017年1月1日01:01:00+0300
2017年2月5日00:00:00+0300
2017年2月4日23:01:00+0200

因此,它遵循上述规则(2017年1月1日午夜,偏移量为
+0200
,一分钟后进入DST(偏移量现在为
+0300
;相反的情况发生在5月2日(DST结束,偏移量回到
+0200


使用上述规则创建一个
ZoneId
不幸的是,您不能扩展
ZoneId
ZoneOffset
,也不能更改它们,因为它们都是不可变的。但是可以创建自定义规则并将它们分配给新的
ZoneId

而且它似乎没有办法直接将规则从
SimpleTimeZone
导出到
ZoneId
,因此您必须手动创建它们

首先,我们需要创建一个
ZoneRules
,该类包含偏移量何时以及如何更改的所有规则。为了创建它,我们需要构建一个包含两个类的列表:

  • ZoneOffsetTransition
    :定义偏移量更改的特定日期。必须至少有一个日期才能使其生效(空列表失败)
  • ZoneOffsetTransitionRule
    :定义一条通用规则,不受特定日期的限制(如“1月的第一个星期日偏移量从X变为Y”)。我们必须有两条规则(一条用于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);
请记住,您不应该将ID设置为“GMT”、“UTC”或任何有效ID(您可以使用
ZoneId.getAvailableZoneIds()
)检查所有现有ID。“GMT”和“UTC”是API内部使用的特殊名称,可能会导致意外行为。因此,请选择一个不存在的名称-我选择了
MyNewTimezone
(如果没有空格,则它将失败,因为
ZoneRegion
会在存在sp时引发异常
// 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);
// 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;
    }
}
// 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));
private ZoneId getZoneOffsetFor(final Date date, final TimeZone timeZone){
  int offsetInMillis = getOffsetInMillis(date, timeZone);
  return ZoneOffset.ofTotalSeconds( offsetInMillis / 1000 );
}

private int getOffsetInMillis(final Date date, final TimeZone timeZone){
  int offsetInMillis = timeZone.getRawOffset();
  if(timeZone.inDaylightTime(date)){
     offsetInMillis += timeZone.getDSTSavings();
  }
  return offsetInMillis;
}