[英]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 的規則發生變化,那么這種方法將需要更新。 是否有可以執行類似功能的庫?
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
將 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/Montreal
、 Africa/Casablanca
或Pacific/Auckland
。 永遠不要使用EST
或IST
等 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 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.Date
, Calendar
,和SimpleDateFormat
。
現在處於維護模式的Joda-Time項目建議遷移到java.time類。
要了解更多信息,請參閱Oracle 教程。 並在 Stack Overflow 上搜索許多示例和解釋。 規范是JSR 310 。
從哪里獲得 java.time 類?
ThreeTen-Extra項目用額外的類擴展了 java.time。 該項目是未來可能添加到 java.time 的試驗場。 您可以在這里找到一些有用的類,比如Interval
, YearWeek
, YearQuarter
,和更多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.