📌

Java の DateTimeFormatter の指定方法について

2023/11/27に公開

はじめに

Java の DateTimeFormatter を使うと時間の出力形式を指定することができます。
使い方やメソッドの内容はドキュメントに記載されています。
しかし、このドキュメントは元々英語で書かれており、日本語に翻訳されたものです。そのため、未翻訳の部分や理解しづらい翻訳箇所がいくつかあります。
これらの問題を解決するために、指定方法を自分なりにまとめてみることにしました。

注意

こちらの記事では基本的に Java の DateTimeFormatter での指定方法について解説しております。一部フレームワークなどでは、古い指定形式である java.text.Formatjava.text.SimpleDateFormat などを採用しているものもあるので、利用しているフレームワークに対応しているドキュメントを参照することをお勧めします。

Java の DateTimeFormatter について

Java で日時の出力の出力形式を自分で設定することができるクラスです。
デフォルトの表示形式では、2023-11-13T17:44:42.109255 のような表示形式になります。
ここに形式指定用のメソッドを追加することで、出力形式を指定することが可能です。

指定方法

DateTimeFormatter ではいくつか予約語が設定されており、それらを組み合わせてダブルクォーテーションで囲むことによって、形式を指定することができます。
年月日をスペースで区切って出力する場合は、以下のように指定します。

"yyyy MM dd"

予約語の詳細については後ほど記載しますが、y は年、M は月、d は日を表示するための予約語になります。それぞれの間にスペースを挟むことによって、年月日を区切って出力することが可能になります。

出力結果
2023 11 03

出力を検証するためのサンプルコード

出力結果を確認するためのサンプルコードを以下に記載します。
この後の記事はこのサンプルコードを少し改変したりしながら進めていきます。

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class dateFormat {
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
        LocalDateTime date = LocalDateTime.of(2023, 11, 3, 10, 10, 10);
        String formatted = date.format(formatter);
        System.out.println(formatted);
    }
}

上記のコードでは日時型である LocalDateTime を使っていますが、指定方法によっては ZonedDatetime を使う必要があるので、使うことになりそうなライブラリを全て import しています。

予約語の詳細

文字 英語での意味 意味
G era 紀元前後
u year
y year of era
D day of year 年の中での日
M もしくは L month of year
d day of month
Q もしくは q quarter of year 第◯四半期
Y week based year 週の始まりを年の初めとして扱う年表記
w week of week based year その年の第◯週
W もしくは F week of month 月第◯週
E day of week 曜日
e もしくは c loclalized day of week 日曜日を基準に曜日を数字表示
a am-pm-of-day AMかPMか
h clock-hour-of-am-pm (1-12) number
K hour-of-am-pm (0-11) 午前午後表記前提での時
k clock-hour-of-am-pm (1-24) 24 時間表記での時
H hour-of-day (0-23) 0 基準の 24 時間表記
m minute of hour
s second-of-minute
S fraction of second ミリ秒
A milli of day 1 日の始まりを基準にした時の経過ミリ秒
n nano of second ナノ秒
N nano of day 1 日の始まりを基準にした時の経過ナノ秒
V time zone ID タイムゾーンID (2 つ連続で使う必要あり)
z time zone name タイムゾーン名
O localized zone offset UTCとの時差 (表示が GMT±◯◯:00 となる)
X zone offset 'Z' for zero UTCとの時差 (±0:00 の時はZ表示)
x zone offset UTCとの時差
Z zone offset UTCとの時差
p pad next 直後のパターンを固定幅に変更

アルファベット以外の文字も定義されている

文字 英語での意味 意味
' escape for text 文字列のエスケープ
'' single quate ' を 2 つ繋げたもの。' が出力される
[ optional section start オプショナル部開始
] optional section end オプショナル部終了
# reserved for future use 将来のための予備
{ reserved for future use 将来のための予備
} reserved for future use 将来のための予備

個人的に仕様がが分かりにくいと思った予約語

y

year of era と書いてあることから分かるように、紀元後の年を出力するようになっています。そのためマイナスで年を扱うことができません。
年がマイナスになる可能性がある場合は u を使うのがおすすめです。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'y:'yyyy 'u:'uuuu")
LocalDateTime date = LocalDateTime.of(-2023, 11, 3, 10, 10, 10);
//y:2024 u:-2023

Y および w

こちらは英語で week based year という方法で年の初めを定義しているものになります。
実際の日付を例に解説します。
2023 年 12 月 30 日(土)と 2023 年 12 月 31 日(日)は同じ 2023 年ですが、指定方法が Yw とすると違う年として出力されます。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY/MM/dd");
LocalDateTime date1230 = LocalDateTime.of(2023, 12, 30, 10, 10, 10);
String formatted = date1230.format(formatter);
System.out.println(formatted);
// 2023/12/30

LocalDateTime date1231 = LocalDateTime.of(2023, 12, 31, 10, 10, 10);
String formatted2 = date1231.format(formatter);
System.out.println(formatted2);
// 2024/12/31

なぜこのような現象が起きてしまうのでしょうか?
理由は週の始まりにあります。
week based year は週の始まりを基準に年の始まりを定義している指定方法です。
上記の例では 12 月 30 日が土曜日で 12 月 31 日が日曜日となっています。Java では日曜日が週の始まりであるため、その週が 1 日でも次の年に入っていると、その週が次の年の始まりと数えられることになります。
そのため、12 月 30 日は 2023 年と表示されているのに対し、12 月 31 日は 2024 年と表示されしまうのです。

この指定方法でバグが発生したりすることも考えられますので、極力使わないことがおすすめです。

V

タイムゾーン ID を表示するための予約語ですが、他の予約語と少し違い、明確に指定の仕方が決められています。
必ず VV というように 2 つつなぎで指定しなければなりません。このパターン以外で指定した場合エラーが出力されてしまいます。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd VV");
ZoneId zone = ZoneId.of("Asia/Tokyo");
ZonedDateTime zonedDateTime = ZonedDateTime.of(2023, 11, 3, 10, 0, 0, 0, zone);
String formatted = zonedDateTime.format(formatter);
System.out.println(formatted);
// 2023/11/3 Asia/Tokyo

V のみだった場合の出力

Exception in thread "main" java.lang.IllegalArgumentException: Pattern letter count must be 2: V
	at java.base/java.time.format.DateTimeFormatterBuilder.parsePattern(DateTimeFormatterBuilder.java:1815)
	at java.base/java.time.format.DateTimeFormatterBuilder.appendPattern(DateTimeFormatterBuilder.java:1772)
	at java.base/java.time.format.DateTimeFormatter.ofPattern(DateTimeFormatter.java:566)
	at dateFormat.main(dateFormat.java:9)

X

時差を表示するための予約語の一つです。
時差を表示する予約語はいくつかあるのですが、この予約語は他と違い時差が ±0 の時に Z と表示される仕様になっています。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd X");
ZoneId zone = ZoneId.of("Europe/London");
ZonedDateTime zonedDateTime = ZonedDateTime.of(2023, 11, 11, 10, 0, 0, 0, zone);
String formatted = zonedDateTime.format(formatter);
System.out.println(formatted);
// 2023/11/11 Z

こちらも特段の理由がない限り、他の時差系の予約語を使うのがおすすめです。

p

これは指定したパターンを固定幅にするための予約語です。
これを指定することによって出力の文字列長を固定幅にすることができます。表示形式を見ながら出ないと説明が難しいので、実際の出力例や指定例を見ながら説明していきます。
まず、指定方法です。p は少し特殊な予約語で、直後のパターンの数に対して同数もしくはそれ以上の p で指定する必要があります。
必要数を満たしていない場合はエラーが発生します。

// y で指定する場合
"ppppyyyy"

次に、この指定によってどのような効果が得られるのかについてです。
先ほど紹介した指定方法では、普通の出力と比較しても特に変化は見られません。

出力結果

2023

しかし、指定方法を以下のようにすると少し変化が見られます。

指定方法

"pppppyyyy"

出力結果

 2023

少し変化が分かりにくいかもしれませんが、後者の年の出力の前に半角スペースが挿入されています。
4つの p を指定した場合と5つの p を指定した場合では固定幅が変化しているため、後者に半角スペースが挿入された訳です。
このように、パターンを固定幅にする必要がある場合にこの予約語が活躍します。

固定幅にする際の注意点が一つあります。
それは、マイナスの数を扱う場合です。

以下のようにしてマイナスの年を表示してみます。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu");
LocalDateTime date = LocalDateTime.of(-2023, 11, 3, 10, 10, 10);
String formatted = date.format(formatter);
System.out.println(formatted);

出力結果

-2023

ここに p を指定して固定幅指定を行うと、エラーが出力されしまいます。

指定方法

"ppppuuuu"

出力結果

Exception in thread "main" java.time.DateTimeException: Cannot print as output of 5 characters exceeds pad width of 4
	at java.base/java.time.format.DateTimeFormatterBuilder$PadPrinterParserDecorator.format(DateTimeFormatterBuilder.java:2485)
	at java.base/java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2402)
	at java.base/java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1849)
	at java.base/java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1823)
	at java.base/java.time.LocalDateTime.format(LocalDateTime.java:1746)
	at dateFormat.main(dateFormat.java:11)

エラーメッセージを意訳すると、「桁数がはみ出ているので固定幅で出力できません」というところでしょうか。
エラー解消のために、以下のように指定方法を変更します。

指定方法

"pppppuuuu"

出力結果

-2023

マイナスを扱う場合の挙動は上記だけでなく他にもあります。
しかし、自分の検証ではなぜそのような結果になるのかがわからないものでした。
参考情報として記載しておきます。

up を 2 つづつ指定した場合(マイナスは表示されないが、エラーにはならない)

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ppuu");
LocalDateTime date = LocalDateTime.of(-1, 11, 3, 10, 10, 10);
String formatted = date.format(formatter);
System.out.println(formatted);
// 01

u を 2 つ、 p を 3 つ指定した場合(マイナスも表示され、エラーにもならない)

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("pppuu");
LocalDateTime date = LocalDateTime.of(-1, 11, 3, 10, 10, 10);
String formatted = date.format(formatter);
System.out.println(formatted);
// -01

さいごに

今回は Java の DateTimeFormatter の指定方法と予約語の意味について解説しました。
この記事を執筆する際にも、英語情報の翻訳や仕様の検証など苦労したので、DateTimeFormatter を利用する方の一助になればと思います。

もし記載ミスや仕様に対する認識の間違いがありましたら指摘お願いいたします。
ありがとうございました。

Discussion