如何将时间戳字符串转换为Epoch时间?

4

我有一个时间戳,格式为2017-18-08 11:45:30.345
我想将它转换成epoch时间,所以我做了以下操作:

String timeDateStr = "2017-18-08 11:45:30.345"; 
DateTimeFormatter dtf  = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
ZonedDateTime     zdt  = ZonedDateTime.parse(timeDateStr, dtf);        
System.out.println(zdt.toInstant().toEpochMilli());

我收到以下错误:

java.time.format.DateTimeParseException: 无法解析文本'2017-18-08 11:45:30.345':无法从TemporalAccessor获取ZonedDateTime

我尝试了不同的格式,但仍然出现错误。


那是打错字了,请检查更新后的问题。感谢您指出。 - User7723337
1
@Assafs 不,不只是这个错误。请也看一下模式符号"h"。 - Meno Hochschild
@User7723337 最小值是60吗? - nafas
你解决了这个问题吗? - Assafs
当这个模式没有关于时区的信息时,ZonedDateTime是否有效? - Ewoks
4个回答

8

注意原问题输入的是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)中:

// 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时区名称(始终采用格式“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")
    // 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类),您还必须注意夏令时问题。我将以我所在的时区作为示例(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之间的所有当地时间存在两次(夏令时和非夏令时),你必须决定你需要哪一个:

// 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的值。
您必须检查您选择的时区何时开始和结束夏令时,并相应地调整。

2
你是唯一看到 h 与 H 错误的回答者(因此得到了点赞)。关于 minute = 60 的值:宽松解析(通过 builder)可以解决这个问题(未经测试,但我想解析器会选择下一个分钟,即下一个小时和分钟为零)。 - Meno Hochschild
@MenoHochschild 我通常避免宽容解析,因为我不喜欢这些自动转换。但我已经测试过,它会调整时间到 13:00:30.345。我已经更新了答案,非常感谢! - user7605325
2
很棒的答案!恭喜。 - J.A.I.L.
他的 ZonedDateTime 变量不能工作,因为 sis 模式没有关于时区的信息,对吧? - Ewoks

2

已经更正了关于 yyyy-dd-MM 的代码。此外,分钟值可以是 1-59 而不是 60。您提供了 60。这是另一种解决问题的简单方法。只需使用 DateFormat 类即可。

String timeDateStr = "2017-18-08 12:59:30.345";
DateFormat df = new SimpleDateFormat("yyyy-dd-MM hh:mm:ss.SSS", Locale.ENGLISH);
try {
    Date d = df.parse(timeDateStr);
    System.out.println(d.toInstant().toEpochMilli());
} catch (ParseException e) {
    e.printStackTrace();
}

0

我只是对nagendra547的答案做了一点小改动

请参考下面的代码:

String timeDateStr = "2017-18-08 12:59:30.345";
DateFormat df = new SimpleDateFormat("yyyy-dd-mm hh:mm:ss.SSS", Locale.ENGLISH);
try {
    Date d = df.parse(timeDateStr);
    System.out.println(d.toInstant().toEpochMilli());
} catch (ParseException e) {
    e.printStackTrace();
}

0

你的代码会失败有以下三个原因:

  1. 你的日期字符串 (2017-18-08 12:60:30.345) 与你使用的格式化程序不匹配。应该使用 yyyy-MM-dd HH:mm:ss.SSS 而不是 yyyy-dd-MM hh:mm:ss.SSS
  2. 分钟的范围是 (0-59),60 不在这个范围内。
  3. 即使你根据上述观点修改了代码,它也不会针对 ZonedDateTime 运行。所以你需要先创建一个 LocalDateTime,然后将一个 ZoneId 传递给它。

代码应该如下所示:

String timeDateStr = "2017-18-08 12:59:30.345"; 
DateTimeFormatter dtf  = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
LocalDateTime date = LocalDateTime.parse(timeDateStr, dtf);
ZonedDateTime     zdt  = date.atZone(ZoneId.of("Europe/London"));
System.out.println(zdt.toInstant().toEpochMilli());

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