繁体   English   中英

在 Java 中将纪元转换为 ZonedDateTime

[英]converting epoch to ZonedDateTime in Java

如何在java ZonedDateTime1413225446.92000这样的纪元转换为ZonedDateTime

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

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

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));

现在解析为ZonedDateTime只是一种方法调用:

    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,它们含糊不清,通常不是真正的时区。

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

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

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

使用Instant ,继续分配时区以获取ZonedDateTime

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

在这里扩展 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 日。” 多久有用?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM