【问题标题】:Java Calendar timezone change not working as expectedJava 日历时区更改未按预期工作
【发布时间】:2017-06-28 22:09:02
【问题描述】:

我正在使用 java 日历在午夜 (GMT) 创建某个日期,然后将时区更改为我的本地时区,以确保请求的时间为 1:00 (GMT +1)。 下面的代码有效,包括。 2 个成功的断言。

TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT");
TimeZone TIMEZONE_LOCAL = TimeZone.getDefault(); // GMT + 1

private Calendar getMidnightGmtCalendarWithLocalTimezone() {
    Calendar calendar = Calendar.getInstance(Locale.GERMAN);
    calendar.set(2017, Calendar.JANUARY, 1, 0, 0, 0);

    calendar.setTimeZone(TIMEZONE_GMT); // Probably not required
    assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY)); // Assert 1

    // Log.i("TAG", "" + calendar.get(Calendar.HOUR_OF_DAY));

    calendar.setTimeZone(TIMEZONE_LOCAL);
    assertEquals(1, calendar.get(Calendar.HOUR_OF_DAY)); // Assert 2
    return calendar;
}

现在我删除了第一个断言,我预计不会有任何变化。现实:第二个断言现在失败了!我试图理解 .get() 的实现,显然它也计算了一些时间,所以它不仅仅是收集值。我仍然不明白为什么我的第二个断言失败了。

当我取消注释 Log 行时,第二个断言再次成功(只是为了确保问题来自 calendar.get(),而不是断言!)

所以第一个问题:为什么会发生这种情况? 第二个问题:当我将时间设置为格林威治标准时间的午夜时,如何确保我有一个本地时区的日历实例? (换句话说,我怎样才能正确转换时区?)

编辑: 我在 Android 上使用它,所以不能使用 Java8。 Joda time(如下所示)是一个很好的选择,我一定会研究一下。但是,作为我的问题的答案,我想知道 Java7 日历是如何工作的,因为我目前必须使用该代码。

【问题讨论】:

  • 如果您使用的是 JDK 8,我建议您放弃 Calendar 并尝试使用新的 java.time 包。您应该这样做 - 它是唯一一个没有超过其支持生命周期的 JDK。跨度>
  • @duffymo 我完全同意你的看法,只是想知道,你能否提供任何关于为什么应该放弃使用Calendar 类的权威描述?
  • 权威? java.util.Calendar 是 JDK 1.0 的经典版本。从一开始就承认它复杂且难以使用。 (如果我没记错的话,由 IBM 编写。)开发 JODA 是为了解决这些问题。它非常成功,Oracle 将它作为 java.time 包折叠到 JDK 8 中。日历是超过 20 年的故障。它已被公认的巨大改进所取代。我根据历史要求权威。这对你来说足够了吗?
  • @duffymo 澄清一下,Joda-Time 启发了 java.time,并由同一个人 Stephen Colbourne 领导。但是 java.time 是重新设计的,不是 Joda-Time 的替代品。所以你关于“折叠成”的句子过于简单化了。否则你的观点是正确的:java.time 类内置于 Java 8 和更高版本中,以取代令人困惑和有缺陷的旧日期时间类,例如 CalendarDate,它们现在是 legacy。 java.time pkg 文档的第一行:日期、时间、瞬间和持续时间的主要 API。
  • @BasilBourque,感谢您的更正。我很欣赏这种教育。

标签: android calendar timezone java-7


【解决方案1】:

所以,我已经运行了以下代码 sn-p:

public static void main(String[] args) throws Exception {
    TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT");

    Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
    calendar.set(2017, Calendar.JANUARY, 1, 0, 0, 0);

    System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
    calendar.setTimeZone(TIMEZONE_GMT); // Probably not required
    System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
}

在执行它时,我在控制台中得到了023,这是正确的。

然后我注释掉了第一个Sysout 语句,它打印了0(我希望它打印23)。

在进一步探索时,我发现this SO 答案正确地解释了日历对象仅存储毫秒而TimeZone 未被考虑在内。

所以,如果我是你,我不会太担心asserts,我会确保我得到一个具有所需时区的Calendar 实例(如果默认时区设置为您需要的时区,则为默认时区) ,例如:

Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
//OR
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Berlin"));
//OR
Calendar calendar = Calendar.getInstance(); //If default timezone is Europe/Berlin

【讨论】:

    【解决方案2】:

    tl;博士

    使用 java.time,而不是 java.util.Calendar

    Instant.parse( "2017-01-01T00:00:00Z" )          // UTC.
           .atZone( ZoneId.of( "Africa/Algiers" ) )  // Time zone with offset +01:00.
    

    避免Calendar

    包括Calendar 在内的旧日期时间类很麻烦、设计不佳、令人困惑且存在缺陷。避开他们。试图理解他们过度使用的 API 毫无意义。

    Java 8 及更高版本中内置的 java.time package 类取代了旧的日期时间类。

    Instant

    Instant 类表示UTC 中时间轴上的时刻,分辨率为纳秒。

    Instant instant = Instant.parse( "2017-01-01T00:00:00Z" );
    

    instant.toString(): 2017-01-01T00:00:00Z

    ZonedDateTime

    您要求在比 UTC 早一小时的时区查看。所以让我们尝试应用时区Africa/Algiers 来获得ZonedDateTime

    ZoneId z = ZoneId.of( "Africa/Algiers" );
    ZonedDateTime zdt = instant.atZone( z );
    

    zdt.toString(): 2017-01-01T01:00+01:00[非洲/阿尔及尔]

    OffsetDateTime

    如果您想通过参数构造 UTC 日期时间而不是解析字符串(如上所示),请将参数传递给 OffsetDateTime 的工厂方法。指定方便的常量ZoneOffset.UTC。月份的编号很合理,1 月至 12 月为 1-12,与 Calendar 形成对比。

    OffsetDateTime odt = OffsetDateTime.of( 2017 , 1 , 1 , 0 , 0 , 0 , 0 , ZoneOffset.UTC ) ;
    

    odt.toString(): 2017-01-01T00:00Z

    与上面类似,我们可以调整到一个时区。针对Daylight Saving Time (DST)等异常进行了调整。

    ZoneId z = ZoneId.of( "Africa/Algiers" );
    ZonedDateTime zdt = odt.atZoneSameInstant( z );
    

    zdt.toString(): 2017-01-01T01:00+01:00[非洲/阿尔及尔]

    看到这个code run line in IdeOne.com

    Locale

    关于Locale 在问题代码中的使用……Locale 与时区完全正交,独立且不同。 Locale 对象仅影响生成的字符串的格式,以表示日期时间值。


    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    从哪里获得 java.time 类?

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-29
      • 1970-01-01
      • 2020-12-02
      • 2023-02-02
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多