获取东部时间的日期和时间输入并转换为 Java 中的 UTC 时间戳

Getting a date and time input in Eastern Time and converting to UTC timestamp in Java

我有一个简单的 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。然后我尝试创建一个即时对象并解析字符串并获取纪元毫秒但我得到的异常是:

"Text '2015-10-16T14:00z' could not be parsed"

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

我认为您尝试解析的文本不是 Instant 应有的格式。你错过了秒和纳秒。有效的 String 类似于“2007-12-03T10:15:30.00Z”。

您应该在 LocalDateTime 上使用 toInstant

Yasmani Llanes 的 基本上是正确的。我来解释一下。

LocalDateTime != UTC 时刻

一个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时刻。它不是。一个是模糊的想法,另一个是真实的。 (好吧,在牛顿意义上是真实的,在爱因斯坦相对论意义上不是那么真实 ;-))

所以,LocalDateTime::toString is not a fully-formed string as expected by the Instant.parse method. Specifically, it has no data pertaining to an offset-from-UTC nor time zone的输出。上一段解释了为什么 这是一个功能而不是错误

你想要的是一个 ZonedDateTime,它基本上是一个 Instant(UTC 时间轴上的一个时刻)加上一个 ZoneId(一个时区)。

ZonedDateTime = Instant + ZoneId

时区是与 UTC(小时和分钟)的偏移量加上过去、现在的一组规则和异常(例如Daylight Saving Time, DST) , 以及未来的调整。

ZoneId = offset-from-UTC + adjustment-rules

你是正确的LocalDateTime in the java.time framework, and this is where it gets a bit confusing. Logically, we should be able to parse directly from an input String to a ZonedDateTime。但是由于调整规则,没有任何时区信息的输入字符串可能对特定区域无效的问题。例如,在 Spring 中,当我们 "spring-ahead" 使用夏令时时,在美国凌晨 2 点的钟声中提前一个小时,没有“02:38”或“20:54” “ 在那一天。时钟从 01:59.59.x 跳到 03:00:00.0。

我的理解是 java.time 框架希望通过传递给 ZonedDateTimeLocalDateTime 对象来处理此调整,而不是让 ZonedDateTime 在解析时直接处理它.两个步骤:(1) 将字符串解析为 LocalDateTime,(2) 将 LocalDateTime 对象和 ZoneId 对象馈送到 ZonedDateTime。为了正确处理当天带有“20:54”的输入字符串,我们需要将其解析为LocalDateTime,然后要求ZonedDateTime使用指定的时区进行调整(导致“03: 54",我认为——阅读 class 文档了解调整行为中使用的详细信息和逻辑。

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

正确的时区名称

你说输入字符串在“Eastern Time". I'm afraid to tell you there is no such thing. The "EST", "EDT", and other such 3-4 letter codes are not official, not standardized, and not unique. You need to learn to use proper time zone names. Perhaps you mean America/New_York (note the underscore) or America/Montreal 或类似的区域。我将任意选择纽约。

变量命名

请注意我是如何更改您的变量名称的。命名变量通常对于清晰度和后期维护非常重要,但对于日期时间工作更是如此。

ISO 8601

顺便说一句,通过字符串交换日期时间值数据的更好方法是使用 ISO 8601 格式,例如 2015-10-15T13:21:09Z。这些格式包括与 UTC 的偏移量,例如上一句中显示的 Z(Zulu,UTC)。 java.time 框架通过在括号中附加时区名称来明智地扩展 ISO 8601 格式。传递没有偏移量或时区信息的日期时间字符串是自找麻烦。

示例代码。

下面是Java 8中的一些示例代码。首先我们将字符串解析为一个LocalDateTime对象。

// 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 转换为时间线上的实际时刻。我们假设输入字符串表示纽约时区的 wall-clock time in Poughkeepsie which uses the New York time zone. So we get a 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 );

您可以轻松适应其他时区。我将任意显示 India time,因为它以两种方式提供对比:领先于 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,所以 class 处理 Daylight Saving Time.

等异常所需的调整
// Get a moment four hours later.
ZonedDateTime later = zdtNewYork.plusHours ( 4 );  // DST and other anomalies handled by ZDT when adding hours.

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

  • 像分配任何其他时区一样分配时区,但请注意 ZoneOffset (a subclass of ZoneId 中定义的方便常量。
  • 或者,从 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]

数据库查询

至于查询数据库,请搜索 Whosebug,因为已经详尽处理了。结果:将来 JDBC 应该能够使用这里显示的 java.time 数据类型。在那之前,转换为 java.sql.Timestamp object. Convenient conversion methods provided for you, such as java.sql.Timestamp.from( Instant instant ).

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