在Java中获取东部时间输入并将其转换为UTC时间戳

5

我有一个简单的Web界面,可以以“2009/10/09 11:00”或“yyyy/MM/dd HH:mm”的形式输入日期和时间。用户所在的时区为东部时间。

我希望能够将这个字符串转换为UTC时间戳,以便我可以基于指定的时间查询我们的NoSQL数据库。

我的代码如下:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
    LocalDateTime dateTime = LocalDateTime.parse(startSearchTime, formatter);
    System.out.println(dateTime);
    LocalDateTime utcTime = dateTime.plusHours(4);
    Instant instant = Instant.parse(utcTime.toString());
    System.out.println(instant.toEpochMilli());

我从UI获取字符串并将其存储在'startSearchTime'中。我通过添加4个小时将其从东部时间转换为UTC。然后,我尝试创建一个即时对象并解析字符串并获取时代毫秒数,但我收到的异常是:

"无法解析文本'2015-10-16T14:00z'"

使用这个新的Java 8 DateTime API,我认为这个任务会很容易,我错过了什么?!

2个回答

10
"The 答案 by Yasmani Llanes is basically correct. I'll expound.

LocalDateTime != UTC Moment

A LocalDateTime is not a real date-time, it is not tied to the time line. It has no real meaning until you adjust it into a time zone to determine a point on the time line, a moment. Your code, LocalDateTime utcTime, with your choice of variable name, shows you have conflated a 'local' date-time with being a UTC moment. It is not. One is a vague idea, the other is real. (well, real in the Newton sense, not so much in the Einstein Relativistic sense ;-) )"

所以,LocalDateTime::toString 的输出并不是 Instant.parse 方法所期望的完整字符串。具体来说,它没有与 UTC 偏移量时区 相关的数据。上一段解释了这是一个特性而非 bug的原因。
你需要的是一个 ZonedDateTime,它基本上是一个 Instant(UTC 时间轴上的时刻)加上一个 ZoneId(时区)。

ZonedDateTime = Instant + ZoneId

一个时区是距离UTC的偏移量(小时和分钟)加上一组规则和异常情况(如夏令时,DST),用于过去、现在和未来的调整。

ZoneId = 偏移量 + 调整规则

你正确地使用了java.time框架中的LocalDateTime,这里有一点让人感到困惑。从一个输入字符串直接解析到ZonedDateTime看似很合理,但是由于调整规则的存在,某些没有时区信息的输入字符串可能对于特定的时区来说无效。例如,在春季转换夏令时时,当美国在凌晨2点钟向前调整一小时时,那天不存在"02:38"或"20:54"。时钟从01:59.59.x跳跃到03:00:00.0。
我的理解是java.time框架希望通过将LocalDateTime对象传递给ZonedDateTime来处理此调整,而不是直接由ZonedDateTime进行处理。两个步骤:(1)将字符串解析为LocalDateTime,(2)将LocalDateTime对象和ZoneId对象提供给ZonedDateTime。为了正确处理那天带有“20:54”的输入字符串,我们需要将其解析为LocalDateTime,然后要求ZonedDateTime使用指定的时区进行调整(我认为结果为“03:54”--请阅读类文档以获取有关调整行为的详细信息和逻辑)。

因此,我们需要在您的代码中添加对ZonedDateTime的调用。使用您创建的LocalDateTime对象,我们需要为ZonedDateTime指定一个ZoneId对象,以便在完成转换为ZonedDateTime时使用。

正确的时区名称

你说输入的字符串处于“东部时间”中。很抱歉告诉你,这种说法是不存在的。“EST”、“EDT”和其他类似的3-4个字母的代码都不是官方的、标准化的,也不是唯一的。你需要学会使用正确的时区名称。也许你指的是America/New_York(注意下划线),或者America/Montreal等时区。我将任意选择纽约。

变量命名

请注意我已更改了你的变量名称。为了清晰和后期维护,命名变量通常非常重要,但在日期时间工作中更加重要。

ISO 8601

By the way, a better way to exchange data of date-time values via strings is to use the ISO 8601 formats such as 2015-10-15T13:21:09Z. These formats include an offset-from-UTC such as the Z (Zulu, UTC) shown in previous sentence. The java.time framework wisely extends the ISO 8601 formats by appending the name of the time zone in brackets. Passing around date-time strings with no offset or time zone info is asking for trouble.

Example code.

Here is some sample code in Java 8. First we parse the string into a LocalDateTime object.

// Parse input string into a LocalDateTime object.
String input = "2009/10/09 11:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "yyyy/MM/dd HH:mm" );
LocalDateTime localDateTime = LocalDateTime.parse ( input , formatter );

将那个无形的LocalDateTime转化为时间轴上的实际时刻,只需指定一个时区即可。我们假设输入字符串表示挂钟时间Poughkeepsie,该地使用纽约时区。因此,我们获取了一个ZoneId对象,代表纽约时区。
// Specify the time zone we expect is implied for this input string.
ZoneId zoneId = ZoneId.of ( "America/New_York" );
ZonedDateTime zdtNewYork = ZonedDateTime.of ( localDateTime , zoneId );

你可以轻松地适应其他时区。我将随意展示印度时间,以提供两种对比:超前于UTC而不是落后,并且它的偏移量不是整小时(+05:30)。

// For fun, adjust into India time, five and a half hours ahead of UTC.
ZonedDateTime zdtKolkata = zdtNewYork.withZoneSameInstant ( ZoneId.of ( "Asia/Kolkata" ) );

我们可以进行日期时间计算,例如添加四个小时。因为我们拥有一个ZonedDateTime,该类处理所需的调整以适应异常情况,比如夏令时
// Get a moment four hours later.
ZonedDateTime later = zdtNewYork.plusHours ( 4 );  // DST and other anomalies handled by ZDT when adding hours.

对于UTC时区,您可以采取以下两种方式之一。

  • 像为任何其他时区分配时区一样,但请注意在ZoneOffsetZoneId的子类)中定义的便捷常量。
  • 或者,从ZonedDateTime中提取Instant。根据定义,Instant始终处于UTC。

两种方法都表示时间轴上的相同时刻。但请注意,在下面的输出中,它们各自的toString实现中默认使用了不同的格式。

// To get the same moment in UTC time zone, either adjust time zone or extract Instant.
ZonedDateTime zdtUtc = zdtNewYork.withZoneSameInstant ( ZoneOffset.UTC );
Instant instant = zdtNewYork.toInstant ();

将内容输出到控制台。

System.out.println ( "input: " + input );
System.out.println ( "localDateTime: " + localDateTime );
System.out.println ( "zdtNewYork: " + zdtNewYork );
System.out.println ( "zdtKolkata: " + zdtKolkata );
System.out.println ( "zdtUtc: " + zdtUtc );
System.out.println ( "instant: " + instant );
System.out.println ( "later: " + later );

运行时。

input: 2009/10/09 11:00
localDateTime: 2009-10-09T11:00
zdtNewYork: 2009-10-09T11:00-04:00[America/New_York]
zdtKolkata: 2009-10-09T20:30+05:30[Asia/Kolkata]
zdtUtc: 2009-10-09T15:00Z
instant: 2009-10-09T15:00:00Z
later: 2009-10-09T15:00-04:00[America/New_York]

数据库查询

关于查询数据库,可以在StackOverflow上搜索,因为这已经被详尽地解决了。总之,在将来,JDBC应该能够使用此处显示的java.time数据类型。在那之前,将其转换为java.sql.Timestamp对象。为您提供方便的转换方法,例如java.sql.Timestamp.from(Instant instant)

java.sql.Timestamp ts = java.sql.Timestamp.from( zdtNewYork.toInstant () );

谢谢您的澄清,这是我在 SO 上看到的最好的解释之一! - smithzo622

0

我相信你尝试解析的文本格式不符合Instant的要求。你缺少了秒和纳秒。一个有效的String应该是像这样的:"2007-12-03T10:15:30.00Z"。

你应该在你的LocalDateTime上使用toInstant


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