3

如何在java中转换1413225446.92000纪元ZonedDateTime

给出的代码需要值,因此这将抛出NumberFormatException上面给出的值。

ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(dateInMillis)), ZoneId.of(TIME_ZONE_PST));
4

3 回答 3

4

java.time 可以直接解析你的字符串

编辑:如果您的毫秒值始终为非负数,则DateTimeFormatter可以解析它。

private static final String TIME_ZONE_PST = "America/Los_Angeles";
private static final DateTimeFormatter epochFormatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NEVER)
        .optionalStart()
        .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
        .optionalEnd()
        .toFormatter()
        .withZone(ZoneId.of(TIME_ZONE_PST));

现在解析成 aZonedDateTime只是一个方法调用:

    ZonedDateTime zdt = ZonedDateTime.parse(dateInMillis, epochFormatter);
    System.out.println(zdt);

输出是:

2014-10-13T11:37:26.920-07:00[美国/洛杉矶]

它不能在负值下正常工作:分数仍会被解析为正数,我假设这是不正确的。为了确保在出现负值的情况下得到通知,我在格式化程序中指定了该数字无法签名。

更通用的解决方案:使用 BigDecimal

如果您需要更通用的解决方案,例如包括负数,我认为最好让BigDecinmal解析数字并进行数学运算。

    BigDecimal bd = new BigDecimal(dateInMillis);
    BigDecimal[] wholeAndFractional = bd.divideAndRemainder(BigDecimal.ONE);
    long seconds = wholeAndFractional[0].longValueExact();
    int nanos = wholeAndFractional[1].movePointRight(9).intValue();
    ZonedDateTime zdt = Instant.ofEpochSecond(seconds, nanos)
            .atZone(ZoneId.of(TIME_ZONE_PST));

输出和以前一样。只是现在我们还可以按预期处理负数:

    String dateInMillis = "-1.5";

1969-12-31T15:59:58.500-08:00[美国/洛杉矶]

甚至科学记数法也被接受:

    String dateInMillis = "1.41322544692E9";

2014-10-13T11:37:26.920-07:00[美国/洛杉矶]

如果字符串中可能有比纳秒更精细的精度,请考虑您希望如何截断或舍入,并BigDecimal相应地指示,有许多选项。

原始答案

Basil Bourque 的回答很好。从小数部分取出纳秒到纳秒的整数可能会带来一两个陷阱。我建议:

    String dateInMillis = "1413225446.92000";
    String[] secondsAndFraction = dateInMillis.split("\\.");
    int nanos = 0;
    if (secondsAndFraction.length > 1) { // there’s a fractional part
        // extend fractional part to 9 digits to obtain nanoseconds
        String nanosecondsString
                = (secondsAndFraction[1] + "000000000").substring(0, 9);
        nanos = Integer.parseInt(nanosecondsString);
        // if the double number was negative, the nanos must be too
        if (dateInMillis.startsWith("-")) {
            nanos = -nanos;
        } 
    }
    ZonedDateTime zdt = Instant
            .ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
            .atZone(ZoneId.of("Asia/Manila"));
    System.out.println(zdt);

这打印

2014-10-14T02:37:26.920+08:00[Asia/Manila]

我们不需要 64 位纳秒,所以我只是使用int.

假设:我假设您的字符串包含一个浮点数并且它可能是有符号的,例如意味着在纪元-1.50一秒半。如果有一天您的纪元时间以科学计数法出现(1.41322544692E9),上述方法将不起作用。

如果不是亚洲/马尼拉,请以地区/城市格式替换您想要的时区,例如美国/温哥华、美国/洛杉矶或太平洋/皮特凯恩。避免使用 PST 之类的三个字母缩写,它们是模棱两可的,而且通常不是真实的时区。

于 2017-12-26T09:20:44.153 回答
1

将数字拆分为一对 64 位长整数:

  • 自 UTC 1970 年第一刻的纪元参考日期以来的整秒数
  • 小数秒的纳秒数

将这些数字传递给工厂方法Instant.ofEpochSecond​(long epochSecond, long nanoAdjustment)

拿着aInstant在手,继续分配一个时区来获得一个ZonedDateTime.

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
于 2017-12-26T07:24:52.433 回答
1

在这里扩展 Basil 和 Ole 的答案,对于时间戳的特殊情况,即在纪元之前。这甚至可能吗?这是Jon Skeet在“All about java.util.Date”中所写的:

Date 类使用“自 Unix 纪元以来的毫秒数”——这是 getTime() 返回的值,由 Date(long) 构造函数或 setTime() 方法设置。由于月球漫步发生在 Unix 纪元之前,该值为负数:实际上是 -14159020000。

Ole 的答案(除了一些额外的断言)之间唯一真正的区别是,在这里,如果日期字符串以负号开头,我们不会反转 nanos 上的符号。这样做的原因是,当将 nanos 传递给Instant 构造函数时,这是一个调整,所以如果我们将 nanos 作为负数发送,它实际上会调整秒数,因此整个 ZonedDateTime 值被 nanos 关闭。

这是来自 JavaDoc,请注意有趣的行为:

此方法允许传入任意数量的纳秒。工厂将更改秒和纳秒的值,以确保存储的纳秒在 0 到 999,999,999 的范围内。例如,以下将导致完全相同的瞬间:

Instant.ofEpochSecond(3, 1);
Instant.ofEpochSecond(4,-999_999_999);
Instant.ofEpochSecond(2, 1000_000_001);

所以第二个参数 nanos,我们没有设置值,它是一个调整。因此,与正时间戳(在 epoch 之后)相同,我们希望发送实际的 nanos。

以 Ole 的代码为基础并添加上述更改:

    String strDateZoned = "Jul 20 1969 21:56:20.599 CDT"; // yes, should use America/Chicago here as Ole points out
    DateTimeFormatter dtfFormatter  = DateTimeFormatter.ofPattern("MMM dd yyyy HH:mm:ss.SSS zzz");
    ZonedDateTime     originalZoned  = ZonedDateTime.parse(strDateZoned, dtfFormatter);

    long epochSeconds = originalZoned.toInstant().getEpochSecond();
    int nanoSeconds = originalZoned.toInstant().getNano();
    String dateInMillis = epochSeconds + "." + nanoSeconds;
    String[] secondsAndFraction = dateInMillis.split("\\.");
    int nanos = 0;
    if (secondsAndFraction.length > 1) { // there’s a fractional part
        // extend fractional part to 9 digits to obtain nanoseconds
        String nanosecondsString
                = (secondsAndFraction[1] + "000000000").substring(0, 9);
        nanos = Integer.parseInt(nanosecondsString);
    }

    ZonedDateTime zdt = Instant
            .ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
            .atZone(ZoneId.of("America/Chicago"));

    String formattedZdt = dtfFormatter.format(zdt);

    System.out.println("zoneDateTime expected    = " + strDateZoned);
    System.out.println("zoneDateTime from millis = " + formattedZdt);

    assertEquals("date in millis is wrong",  "-14159020.599000000", dateInMillis);
    assertEquals("date doesn't match expected",strDateZoned, dtfFormatter.format(zdt));

代码输出:

zoneDateTime expected    = Jul 20 1969 21:56:20.599 CDT
zoneDateTime from millis = Jul 20 1969 21:56:20.599 CDT

如果我们在秒部分为负的情况下反转 nanos 上的符号,我们可以看到格式化的 ZonedDateTime 的差异:

org.junit.ComparisonFailure: date doesn't match expected 
Expected :Jul 20 1969 21:56:20.599 CDT
Actual   :Jul 20 1969 21:56:19.401 CDT

PS 'All About Dates' 帖子中关于 Jon Skeet 所说的“宽大处理”以及我在其他地方看到的称为“规范化”的更多想法,这可能是由于 POSIX 的影响:

没有明显的理由是宽松的:“在所有情况下,为这些目的提供给方法的参数不必在指定的范围内;例如,日期可以指定为 1 月 32 日,并被解释为 2 月 1 日。” 多久有用一次?

于 2019-08-27T20:32:25.617 回答