Java日历时区更改无法按预期工作

时间:2017-02-10 14:10:52

标签: android calendar timezone java-7

我使用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(),而不是断言!)

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

修改 我在Android上使用它,所以不能使用Java8。 Joda时间(如下所示)是一个很好的选择,我肯定会对此进行研究。但是,作为我的问题的答案,我想知道如何使用Java7 Calendar,因为我现在已经绑定了该代码。

2 个答案:

答案 0 :(得分:4)

TL;博士

使用java.time,而不是stFields

<CFHTTPPARAM VALUE="#stFields#" TYPE="body">

避免java.util.Calendar

包括serializeJSON function在内的旧日期时间类很麻烦,设计很差,容易混淆和有缺陷。避免他们。尝试理解他们过度的API是没有意义的。

遗留日期时间类被Java 8及更高版本中内置的Calendar类所取代。

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

java.time package类代表Instant中时间轴上的一个时刻,分辨率为纳秒。

Calendar
  

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

Instant

您要求在比UTC早一个小时的时区内看到它。因此,让我们尝试应用时区UTC来获取Africa/Algiers

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

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

ZonedDateTime

如果要通过参数构造ZonedDateTime日期时间而不是解析字符串(如上所示),请将参数传递给UTC的工厂方法。指定方便常量OffsetDateTime。与ZoneId z = ZoneId.of( "Africa/Algiers" ); ZonedDateTime zdt = instant.atZone( z ); 相比,月份的数字是明智的,1月到12月是1-12。

OffsetDateTime
  

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

与上述类似,我们可以调整为时区。对ZoneOffset.UTC等异常进行了调整。

Calendar

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

请参阅此Daylight Saving Time (DST)

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

关于在问题代码中使用ZoneId z = ZoneId.of( "Africa/Algiers" ); ZonedDateTime zdt = odt.atZoneSameInstant( z ); ... Locale与时区完全正交,分开且不同。 Locale对象仅影响生成的字符串的格式,以表示日期时间值。

关于java.time

code run line in IdeOne.com框架内置于Java 8及更高版本中。这些类取代了麻烦的旧java.time日期时间类,例如legacyjava.util.Date和&amp; Calendar

现在位于SimpleDateFormatJoda-Time项目建议迁移到maintenance mode类。

要了解详情,请参阅java.time。并搜索Stack Overflow以获取许多示例和解释。规范是Oracle Tutorial

从哪里获取java.time类?

  • JSR 310Java SE 8以及之后
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小功能和修复。
  • SE 9Java SE 6
    • 大部分java.time功能都被反向移植到Java 6&amp; 7 {in SE 7
  • ThreeTen-Backport
    • Android项目专门为Android调整 ThreeTen-Backport (如上所述)。
    • 请参阅ThreeTenABP

How to use ThreeTenABP…项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如ThreeTen-ExtraIntervalYearWeekYearQuarter

答案 1 :(得分:0)

所以,我运行了以下代码片段:

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这样的答案正确地解释了日历对象只存储了毫秒,并且没有考虑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