以往處理日期是用 java.util.Date,以及 java.util.Calendar,但 Date 裡面不存在時區概念,只是 Timestamp 的一個 wrapper,Calendar 也很奇怪的將一月份的數值設定為 0,Java 8 以後可以改用 java.time.* API
java.time 這個 package 裡面有這些 class
- Instant – represents a point in time (timestamp)
- LocalDate – represents a date (year, month, day)
- LocalDateTime – same as LocalDate, but includes time with nanosecond precision
- OffsetDateTime – same as LocalDateTime, but with time zone offset
- LocalTime – time with nanosecond precision and without date information
- ZonedDateTime – same as OffsetDateTime, but includes a time zone ID
- OffsetLocalTime – same as LocalTime, but with time zone offset
- MonthDay – month and day, without year or time
- YearMonth – month and year, without day or time
- Duration – amount of time represented in seconds, minutes and hours. Has nanosecond precision
- Period – amount of time represented in days, months and years
有 Timezone 的時間
Date 跟 Instant 的概念不同,處理不同時區的做法不同,但 Instant 的做法比較直覺
Instant nowInstant = Instant.now();
ZoneId zoneIdTaipei = ZoneId.of("Asia/Taipei");
ZoneId zoneIdTokyo = ZoneId.of("Asia/Tokyo");
ZonedDateTime nowZonedDateTimeTaipei = ZonedDateTime.ofInstant(nowInstant, zoneIdTaipei);
ZonedDateTime nowZonedDateTimeTokyo = ZonedDateTime.ofInstant(nowInstant, zoneIdTokyo);
System.out.println("nowZonedDateTimeTaipei="+nowZonedDateTimeTaipei);
System.out.println("nowZonedDateTimeTokyo="+nowZonedDateTimeTokyo);
// 跟想像的一樣,將現在的時間放到不同時區,可得到不同時區的正確時間
// nowZonedDateTimeTaipei=2023-06-14T14:42:44.873547+08:00[Asia/Taipei]
// nowZonedDateTimeTokyo=2023-06-14T15:42:44.873547+09:00[Asia/Tokyo]
Date nowDate = new Date();
System.out.println("nowDate="+nowDate);
TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
Calendar calendar = Calendar.getInstance(tz);
calendar.setTime(nowDate);
Date nowDate2 = calendar.getTime();
System.out.println("nowDate2="+nowDate2);
// 因為 Date 沒有時區的概念,是儲存 GMT 1970/1/1 00:00:00 以後所經過的 ms 數值
// 但列印 Date 時,會自動根據執行程式的時區,轉換為該時區的時間
// 把現在時間 Date 放到 Tokyo 時區,取回新的 Date 以後,結果兩個 Date 很奇怪的結果是一樣的
// nowDate=Wed Jun 14 14:42:44 CST 2023
// nowDate2=Wed Jun 14 14:42:44 CST 2023
// 要搭配 SimpleDateFormat
SimpleDateFormat sdfTaipei = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdfTaipei.setTimeZone(TimeZone.getTimeZone("Asia/Taipei"));
SimpleDateFormat sdfTokyo = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdfTokyo.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println();
System.out.println("nowDate="+nowDate);
System.out.println("nowDate Taipei="+sdfTaipei.format(nowDate));
System.out.println("nowDate Tokyo="+sdfTokyo.format(nowDate));
// nowDate=Wed Jun 14 14:53:19 CST 2023
// nowDate Taipei=2023-06-14 14:53:19
// nowDate Tokyo=2023-06-14 15:53:19
java.time 的優點
immutable 且為 thread-safe,所有的 method 回傳的物件都是產生一個新的物件,物件本身的狀態是永久不變的,因此為 thread-safe。java.util.Date 並不是 thread-safe
因為 method 會回傳新的物件,因此可以做 method chaining
ZonedDateTime nextFriday = LocalDateTime.now()
.plusHours(1)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
.atZone(ZoneId.of("Asia/Taipei"));
System.out.println("plus 1hr nextFriday="+nextFriday);
新舊寫法
以下是一些特定工作,新舊 API 的不同寫法
/* get current timestamp */
Date now = new Date();
Instant instant = Instant.now();
// 帶有時區的現在時間
ZonedDateTime zonedDateTime = ZonedDateTime.now();
/* 特定時間 */
Date sDate = new GregorianCalendar(1990, Calendar.JANUARY, 15).getTime();
// New
LocalDate sLocalDate = LocalDate.of(1990, Month.JANUARY, 15);
/* 時間的加減 */
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBeforeOld = calendar.getTime();
// New
LocalDateTime fiveHoursBeforeNew = LocalDateTime.now().minusHours(5);
/* 修改特定欄位 */
// Old
GregorianCalendar calc = new GregorianCalendar();
calc.set(Calendar.MONTH, Calendar.JUNE);
Date inJuneOld = calc.getTime();
// New
LocalDateTime inJuneNew = LocalDateTime.now().withMonth(Month.JUNE.getValue());
/* Date Time truncating */
// Old
Calendar nowCalc = Calendar.getInstance();
nowCalc.set(Calendar.MINUTE, 0);
nowCalc.set(Calendar.SECOND, 0);
nowCalc.set(Calendar.MILLISECOND, 0);
Date truncatedOld = nowCalc.getTime();
// New
LocalTime truncatedNew = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
/* TimeZone 轉換 */
// Old
GregorianCalendar cal2 = new GregorianCalendar();
cal2.setTimeZone(TimeZone.getTimeZone("Asia/Taipei"));
Date centralEasternOld = calendar.getTime();
// New
ZonedDateTime centralEasternNew = LocalDateTime.now().atZone(ZoneId.of("Asia/Taipei"));
/* Time difference */
// Old
GregorianCalendar calc3 = new GregorianCalendar();
Date nowdate = new Date();
calc3.add(Calendar.HOUR, 1);
Date hourLater = calc3.getTime();
long elapsed = hourLater.getTime() - nowdate.getTime();
// New
LocalDateTime nowLocalDateTime = LocalDateTime.now();
LocalDateTime hourLaterLocalDateTime = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(nowLocalDateTime, hourLaterLocalDateTime);
/* Date formatter, parsing */
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date nowdate2 = new Date();
String formattedDateOld = dateFormat.format(nowdate2);
try {
Date parsedDateOld = dateFormat.parse(formattedDateOld);
} catch (ParseException e) {
throw new RuntimeException(e);
}
// New
LocalDate nowLocalDate = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDateNew = nowLocalDate.format(formatter);
LocalDate parsedDateNew = LocalDate.parse(formattedDateNew, formatter);
新舊 classes 之間能夠互相轉換
// GregorianCalendar 轉為 Instant
Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
// GregorianCalendar 轉為 ZonedDateTime
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
// Instant 轉為 Date
Date dateFromInstant = Date.from(Instant.now());
// ZonedDateTime 轉為 GregorianCalendar
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
// Date 轉為 Instant
Instant instantFromDate = new Date().toInstant();
// TimeZone 轉為 ZoneId
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("Asia/Taipei").toZoneId();
References
Set the Time Zone of a Date in Java | Baeldung
Convert Date to LocalDate or LocalDateTime and Back | Baeldung
Migrating to the New Java 8 Date Time API | Baeldung
How to set time zone of a java.util.Date? - Stack Overflow