注意:原问题输入的是2017-18-08 12:60:30.345
(分钟字段中有60
),然后进行了编辑(时间从12:60
更改为11:45
),但我决定继续讨论原始输入(12:60
),因为这也适用于编辑后的版本(11:45
)。
ZonedDateTime
需要时区或偏移量,但输入的String
没有它(仅包括日期和时间)。
输入还有其他细节:
- 分钟值为
60
,不被接受:有效值为0到59(实际上有一种方法可以接受此值,请参见下面的"宽松解析")
hh
是上午/下午的时钟小时字段,因此还需要AM/PM指示符才能完全解析。由于您没有它,应改用HH
模式
因此,模式必须是yyyy-dd-MM HH:mm:ss.SSS
,输入不能将分钟值设为60
(除非您使用宽松解析,我将在下面解释),并且无法直接将其解析为ZonedDateTime
,因为它没有时区/偏移量指示符。
一种替代方案是将其解析为LocalDateTime
,然后定义此日期所在的时区/偏移量。在下面的示例中,我假设它在协调世界时(UTC)中:
String timeDateStr = "2017-18-08 12:59:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
Instant instant = dt.toInstant(ZoneOffset.UTC);
System.out.println(instant.toEpochMilli());
这将输出:
1503061170345
这相当于在
UTC中的时间为
2017-18-08 12:59:30.345
.
如果您想要另一个时区的日期,可以使用
ZoneId
类:
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时区名称(始终采用格式“Region/City”,例如“America/Sao_Paulo”或“Europe/Berlin”)。避免使用3个字母的缩写(如CST或PST),因为它们是模棱两可的并且不标准。
您可以通过调用ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。
您也可以使用系统的默认时区ZoneId.systemDefault(),但是这可能会在运行时甚至在不通知的情况下更改,因此最好明确使用特定的时区。
此外,还有将LocalDateTime转换为偏移量(如“-05:00”或“+03:00”)的选项:
// get the LocalDateTime in +03:00 offset
System.out.println(dt.toInstant(ZoneOffset.ofHours(3)).toEpochMilli());
输出结果将等同于偏移量为+03:00
(比协调世界时提前3小时)的本地日期/时间:
1503050370345
宽松解析
正如 @MenoHochschild 在评论中提醒我的那样,您可以使用宽松解析来接受分钟字段中的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")
.withResolverStyle(ResolverStyle.LENIENT);
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
在这种情况下,60分钟调整为下一个小时, 而
LocalDateTime
将会是:
2017-08-18T13:00:30.345
夏令时
如果您决定使用UTC或固定偏移量(使用ZoneOffset
类),则可以忽略本节。
但是,如果您决定使用时区(使用ZoneId
类),您还必须注意夏令时问题。我将以我所在的时区作为示例(America/Sao_Paulo
)。
在圣保罗,夏令时始于2017年10月15日:在午夜时,时钟向前调整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]
结束夏令时后,在2018年2月18日午夜,时钟需要往回拨1小时,从午夜调整到17日的23:00。因此,在23:00至23:59之间的所有当地时间存在两次(夏令时和非夏令时),你必须决定你需要哪一个:
LocalDateTime d = LocalDateTime.of(2018, 2, 17, 23, 0, 0, 0);
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST);
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST);
请注意,夏令时开始前后的日期具有不同的偏移量(
-02:00
和
-03:00
)。这会影响到epochMilli的值。
您必须检查您选择的时区何时开始和结束夏令时,并相应地调整。