簡體   English   中英

如何將 ZonedDateTime 轉換為日期?

[英]How to convert ZonedDateTime to Date?

我正在嘗試在我的數據庫中設置一個與服務器無關的日期時間,我相信這樣做的最佳做法是設置一個 UTC 日期時間。 我的數據庫服務器是 Cassandra,Java 的數據庫驅動程序只能識別日期類型。

因此,假設在我的代碼中,我現在使用新的 Java 8 ZonedDateTime 來獲取 UTC( ZonedDateTime.now(ZoneOffset.UTC) ),我如何將此 ZonedDateTime 實例轉換為“傳統”Date 類?

您可以將ZonedDateTime轉換為瞬間,您可以直接使用Date。

Date.from(java.time.ZonedDateTime.now().toInstant());

TL;博士

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

雖然上面的代碼沒有意義。 java.util.DateInstant表示以UTC為單位的時刻,始終為UTC。 上面的代碼具有相同的效果:

new java.util.Date()  // Capture current moment in UTC.

使用ZonedDateTime沒有任何好處。 如果您已有ZonedDateTime ,請通過提取Instant來調整為UTC。

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

其他答案正確

ssoltanid答案正確地解決了您的具體問題,如何將新學校的java.time對象( ZonedDateTime )轉換為老式的java.util.Date對象。 從ZonedDateTime中提取Instant並傳遞給java.util.Date.from()

數據丟失

請注意,您將遭受數據丟失 ,因為Instant跟蹤自紀元以來的納秒 ,而java.util.Date跟蹤自紀元以來的毫秒

圖表比較了毫秒,微秒和納秒的分辨率

您的問題和意見提出了其他問題。

保持服務器的UTC

您的服務器通常應將其主機操作系統設置為UTC作為最佳做法。 在我所知道的Java實現中,JVM將此主機操作系統設置作為其默認時區。

指定時區

但是你永遠不應該依賴JVM的當前默認時區。 啟動JVM時傳遞的標志可以設置另一個時區,而不是選擇主機設置。 更糟糕的是:任何時候任何應用程序的任何線程中的任何代碼都可以調用java.util.TimeZone::setDefault來在運行時更改該默認值!

Cassandra Timestamp類型

任何體面的數據庫和驅動程序都應自動處理調整傳遞的UTC日期時間以進行存儲。 我不使用Cassandra,但似乎對日期時間有一些基本的支持。 文檔說它的Timestamp類型是相同紀元(1970年第一個UTC時刻)的毫秒數。

ISO 8601

此外,Cassandra接受ISO 8601標准格式的字符串輸入。 幸運的是,java.time使用ISO 8601格式作為解析/生成字符串的默認值。 Instant類的toString實現可以很好地完成。

精度:毫秒級與Nanosecord

但首先我們需要將ZonedDateTime的納秒精度降低到毫秒。 一種方法是使用毫秒創建一個新的Instant。 幸運的是,java.time有一些方便的轉換為毫秒和從毫秒轉換的方法。

示例代碼

以下是Java 8 Update 60中的一些示例代碼。

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

或者根據這個Cassandra Java驅動程序文檔 ,您可以傳遞一個java.util.Date實例(不要與java.sqlDate混淆)。 所以你可以從上面代碼中的instantTruncatedToMilliseconds創建一個juDate。

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

如果經常這樣做,你可以做一個單行。

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

但是創建一個小實用方法會更簡潔。

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

請注意所有這些代碼與問題中的區別。 問題的代碼試圖將ZonedDateTime實例的時區調整為UTC。 但這不是必要的。 概念:

ZonedDateTime = Instant + ZoneId

我們只提取已經是UTC的Instant部分(基本上是UTC,閱讀類doc以獲得精確的細節)。


關於java.time

java.time框架內置於Java 8及更高版本中。 這些類取代了麻煩的舊遺留日期時間類,如java.util.DateCalendarSimpleDateFormat

現在處於維護模式Joda-Time項目建議遷移到java.time類。

要了解更多信息,請參閱Oracle教程 並搜索Stack Overflow以獲取許多示例和解釋。 規范是JSR 310

您可以直接與數據庫交換java.time對象。 使用符合JDBC 4.2或更高版本的JDBC驅動程序 不需要字符串,不需要java.sql.*類。

從哪里獲取java.time類?

ThreeTen-Extra項目使用其他類擴展了java.time。 該項目是未來可能添加到java.time的試驗場。 您可以在這里找到一些有用的類,比如IntervalYearWeekYearQuarter ,和更多

以下是將當前系統時間轉換為UTC的示例。 它涉及將ZonedDateTime格式化為String,然后使用java.text DateFormat將String對象解析為日期對象。

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }

如果您使用Android的ThreeTen backport並且無法使用較新的Date.from(Instant instant) (需要最少的API 26),您可以使用:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

要么:

Date date = DateTimeUtils.toDate(zdt.toInstant());

另請閱讀Basil Bourque的回答中的建議

接受的答案對我不起作用。 返回的日期始終是本地日期,而不是原始時區的日期。 我住在UTC+2。

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

我想出了兩種從 ZonedDateTime 獲取正確日期的替代方法。

假設您有夏威夷的 ZonedDateTime

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

或最初詢問的UTC

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

備選方案 1

我們可以使用 java.sql.Timestamp。 這很簡單,但它也可能會削弱您的編程完整性

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

備選方案 2

我們從millis創建日期(之前 在這里回答)。 請注意,本地 ZoneOffset 是必須的。

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);

您可以使用Java 8及更高版本中內置的java.time類來完成此操作。

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

如果您現在只對此感興趣,那么只需使用:

Date d = new Date();

我用這個。

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

因為Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); 這不行! 如果你在計算機上運行你的應用程序,那不是問題。 但是如果您在AWS或Docker或GCP的任何區域中運行,都會產生問題。 因為計算機不是雲上的時區。 您應該在Code中設置正確的時區。 例如,亞洲/台北。 然后它將在AWS或Docker或GCP中更正。

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}

對於像beehuang這樣的碼頭工具應用程序,你應該設置你的時區。

或者,您可以使用withZoneSameLocal 例如:

2014-07-01T00:00 + 02:00 [GMT + 02:00]由...轉換

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

星期二01月01 00:00:00 CEST 2014

Date.from(zonedDateTime.toInstant())

2014年1月30日22:00:00 UTC

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM