【備忘録】Javaで日付時刻を文字列に変換

# 初めに

2021年現在でも「Java 日付 文字列」などで検索するといまだに`Date`と`SimpleDateFormat`が上位に出てくるので、`java.time`時代の参照すべきドキュメントと概要を備忘録として残す。

検証はOpenJDK11を使用していており、Oracleドキュメントのリンク先はJava17になっているが、基本的にはJava8から使えるはず。

# 日時オブジェクト

https://www.ne.jp/asahi/hishidama/home/tech/java/datetime.html

本記事ではJava8から導入された`java.time`パッケージのオブジェクトで日時を保持する。

```java
LocalDateTime now = LocalDateTime.now(); // 現在日時
LocalDateTime ldt = LocalDateTime.of(2021, 12, 10, 20, 30, 0); // 指定した日時
```

# 文字列に変換

## toString

日時オブジェクトの`toString`メソッドを使用するとISO-8601暦体系になる。

```java
LocalDateTime ldt1 = LocalDateTime.of(1999, 1, 1, 0, 0, 0);
System.out.println(ldt1); // -> "1999-01-01T00:00" ※秒が0の場合は省略される

LocalDateTime ldt2 = LocalDateTime.of(2021, 12, 31, 23, 59, 59);
System.out.println(ldt2); // -> "2021-12-31T23:59:59"

LocalDateTime ldt3 = LocalDateTime.of(2021, 12, 31, 23, 59, 59, 1200);
// 7つ目の引数はナノ秒
System.out.println(ldt3); // -> "2021-12-31T23:59:59.000001200"
```

## DateTimeFormatter

https://qiita.com/riekure/items/d83d4ea5d8a19a267453

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html

または、[Java11の日本語ドキュメント](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/format/DateTimeFormatter.html)／[Java8の日本語ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/time/format/DateTimeFormatter.html)

従来の`SimpleDateFormat`ライクな指定をしたい場合は`DateTimeFormatter`を使用する。
`SimpleDateFormat`同様シンボル文字を表示桁数分重ねる形式をとる。

```java
DateTimeFormatter year2fmtr = DateTimeFormatter.ofPattern("yy");
System.out.println(ldt1.format(year2fmtr)); // -> "99"
System.out.println(ldt2.format(year2fmtr)); // -> "21"

DateTimeFormatter year4fmtr = DateTimeFormatter.ofPattern("yyyy");
System.out.println(ldt1.format(year4fmtr)); // -> "1999"
System.out.println(ldt2.format(year4fmtr)); // -> "2021"

// DateTimeFormatterでは月は大文字でM
DateTimeFormatter month1fmtr = DateTimeFormatter.ofPattern("M");
System.out.println(ldt1.format(month1fmtr)); // -> "1"
System.out.println(ldt2.format(month1fmtr)); // -> "12"

DateTimeFormatter month2fmtr = DateTimeFormatter.ofPattern("MM");
System.out.println(ldt1.format(month2fmtr)); // -> "01"
System.out.println(ldt2.format(month2fmtr)); // -> "12"

// ※実行環境のロケールは日本
DateTimeFormatter month3fmtr = DateTimeFormatter.ofPattern("MMM");
System.out.println(ldt1.format(month3fmtr)); // -> "1月"
System.out.println(ldt2.format(month3fmtr)); // -> "12月"
```

事前定義されているフォーマッタを使用することも可能。

```java
System.out.println(ldt2.format(DateTimeFormatter.ISO_DATE)); // -> "2021-12-31"
```

## String.format

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html#format(java.lang.String,java.lang.Object...)

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Formatter.html

または、[Java11の日本語ドキュメント](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Formatter.html)／[Java8の日本語ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Formatter.html)

`String.format`の日付には`TemporalAccessor`に適用することが可能。

```java
// Formatterでは月は小文字でm
// "m","d"一文字で2桁表示を示す
System.out.println(String.format("test_%1$tY-%1$tm-%1$td.log", ldt2)); // -> "test_2021-12-31.log"
```

書式はDateTimeFormatterより複雑で`%<インデックス>$t<パターン>`で1セット。
オブジェクトと値をはめ込む位置が1:1の場合はとインデックス部分(`<インデックス>$`)は不要だが、大抵は1つの日時オブジェクトに対して年・月など2つ以上の位置にはめ込むので実質的にほぼ必須。

```java
// OK
System.out.println(String.format("test_%tY.log", ldt2)); // -> "test_2021.log"
// NG
System.out.println(String.format("test_%tY-%tm-%td.log", ldt2));
// -> java.util.MissingFormatArgumentException: Format specifier '%tm'
```

### 注意点

`String.format`は一般的に計算コストが高いので性能にシビアな環境や大量にループする中で使う場合は要注意。
※Java17で幾分改善されたらしい。

https://qiita.com/tana6/items/818d30f1974704746fcc

https://let-value.com/articles/2111/java17-string-format/