【问题标题】:converting epoch to ZonedDateTime in Java在 Java 中将 epoch 转换为 ZonedDateTime
【发布时间】:2017-12-26 07:17:46
【问题描述】:

如何在java中将像1413225446.92000这样的纪元转换为ZonedDateTime

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

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

【问题讨论】:

  • 太平洋标准时间时区?当心那些三个(和四个)字母的时区缩写,它们是模棱两可的,通常不是真正的时区。菲律宾标准时间、太平洋标准时间还是皮特凯恩标准时间?而是给出时区,例如 America/Vancouver,即 region/city
  • 我同意@Ole。在我的代码中 TIME_ZONE_PST 指的是 America/Los_Angeles
  • 很好,Sajin Surendran。不过,我建议重命名,因为洛杉矶一年中的大部分时间都在 PDT,而不是 PST。
  • Sajin Surendran,它可能不再感兴趣,只是以防你好奇:我想了一个不同的方法来解决你的任务并编辑了我的答案。

标签: java epoch datetime-conversion zoneddatetime


【解决方案1】:

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’s answer 不错。从小数部分取出纳秒到纳秒的整数可能会带来一两个陷阱。我建议:

    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),上述方法将不起作用。

如果不是亚洲/马尼拉,请用 region/city 格式替换您想要的时区,例如 America/Vancouver、America/Los_Angeles 或 Pacific/Pitcairn。避免使用 PST 之类的三个字母缩写,它们不明确且通常不是真实的时区。

【讨论】:

  • 您不妨为 nanos 使用 64 位 longInstant.ofEpochSecond 的参数是这样传递的。
  • 嗯,这会浪费一些位,因为 nanos 存储为 int,并且永远不会 > 999_999_999。但无论如何,就这里的解决方案而言,对处理负数的一个小修正:你不想要nanos = -nanos,因为 nanos arg 是一个偏移调整,如果你将它作为负数发送,你调整纪元秒,当你想设置纳米。 Instant
  • 感谢@michaelok 的评论和回答。根据您的假设,您的答案是正确的。我的是在我的假设下。哪些假设是正确的,只有 OP 才能知道。
【解决方案2】:

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

  • 自世界标准时间 1970 年第一刻的纪元参考日期以来的整秒数
  • 小数秒的纳秒数

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

有了Instant,继续分配时区以获取ZonedDateTime

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

【讨论】:

    【解决方案3】:

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

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

    Ole 的答案(除了一些额外的断言)之间唯一真正的区别是,如果日期字符串以负号开头,我们不会反转 nanos 上的符号。这样做的原因是,当将 nanos 传递给 Instant constructor 时,这是一个调整,所以如果我们将 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
    

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

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

    【讨论】:

      猜你喜欢
      • 2018-12-24
      • 2015-11-07
      • 2017-05-31
      • 2020-01-19
      • 2019-09-02
      • 1970-01-01
      • 2019-08-03
      • 2018-09-25
      • 1970-01-01
      相关资源
      最近更新 更多