使用Joda解析带时区的日期时间

14
我有两个时间戳,它们以两种不同的格式描述了同一时刻。 2010-10-03 18:58:072010-10-03T16:58:07.000+02:00
我使用Joda-Time中的两个不同日期格式化程序解析时间戳。最终,我希望有两个DateTime对象,在表示相同时间瞬间方面相等。
日期格式化程序提供了几种方法来控制时区和语言环境,但我无法使其正常工作。
这是我想要工作的代码:
    final String date1 = "2010-10-03 18:58:07"; // Europe/Berlin local time
    final DateTimeFormatter formatter1 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
    final DateTime dateTime1 = formatter1.parseDateTime(date1);

    final String date2 = "2010-10-03T16:58:07.000+02:00"; // Europe/Berlin local time with time zone
    final DateTimeFormatter formatter2 = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    final DateTime dateTime2 = formatter2.parseDateTime(date2);

    Assert.assertTrue(dateTime1.isEqual(dateTime2));
4个回答

7
如果您的默认时区是Europe/Berlin,那么2010年10月3日18:58:07对应于2010年10月3日T16:58:07.000+00:00。
您可能误解了字符串表示中的时区字段。您的时间戳2010-10-03T16:58:07.000+02:00意味着“在比格林尼治标准时间快两个小时的时区中,现在是16:58:07”,或者换句话说,“现在是柏林时间16:58:07”。我猜想您希望它表示的是16:58:07格林尼治标准时间?

您在“2010-10-03T16:58:07.000+02:00意味着它不是在一个与GMT相差2小时的时区中的16:58:07”中有一个错误。应该改为:“2010-10-03T16:58:07.000+02:00意味着它是在一个与UTC相差2小时的时区中的16:58:07”。 - MicSim
是的,我希望它是格林威治标准时间16:58:07。然后看起来时间戳出了问题。这些时间戳来自不同服务器的两个不同的日志文件。我非常确定它们指定了相同的事件,所以这就是我的困惑所在。 - wilfried
@MacSim:当然,我写得有点太快了。 - jarnbjo
当我们将第一个时间戳生成在欧洲/莫斯科时区,第二个时间戳生成在欧洲/柏林时,一切都变得合理。我只需要将这段代码添加到第二个DateTimeFormatter中:withZone(DateTimeZone.forID("Europe/Berlin"))测试就可以通过了。虽然我不知道我们在莫斯科有服务器。 :D - wilfried
我必须将它添加到第一个 DateTimeFormatter 中,抱歉。 - wilfried

4
你的两个时间戳并不代表同一时刻(正如jambjo已经指出的那样)。在维基百科上查看时区作为与UTC的偏移量。此外,还要查看parseDateTime文档以了解其工作原理。如果没有提供任何时区,则将应用默认时区(即如果您在柏林,则为UTC+2的柏林时区)。因此:
  • 2010-10-03 18:58:07变为2010-10-03T18:58:07.000+02:00(在柏林为18:58,与UTC相差2小时,即UTC为16:58),符合预期。
  • 2010-10-03T16:58:07.000+02:00保持不变,因为提供了时区(即在柏林为16:58,与UTC相差2小时,即UTC为14:58)
希望你明白了。您需要使用withZone方法调整时间以获得所需的结果。

1

简短概述

使用现代的java.time类替代Joda-Time

LocalDateTime                                   // Represent a date and time-of-day without the context of a time zone or offset-from-UTC. *Not* a moment, *not* a point on the timeline.
.parse(                                         // Parse text into a date-time value.
    "2010-10-03 18:58:07".replace( " " , "T" )  // Replace SPACE in middle with a `T` to comply with ISO 8601 standard used by default in *java.time* when parsing/generating strings.
)                                               // Returns a `LocalDateTime` object.
.atZone(                                        // Assign the time zone we know for certain was intended for this input. 
    ZoneId.of( "Europe/Moscow" )                // Real time zones are named in `Continent/Region` format, never 2-4 letter codes such as CST, PST, IST, CEST, etc.
)                                               // Returns a `ZonedDateTime` object, a date with time-of-day and with a time zone assigned to determine a moment.
.toInstant()                                    // Adjust from time zone to UTC. 
.equals(
    OffsetDateTime                              // Represent a date and time-of-day with an offset-from-UTC but not a full time zone.
    .parse( "2010-10-03T16:58:07.000+02:00" )   // Parse a standard ISO 8601 string.
    .toInstant()                                // Adjust from offset to UTC (in other words, an offset of zero hours-minutes-seconds). 
)                                               // Returns `boolean`. 

详情

jarnbjo的回答是正确的,您误解了与UTC偏移量时区值的含义。

现在,在2018年,Joda-Time项目处于维护模式。该项目的主要作者Stephen Colebourne继续创建了JSR 310并编写了其实现,即在OpenJDK中找到的java.time类。

第一个输入

您输入的字符串2010-10-03 18:58:07几乎符合ISO 8601标准格式。为了符合要求,请将中间的空格替换为T

String input1 = "2010-10-03 18:58:07".replace( " " , "T" ) ;

那个字符串缺乏任何时区指示符或UTC偏移量。因此解析为LocalDateTime
LocalDateTime ldt = LocalDateTime.parse( input1 ) ;

这个值并不代表一个瞬间,也不是时间轴上的一个点。如果没有区域或偏移量的上下文,它可能是全球时区范围内许多瞬间中的任何一个,该范围大约为26-27小时。在您的评论中,您透露该输入字符串似乎表示欧洲/莫斯科时区的日期和时间。因此,我们可以指定该区域来确定一个瞬间,即时间轴上的一个点。
ZoneId zMoscow = ZoneId.of( "Europe/Moscow" ) ;
ZonedDateTime zdtMoscow = ldt.atZone( zMoscow ) ;  // Determine a moment by assigning a time zone.

zdtMoscow.toString(): 2010-10-03T18:58:07+04:00[Europe/Moscow]

第二个输入

您的第二个输入2010-10-03T16:58:07.000+02:00符合标准ISO 8601格式。

此输入携带比UTC提前两小时的偏移量。因此,此字符串表示UTC中的14:58:07的当天时间。

我们可以将其解析为OffsetDateTime以尊重给定的偏移量。

OffsetDateTime odt2 = OffsetDateTime.parse( "2010-10-03T16:58:07.000+02:00" ) ;

odt2.toString(): 2010-10-03T16:58:07+02:00

比较

这两个输入是否表示相同的时刻,相同的时间轴上的点?

一种比较方法是将两者都调整为UTC。根据定义,Instant始终处于UTC中。

提示:养成以UTC思考、工作、存储、交换和记录的习惯。把UTC看作是“唯一真实的时间”。

Instant instant1 = zdtMoscow.toInstant() ;  // Adjust from time zone to UTC.
Instant instant2 = odt2.toInstant() ;       // Adjust from offset to UTC.

boolean equality = instant1.equals( instant2 );

当运行时,我们会看到结果末尾有一个Z。这意味着UTC,并发音为Zulu。确实,我们可以看到这两个值表示相同的时刻,即UTC下的下午3点。

instant1.toString(): 2010-10-03T14:58:07Z

instant2.toString(): 2010-10-03T14:58:07Z

equality: true


关于java.time

java.time框架是Java 8及其以后版本内置的。这些类取代了问题多多的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在处于 维护模式,建议迁移到 java.time 类。

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

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC驱动程序 。无需字符串,无需 java.sql.* 类。

如何获取 java.time 类?

ThreeTen-Extra项目通过添加额外的类来扩展java.time。该项目是java.time可能未来增加内容的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter以及更多


0

我曾经遇到和你一样的问题,我发现最好的方法是解析数据。

我最初的格式是:2018-01-22 09:25:14.000 +0000

只需选择该列,然后在“数据”选项卡中单击“文本到列”即可。

我使用了带有空格的分隔符将此格式解析为3个不同的列。

因此,最终我得到:

列A 2018-01-22 列B 09:25:14.000 列C +0000


哪一栏?哪一个选项卡?你是在想电子表格吗?这个问题是关于Java编程的。 - Basil Bourque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接