簡體   English   中英

在 DST 當天將 GregorianCalendar 轉換為 Date 會丟失一個小時嗎?

[英]Converting GregorianCalendar to Date on day of DST loses an hour?

我正在解決將僅表示當前日期(即// 2013-03-10 00:00:00)的 GregorianCalendar 轉換為 java.util.Date 對象的問題。 這個測試背后的想法是取兩個日期 - 一個只有當前日期,一個只有當前時間(即// 1970-01-01 12:30:45),並將它們組合成一個代表日期的日期和時間 (2013-03-10 12:30:45)。

在 DST 切換發生的那天,測試失敗了 - 因為將 GregorianCalendar 轉換為日期對象(Date date = dateCal.getTime(); 在下面的代碼中)丟失了一個小時,因此回滾到 (2013-03-09 23:00:00)。 我怎樣才能讓這不會發生?

public static Date addTimeToDate(Date date, Date time) {
    if (date == null) {
        throw new IllegalArgumentException("date cannot be null");
    } else if (time == null) {
        throw new IllegalArgumentException("time cannot be null");
    } else {
        Calendar timeCal = GregorianCalendar.getInstance();
        timeCal.setTime(time);

        long timeMs = timeCal.getTimeInMillis() + timeCal.get(Calendar.ZONE_OFFSET) + timeCal.get(Calendar.DST_OFFSET);
        return addMillisecondsToDate(date, timeMs);
    }
}


@Test
public void testAddTimeToDate() {
    Calendar expectedCal = Calendar.getInstance();
    Calendar dateCal = Calendar.getInstance();
    dateCal.clear();
    dateCal.set(expectedCal.get(Calendar.YEAR), expectedCal.get(Calendar.MONTH), expectedCal.get(Calendar.DAY_OF_MONTH));

    Calendar timeCal = Calendar.getInstance();
    timeCal.clear();
    timeCal.set(Calendar.HOUR_OF_DAY, expectedCal.get(Calendar.HOUR_OF_DAY));
    timeCal.set(Calendar.MINUTE, expectedCal.get(Calendar.MINUTE));
    timeCal.set(Calendar.SECOND, expectedCal.get(Calendar.SECOND));
    timeCal.set(Calendar.MILLISECOND, expectedCal.get(Calendar.MILLISECOND));

    Date expectedDate = expectedCal.getTime();
    Date date = dateCal.getTime();
    Date time = timeCal.getTime();

    Date actualDate = DateUtil.addTimeToDate(date, time);

    assertEquals(expectedDate, actualDate);
}

為什么要在計算中包含時區偏移量? 當您在 Java 中使用毫秒時,它們始終使用 UTC。 您不需要進行任何額外的轉換。

您最大的問題可能是嘗試手動進行這些日期/時間計算。 您應該使用 Calendar 類本身來處理計算。

我試過了,沒有區別。 甚至不同的語言環境,並用 Calendar 替換 GregorianCalendar ..

用過的:

private static Date addMillisecondsToDate(Date date, long timeMs) {
    return new Date(date.getTime() + timeMs);
}

即將推出的 Java 8 具有更好的日期/時間支持。

這就是我最終重構我的方法以補償由於夏令時而損失/增加的小時數的方式:

public static Date addTimeToDate(Date date, Date time) {
    if (date == null) {
        throw new IllegalArgumentException("date cannot be null");
    } else if (time == null) {
        throw new IllegalArgumentException("time cannot be null");
    } else {
        Calendar dateCal = GregorianCalendar.getInstance();
        dateCal.setTime(date);

        Calendar timeCal = GregorianCalendar.getInstance();
        timeCal.setTime(time);
        int zoneOffset = timeCal.get(Calendar.ZONE_OFFSET);

        if (dateCal.get(Calendar.MONTH) == Calendar.MARCH) {
            if (Calendar.SUNDAY == dateCal.get(Calendar.DAY_OF_WEEK) && dateCal.get(Calendar.DAY_OF_MONTH) >= 7
                    && dateCal.get(Calendar.DAY_OF_MONTH) <= 14 && timeCal.get(Calendar.HOUR_OF_DAY) >= 3) {
                zoneOffset -= TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
            }
        } else if (dateCal.get(Calendar.MONTH) == Calendar.NOVEMBER) {
            if (Calendar.SUNDAY == dateCal.get(Calendar.DAY_OF_WEEK) && dateCal.get(Calendar.DAY_OF_MONTH) <= 7
                    && timeCal.get(Calendar.HOUR_OF_DAY) >= 3) {
                zoneOffset += TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
            }
        }
        long timeMs = timeCal.getTimeInMillis() + zoneOffset + timeCal.get(Calendar.DST_OFFSET);
        return addMillisecondsToDate(date, timeMs);
    }
}

我不喜歡這種方法,因為如果 DST 的規則發生變化,那么這種方法將需要更新。 是否有可以執行類似功能的庫?

tl;博士

ZonedDateTime.of(
    LocalDate.parse( "2013-03-10" ) ,
    LocalTime.parse( "12:30:45" ) ,
    ZoneId.of( "Africa/Tunis" )
)                                    // Instantiate a `ZonedDateTime` object.
.toString()                          // Moment seen through wall-clock time of people in Tunisia time zone.

2013-03-10T12:30:45+01:00[非洲/突尼斯]

ZonedDateTime.of(
    LocalDate.parse( "2013-03-10" ) ,
    LocalTime.parse( "12:30:45" ) ,
    ZoneId.of( "Africa/Tunis" )
)
.toInstant()                         // Convert to `Instant` from `ZonedDateTime`, for UTC value.
.toString()                          // Same moment, adjusted into wall-clock time of UTC. The Tunisian wall-clock is an hour ahead of UTC, but both represent the same simultaneous moment, same point on the timeline.

2013-03-10T11:30:45Z

UTC 與分區

將 GregorianCalendar 轉換為日期對象……丟失了一個小時,因此回滾了

GregorianCalendar包括時區。 如果不指定時區,則隱式分配 JVM 的當前默認時區。 相比之下, java.util.Date始終采用 UTC。 令人困惑的是, Date::toString方法在生成字符串時動態分配 JVM 的當前默認時區,造成分配時區的錯覺,而實際上內部值為 UTC。 一個可怕的混亂混亂。

我們無法進一步診斷您的具體情況,因為您沒有提供有關您機器上所涉及時區的信息。

但這一切都沒有實際意義,因為您應該改用java.time類。

避免遺留的日期時間類

您正在使用麻煩的舊日期時間類,這些類現在是遺留的,被現代java.time類所取代。

時間

這個測試背后的想法是取兩個日期 - 一個只有當前日期,一個只有當前時間(即// 1970-01-01 12:30:45),並將它們組合成一個代表日期的日期和時間 (2013-03-10 12:30:45)。

對於一天中的某個時間,請使用LocalTime 對於僅限日期,請使用LocalDate

LocalDate ld = LocalDate.parse( "2013-03-10" ) ;
LocalTime lt = LocalTime.parse( "12:30:45" ) ;

這些都沒有時區,也沒有從 UTC 偏移。 因此,在分配區域或偏移量之前,它們沒有任何意義。

continent/region的格式指定正確的時區名稱,例如America/MontrealAfrica/CasablancaPacific/Auckland 永遠不要使用ESTIST等 3-4 個字母的縮寫,因為它們不是真正的時區,不是標准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;

將區域分配給日期和時間以獲得ZonedDateTime

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

現在我們有一個實際的時刻,時間軸上的一個點。 如果您傳遞的ZonedDateTime在該區域的特定日期無效, LocalTime ZonedDateTime類會調整您的時間。 在諸如夏令時 (DST)之類的異常情況下需要進行此類調整。 請務必閱讀文檔以了解該調整的算法,看看您是否同意其方法。

要在 UTC 中查看同一時刻,請提取Instant 時間線上的相同點,不同的掛鍾時間。

Instant instant = zdt.toInstant() ;

關於java.time

java.time框架內置於 Java 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.DateCalendar ,和SimpleDateFormat

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

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

從哪里獲得 java.time 類?

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

暫無
暫無

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

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