日付の書式化とパース

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • DateTimeFormatter で日付・時刻を書式化 / パース ─ Java 8 の現代 API
  • ofPattern("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 の世界ではそれぞれ formatparse という対になる操作で表現されます。両方をひとつのオブジェクトでまとめて担当するのが、本章の主役 java.time.format.DateTimeFormatter です。

古い Java では SimpleDateFormat というクラスがこの役割を担っていましたが、スレッドセーフ ではないなどの欠点があり、Java 8 以降では DateTimeFormatter が標準です。本レッスンでは新しい方だけを覚えれば OK です。

diagram (will load when visible)

この図のとおり、formatparse はちょうど往復切符の関係です。同じ DateTimeFormatter を使うのが定石で、片道だけのために 2 つ書く必要はありません。

DateTimeFormatter.ofPattern() でカスタム書式を作る

書式を作る基本は DateTimeFormatter.ofPattern("...") の 1 行です。引数に渡す文字列が「パターン」で、ここに yyyyMM などの記号を並べると、その箇所が実際の日付の値に置き換わって出力されます。

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)
yyyy4 桁の年2026
MM2 桁の月 (0112)05
dd2 桁の日 (0131)19
HH24 時間制の時 (0023)07
mm2 桁の分30
ss2 桁の秒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 という例外がスローされます。文字列の前後の 半角スペース も区別されます。

DateTimeParseExceptionRuntimeException の仲間なので、コンパイラからは catch を強制されません。ですが、ユーザー入力を parse するときは必ず try-catch で囲んで、エラーメッセージをユーザーに返す設計にしておくと安心です。

ロケール対応 ─ 曜日を日本語で出す

E パターンをそのまま使うと、デフォルトでは Tue Wed のように英語表記になります。日本語の に切り替えたいときは Locale.JAPANESEwithLocale で渡します。

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 を切り替えると、月の名前 (January1月) や曜日の表記が国・言語ごとに変わります。アプリの多言語対応では withLocale を使い分けるのが基本です。

diagram (will load when visible)

よくある間違い

書式化と parse で初学者がはまる典型的な落とし穴を 3 つ紹介します。エラーメッセージで悩む前に、まずこのリストを確認するクセをつけておきましょう。

  • yyyyYYYY を混同する — 小文字の yyyy は普通の暦年ですが、大文字の YYYY週ベースの年 を意味します。年末年始をまたぐ ISO 週で結果がズレるため、Windows のメモ帳で書いた仕様書をコピペしたら 1 月 1 日が 2027 年扱いになる、というバグが起きやすいです。日付 を出すときはほぼ常に小文字の yyyy が正解です
  • MMmm の取り違え — 大文字の MM は月、小文字の mm は分。これを逆に書くと、yyyy/mm/dd というパターンが「年/分/日」と解釈されます。LocalDate には時刻情報がないため分は常に 0 となり、2026/00/19 のような意図しない出力になります。コンパイルエラーにならない分、見つけにくい不具合です
  • HHhh の取り違えHH/hhLocalDateTimeLocalTime など時刻を持つ型で使うパターンです。LocalDate には時刻フィールドがないため、このミスが実害として現れるのは時刻を扱う型が対象のときです。24 時間制で表示したいのに hh と書くと 12 時間制 (112) になります。午後 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.ROOT ZoneId.of("Asia/Tokyo") を明示
  • パターンの mmMM 混同mm は分、MM は月。タイポで深夜に呼び出される事故

パフォーマンス考慮事項

  • DateTimeFormatter はスレッドセーフ ─ 1 つを全スレッドで共有可、SimpleDateFormat よりずっと良い
  • フォーマット解析は中程度のコスト ─ ナノ秒ではなくマイクロ秒オーダー、ホットパスでは要注意
  • parse は正規表現的な処理 ─ 大量データなら自前パーサーが速いことも
  • 事前定義 (ISO_DATE 等) ─ よく使う ISO 系は事前生成されていて軽い
この章のポイント

ここまでの要点 DateTimeFormatter.ofPattern(...) で書式化 / パース。スレッドセーフで static final で共有、ロケール / TZ を明示、mm vs MM に注意。

やってみよう

今回の課題は、ここまで学んだ DateTimeFormatter.ofPattern を使って、年・月・日の数値から "YYYY年MM月DD日" 形式の文字列を作るメソッドを書くことです。やる手順は次のとおりです。

  1. LocalDate.of(year, month, day)LocalDate を作る
  2. DateTimeFormatter.ofPattern("yyyy年MM月dd日") でフォーマッタを作る
  3. 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 つができることを確認しましょう。

  1. DateTimeFormatter の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. DateTimeFormatter とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. java.time.LocalDatejava.time.format.DateTimeFormatterimport すること
  2. DateTimeFormatter.ofPattern("yyyy年MM月dd日") を使い、月・日が必ず 2 桁ゼロ埋めになる書式を指定すること
  3. 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日"

ヒント

main.java
main.java
学習モード

メモ

日付の書式化とパース

⌘S で保存