关于java:如何将时间戳字符串转换为纪元时间?

How to convert timestamp string to epoch time?

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

1
2
3
4
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: Text '2017-18-08 11:45:30.345' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor

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


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

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

输入中还有另一个细节:

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

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

一种替代方法是将其解析为LocalDateTime,然后定义此日期所在的时区/偏移量。在下面的例子中,我假设它是UTC:

1
2
3
4
5
6
7
8
9
// 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类:

1
2
3
// 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_PauloEurope/Berlin)。
避免使用3个字母的缩写(如CSTPST),因为它们不明确且不标准。

您可以通过调用ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。

您也可以将系统的默认时区与ZoneId.systemDefault()一起使用,但即使在运行时也可以在不事先通知的情况下进行更改,因此最好明确使用特定的时区。

还可以选择将LocalDateTime转换为偏移量(如-05:00+03:00):

1
2
// get the LocalDateTime in +03:00 offset
System.out.println(dt.toInstant(ZoneOffset.ofHours(3)).toEpochMilli());

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

1503050370345

宽松解析

正如@MenoHochschild在评论中提醒我的那样,你可以使用宽松的解析来接受分钟字段中的60(使用java.time.format.ResolverStyle类):

1
2
3
4
5
6
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(夏令时)问题。我将使用我居住的时区作为示例(America/Sao_Paulo)。

在圣保罗,夏令时开始于2017年10月15日:午夜,时钟从午夜向凌晨1点转移到凌晨1点。因此,此时区内不存在00:00至00:59之间的所有当地时间。如果我在此间隔中创建本地日期,则会将其调整为下一个有效时刻:

1
2
3
4
5
6
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日午夜,时钟从17日午夜到晚上23点转移1小时。因此,从23:00到23:59的所有当地时间都存在两次(在DST和非DST中),您必须决定您想要哪一个:

1
2
3
4
5
6
7
8
9
10
// 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-03:00)。这会影响epochMilli的价值。

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


更正了有关yyyy-dd-MM的代码。微小的值也可能是1-59而不是60.你提供了60.这是解决问题的另一种简单方法。只需使用DateFormat类。

1
2
3
4
5
6
7
8
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();
}


您的代码将因以下3个原因而失败。

  • 您的日期字符串(2017-18-08 12:60:30.345)与您使用的格式化程序不匹配。 它应该是yyyy-MM-dd HH:mm:ss.SSS而不是yyyy-dd-MM hh:mm:ss.SSS
  • 分钟范围是(0-59),60不在此范围内。
  • 即使你已经纠正了基于上述点的代码,它也不会运行ZonedDateTime。 因此,您需要先创建LocalDateTime,然后再将ZoneId传递给它。
  • 代码应如下所示:

    1
    2
    3
    4
    5
    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());

    只是我在nagendra547的答案中做了一些改变

    请访问以下代码: -

    1
    2
    3
    4
    5
    6
    7
    8
    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();
    }