JavaSE8 Goldへの道(Upgrade to Java SE 8 Programmer 1Z0-810 試験対策)12回目です。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
今回はDate/Time APIでの書式設定と、タイムゾーンを表すクラスの扱い方を解説します。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
今回はDate/Time APIでの書式設定と、タイムゾーンを表すクラスの扱い方を解説します。
書式設定
Date/Time APIでは従来の
書式設定はこのクラスを使います。
引数として
いくつかのフォーマットは事前定義されています。
java.text.DateFormat(SimpleDateFormat)
に代わり、新たにjava.time.format.DateTimeFormatter
が用意されました。書式設定はこのクラスを使います。
parse
,format
メソッドは各日時クラスとDateTimeFormatter
の両方に実装されていますが、後者のとくにparse
メソッドの方は戻り値の型がTemporalAccessor
になっているため、日時クラスの方を使うほうが良いでしょう。引数として
DateTimeFormatter
を渡します。いくつかのフォーマットは事前定義されています。
定数 | 例 |
---|---|
BASIC_ISO_DATE | '20111203' |
ISO_LOCAL_DATE | '2011-12-03' |
ISO_OFFSET_DATE | '2011-12-03+01:00' |
ISO_ZONED_DATE_TIME | '2011-12-03T10:15:30+01:00[Europe/Paris]' |
全ての一覧はAPIドキュメントに載っています。
SimpleDateFormat
のように独自に指定したい場合はofPattern
メソッドを使って行います。format
フォーマットの例(こちらの参考サイトのサンプルコードより。以降同様。)
// Current date and time LocalDateTime dateTime = LocalDateTime.now(); // Format as basic ISO date format String asBasicIsoDate = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE); System.out.println("BASIC ISO DATE : " + asBasicIsoDate); // Format as ISO week date String asIsoWeekDate = dateTime.format(DateTimeFormatter.ISO_WEEK_DATE); System.out.println("ISO WEEK DATE : " + asIsoWeekDate); // Format ISO date time String asIsoDateTime = dateTime.format(DateTimeFormatter.ISO_DATE_TIME); System.out.println("ISO DATE TIME : " + asIsoDateTime); // Use a custom pattern: day / month / year String asCustomPattern = dateTime.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); System.out.println("Custom pattern : " + asCustomPattern); // Use short Belarusian date/time formatting DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(new Locale("be")); String belarusDateTime = dateTime.format(formatter); System.out.println("Belarusian locale : " + belarusDateTime); // Use short US date/time formatting DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(new Locale("en-US")); String usDateTime = dateTime.format(formatter1); System.out.println("US locale : " + usDateTime); // FormatStyle.FULL、ロケール日本で出力(ZonedDateTimeを使用) DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.JAPAN); String jpDateTime = ZonedDateTime.now().format(formatter2); System.out.println("JP locale : " + jpDateTime);
実行結果
BASIC ISO DATE : 20180424 ISO WEEK DATE : 2018-W17-2 ISO DATE TIME : 2018-04-24T11:55:29.746 Custom pattern : 24/04/2018 Belarusian locale : 24.4.18 11.55 US locale : 4/24/18 11:55 AM JP locale : 2018年4月24日 11時55分29秒 JST
最後の
タイムゾーン情報がないとだめなようです。
まあおそらく通常の用途では
FormatStyle.FULL
を指定した場合ではLocalDateTime
だとjava.time.DateTimeException
になってしまいました。タイムゾーン情報がないとだめなようです。
まあおそらく通常の用途では
DateTimeFormatter.BASIC_ISO_DATE
とofPattern
メソッドによるフォーマット指定がメインになるでしょう。parse
parse
の方にはDateTimeFormatter
を引数に取らないものもあり、DateTimeFormatter.ISO_LOCAL_DATE_TIME
で解析されます。パースの例
// Parsing date strings LocalDate fromIsoDate = LocalDate.parse("2015-07-20"); System.out.println("From ISO date : " + fromIsoDate); LocalDate fromIsoWeekDate = LocalDate.parse("2015-W01-2", DateTimeFormatter.ISO_WEEK_DATE); System.out.println("From ISO week date : " + fromIsoWeekDate); LocalDate fromCustomPattern = LocalDate.parse("25.07.2015", DateTimeFormatter.ofPattern("dd.MM.yyyy")); System.out.println("From custom pattern : " + fromCustomPattern);
実行結果
From ISO date : 2015-07-20 From ISO week date : 2014-12-30 From custom pattern : 2015-07-25
※
SimpleDateFormat
では「yyyy/MM/dd」に対して「2018/4/5」を渡しても問題なく解析されましたが、Date/Time APIでは桁も厳密になっていて合わないと例外がスローされます。ResolverStyleについて
多分試験範囲外なのですが、使うときに重要だと思うので触れておきます。
従来の
例えば「4/31」など存在しない日付を指定した時、厳密でない解析では「5/1」として解釈されます。
Date/Time APIでもResolverStyleとして定義されています。これには厳密(STRICT)、スマート(SMART)、および非厳密(LENIENT)の3つがあります。デフォルトはスマートです。
スマートが新しいですが、APIドキュメントにはあまり詳しく書いていません。
以下のひしだまさんのページによれば、絶対にありえない値(32日とか13月とか)は例外、範囲内だけど存在しない場合は切り詰め(4/31→4/30)という感じのようです。
従来の
DateFormat
にもsetLenient
メソッドで「厳密な解析」を指定することができました。例えば「4/31」など存在しない日付を指定した時、厳密でない解析では「5/1」として解釈されます。
Date/Time APIでもResolverStyleとして定義されています。これには厳密(STRICT)、スマート(SMART)、および非厳密(LENIENT)の3つがあります。デフォルトはスマートです。
スマートが新しいですが、APIドキュメントにはあまり詳しく書いていません。
以下のひしだまさんのページによれば、絶対にありえない値(32日とか13月とか)は例外、範囲内だけど存在しない場合は切り詰め(4/31→4/30)という感じのようです。
で、厳密な場合に書式「yyyy」の動作が変わります。これは暦(ERA)に対する年なので、暦が指定されない場合例外になってしまいます。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") .withResolverStyle(ResolverStyle.STRICT); LocalDate ldate = LocalDate.parse("2018/04/24", formatter); Exception in thread "main" java.time.format.DateTimeParseException: Text '2018/04/24' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {MonthOfYear=4, YearOfEra=2018, DayOfMonth=24},ISO of type java.time.format.Parsed at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920) at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855) at java.time.LocalDate.parse(LocalDate.java:400) at test.Test3.main(Test3.java:16) Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {MonthOfYear=4, YearOfEra=2018, DayOfMonth=24},ISO of type java.time.format.Parsed at java.time.LocalDate.from(LocalDate.java:368) at java.time.format.Parsed.query(Parsed.java:226) at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851) ... 2 more
これを回避するには、
- 暦として書式に「G」を追加し、「西暦」を指定する
- 書式に「uuuu」を指定する
があります。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd G") .withResolverStyle(ResolverStyle.STRICT); LocalDate ldate = LocalDate.parse("2018/04/24 西暦", formatter);
日本語がアレな場合はロケールを英語(Locale.ENGLISH)に変えて「AD」でもいけます。
もう一つの書式「uuuu」は暦無しの年を表し、これなら暦なしでも大丈夫です。こちらの方が面倒がないでしょう。
もう一つの書式「uuuu」は暦無しの年を表し、これなら暦なしでも大丈夫です。こちらの方が面倒がないでしょう。
タイムゾーンの扱い
タイムゾーンは
従来は
前回出てきた
生成には
java.time.ZoneId
クラスで扱います。従来は
java.util.TimeZone
でした。前回出てきた
ZoneOffset
はZoneId
のサブクラスです。イメージ的には逆のような気もしますが・・・生成には
ZoneId#of
メソッドを使いますが、記述の仕方にはいくつかあります。- 「Z」のみでUTCを表す
- 「+(-)n」または「+(-)n:nn」(n:数値)
- 「GMT/UTC/UT+(-)n」または「GMT/UTC/UT+(-)n:nn」
- 地域ベースID(Asia/Tokyoなど)
- 短い略称(JSTなど)
例を示します。
//Zのみ ZoneId utc = ZoneId.of("Z"); System.out.println(utc + ",class=" + utc.getClass().getName()); //オフセット数値のみ ZoneId plus1 = ZoneId.of("+01:00"); System.out.println(plus1 + ",class=" + plus1.getClass().getName()); ZoneId minus3 = ZoneId.of("-3"); System.out.println(minus3 + ",class=" + minus3.getClass().getName()); //GMT+といった書き方 ZoneId gmt1 = ZoneId.of("GMT+01:00"); System.out.println(gmt1 + ",class=" + gmt1.getClass().getName()); ZoneId utc1 = ZoneId.of("UTC+1"); System.out.println(utc1 + ",class=" + utc1.getClass().getName()); //地域ベースID ZoneId tokyo = ZoneId.of("Asia/Tokyo"); System.out.println(tokyo + ",class=" + tokyo.getClass().getName()); //略称 ZoneId jst = ZoneId.of("JST", ZoneId.SHORT_IDS); System.out.println(jst + ",class=" + jst.getClass().getName()); //システムデフォルト ZoneId def= ZoneId.systemDefault(); System.out.println(def + ",class=" + def.getClass().getName());
実行結果
Z,class=java.time.ZoneOffset +01:00,class=java.time.ZoneOffset -03:00,class=java.time.ZoneOffset GMT+01:00,class=java.time.ZoneRegion UTC+01:00,class=java.time.ZoneRegion Asia/Tokyo,class=java.time.ZoneRegion Asia/Tokyo,class=java.time.ZoneRegion Asia/Tokyo,class=java.time.ZoneRegion
Zもしくは数字だけの場合
いずれも
略称で取得する場合、第二引数にマッピング情報を渡す必要があります。これは
略称は外部から与えるようになっているくらいなので、できれば使わない方が良いでしょう。
※
用途としては、前回紹介した
ZoneOffset
、それ以外に接頭辞を持つものと地域ベースIDはZoneRegion
という非公開のクラスが返されています。いずれも
ZoneId
のサブクラスなので普通に使う分にはあまり関係がないのでしょう。略称で取得する場合、第二引数にマッピング情報を渡す必要があります。これは
ZoneId.SHORT_IDS
に定義されています。略称は外部から与えるようになっているくらいなので、できれば使わない方が良いでしょう。
※
ZoneOffset
の方は生成方法にもう少し種類があって、ofHours
など数値でオフセット時間を指定して取得する方法があります。用途としては、前回紹介した
ZonedDateTime
を生成するときに渡すのが基本の使い方だと思います。夏時間の扱い
地域ベースIDで取得した場合、サマータイムの考慮が含まれます。これは
日本はかつてサマータイムを実施していた時期があったことから、東京のタイムゾーンではこのメソッドはtrueを返します。
ちょうど夏時間の開始・終了をまたぐように生成すると、以下のようになります。
java.time.zone.ZoneRules
に保持されており、サマータイムの実施期間はオフセットが遷移するようになっています。isFixedOffset
メソッドでオフセットが固定かどうかがわかります。日本はかつてサマータイムを実施していた時期があったことから、東京のタイムゾーンではこのメソッドはtrueを返します。
ちょうど夏時間の開始・終了をまたぐように生成すると、以下のようになります。
//ロサンゼルスの夏時間開始 2016/3/13 14:00 ZoneId zone = ZoneId.of("America/Los_Angeles"); LocalDate date = LocalDate.of(2016, Month.MARCH, 13); LocalTime time1 = LocalTime.of(1, 00); //13:00 LocalTime time2 = time1.plusHours(1); //14:00 ZonedDateTime zdt1 = ZonedDateTime.of(date, time1, zone); System.out.println(zdt1); ZonedDateTime zdt2 = ZonedDateTime.of(date, time2, zone); System.out.println(zdt2); long between = ChronoUnit.HOURS.between(zdt1, zdt2); System.out.println(between);
実行結果
2016-03-13T01:00-08:00[America/Los_Angeles]
2016-03-13T03:00-07:00[America/Los_Angeles]
1
14:00はスキップされ、13時の1時間後は15時となります。その代わりオフセットが-7:00から-8:00になります。2016-03-13T03:00-07:00[America/Los_Angeles]
1
オフセットが代わっているだけなので、時間の差を取ると1となります。
夏時間終了時は同じ時間が2周回ってきます。オフセットが元に戻りますが、差を取るとやはり1時間です。
蛇足 和暦について
これは試験範囲にもなく、今のタイミングで使うべきでもないと思いますが、Date/Time APIではISO暦以外の暦体系も扱えるようになっています。
その中でも和暦については標準で対応しています。
当然次の元号に対応する際にはJVMの更新が必要になるでしょうが、自前で対応する必要がないのは良いですね。
その中でも和暦については標準で対応しています。
java.time.chrono.JapaneseDate
クラスで扱うことができます。当然次の元号に対応する際にはJVMの更新が必要になるでしょうが、自前で対応する必要がないのは良いですね。
と、い、う、わ、け、で
タイムゾーンについてでした。
自分はタイムゾーンを意識したアプリケーションを書いたことがないのですが、今回勉強したことが役に立つ時が来る・・・かなぁ。
自分はタイムゾーンを意識したアプリケーションを書いたことがないのですが、今回勉強したことが役に立つ時が来る・・・かなぁ。
今回も以下のサイトとGoldの通常試験の参考書を参考にしています。