日付の書式化とパース
このレッスンで分かること
DateTimeFormatterで日付・時刻を書式化 / パース ─ Java 8 の現代 APIofPattern("yyyy-MM-dd")でパターン指定、ISO_LOCAL_DATEなどの定義済みも豊富- スレッドセーフ ─
SimpleDateFormatと違ってstatic finalで共有可format(temporal)で出力、LocalDate.parse(text, formatter)で入力
日付の書式化とパース とは
DateTimeFormatter.ofPattern() で LocalDate を任意の書式に変換し、逆に文字列から日付へ parse する方法を学ぼう。
LocalDate を「人間が読める文字列」に変える
LocalDate.of(2026, 5, 19) を System.out.println で表示すると 2026-05-19 のように ISO-8601 形式で出てきます。これは機械同士のデータ交換にはとても便利な書式ですが、業務アプリで画面に出すときは "2026年05月19日" "2026/05/19" "令和8年5月19日" のように、もっと日本語ユーザーに優しい形に整えたいことがほとんどです。逆に、ユーザーがフォームに入力した "2026/05/19" という文字列を、Java の LocalDate オブジェクトに戻したい場面もよくあります。
「日付の見た目を変える」と「文字列を日付に戻す」は、Java の世界ではそれぞれ
formatとparseという対になる操作で表現されます。両方をひとつのオブジェクトでまとめて担当するのが、本章の主役java.time.format.DateTimeFormatterです。
古い Java では SimpleDateFormat というクラスがこの役割を担っていましたが、スレッドセーフ ではないなどの欠点があり、Java 8 以降では DateTimeFormatter が標準です。本レッスンでは新しい方だけを覚えれば OK です。
この図のとおり、format と parse はちょうど往復切符の関係です。同じ DateTimeFormatter を使うのが定石で、片道だけのために 2 つ書く必要はありません。
DateTimeFormatter.ofPattern() でカスタム書式を作る
書式を作る基本は DateTimeFormatter.ofPattern("...") の 1 行です。引数に渡す文字列が「パターン」で、ここに yyyy や MM などの記号を並べると、その箇所が実際の日付の値に置き換わって出力されます。
Java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Demo {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 5, 19);
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
String text = date.format(f);
System.out.println(text); // 2026年05月19日
}
}注目すべきは date.format(f) の部分です。LocalDate 側の format メソッドに DateTimeFormatter を渡しています。逆に f.format(date) と書いても同じ結果になるので、覚えやすい方で OK です。
DateTimeFormatterは一度作れば何度でも使い回せます。アプリ内で頻繁に使う書式はprivate static finalフィールドにキャッシュしておくと、newするコストを節約できます。
代表的なパターン文字
パターン文字には決まった意味があります。よく使うものを表で押さえておきましょう。
| 記号 | 意味 | 例 (2026-05-19 07:30:05) |
|---|---|---|
yyyy | 4 桁の年 | 2026 |
MM | 2 桁の月 (01〜12) | 05 |
dd | 2 桁の日 (01〜31) | 19 |
HH | 24 時間制の時 (00〜23) | 07 |
mm | 2 桁の分 | 30 |
ss | 2 桁の秒 | 05 |
E | 曜日の略称 | Tue または 火 |
大文字小文字が 厳密に区別される ことに注意してください。たとえば MM は「月」、mm は「分」、HH は「24 時間制の時」、hh は「12 時間制の時 (1〜12)」とまったく別物です。これを取り違えると、コンパイルは通ってしまうのに実行結果がまるで違う、というやっかいなバグになります。
Java
DateTimeFormatter slash = DateTimeFormatter.ofPattern("yyyy/MM/dd");
DateTimeFormatter dot = DateTimeFormatter.ofPattern("yyyy.MM.dd");
DateTimeFormatter weekday = DateTimeFormatter.ofPattern("yyyy年MM月dd日 (E)");パターン文字以外の / . 年 月 日 ( ) 半角スペース などはそのまま出力されます。記号の前後を区切りたいときは、英字を ' (シングルクォート) で囲むとリテラルとして扱うこともできますが、日本語の漢字は ' で囲まなくてもパターン文字とぶつからないのでそのまま書けます。
文字列から日付へ ─ parse
書式化の逆、つまり "2026年05月19日" のような文字列を LocalDate に戻すには LocalDate.parse を使います。第 2 引数に書式を渡してあげるのがコツです。
Java
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate d = LocalDate.parse("2026年05月19日", f);
System.out.println(d); // 2026-05-19ここで気をつけたいのは、書式が parse 側の文字列と 完全に一致 していないとダメだという点です。たとえばパターンが yyyy年MM月dd日 なのに、入力文字列が "2026年5月19日" (月が 1 桁) になっていると、DateTimeParseException という例外がスローされます。文字列の前後の 半角スペース も区別されます。
DateTimeParseExceptionはRuntimeExceptionの仲間なので、コンパイラからは catch を強制されません。ですが、ユーザー入力を parse するときは必ずtry-catchで囲んで、エラーメッセージをユーザーに返す設計にしておくと安心です。
ロケール対応 ─ 曜日を日本語で出す
E パターンをそのまま使うと、デフォルトでは Tue Wed のように英語表記になります。日本語の 火 水 に切り替えたいときは Locale.JAPANESE を withLocale で渡します。
Java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class WeekdayDemo {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 5, 19);
DateTimeFormatter f = DateTimeFormatter
.ofPattern("yyyy年MM月dd日 (E)")
.withLocale(Locale.JAPANESE);
System.out.println(date.format(f)); // 2026年05月19日 (火)
}
}Locale を切り替えると、月の名前 (January ↔ 1月) や曜日の表記が国・言語ごとに変わります。アプリの多言語対応では withLocale を使い分けるのが基本です。
よくある間違い
書式化と parse で初学者がはまる典型的な落とし穴を 3 つ紹介します。エラーメッセージで悩む前に、まずこのリストを確認するクセをつけておきましょう。
yyyyとYYYYを混同する — 小文字のyyyyは普通の暦年ですが、大文字のYYYYは週ベースの年を意味します。年末年始をまたぐ ISO 週で結果がズレるため、Windowsのメモ帳で書いた仕様書をコピペしたら 1 月 1 日が2027年扱いになる、というバグが起きやすいです。日付を出すときはほぼ常に小文字のyyyyが正解ですMMとmmの取り違え — 大文字のMMは月、小文字のmmは分。これを逆に書くと、yyyy/mm/ddというパターンが「年/分/日」と解釈されます。LocalDateには時刻情報がないため分は常に0となり、2026/00/19のような意図しない出力になります。コンパイルエラーにならない分、見つけにくい不具合ですHHとhhの取り違え —HH/hhはLocalDateTimeやLocalTimeなど時刻を持つ型で使うパターンです。LocalDateには時刻フィールドがないため、このミスが実害として現れるのは時刻を扱う型が対象のときです。24 時間制で表示したいのにhhと書くと 12 時間制 (1〜12) になります。午後 3 時が03と表示されてしまい、AM/PMの区別 (aパターン) を入れ忘れると03:00が午前か午後か分からなくなります。業務アプリでは原則HHを使うと覚えてください
もう 1 つ、parse 時に DateTimeParseException が出る原因として一番多いのが「フォーマット文字列と入力文字列の桁数が合っていない」というケースです。yyyy年M月d日 (1 桁の月日も許容) と yyyy年MM月dd日 (常に 2 桁) は別物なので、入力データの仕様に合わせてパターン側を揃えましょう。
業務でよくある対策として、ユーザー入力を受け取った直後に
replaceAllで全角数字を半角に直したり、trim()で前後の半角スペースを取り除いてから parse する、というパイプラインを組むことが多いです。
実務で遭遇するパターン
中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。
- ログのタイムスタンプ整形 ─
formatter.format(LocalDateTime.now())で2024-01-15 14:30:00の形 - ファイル名生成 ─
report-20240115.csvのような名前をofPattern("yyyyMMdd")で - ユーザー表示の日付 ─ ロケール対応で
"yyyy年MM月dd日"の和暦風表示 - API レスポンスの日時 ─ ISO 8601
2024-01-15T14:30:00Zで送信、ISO_DATE_TIMEで扱う
コードレビューで指摘されがちなポイント
PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。
SimpleDateFormatを使っている ─ 古い、スレッド非セーフ、DateTimeFormatterへの置き換えnew DateTimeFormatterを毎回作成 ─static finalで共有- ロケール / TZ 未指定 ─ サーバー環境で結果が変わる。
Locale.ROOTZoneId.of("Asia/Tokyo")を明示 - パターンの
mmとMM混同 ─mmは分、MMは月。タイポで深夜に呼び出される事故
パフォーマンス考慮事項
DateTimeFormatterはスレッドセーフ ─ 1 つを全スレッドで共有可、SimpleDateFormatよりずっと良い- フォーマット解析は中程度のコスト ─ ナノ秒ではなくマイクロ秒オーダー、ホットパスでは要注意
parseは正規表現的な処理 ─ 大量データなら自前パーサーが速いことも- 事前定義 (
ISO_DATE等) ─ よく使う ISO 系は事前生成されていて軽い
ここまでの要点
DateTimeFormatter.ofPattern(...) で書式化 / パース。スレッドセーフで static final で共有、ロケール / TZ を明示、mm vs MM に注意。
やってみよう
今回の課題は、ここまで学んだ DateTimeFormatter.ofPattern を使って、年・月・日の数値から "YYYY年MM月DD日" 形式の文字列を作るメソッドを書くことです。やる手順は次のとおりです。
LocalDate.of(year, month, day)でLocalDateを作るDateTimeFormatter.ofPattern("yyyy年MM月dd日")でフォーマッタを作るdate.format(formatter)の結果をreturnする
テストでは formatJapaneseDate(2026, 5, 19) が "2026年05月19日" になるか、formatJapaneseDate(2024, 1, 1) が "2024年01月01日" になるかなどを比較しています。月や日が 1 桁の場合に 0 埋めで 01 のように 2 桁になることが大事なポイントです。これは MM dd のパターンを使えば自動で実現されるので、自分で String.format を組む必要はありません。
余裕があれば、パターンを yyyy/MM/dd yyyy.MM.dd yyyy年MM月dd日 (E) などに差し替えて、出力がどう変わるか試してみてください。withLocale(Locale.JAPANESE) を付けたときの曜日表示の違いも面白い実験対象です。DateTimeFormatter は一度慣れると、業務アプリのほぼすべての日付処理で武器になります。
よくある質問
Q. 中級の内容は実務でどれくらい使いますか?
A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。
Q. Stream と for ループはどっちで書くべき?
A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。
Q. 次のステップでは何を学ぶべきですか?
A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。
次のレッスン
次は 日付の加減算 で、DateTimeFormatter を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- DateTimeFormatter の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. DateTimeFormatter とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
java.time.LocalDateとjava.time.format.DateTimeFormatterをimportすることDateTimeFormatter.ofPattern("yyyy年MM月dd日")を使い、月・日が必ず 2 桁ゼロ埋めになる書式を指定することLocalDate.of(year, month, day)で日付を作り、date.format(formatter)の結果をreturnすること
入出力例
test-cases.txt
formatJapaneseDate(2026, 5, 19) → "2026年05月19日"
formatJapaneseDate(2024, 1, 1) → "2024年01月01日"
formatJapaneseDate(2023, 12, 31) → "2023年12月31日"
formatJapaneseDate(2024, 2, 29) → "2024年02月29日"