java8 time
TRANSCRIPT
java.time.*@kojilin
2014/03/29@TWJUG
Java的Data和Time•java.util.Date
•Since 1.0
•java.util.Calendar
•Since 1.1
•java.time.*
•Since 1.8
•為取代而生
Date•雖然叫 Date, 但實際上是 timestamp,
#toString 又帶有 time zone
•存取麻煩
•年是1900 + ?
•月份從 0 開始
•不支援國際化
•Unix time
Calendar•為了國際化而導入
•雖然叫 Calendar, 但實際上是 Date 和 Time
•建立和編輯日期與時間
•存取和運算比 java.util.Date 稍好一點,但仍不敷使用
Calendar.Builder
Calendar cal1 = new Calendar.Builder() .setDate(2014, Calendar.MARCH, 29) .build();
•Java 8 新增,讓建立和變更能方便一點
Calendar cal2 = new Calendar.Builder() .setCalendarType("japanese") .setFiled(YEAR, 1, DAY_OF_YEAR, 1) .build();
java.time.*•JSR-310
•為了 JDK 設計
•從 Joda-Time 啓發和演化
•ISO 8601 為基礎
•Immutable
•Type-safe
•考慮到 XML 和 資料庫
ISO-8601•國際標準化組織的國際標準ISO 8601是日期
和時間的表示方法
•hh:mm:ss.s
•YYYY-MM-DDThh:mm:ss
•nYnMnD
•Gregorian Calendar (1582)
ThreeTen backport project
•https://github.com/ThreeTen/threetenbp
•JSR-310 backport to JDK 7
•最終會有一個釋出會和 JDK 8 正式版本相同
以人的角度看時間? vs
以機器的角度看時間?
機器•Instant
•從 Java Epoch 開始經過的時間
•1970-01-01T00:00:00Z
•支援到奈秒
•Duration
•兩個 Instant 之間的差距
•Clock
•模擬系統時鐘
InstantInstant now = Instant.now();
//2014-03-29T14:23:25.223Z now.toString();
//2014-03-29T14:23:28.223Z now.plusSeconds(3);
//2014-03-27T14:23:25.223Z now.minus(2, ChronoUnit.DAYS);
Duration•顯示時間的量
•使用 seconds 和 nanoseconds 表示
•toString 結果會是 PTnHnMnS
•顯示時可以用 hours,minutes,seconds 和 nanoseconds
•用 of 建立Duration.ofHours(2);
Duration.ofDays(1);
•從兩個 Temporal 取得
!
•有 Day 相關的方法,但這邊的 Day 就是 24 Hours 的意思
// 兩個參數都必須支援 ChronoUnit.SECONDSDuration.between(LocalDateTime.now(), LocalDateTime.now().plusDays(2));
!
!
!
// 現在時間加 24LocalDateTime.now().plus( Duration.ofDays(1));
人
•出生於 1983/12/11
•2月3日生
•營業時間是 14:00 到 22:00
•兩天,三小時
日期與時間•LocalDate
•LocalTime
•LocalDateTime
•OffsetTime
•OffsetDateTime
•ZonedDateTime
Local 系列•沒有帶時差和時區的資訊
•LocalDate
•12月5日生日
•LocalTime
•14:00 開始營業
•LocalDateTime
LocalDateLocalDate now = LocalDate.now();
now.toString(); // 2014-03-29
now.getDayOfWeek(); // SATURDAY
now.getDayOfMonth(); // 29
now.isLeapYear(); // false
// 2014-04-10 now.plusDays(12).toString();
LocalTimeLocalTime now = LocalTime.now();
now.toString(); // 14:20:42.270
now.getHour(); // 14
now.getMinute(); // 20
LocalTime.MIDNIGHT; // 00:00
LocalDateTimeLocalDateTime now = LocalDateTime.now();
// 2014-03-29T14:20:42.270 now.toString();
now.getDayOfMonth(); // 29
now.getHour(); // 14
•LocalDate <=> LocalDateTime
Local 之間的轉換
LocalDate localDate = LocalDate.now();
// LocalDate to LocalDateTimeLocalDateTime localDateTime = localDate.atTime(14, 20, 20);
// LocalDateTime to LocalDatelocalDate = localDateTime.toLocalDate();
•LocalTime <=> LocalDateTime
LocalTime localTime = LocalTime.now();
// LocalTime to LocalDateTimeLocalDateTime localDateTime = localTime.atDate( LocalDate.of(2014, 3, 29));
// LocalDateTime to LocalTimelocalTime = localDateTime.toLocalTime();
時差和 time zone•OffsetTime
•OffsetDateTime
•ZonedDateTime
•ZonedDateTime != OffsetDateTime
•時差是固定的,Zoned 則會依照狀況變換時差,例如:日光節約時間, 更改時差
ZonedDateTime dateTime = ZonedDateTime.of( LocalDate.of(1975, 3, 31), LocalTime.of(23, 0), ZoneId.of("Asia/Taipei"));
!
//1975-03-31T23:00+08:00[Asia/Taipei]dateTime.toString();
//1975-04-01T01:00+09:00[Asia/Taipei]dateTime.plusHours(1);
•台灣1975日光節約時間爲4月1日到9月30日
ZonedDateTime dateTime = ZonedDateTime.of( LocalDate.of(1979, 6, 30), LocalTime.of(23, 0), ZoneId.of("Asia/Taipei"));
!
//1979-06-30T23:00+09:00[Asia/Taipei]dateTime.toString();
•台灣1979日光節約時間爲7月1日到9月30日
Time zone•ZoneOffset
•-18:00~+18:00(-12:00 ~ +14:00)
•ZoneRules
•切換的規則
•ZoneId
•例如 Asia/Taipei, +8
Time zone data•tz database 有世界時區的分類和命名
•IANA Time Zone Database (TZDB)
•$JDK_HOME/jre/lib/tzdb.dat
•透過 ZoneRulesProvider 讀取
•一直有更新,所以記得要持續更新 JRE 或透過 Oracle 提供的工具更新
ZoneRuleProvider•Service provider interface
•可以在 META-INF/services 自己提供
•系統預設是 TzdbZoneRuleProvider
•提供動態更新的方法
•帶來很多複雜的問題,例如:取消或減少暫存造成效能問題,或是程式中拿不同規則來做運算
•通常不建議動態更新
public static boolean refresh() {
boolean changed = false;
for (ZoneRulesProvider provider : PROVIDERS) {
changed |= provider.provideRefresh();
}
return changed;
}
•ZoneRulesProvider.refresh
public static boolean refresh() {
boolean changed = false;
for (ZoneRulesProvider provider : PROVIDERS) {
changed |= provider.provideRefresh();
}
return changed;
}
•ZoneRulesProvider.refresh
•回傳的 boolean 可以讓應用程式知道有沒有更新
•系統預設的 provider#provideRefresh 是回傳 false
DateTime 之間的轉換•LocalDateTime, LocalDate, LocalTime
•OffsetDateTime, OffsetTime
•ZonedDateTime
•補足需要的資訊或拿掉多餘的資訊就可以彼此間轉換
DateTime 之間的轉換•LocalDateTime, LocalDate, LocalTime
•OffsetDateTime, OffsetTime
•ZonedDateTime
•補足需要的資訊或拿掉多餘的資訊就可以彼此間轉換
ZonedDateTime zdt = LocalDateTime.now() .atZone(ZoneId.of(“Asia/Taipei”));
LocalDateTime ldt = zdt.toLocalDateTime();
Period•顯示時間的量
•使用 years, months 和 days 表示
•toString 結果會是 PnYnMnD
•用 of 建立
Period.ofWeeks(2);
// 兩個參數都是 LocalDate Period.between(localDate1, LocalDate.now());
!
!
// 會是1975-04-01T23:00+09:00[Asia/Taipei]// 如果是 Duration, 則會是加上 24 小時ZonedDateTime.of( LocalDate.of(1975, 3, 31), LocalTime.of(23, 0), ZoneId.of("Asia/Taipei")) .plus(Period.ofDays(1));
•從兩個 LocalDate 取得
!
!
•用在 ZonedDateTime 時有考慮到 DST
Formatting•用 DateTimeFormatter 取代
SimpleDateFormat
•Immutable - Thread safe
•可以利用 DateTimeFormatterBuilder 建立新格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
localDate.format(dateTimeFormatter);
offset.format(dateTimeFormatter);
ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
!
LocalDate ld = LocalDate.parse("2011-12-03", ISO_LOCAL_DATE);
Parsing•Instant, Date 和 Time 類別都用一樣方式
parse
•Period, Duration 用 ISO-8601格式
Instant.parse("…", dateTimeFormatter);
LocalDate.parse("…", dateTimeFormatter);
//1 Year 2 months 3days Period.parse("P1Y2M3D");
//2 days 3 hours 2.21 secondsDuration.parse("P2DT3H2.21S");
•有些格式變得比較嚴格
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyy/MM/dd");
//okLocalDate.parse("2014/03/02", formatter);
// error, DateTimeParseException//LocalDate.parse("2014/3/2", formatter);
//oknew SimpleDateFormat("yyyy/MM/dd") .parse("2014/3/2");
Clock•透過 Inject 便於測試
•可以建立多種類型的 Clock
•讓測試可以獨立於 time zone
private Clock clock; // dependency inject
public void process(LocalDate event) {
if(event.isBefore(LocalDate.now(clock)){ ...
//使用系統 Clock.systemDefaultZone();
//使用指定 ZoneId Clock.system(ZoneId.of("Asia/Tokyo"));
//固定時間 Instant instant = … ;
Clock.fixed(instant, ZoneIf.of("Asia/Taipei"));
//使用 30秒 tick 的時鐘Clock.tick(Clock.systemDefault(), Duration.ofSeconds(30));
其他表示方式
•Year
•Month
•YearMonth
•MonthDay
•DayOfWeek
現在LocalTime.now();
LocalTime.now(Clock.fixed(...));
LocalDate.now();
LocalDateTime.now();
OffsetDateTime.now();
of•Static factory method
LocalDate.of(2014, 3, 29);
LocalTime.of(14, 5, 25);
Year.of(-1);
at•Combines this object with another
LocalDate.of(2014, 3, 29).atTime( LocalTime.of(14, 5, 25));
parse•Static factory method focussed on
parsing
LocalDate.parse("2014-03-29");
LocalTime.parse("14:23:20");
with•The immutable equivalent of a setter
LocalDate.now().withMonth(4);
LocalTime.now().withMinute(30);
plus, minus•Adds/Subtracts an amount to an object
LocalDate.now().plusDays(4) .plusYear(2);
LocalTime.now().plusMinutes(30) .plusHours(4) .minusSeconds(30);
isBefore, isAfter•Check order
LocalDate.now().isBefore( LocalDate.of(2014, 12, 3));
LocalTime.now().isAfter( LocalTime.of(4, 12, 31));
until, between•Amount of time until another
LocalDate.of(2014, 12, 3).until( LocalDate.of(2014, 12, 7), ChronoUnit.WEEKS);
ChronoUnit.DAYS.between( LocalDate.of(2014, 12, 3), LocalDate.of(2014, 12, 7));
Chronology•Chronology
•曆
•Era
•紀年
•ChronoLocalDate
•ChronoLocalDateTime
•ChronoZonedDateTime
民國年•MinguoDate
!
!
•和 ISO-8601 之間轉換
//Minguo ROC 103-03-29 MinguoDate date = MinguoDate.of(103, 3, 29);
LocalDate.from( MinguoDate.of(103, 3, 29));MinguoDate.from( LocalDate.now());
與 Date 的轉換•LocalDate 和 LocalTime 本身沒有 Instant 值,所以必須要先轉換成能取得 Instant
•從 LocalDateTime 到 DateLocalDate now = LocalDate.now();
Instant instant = now.atZone(ZoneId.systemDefault()) .toInstant();
Date date = Date.from(instant);
•從 Date 到 LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime = LocalDateTime.ofInstant( instant, ZoneId.systemDefault());
可擴充的介面設計•TemporalAccessor
•Temporal
•TemporalField
•TemporalAmount
•TemporalUnit
•TemporalQuery
•TemporalAdjuster
•可讀取的 Date&Time
•可修改的 Date&Time
•Date&Time 的 filed
•時間的量
•Date&Time 的單位
•查詢 Date&Time
•修改 Date&Time
TemporalQuery
@FunctionalInterface
public interface TemporalQuery<R>{
R queryFrom(TemporalAccessor temporal);
}
//DaysLocalDate.of(2014, 3, 29) .query(TemporalQueries.precision());
//2014-03-29T16:30:26.043 ZonedDateTime.now .query(LocalDateTime::from);
//SATURDAYLocalDate.of(2014, 3, 29) .query(DayOfWeek::from);
TemporalAdjuster
@FunctionalInterface
public interface TemporalAdjuster{
Temporal adjustInfo(Temporal temporal);
}
//2014-03-31 LocalDate.of(2014, 3, 29) .with(TemporalAdjusters.lastDayOfMondth());
//2014-04-05LocalDate.of(2014, 3, 29) .with(TemporalAdjusters.next( DayOfWeek.SATURDAY));
//2014-01-02LocalDate.now().with(t -> t.with(ChronoField .DAY_OF_YEAR, 2));
JDBC• LocalDate
• LocalTime
• LocalDateTime
• OffsetTime
• OffsetDateTime
• DATE
• TIME WITHOUT TIME ZONE
• TIMESTAMP WITHOUT TIME ZONE
• TIME WITH TIME ZONE
• TIMESTAMP WITH TIMZ ZONE
LocalDateTime << SQLResultSet rs = …;
…
java.sql.Time time = rs.getTime(…);
LocalTime lt = time.toLocalTime();
java.sql.Date date = rs.getDate(…);
LocalDate ld = date.toLocalDate();
java.sql.Timestamp ts = rs.getTimeStamp(…);
LocalDateTime ldt = ts.toLocalDateTime();
LocalDateTime >> SQLPreparedStatement pstmt = …;
pstmt.setTime(1, Time.valueOf(LocalTime.now()));
pstmt.setDate(2, Date.valueOf(LocalDate.now()));
pstmt.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now()));
總結 JSR 310 的好處•良好的命名和 API 設計
•日期和時間的分別處理
•Immutable
•精確到奈秒
•多樣化的操作
•存取各種 filed 方便