Java:日付とユーティリティ
1学習の目的
- LocalDate で日付を安全に扱えるようになる。納期の計算・期限の判定など、業務システムで必ず出てくる処理が書けるようになる。
- Math・String.format・StringBuilder といった日常的に使う道具を身につける。知らないと自分で実装してしまう処理が、実は1行で済む。
2基礎解説
日付の計算は驚くほど間違えやすい処理です。月末、うるう年、月をまたぐ計算——自分で実装すると必ずどこかで破綻します。LocalDate はこれらをすべて正しく処理してくれます。
| クラス | 扱うもの | 例 |
|---|---|---|
| LocalDate | 日付のみ | 2026-09-08 |
| LocalDateTime | 日付+時刻 | 2026-09-08T14:30 |
| DateTimeFormatter | 表示の書式 | 2026/09/08 |
| ChronoUnit | 期間の計算 | 日数差など |
- LocalDate は変更できない(イミュータブル)。d.plusDays(7) は新しい日付を返すだけで、元の d は変わらない——結果を受け取らないと意味がない。
- 月またぎ・うるう年は自動で処理される。1月31日.plusMonths(1) は2月28日(または29日)になる。自分で計算しようとしてはいけない。
- 日数差は ChronoUnit.DAYS.between(開始, 終了)。「納期まであと何日か」といった計算が1行で書ける。
- 表示の書式は DateTimeFormatter。yyyy/MM/dd のように指定する。MM は月、mm は分——大文字小文字を間違えると意図しない結果になる。
- 存在しない日付は例外になる。LocalDate.of(2026, 2, 30) は DateTimeException で拒否される——不正なデータが入り込まない。
現場使用例:納期の算出、有効期限の判定、月次レポートの期間指定、経過日数の計算、ログの日時記録。
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
// ① 日付の生成
LocalDate d = LocalDate.of(2026, 9, 8);
LocalDate today = LocalDate.now(); // 今日
// ② 部品の取り出し
d.getYear(); // 2026
d.getMonthValue(); // 9
d.getDayOfMonth(); // 8
d.getDayOfWeek(); // TUESDAY
// ③ 計算(元は変わらない)
LocalDate week = d.plusDays(7); // 2026-09-15
LocalDate month = d.plusMonths(1); // 2026-10-08
LocalDate last = d.minusYears(1); // 2025-09-08
// ④ 日数差
long days = ChronoUnit.DAYS.between(d, LocalDate.of(2026, 12, 31)); // 114
// ⑤ 比較
d.isBefore(other); // より前か
d.isAfter(other); // より後か
d.isEqual(other); // 同じ日か
// ⑥ 書式(MM は月、mm は分)
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String text = d.format(f); // "2026/09/08"
// ⑦ 文字列から日付へ
LocalDate parsed = LocalDate.parse("2026-01-15");
// ⑧ 月末日を求める
d.withDayOfMonth(d.lengthOfMonth()); // 2026-09-30
// ⑨ Math の便利メソッド
Math.round(3.7); // 4(四捨五入)
Math.max(3, 7); // 7
Math.abs(-5); // 5
Math.pow(2, 10); // 1024.0
// ⑩ String.format の書式
String.format("%,d", 1234567); // "1,234,567"
String.format("%.2f", 3.14159); // "3.14"
String.format("%5d", 42); // " 42"(右寄せ5桁)
// ⑪ StringBuilder(大量の文字列連結に)
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 3; i++) {
sb.append(i).append(",");
}
sb.toString(); // "1,2,3,"
3基本ドリル(10問)
出力 == 2026年9月8日
LocalDate.of(2026, 9, 8) で作り、getYear などで取り出す。
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 9, 8);
System.out.println(d.getYear() + "年" + d.getMonthValue()
+ "月" + d.getDayOfMonth() + "日");
}
}月は1〜12でそのまま指定できる。古い Date クラスでは0始まりで混乱の元だったが、LocalDate では直感的になった。
A. 7日後に変わる B. 変わらない C. null になる D. エラー 選択★☆☆無料
解答 == B
LocalDate はイミュータブル。
B
新しい日付が返されるだけ。d = d.plusDays(7); のように受け取らないと、計算結果が捨てられる。
出力2行。2026-09-15 / 2026-10-08
plusDays と plusMonths を使う。
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 9, 8);
System.out.println(d.plusDays(7));
System.out.println(d.plusMonths(1));
}
}月またぎも自動で処理される。9月8日の1ヶ月後は10月8日——日数ではなく「月」として加算されている。
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.____(2026, 1, 15);
System.out.println(d);
}
}出力 == 2026-01-15
「〜の」を意味する2文字。Path.of と同じ。
of
of は「指定した値の」という意味。Javaの新しいAPIでは、この命名が広く使われている。
A. 分 B. 月 C. ミリ秒 D. 曜日 選択★☆☆無料
解答 == B
小文字の mm は分。
B
大文字MMが月、小文字mmが分。間違えると「2026/30/08」のような意味不明な表示になる——実務でよくある間違い。
出力 == 114
ChronoUnit.DAYS.between(開始, 終了) を使う。
import java.time.*;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
LocalDate from = LocalDate.of(2026, 9, 8);
LocalDate to = LocalDate.of(2026, 12, 31);
System.out.println(ChronoUnit.DAYS.between(from, to));
}
}月の日数差やうるう年を意識しなくてよい。自分で計算しようとすると、ほぼ確実にどこかで間違える。
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 9, 8);
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println(d.____(f));
}
}出力 == 2026/09/08
「書式化する」を意味する6文字のメソッド。
format
format で表示用の文字列に変換する。逆に文字列から日付を作るなら LocalDate.parse を使う。
A. 3月2日になる B. 例外が発生する C. 2月28日になる D. null 選択★★☆広告解放
解答 == B
2月30日は存在しない。
B
DateTimeException で拒否される。不正な日付がシステムに入り込むのを、生成時点で防いでくれる。
出力 == 1,2,3,
append をつなげて書ける。最後に toString() する。
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 3; i++) {
sb.append(i).append(",");
}
System.out.println(sb.toString());
}
}ループ内で += を使うと、毎回新しい文字列が作られて非効率。数千回の連結では、StringBuilder が圧倒的に速い。
出力 == 4 / 7 / 5
Math.round、Math.max、Math.abs を使う。
public class Main {
public static void main(String[] args) {
System.out.println(Math.round(3.7) + " / "
+ Math.max(3, 7) + " / "
+ Math.abs(-5));
}
}Math は import が不要(java.lang パッケージのため)。他にも min・pow・sqrt などがある。
4実践シナリオ(5問)
出力 == 納期:2026/09/13
plusDays(5) して format する。
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
LocalDate order = LocalDate.of(2026, 9, 8);
LocalDate due = order.plusDays(5);
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println("納期:" + due.format(f));
}
}実際の営業日計算は土日祝を除く必要があるが、まずは単純な日数加算から。祝日は国ごとに違うので、外部データが必要になる。
出力2行。期限内です / 残り114日
isBefore で判定し、ChronoUnit.DAYS.between で日数を求める。
import java.time.*;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 9, 8);
LocalDate expiry = LocalDate.of(2026, 12, 31);
System.out.println(today.isBefore(expiry) ? "期限内です" : "期限切れです");
System.out.println("残り" + ChronoUnit.DAYS.between(today, expiry) + "日");
}
}期限管理は業務システムの定番処理。ポイントの失効、契約の更新、在庫の賞味期限——どれも同じ形で書ける。
出力2行。2026-09-30 / 2026-02-28
withDayOfMonth(lengthOfMonth()) で月末が求まる。
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDate sep = LocalDate.of(2026, 9, 1);
LocalDate feb = LocalDate.of(2026, 2, 1);
System.out.println(sep.withDayOfMonth(sep.lengthOfMonth()));
System.out.println(feb.withDayOfMonth(feb.lengthOfMonth()));
}
}うるう年の判定が不要。2026年は平年なので2月は28日、2028年なら29日が自動的に返る。
出力3行。文字数は揃うが表示幅はずれる
String.format("%-8s|", 名前) で出力する。全角は1文字として数えられる。
public class Main {
public static void main(String[] args) {
String[] names = {"ノートPC", "マウス", "モニター"};
StringBuilder sb = new StringBuilder();
for (String name : names) {
sb.append(String.format("%-8s|%n", name));
}
System.out.print(sb.toString());
}
}「ノートPC」は5文字、「マウス」は3文字——文字数では揃っているのに、表示は揃わない。全角文字は半角の約2倍の幅で表示されるためだ。%-8s は文字数で数えるので、日本語混じりの表では桁がずれる。等幅フォントで表を作るなら、全角を2文字分として自分で計算する必要がある——日本語を扱う開発者が必ず遭遇する問題。
出力 == 2026年1月15日(木)
LocalDate.parse で変換し、DateTimeFormatter.ofPattern("yyyy年M月d日(E)", Locale.JAPAN) で整形する。
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.parse("2026-01-15");
DateTimeFormatter f = DateTimeFormatter.ofPattern(
"yyyy年M月d日(E)", Locale.JAPAN);
System.out.println(d.format(f));
}
}Locale.JAPAN を指定すると曜日が日本語になる。指定しないと環境によって「Thu」と英語で出るため、明示するのが安全。
5仕上げ課題
Order クラス:name(商品名)、orderDate(LocalDate)、price(int)、qty(int)
データ(基準日は2026年9月8日)
・ノートPC / 2026-09-01 / 128000 / 2
・マウス / 2026-08-15 / 3200 / 10
・モニター / 2026-09-05 / 45000 / 1
処理
① 各注文を「ノートPC 2026/09/01 256,000円(7日経過)」の形式で出力
・商品名は左寄せ10桁、金額は3桁区切り
・経過日数は注文日から基準日までを計算
② 納期は注文日の14日後とし、基準日時点で期限を過ぎた注文に「※要確認」を付ける
③ 合計金額を出力
④ 最も古い注文の日付を「最古:2026年8月15日」の形式で出力
期待される出力(5行)
ノートPC 2026/09/01 256,000円(7日経過)
マウス 2026/08/15 32,000円(24日経過)※要確認
モニター 2026/09/05 45,000円(3日経過)
---
合計:333,000円 / 最古:2026年8月15日
※ 商品名の桁がずれて見えるのは正常です(S04で確認した全角の問題) 総合★★★広告解放
出力5行が完全一致
%-10s で左寄せ、%,9d で右寄せ3桁区切り。経過日数は ChronoUnit.DAYS.between(注文日, 基準日)。納期超過は 注文日.plusDays(14).isBefore(基準日) で判定。最古は Comparator で比較するか、ループで更新する。
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
public class Main {
static class Order {
String name;
LocalDate orderDate;
int price;
int qty;
Order(String name, LocalDate orderDate, int price, int qty) {
this.name = name;
this.orderDate = orderDate;
this.price = price;
this.qty = qty;
}
int amount() {
return price * qty;
}
}
public static void main(String[] args) {
LocalDate baseDate = LocalDate.of(2026, 9, 8);
List<Order> orders = List.of(
new Order("ノートPC", LocalDate.of(2026, 9, 1), 128000, 2),
new Order("マウス", LocalDate.of(2026, 8, 15), 3200, 10),
new Order("モニター", LocalDate.of(2026, 9, 5), 45000, 1));
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
int total = 0;
LocalDate oldest = orders.get(0).orderDate;
StringBuilder sb = new StringBuilder();
for (Order o : orders) {
long days = ChronoUnit.DAYS.between(o.orderDate, baseDate);
boolean overdue = o.orderDate.plusDays(14).isBefore(baseDate);
sb.append(String.format("%-10s%s %,9d円(%d日経過)%s%n",
o.name,
o.orderDate.format(f),
o.amount(),
days,
overdue ? "※要確認" : ""));
total += o.amount();
if (o.orderDate.isBefore(oldest)) {
oldest = o.orderDate;
}
}
System.out.print(sb.toString());
DateTimeFormatter jp = DateTimeFormatter.ofPattern("yyyy年M月d日");
System.out.println("---");
System.out.println("合計:" + String.format("%,d", total)
+ "円 / 最古:" + oldest.format(jp));
}
}このレポートには、実務の日付処理が凝縮されている。経過日数の計算、納期超過の判定、最古日付の抽出——どれも業務システムで日常的に必要になる処理だ。
もし日付を「20260901」のような数値で管理していたらどうなるか。経過日数を求めるには、月の日数やうるう年を自分で計算する必要がある。8月15日から9月8日まで何日か——8月は31日だから16日+8日で24日、と手計算するコードを書くことになる。LocalDate なら1行で済み、しかも間違えない。
%,9d による数値の右寄せは有効に働いている。桁数が揃うので、256,000 と 32,000 の大小が一目で分かる。一方 %-10s のほうは、S04で見たとおり全角文字のせいで見た目が揃わない。日本語を含む表を等幅で整形したいなら、全角を2文字分として数える処理を自分で書く必要がある——Javaの標準機能だけでは解決できない、日本語環境特有の課題だ。
StringBuilder を使った理由も実務的だ。3件なら差は無いが、1万件の明細を出力するなら性能差が歴然と現れる。文字列を += でつなぐと、毎回新しい文字列が作られるためだ。
次章がいよいよ最終回だ。JV01からここまでの17章すべてを統合し、在庫管理システムを完成させる。