はじめに
ふと仕事で「uuuuMMdd」というフォーマットを見かけました。
あれ?「yyyyMMdd」じゃないんだ?なんだろう。
当時はあまり意識しなかったのですが、今更ながら興味が湧いて調べたので、整理の意味も含めて残します。
前提
Java8で「DateTimeFormatter」が出てきて、文字列を日付型に変えたい。日付を文字列に変えたい。等の際に使うようになりました。
文字列を日付型に変える
Geminiにサンプルを作ってもらう
String dateStr = "2026/02/15";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("変換後: " + date); // 2026-02-15
jshellの結果
jshell> String dateStr = "2026/02/15";
dateStr ==> "2026/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 2026-02-15
jshell> System.out.println("変換後: " + date); // 2026-02-15
変換後: 2026-02-15
日付型を文字列に変える
Geminiにサンプルを作ってもらう
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
String text = now.format(formatter);
System.out.println("現在時刻: " + text);
jshell> LocalDateTime now = LocalDateTime.now();
now ==> 2026-02-15T13:56:02.799040500
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOf ... ':'Value(SecondOfMinute,2)
jshell> String text = now.format(formatter);
text ==> "2026/02/15 13:56:02"
jshell> System.out.println("現在時刻: " + text);
現在時刻: 2026/02/15 13:56:02
uuuuとyyyyの違い
正確には、「u」と「y」の違い。
「u」は、「year」。「y」は「year-of-era」。
「era」?「時代」?
よくわからないので、動作が異なるパターンを知るには、「STRICT」を知る必要がある。
STRICTとは
変換時に正確性を問うモードかな?
日付変換時に存在する日付か?厳格に問う。
とはいえ、13月とかは「STRICT」を付けなくてもエラーにならない。
が、「うるう年」(2月29日)等は「STRICT」を付けないと「エラー」にならない
2026年13月の場合
「STRICT」を付けなくてもエラーになる!
jshell> String dateStr = "2026/13/15";
dateStr ==> "2026/13/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
| 例外java.time.format.DateTimeParseException: Text '2026/13/15' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 13
| at DateTimeFormatter.createError (DateTimeFormatter.java:2079)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2014)
| at LocalDate.parse (LocalDate.java:437)
| at (#38:1)
| 原因: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13
| at ValueRange.checkValidIntValue (ValueRange.java:338)
| at ChronoField.checkValidIntValue (ChronoField.java:740)
| at IsoChronology.resolveYMD (IsoChronology.java:645)
| at IsoChronology.resolveYMD (IsoChronology.java:127)
| at AbstractChronology.resolveDate (AbstractChronology.java:437)
| at IsoChronology.resolveDate (IsoChronology.java:587)
| at IsoChronology.resolveDate (IsoChronology.java:127)
| at Parsed.resolveDateFields (Parsed.java:372)
| at Parsed.resolveFields (Parsed.java:278)
| at Parsed.resolve (Parsed.java:265)
| at DateTimeParseContext.toResolved (DateTimeParseContext.java:331)
| at DateTimeFormatter.parseResolved0 (DateTimeFormatter.java:2114)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2010)
| ...
jshell> System.out.println("変換後: " + date);
変換後: 2026-02-28
うるう年の場合(2026年02月29日の場合)
「STRICT」が無い場合は、エラーにならず「2026-02-28」になる。
jshell> String dateStr = "2026/02/29";
dateStr ==> "2026/02/29"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 2026-02-28
jshell> System.out.println("変換後: " + date);
変換後: 2026-02-28
「STRICT」がある場合は、エラーになる!
「.withResolverStyle(ResolverStyle.STRICT);」を付ける。
jshell> String dateStr = "2026/02/29";
dateStr ==> "2026/02/29"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd").withResolverStyle(ResolverStyle.STRICT);
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
| 例外java.time.format.DateTimeParseException: Text '2026/02/29' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=29, YearOfEra=2026, MonthOfYear=2},ISO of type java.time.format.Parsed
| at DateTimeFormatter.createError (DateTimeFormatter.java:2079)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2014)
| at LocalDate.parse (LocalDate.java:437)
| at (#109:1)
| 原因: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=29, YearOfEra=2026, MonthOfYear=2},ISO of type java.time.format.Parsed
| at LocalDate.from (LocalDate.java:405)
| at Parsed.query (Parsed.java:247)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2010)
| ...
というところで、「STRICT」を付けたほうが安全かな。と。
「uuuu」の話に戻ると。
「yyyy/MM/dd」を「STRICT」付けた状態で変換すると。
jshell> String dateStr = "2026/02/15";
dateStr ==> "2026/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd").withResolverStyle(ResolverStyle.STRICT);
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
| 例外java.time.format.DateTimeParseException: Text '2026/02/15' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=15, YearOfEra=2026, MonthOfYear=2},ISO of type java.time.format.Parsed
| at DateTimeFormatter.createError (DateTimeFormatter.java:2079)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2014)
| at LocalDate.parse (LocalDate.java:437)
| at (#120:1)
| 原因: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=15, YearOfEra=2026, MonthOfYear=2},ISO of type java.time.format.Parsed
| at LocalDate.from (LocalDate.java:405)
| at Parsed.query (Parsed.java:247)
| at DateTimeFormatter.parse (DateTimeFormatter.java:2010)
| ...
エラーになる。なんで?
と思い、Geminiに尋ねると、「西暦」と「G」を付ければよい。というお話。
今度は「G」を理解する。
「G」の「era」とは
「G」というフォーマットを確認すると、「era」。「y」は「year-of-era」。
もう少し詳しく理解すると、「西暦」、「紀元前」等の雰囲気。
西暦。を付けると通る!
jshell> String dateStr = "西暦2026/02/15";
dateStr ==> "西暦2026/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyyyy/MM/dd").withResolverStyle(ResolverStyle.STRICT)
;
formatter ==> Text(Era,SHORT)Value(YearOfEra,4,19,EXCEEDS_PAD)' ... r,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 2026-02-15
jshell> System.out.println("変換後: " + date); // 2026-02-15
変換後: 2026-02-15
「紀元前も通る!」
jshell> String dateStr = "紀元前0001/02/15";
dateStr ==> "紀元前0001/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyyyy/MM/dd").withResolverStyle(ResolverStyle.STRICT)
;
formatter ==> Text(Era,SHORT)Value(YearOfEra,4,19,EXCEEDS_PAD)' ... r,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 0000-02-15
jshell> System.out.println("変換後: " + date); // 0000-02-15
変換後: 0000-02-15
余談だけど、「令和」とか使いたければ、「withChronology(JapaneseChronology.INSTANCE)」を付ければ、イケる。
jshell> String dateStr = "令和8/02/15";
dateStr ==> "令和8/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gy/MM/dd").withResolverStyle(ResolverStyle.STRICT).withChronology(JapaneseChronology.INSTANCE);
formatter ==> Text(Era,SHORT)Value(YearOfEra)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 2026-02-15
jshell> System.out.println("変換後: " + date); // 2026-02-15
変換後: 2026-02-15
「y」を使いたければ、「西暦」「紀元前」とかを意識する必要があると。
「year-of-era」の「era」はそういうことか。
で、「u」の出番。
「u」は、「year」。つまり、「era」というのが付与されていない。
なので、「西暦」「紀元前」等を意識せずに使える。ということか。
jshell> String dateStr = "2026/02/15";
dateStr ==> "2026/02/15"
jshell> DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu/MM/dd").withResolverStyle(ResolverStyle.STRICT);
formatter ==> Value(Year,4,19,EXCEEDS_PAD)'/'Value(MonthOfYear,2)'/'Value(DayOfMonth,2)
jshell> LocalDate date = LocalDate.parse(dateStr, formatter);
date ==> 2026-02-15
jshell> System.out.println("変換後: " + date); // 2026-02-15
変換後: 2026-02-15
まとめ
冒頭の通り、「u」は「year」、「y」は「year-of-era」。
「era」の違いがある。
「era」とは、フォーマット上、「G」となり「西暦」、「紀元前」等。
「y」を「厳密」に扱う場合は、「era」を意識する必要がある。
「u」は「厳密」に扱っても、「era」を意識しなくても良さそう。
余談
1つのことを理解するために、「era」、「STRICT」等を理解することがあり、お芋掘りの気分で大変だけど、知識が増えてなかなか楽しい。


