注意:originally 问题的输入是 2017-18-08 12:60:30.345(分钟字段中有 60),然后是 it was edited(时间 从12:60 更改为11:45),但我决定继续讨论原始输入(12:60),因为它也适用于编辑版本(11:45)。
ZonedDateTime 需要时区或偏移量,但输入 String 没有它(它只有日期和时间)。
输入中还有另一个细节:
所以模式必须是yyyy-dd-MM HH:mm:ss.SSS,输入不能有60 作为分钟值(除非你使用宽松的解析,我将在下面解释)并且你不能直接将它解析为@ 987654344@ 因为它没有时区/偏移量指示符。
另一种方法是将其解析为LocalDateTime,然后定义该日期所在的时区/偏移量。在下面的示例中,我假设它位于UTC:
// change 60 minutes to 59 (otherwise it doesn't work)
String timeDateStr = "2017-18-08 12:59:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
// parse to LocalDateTime
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
// assume the LocalDateTime is in UTC
Instant instant = dt.toInstant(ZoneOffset.UTC);
System.out.println(instant.toEpochMilli());
这将输出:
1503061170345
相当于UTC中的2017-18-08 12:59:30.345。
如果您想要另一个时区的日期,您可以使用ZoneId 类:
// get the LocalDateTime in some timezone
ZonedDateTime z = dt.atZone(ZoneId.of("Europe/London"));
System.out.println(z.toInstant().toEpochMilli());
输出是:
1503057570345
请注意,结果是不同的,因为相同的本地日期/时间在每个时区代表不同的Instant(在世界的每个地方,本地日期/时间2017-18-08 12:59:30.345 发生在不同的瞬间)。
另请注意,API 使用IANA timezones names(始终采用Region/City 格式,如America/Sao_Paulo 或Europe/Berlin)。
避免使用三个字母的缩写(如CST 或PST),因为它们是ambiguous and not standard。
您可以致电ZoneId.getAvailableZoneIds() 获取可用时区列表(并选择最适合您系统的时区)。
您也可以将系统的默认时区与ZoneId.systemDefault() 一起使用,但这可以在不通知的情况下更改,即使在运行时也是如此,因此最好明确使用特定的时区。
还有将LocalDateTime 转换为offset 的选项(如-05:00 或+03:00):
// get the LocalDateTime in +03:00 offset
System.out.println(dt.toInstant(ZoneOffset.ofHours(3)).toEpochMilli());
输出将等同于偏移量+03:00 中的本地日期/时间(比 UTC 提前 3 小时):
1503050370345
宽松解析
作为@MenoHochschild reminded me in the comments,您可以使用宽松解析在分钟字段中接受60(使用java.time.format.ResolverStyle类):
String timeDateStr = "2017-18-08 12:60:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS")
// use lenient parsing
.withResolverStyle(ResolverStyle.LENIENT);
// parse to LocalDateTime
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
在这种情况下,将 60 分钟调整为下一小时,LocalDateTime 将是:
2017-08-18T13:00:30.345
夏令时
如果您决定使用 UTC 或固定偏移量(使用 ZoneOffset 类),您可以忽略此部分。
但如果您决定使用时区(使用ZoneId 类),您还必须注意DST (Daylight Saving Time) 问题。我将以我所在的时区为例 (America/Sao_Paulo)。
在圣保罗,夏令时从 2017 年 10 月 15 日th开始:午夜时分,时钟从午夜向前移动 1 小时到凌晨 1 点。因此,该时区不存在 00:00 到 00:59 之间的所有当地时间。如果我在此间隔内创建本地日期,则会将其调整为下一个有效时刻:
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// October 15th 2017 at midnight, DST starts in Sao Paulo
LocalDateTime d = LocalDateTime.of(2017, 10, 15, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to 2017-10-15T01:00-02:00[America/Sao_Paulo]
DST 结束时:2018 年 2 月 18 日th 午夜,时钟将后移 1 小时,从午夜到 17th。因此,从 23:00 到 23:59 的所有当地时间都存在 两次(在 DST 和非 DST 中),您必须决定要使用哪一个:
// February 18th 2018 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 17th exist twice
LocalDateTime d = LocalDateTime.of(2018, 2, 17, 23, 0, 0, 0);
// by default, it gets the offset before DST ends
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST); // before DST end: 2018-02-17T23:00-02:00[America/Sao_Paulo]
// get the offset after DST ends
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST); // after DST end: 2018-02-17T23:00-03:00[America/Sao_Paulo]
请注意,夏令时结束前后的日期有不同的偏移量(-02:00 和-03:00)。这会影响 epochMilli 的值。
您必须检查 DST 在您选择的时区的开始和结束时间,并相应地检查调整。