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

Use*_*337 4 java timestamp epoch datetime-parsing java-time

我的格式有时间戳2017-18-08 11:45:30.345.
我想将它转换为纪元时间,所以我在下面做:

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());
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

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

我也尝试过不同的格式,但仍然会出错.

小智 7

注意:最初问题有输入2017-18-08 12:60:30.345(60在分钟字段中),然后它被编辑(时间从更改12:6011:45),但我决定保持这个答案讨论原始输入(12:60),因为它也适用于编辑版本(11:45).


ZonedDateTime需要时区或偏移量,但输入String没有它(它只有日期和时间).

输入中还有另一个细节:

  • 分钟值是60,不接受:有效值从0到59(实际上有一种方法可以接受这个,请参阅下面的"宽容解析")
  • hhmid-hour-am-pm字段,因此它还需要完全解析AM/PM指示符.由于您没有它,您应该使用该HH模式

所以模式必须是yyyy-dd-MM HH:mm:ss.SSS,输入不能60作为分钟值(除非你使用宽松解析,我将在下面解释)并且你不能直接解析它到a,ZonedDateTime因为它没有时区/偏移代号.

一种替代方法是将其解析为a 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());
Run Code Online (Sandbox Code Playgroud)

这将输出:

1503061170345

这是相当于2017-18-08 12:59:30.345UTC.

如果您希望在另一个时区中使用该日期,则可以使用ZoneId该类:

// get the LocalDateTime in some timezone
ZonedDateTime z = dt.atZone(ZoneId.of("Europe/London"));
System.out.println(z.toInstant().toEpochMilli());
Run Code Online (Sandbox Code Playgroud)

输出是:

1503057570345

请注意,结果是不同的,因为相同的本地日期/时间Instant在每个时区中表示不同(在世界的每个部分中,本地日期/时间2017-18-08 12:59:30.345发生在不同的时刻).

另请注意,API使用IANA时区名称(始终采用格式Region/City,如America/Sao_PauloEurope/Berlin).避免使用3个字母的缩写(如CSTPST),因为它们不明确且不标准.

您可以通过调用获取可用时区列表(并选择最适合您系统的时区)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());
Run Code Online (Sandbox Code Playgroud)

输出将等于偏移中的本地日期/时间+03:00(比UTC早3小时):

1503050370345


宽松解析

正如@MenoHochschild在评论中提醒我的那样,你可以使用lenient parsing 60在minutes字段中接受(使用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);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,60分钟调整到下一个小时,LocalDateTime将是:

2017-08-18T 13 : 00 :30.345


夏令时

如果您决定使用UTC或固定偏移量(使用ZoneOffset类),则可以忽略此部分.

但是,如果您决定使用时区(带ZoneId课程),则还必须处理DST(夏令时)问题.我将使用我居住的时区作为示例(America/Sao_Paulo).

在圣保罗,DST开始于10月15日 2017年:在午夜,时钟对应的小时轮班从午夜到凌晨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]
Run Code Online (Sandbox Code Playgroud)

当夏令时结束:在2月18日午夜2018,时钟移 1小时后,从午夜到23 PM的17 .因此,从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]
Run Code Online (Sandbox Code Playgroud)

请注意,DST结束前后的日期具有不同的偏移量(-02:00-03:00).这会影响epochMilli的价值.

您必须检查DST何时开始并以您选择的时区结束,并相应地检查调整.

  • 你是唯一一个同时看到h与H错误的回答者(因此也就是upvoting).关于分钟= 60-值:嗯,宽松的解析(通过构建器)可以解决这个问题(没有测试,但我想,然后解析器将选择下一分钟,即下一小时和分钟=零). (2认同)
  • 很棒的答案!恭喜. (2认同)