DateTimeFormatterのyyyyとYYYYの違いに注意


# DateTimeFormatterのyyyyとYYYYの違いについて

Javaで日付と時刻をフォーマットするためのクラスとして`DateTimeFormatter`があります。このクラスは、日付や時刻を文字列に変換したり、文字列から日付や時刻を解析するためのフォーマットパターンを指定できます。たとえば、`"yyyy-MM-dd"`のようなフォーマットを指定すると、日付を「2025-01-06」のような形式に変換できます。  
（Java 8以降なら変わらないと思いますが、Java21で動作確認しました）

```java
|  JShellへようこそ -- バージョン21.0.4
|  概要については、次を入力してください: /help intro
jshell> import java.time.format.DateTimeFormatter;

jshell> import java.time.LocalDate;

jshell> LocalDate date = LocalDate.now();
date ==> 2025-01-12

jshell> var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)

jshell> date.format(formatter);
$7 ==> "2025-01-12"
```

このうち年を表すパターン文字列については[ドキュメント](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)に
```
 Symbol  Meaning                     Presentation      Examples
 ------  -------                     ------------      -------
 y       year-of-era                 year              2004; 04
 Y       week-based-year             year              1996; 96
```

と2つありましたので、この違いを調べてみました。

## yyyyとYYYYの違い

それぞれの特徴を以下にまとめます。

### yyyy: 年（Year-of-era）

- `yyyy`はカレンダー年（通常の年）を表します。
- 年の始まりは1月1日で、年末は12月31日です。
- 例：`2025-01-01`は`yyyy`で「2025」となります。

### YYYY: 週基準の年（Week-based-year）
`YYYY`はISO週番号に基づく年を表します。

https://en.wikipedia.org/wiki/ISO_week_date#First_week

具体的にどういうことかというと、

> The ISO 8601 definition for week 01 is the week with the first Thursday of the Gregorian year (i.e., of January) in it.

とのことで、「最初の木曜日を含む週」がその年の最初の週となります。  

つまり、以下のようになります。

```java
jshell> var formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd");
formatter ==> Localized(WeekBasedYear,4,19,EXCEEDS_PAD)'-'Value ... r,2)'-'Value(DayOfMonth,2)

jshell> var date = LocalDate.parse("2024-12-31") ;
date ==> 2024-12-31

jshell> date.format(formatter);
$8 ==> "2025-12-31"
```

`2024-12-31`を`YYYY-MM-dd`で文字列にフォーマットすると`2025-12-31`になってしまいました。

![202412.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/e7608408-7bbb-c100-6e3d-d13a67849a45.png)

これは2024-12-31の週の木曜日は1/2で2025年であるためです。

### `uuuu` について

あまり使わないと思いますが、`DateTimeFormatter` の書式指定子には `uuuu` もあります。

```
 Symbol  Meaning                     Presentation      Examples
 u       year                        year              2004; 04
```

この指定子は `y`（year-of-era）とよく似ていますが、いくつかの違いがあります。

- **`uuuu`**:
  - ISOベースのカレンダーシステムで使用される「年」をそのまま表します。
  - 負の値もそのまま扱い、西暦0年や負の年も表現できます。

具体的には以下のようになります。

```java
jshell> var date = LocalDate.of(-44, 3, 15);
date ==> -0044-03-15

jshell> var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
formatter ==> Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)

jshell> date.format(formatter);
$13 ==> "0045-03-15"

jshell> var formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
formatter ==> Value(Year,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)

jshell> date.format(formatter);
$15 ==> "-0044-03-15"
```

紀元前44年を扱っているつもりなのですが、yyyyだと紀元45年になってしまいました。uuuuだと紀元前44年になっています。

## まとめ

ドキュメントのExamples

```
 Symbol  Meaning                     Presentation      Examples
 ------  -------                     ------------      -------
 y       year-of-era                 year              2004; 04
 Y       week-based-year             year              1996; 96
```

だと一見どちらでも良さそうです。

また、例えば、Pythonだと

```python
Python 3.12.7 (main, Oct 30 2024, 03:17:17) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> date = datetime(2024, 12, 31)
>>> date.strftime("%Y-%m-%d")
'2024-12-31'
```

のようになり、年のフォーマットにはYを使います。  
そのため何気なくYYYYを使ってしまいそうです。

しかし、`week-based-year`も必要な人はどこかにいるのだと思いますが、**基本的にDateTimeFormatterでYYYYを使っていたらバグの可能性が高いです。**