首页
学习
活动
专区
圈层
工具
发布

()更改DST
EN

Stack Overflow用户
提问于 2021-11-24 23:52:24
回答 2查看 109关注 0票数 4

首先,我想说明一下,我知道Java类正在被其他更好的库所取代。也许我偶然发现了“日历”不受欢迎的原因之一。

我在日历中遇到了令人沮丧的行为,因为它涉及到夏令时结束时的重叠时间。

代码语言:javascript
复制
public void annoying_issue()
{
    Calendar midnightPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
    midnightPDT.set(Calendar.YEAR, 2021);
    midnightPDT.set(Calendar.MONTH, 10);
    midnightPDT.set(Calendar.DAY_OF_MONTH, 7);
    midnightPDT.set(Calendar.HOUR_OF_DAY, 0);
    midnightPDT.set(Calendar.MINUTE, 0);
    midnightPDT.set(Calendar.SECOND, 0);
    midnightPDT.set(Calendar.MILLISECOND, 0);

    Calendar oneAMPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
    oneAMPDT.setTimeInMillis(midnightPDT.getTimeInMillis() + (60*60*1000));//this is the easiest way I've found to get to the first 1am hour at DST overlap

    System.out.println(new Date(midnightPDT.getTimeInMillis()));//prints the expected "Sun Nov 7 00:00:00 PDT 2021" 
    System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PDT 2021" also expected

    oneAMPDT.clear(Calendar.MINUTE);//minute is already 0 so no change should occur... RIGHT!? 
    
    //WRONG!!!!
    //The time is now in PST! The millisecond value has increased by 3600000, too!!
    System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PST 2021"
}

按照注释,您将看到清除日历中的分钟字段实际上将其移动了一个小时!该死的!?

当我使用oneAMPDT.set(Calendar.MINUTE, 0)时也会发生这种情况。

这是预期的行为吗?有什么办法可以防止这种情况发生吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-11-25 00:24:09

避免旧式日期-时间类;需要时进行转换

正如您注意到的,Calendar几年前被JSR 310中定义的java.time类所取代(一致通过)。正如您注意到的,避免使用CalendarDate等有很多原因。

如果您必须有一个Calendar对象来与尚未更新为java.time的旧代码进行互操作,则在使用java.time完成工作后进行转换。

java.time

指定所需的时区。注意,US/Pacific只是实际时区America/Los_Angeles的别名。

代码语言:javascript
复制
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" ) ;

指定你想要的时刻。

代码语言:javascript
复制
LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 ) ;

在您的代码中,您似乎假设一天中的第一个时刻发生在00:00。情况并不总是如此。某些时区中的某些日期可能在另一个时间开始。因此,让java.time确定一天中的第一个时刻。

代码语言:javascript
复制
ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles ) ;

firstMomentOfThe7thInLosAngeles.toString():2021-11-07T00:00-07:00美国/洛杉矶

但你又跳到了另一个时刻,凌晨1点

代码语言:javascript
复制
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) ) ;

oneAmOnThe7thLosAngeles.toString():2021-11-07T01:00-07:00美国/洛杉矶

该日的时间可能存在,也可能不存在于该区域的那个日期。如果需要,ZonedDateTime类将进行调整。

您使用了变量的名称midnightPDT。我建议避免使用midnight这个术语,因为它的使用混淆了日期时间的处理,而没有一个精确的定义。如果这是你的意思,我建议使用“一天中的第一分钟”这个词。

您提取自1970年第一时刻的划时代引用以来的毫秒数,如世界协调时,1970-01-01T00:00Z所示。

代码语言:javascript
复制
Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant() ;
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli() ;

firstMomentOfThe7thInLosAngelesAsSeenInUtc.toString():2021-11-07T07:00:00Z millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000

你在凌晨1点的时候也这么做。

代码语言:javascript
复制
Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant() ;
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli() ;

oneAmOnThe7thLosAngelesAsSeenInUtc.toString():2021-11-07T08:00:00Z millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000

我们应该看到一个小时的差别。1小时=3 600 000= 60 * 60 *1 000。

代码语言:javascript
复制
long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles );  // 3,600,000 = 60 * 60 * 1,000.

差夫= 3600000

切割器

然后继续提到夏令时(DST)切割器。美国DST的截止日期是凌晨2点,而不是凌晨1点。在凌晨2点到达的那一刻,时钟又回到凌晨1点,第二次是凌晨1点-2点。

为了达到切入点,让我们加一个小时。

代码语言:javascript
复制
ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );

cutover_Addition =2021-11-07T01:00-08:00美国/洛杉矶

请注意,一天的时间显示相同的(上午1),但与世界协调时的偏移已经从世界协调时后的7小时改为现在的世界协调时后8小时。这就是你所寻求的时差。

让我们计算一下这第三个时刻从时代开始的毫秒数。在我们有一天的第一分钟(00:00),然后第一个发生凌晨1时,现在我们有第二个发生在这个“撤退”日期2021年11月7日上午1点。

代码语言:javascript
复制
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();

1636275600000 Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc,cutover_Addition.toInstant() )= PT2H Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc,cutover_Addition.toInstant() )= PT1H

ZonedDateTime类确实提供了在切入的这些时刻使用的一对方法:withEarlierOffsetAtOverlapwithLaterOffsetAtOverlap

代码语言:javascript
复制
ZonedDateTime cutover_OverlapEarlier =
        cutover_Addition
                .withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
        cutover_Addition
                .withLaterOffsetAtOverlap();

cutover_OverlapEarlier =2021-11-07T01:00-07:00美国/洛杉矶 cutover_OverlapLater =2021-11-07T01:00-08:00美国/洛杉矶

Calendar

如果您确实需要一个Calendar对象,只需转换即可。

代码语言:javascript
复制
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles ) ;
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles ) ;
Calendar z = GregorianCalendar.from( cutover_Addition );

如果您的目标仅仅是挣扎于理解Calendar类行为,我建议您停止受虐行为。没有任何意义。Sun、Oracle和JCP社区都放弃了对这些可怕的遗留日期-时间类。我建议你也这么做。

示例代码

把上面所有的代码拼凑在一起。

代码语言:javascript
复制
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" );

LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 );

ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles );
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) );

Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant();
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli();

Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant();
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli();

long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles );  // 3,600,000 = 60 * 60 * 1,000.

ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();
ZonedDateTime cutover_OverlapEarlier =
        cutover_Addition
                .withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
        cutover_Addition
                .withLaterOffsetAtOverlap();

如果需要,请转换为遗留类。

代码语言:javascript
复制
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles );
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles );
Calendar z = GregorianCalendar.from( cutover_Addition );

转储到控制台。

代码语言:javascript
复制
System.out.println( "firstMomentOfThe7thInLosAngeles = " + firstMomentOfThe7thInLosAngeles );
System.out.println( "oneAmOnThe7thLosAngeles = " + oneAmOnThe7thLosAngeles );

System.out.println( "firstMomentOfThe7thInLosAngelesAsSeenInUtc = " + firstMomentOfThe7thInLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_FirstMomentOf7thLosAngeles = " + millisSinceEpoch_FirstMomentOf7thLosAngeles );

System.out.println( "oneAmOnThe7thLosAngelesAsSeenInUtc = " + oneAmOnThe7thLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_OneAmOn7thLosAngeles = " + millisSinceEpoch_OneAmOn7thLosAngeles );

System.out.println( "diff = " + diff );

System.out.println( "x = " + x );
System.out.println( "y = " + y );
System.out.println( "z = " + z );

System.out.println( "cutover_Addition = " + cutover_Addition );
System.out.println( "millisSinceEpoch_Cutover = " + millisSinceEpoch_Cutover );
System.out.println( "Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "cutover_OverlapEarlier = " + cutover_OverlapEarlier );
System.out.println( "cutover_OverlapLater = " + cutover_OverlapLater );

跑的时候。

代码语言:javascript
复制
firstMomentOfThe7thInLosAngeles = 2021-11-07T00:00-07:00[America/Los_Angeles]
oneAmOnThe7thLosAngeles = 2021-11-07T01:00-07:00[America/Los_Angeles]
firstMomentOfThe7thInLosAngelesAsSeenInUtc = 2021-11-07T07:00:00Z
millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000
oneAmOnThe7thLosAngelesAsSeenInUtc = 2021-11-07T08:00:00Z
millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000
diff = 3600000
x = java.util.GregorianCalendar[time=1636268400000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
y = java.util.GregorianCalendar[time=1636272000000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
z = java.util.GregorianCalendar[time=1636275600000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]
cutover_Addition = 2021-11-07T01:00-08:00[America/Los_Angeles]
millisSinceEpoch_Cutover = 1636275600000
Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT2H
Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT1H
cutover_OverlapEarlier = 2021-11-07T01:00-07:00[America/Los_Angeles]
cutover_OverlapLater = 2021-11-07T01:00-08:00[America/Los_Angeles]
票数 4
EN

Stack Overflow用户

发布于 2021-11-25 07:31:28

java.time

这是预期的行为吗?不是的。我认为这是一只虫子。

有什么办法可以防止这种情况发生吗?是的,您已经提到或至少暗示的方式:使用ZonedDateTime而不是Calendar。巴兹尔·伯克已经说过了。作为一个适度的补充,我想展示从CalendarZonedDateTime的全程往返,将分钟设置为0并转换回Calendar。如果您需要它来与您的遗留代码进行互操作性。

代码语言:javascript
复制
    GregorianCalendar oneAmPdt = new GregorianCalendar(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
    oneAmPdt.clear();
    oneAmPdt.set(2021, Calendar.NOVEMBER, 7, 0, 0);
    oneAmPdt.add(Calendar.HOUR_OF_DAY, 1);
    System.out.println(oneAmPdt.getTime());

    ZonedDateTime zdt = oneAmPdt.toZonedDateTime();

    // Minute is already 0 so no change should occur... RIGHT!?
    zdt = zdt.withMinute(0);

    oneAmPdt = GregorianCalendar.from(zdt);

    System.out.println(oneAmPdt.getTime());

输出:

星期日07 01:00:00 PDT 2021太阳11月07 01:00 PDT 2021

但我用的是GregorianCalendar而不是Calendar?你也是。GregorianCalendar是从Calendar.getIntance()获得的Calendar的子类。在某些环境中,您可能会得到一个不同的子类来反映那里使用的日历系统,并且您对set的初始调用不会给出您预期的结果。在这种情况下,您需要一个GregorianCalendar (如果从一开始就没有ZonedDateTime )。

在修改旧代码时,即使不是为了规避旧CalendarGregorianCalendar类中的错误,我也可能以上述方式进行修改。这是向java.time长期过渡的一小步。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70104308

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档