簡體   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