为什么 JodaTime 时区会改变日期时间?

Why is JodaTime timezone shifting a date time?

将字符串“2017-04-21T17:46:00Z”传递给第一种方法时,生成的格式化日期字符串为“06:46 21 Apr 2017”。为什么整点是十一小时?输入字符串由 JSON 中的 HTTP 服务器应用程序提供。我以为 Z 后缀指的是祖鲁语,即 GMT。

private static final String DATE_TIME_FORMAT = "hh:mm dd MMM yyyy";

public static String formatTimestamp(String dateTimestamp) {
    DateTime dateTime = getDateTimeFromTimestamp(dateTimestamp);
    DateTimeFormatter fmt = DateTimeFormat.forPattern(DATE_TIME_FORMAT);
    return fmt.print(dateTime);
}

private static DateTime getDateTimeFromTimestamp(String dateTimestamp) {
    return new DateTime(dateTimestamp);
}

我怀疑它与时区有关,但不清楚如何或在哪里。在英国的 Android 设备上,代码是 运行,在 GMT 时区。

我已经用 java 7 和 joda-time 2.7 进行了测试(但不是 Android 的版本)

这就是我重现问题的方法:

// changing my default timezone (because I'm not in UK)
DateTimeZone.setDefault(DateTimeZone.forID("Europe/London"));
// calling your method
System.out.println(formatTimestamp("2017-04-21T17:46:00Z"));

输出为

06:46 21 Abr 2017

为了检查有什么问题,我已将日期格式更改为:

DATE_TIME_FORMAT2 = "hh:mm a dd MMM yyyy Z z zzzz";

其中 a 表示 "AM or PM",Z 是时区 offset/id,z 是时区 "short" 名称,zzzz 是时区 "long" 名称。使用这种格式,输出是:

06:46 PM 21 Abr 2017 +0100 BST British Summer Time

所以创建的日期时间是下午 6 点,比输入早一个小时,而不是你想的十一小时(实际上如果你将格式更改为 HH 而不是 hh,时间将是18 而不是 06).

还要注意时区字段:+0100 BST British Summer Time。第一部分(+0100)表示这个DateTime比GMT早一小时,BST British Summer Time表示在British's Daylight Saving Time.


因此,要使输出等于输入,您有 2 个选择:

1. 将您的默认时区更改为 UTC:

DateTimeZone.setDefault(DateTimeZone.UTC);
System.out.println(formatTimestamp("2017-04-21T17:46:00Z"));

输出将是:

05:46 21 Apr 2017

如果您想将小时数更改为 17:46,请更改您的日期格式,将 hh 替换为 HH

2. 使用接收 DateTimeZone:

DateTime 构造函数
private static DateTime getDateTimeFromTimestamp(String dateTimestamp) {
    // creates a DateTime in UTC
    return new DateTime(dateTimestamp, DateTimeZone.UTC);
}

输出将与备选方案 1 相同,但在这种情况下,您无需更改默认时区。

对我来说,备选方案 2 更有意义,因为:

  • 您不需要更改默认时区(这可能会导致应用程序的其他部分出现一些混乱)
  • 您已经知道此代码处理的所有日期都是 UTC 时间 (because of the "Z" in the end)

使用java.time

seems to be correct and informative. But FYI, the Joda-Time project is now in maintenance mode, with the team advising migration to the java.time 类。对于 Android,请参阅下面底部的最后一个项目符号。

您输入的字符串是标准 ISO 8601 格式。 java.time 类 在解析和生成字符串时使用标准格式。因此无需指定格式模式。

Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds(最多九 (9) 位小数)。

String input = "2017-04-21T17:46:00Z" ;
Instant instant = Instant.parse( input ) ;

instant.toString(): 2017-04-21T17:46:00Z

如需更灵活的格式,请转换为 OffsetDateTime object were you can specify any offset-from-UTC in hours and minutes. We want UTC itself (an offset of zero) so we can use the constant ZoneOffset.UTC

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

odt.toString(): 2017-04-21T17:46Z

定义格式模式以匹配您所需的格式。请注意,您必须指定 Locale 以确定 (a) 用于翻译日期名称、月份名称等的人类语言,以及 (b) 决定缩写、大写、标点符号、分隔符问题的文化规范, 等等。

DateTimeFormatter f = DateTimeFormatter.ofPattern( "hh:mm dd MMM yyyy" , Locale.US ) ;
String output = odt.format( f ) ;

output: 05:46 21 Apr 2017

如果您想通过 Europe/LondonPacific/Auckland 等地区挂钟时间的镜头看到同一时刻,请应用时区以获得 ZonedDateTime

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 ESTISTBST 等 3-4 个字母的缩写,因为它们 不是 真实时区,未标准化,也不是甚至是独一无二的(!)。

ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

请注意,由于夏令时 (DST),一天中的时间是一个小时。

zdt.toString(): 2017-04-21T18:46+01:00[Europe/London]

看到这个code run live at IdeOne.com

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

java.time类在哪里获取?