Note: originally the question had the input 2017-18-08 12:60:30.345
(with 60
in the minutes field), then it was edited (the time changed from 12:60
to 11:45
), but I decided to keep this answer discussing about the original input (12:60
), as it also works for the edited version (11:45
).
ZonedDateTime
需要时区或偏移量,但输入String
没有它(它只有日期和时间)。
输入中还有其他详细信息:
- 分钟值为
60
,这是不被接受的:有效值是从0到59(实际上有一种方法可以接受这个,请参阅“宽松解析” below)
- the
hh
is the 上午-下午的时钟时间 field,因此还需要完全解析 AM/PM 指示符。由于您没有它,因此您应该使用HH
模式代替
所以模式必须是yyyy-dd-MM HH:mm:ss.SSS
,输入不能有60
作为分钟值(除非您使用宽松的解析,我将在下面解释)并且您不能直接将其解析为ZonedDateTime
因为它没有时区/偏移量指示符。
一种替代方法是将其解析为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
这相当于2017-18-08 12:59:30.345
in UTC.
如果您想要另一个时区的日期,您可以使用ZoneId
class:
// 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 时区名称(始终采用以下格式Region/City
, like America/Sao_Paulo
or Europe/Berlin
)。
避免使用 3 个字母的缩写(例如CST
or PST
) 因为他们是模棱两可且不标准.
您可以通过调用获取可用时区列表(并选择最适合您的系统的时区)ZoneId.getAvailableZoneIds()
.
您还可以使用系统默认时区 with ZoneId.systemDefault()
,但是即使在运行时,这也可以在不通知的情况下进行更改,因此最好显式使用特定的更改。
还有一个选项可以转换LocalDateTime
to an offset (like -05:00
or +03:00
):
// get the LocalDateTime in +03:00 offset
System.out.println(dt.toInstant(ZoneOffset.ofHours(3)).toEpochMilli());
输出将等于偏移量中的本地日期/时间+03:00
(比 UTC 早 3 小时):
1503050370345
宽松的解析
As @MenoHochschild 在评论中提醒我,你可以使用宽松的解析来接受60
在分钟字段中(使用java.time.format.ResolverStyle
class):
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
class),您可以忽略此部分。
但是如果您决定使用时区(与ZoneId
类),你还必须照顾DST(夏令时)问题。我将使用我居住的时区作为示例(America/Sao_Paulo
).
In São Paulo, DST starts at October 15th 2017: at midnight, clocks shift 1 hour forward from midnight to 1 AM. So all local times between 00:00 and 00:59 don't exist in this timezone. If I create a local date in this interval, it's adjusted to the next valid moment:
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]
When DST ends: in February 18th 2018 at midnight, clocks shift back 1 hour, from midnight to 23 PM of 17th. So all local times from 23:00 to 23:59 exist twice (in DST and in non-DST), and you must decide which one you want:
// 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]
请注意,DST 结束之前和之后的日期具有不同的偏移量(-02:00
and -03:00
)。这会影响 epochMilli 的值。
您必须检查您选择的时区中 DST 的开始和结束时间,并检查相应的调整。