JavaのDateFormatの小文字vs大文字

# はじめに
JavaのDateTimeFormatterは決められた半角英字を用いて、どのような文字列とするかを決めます。
その中で`y`と`Y`のように大文字と小文字だけの違いで別物を表現しようとしている文字列が幾つもあります。
ここではその違いについてまとめました。

# 環境
Java11を使用しています。
今回のように短いコードを描く場合はJshellが便利なので、JShell上で試しています。

起動後、使用するであろうクラスをimportしておきます、。

``` sh
tasogarei $ jshell
|  JShellへようこそ -- バージョン11.0.1
|  概要については、次を入力してください: /help intro

jshell> import java.time.*

jshell> import java.time.format.DateTimeFormatter
```

# g vs G
`g`はユリウス通日を表示し、`G`は紀元を表します。

``` Java
jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("g"));
$83 ==> "58499"

jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("G"));
$84 ==> "西暦"
```

ユリウス通日について知りたい方はWikipediaをご覧ください（自分も初めて知りました）
https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%AA%E3%82%A6%E3%82%B9%E9%80%9A%E6%97%A5

# m vs M
これは簡単で`m`は分、`M`は月を表します。
それぞれ2つ重ねると1桁の場合に0埋めしたStringを返却してくれるため、桁数を揃えたい場合は2つ記述するのが一般的です。
両者とも表現するべき桁数が2桁までとなるため、3つ記述すると実行時にエラーとなります。

``` Java
jshell> LocalDateTime.of(2019,12,12,12,1,1).format(DateTimeFormatter.ofPattern("mmm"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: m
|        at DateTimeFormatterBuilder.parseField (DateTimeFormatterBuilder.java:1952)
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1734)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#24:1)
```

ただし、`M`は状況依存となっているため、実は`L`を使う方が正解の場合もあるかもしれません。
`L`はスタンドアロン方式となっているため、常に同じ値が表示されます。
ただし、`SimpleDateFormat`についてのみこの違いがあるだけで`DateTimeFormatter`を使用した場合は両方とも問題なく使用できます。

# d vs D
これは似たような意味のようでまるで違う動きをします。

まずは`d`ですが説明に「day-of-month」とあるように月ベースの日付を表示してくれます。
私たちが一般的に使用する日付ですね。

``` Java
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("dd"));
$3 ==> "02"
```

それに対して`D`は「day-of-year」となっており、こちらは年ベースの日付を表示してくれます。
1年のはじめである1月1日からの日数ですね。

``` Java
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("DDD"));
$4 ==> "033"
```

# y vs Y
まずこの2つの違いを見る前に指定する個数によって表示形式が異なる点についてコードの結果から見ていきましょう。
一般的には2つ指定して下2桁か4つ指定して全部表示してもらうかのどちらを使用することになるかと思います。
あまり4桁以外の年を表示するケースはないと思うので、ここでは4桁のみ見てみます。

``` Java
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("y"));
$5 ==> "2019"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("yy"));
$6 ==> "19"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("yyy"));
$7 ==> "2019"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("yyyy"));
$8 ==> "2019"
```

大文字の`Y`についても年を表現するためにあるので、個数での指定については変わりありません。

``` Java
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("Y"));
$9 ==> "2019"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("YY"));
$10 ==> "19"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("YYY"));
$11 ==> "2019"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("YYYY"));
$12 ==> "2019"
```

そして違いですが、まずは下記のコードを見てみてください。

``` Java
jshell> LocalDate.of(2018,12,30).format(DateTimeFormatter.ofPattern("YYYY"));
$44 ==> "2019"

jshell> LocalDate.of(2018,12,30).format(DateTimeFormatter.ofPattern("yyyy"));
$45 ==> "2018"
```

同じ日付を指定したのにも関わらず年が1年ずれています。
これこそが違いで、`Y`はGregorianCalendarによる年を表示するためにあり、`y`は紀元年を表示するためにあるので、違いが現れます。
GregorianCalendarについてはJavaDocを見ていただくとしますが、年末年始についてはずれる可能性がある考え方と覚えておけば良いと思います。
なので、年末年始の数日間だけ年の表示が不正になるというなんともテストだけではすり抜けそうなバグを埋め込む温床となりえたりします。
昔やってしまって大変ひどい目にあったので、間違いありません。

ちなみに`y`は紀元に対しての年を表示するため、紀元が西暦となっていなければ思った値にならない可能性があります。
デフォルトでは紀元は西暦となっています（ロケール変更したらまでは確認していません）。

``` Java
jshell> LocalDate.of(2018,12,30).format(DateTimeFormatter.ofPattern("G"));
$46 ==> "西暦"
```

あまり問題にはならないと思いますが、正確には`u`が年そのまま表示する際に使用するとありますので、`u`を使うのが正解かもしれません。
考え方とかによると思うので、なんとも言えないところです。

``` Java
jshell> LocalDate.of(2018,12,30).format(DateTimeFormatter.ofPattern("u"));
$47 ==> "2018"
```

# k vs Kとh vs H
`k`と`K`、`h`と`H`は時刻を表示するものですが、それぞれ表示してくれるものが違います。
以下の表でそれぞれ何が表示されるのかまとめました。

| k    | K    | h    | H    |
|------|------|------|------|
| 1-24 | 0-11 | 1-12 | 0-23 |

小文字は24時表記で大文字は12時間表記という大きな違いがあり、`k`と`h`で0時とすると24(12)時とするというのが変わります。

# w vs W
`w`と`W`は両方とも同じく週を表す表現ですが、それぞれ基準が異なります。
`w`はその年の`W`はその月の何周目かを表示するという違いがあります。

``` Java
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("W"));
$55 ==> "1"

jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("w"));
$56 ==> "5"
```

# s vs S
`s`は一般的な秒を表現するのに対して`S`は`fraction-of-second`を表現するものです。

``` Java
jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("s"));
$57 ==> "24"

jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("S"));
$58 ==> "7"
```

`S`はなるほどよくわからんなので、複数指定して動作の違いについて見てみました。


``` Java
jshell> LocalDateTime.of(2019,1,1,0,0,12,10);
$75 ==> 2019-01-01T00:00:12.000000010

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSS"));
$76 ==> "000"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSS"));
$77 ==> "0000"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSSS"));
$78 ==> "00000"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSSSS"));
$79 ==> "000000"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSSSSS"));
$80 ==> "0000000"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSSSSSS"));
$81 ==> "00000001"

jshell> LocalDateTime.of(2019,1,1,0,0,12,10).format(DateTimeFormatter.ofPattern("SSSSSSSSS"));
$82 ==> "000000010"
```

どうやら、秒以下の桁数をどれくらいまで表示するかのようです。
`of`の指定で10ナノ秒を指定してあげて動作の違いを見てみてやっと判別可能なレベルです。

# q vs Q
`q`と`Q`は珍しく同じ意味で四半期の何回目であるかを表します。
なぜ多くの文字の意味が違うのにこれだけ一緒なのかは知りません。

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("q"));
$87 ==> "4"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("Q"));
$88 ==> "4"
```

# e vs E
`e`はローカライズされた曜日、`E`は一般的な曜日を表示します。

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("E"));
$96 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("e"));
$97 ==> "3"
```

`e`と`E`は指定した個数によって表示が変わるという面白い動作をしますので、指定個数を変えて実行してみます。

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("ee"));
$101 ==> "03"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("eee"));
$102 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("eeee"));
$103 ==> "火曜日"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("eeeee"));
$104 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("eeeeee"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: e
|        at DateTimeFormatterBuilder.parseField (DateTimeFormatterBuilder.java:1903)
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1734)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#105:1)
```

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("EE"));
$108 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("EEE"));
$109 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("EEEE"));
$110 ==> "火曜日"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("EEEEE"));
$111 ==> "火"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("EEEEEE"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: E
|        at DateTimeFormatterBuilder.parseField (DateTimeFormatterBuilder.java:1903)
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1734)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#112:1)
```

ちなみにローカライズ曜日と一般的な曜日の表示が変わるケースはよくわかりませんでした。

さらにちなみに`c`は`e`と同じ表示するとされていますが、2つ以上指定するとエラーとなります。
`c`の型がnumberで数字を扱い、`e`がtextで文字列を扱うからのようです。

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("c"));
$113 ==> "3"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("cc"));
|  例外java.lang.IllegalArgumentException: Invalid pattern "cc"
|        at DateTimeFormatterBuilder.parseField (DateTimeFormatterBuilder.java:1867)
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1734)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#114:1)
```

また、`e`と`c`については`SimpleDateFormatter`では定義されておらず、`DateTimeFormatter`からの登場なので注意が必要です。


``` Java
jshell> new SimpleDateFormat("E").format(new Date())
$144 ==> "水"

jshell> new SimpleDateFormat("e").format(new Date())
|  例外java.lang.IllegalArgumentException: Illegal pattern character 'e'
|        at SimpleDateFormat.compile (SimpleDateFormat.java:845)
|        at SimpleDateFormat.initialize (SimpleDateFormat.java:653)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:624)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:599)
|        at (#145:1)

jshell> new SimpleDateFormat("c").format(new Date())
|  例外java.lang.IllegalArgumentException: Illegal pattern character 'c'
|        at SimpleDateFormat.compile (SimpleDateFormat.java:845)
|        at SimpleDateFormat.initialize (SimpleDateFormat.java:653)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:624)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:599)
|        at (#146:1)
```

# a vs A
`a`は日の中の午前午後を表現し、`A`は日の中で何ms経過したかを表します。


``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("a"));
$147 ==> "午前"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("A"));
$148 ==> "12000"
```

# n VS N
`n`は時刻のナノ秒部分を表現し、`N`は日の中で何ns経過したかを表します。

``` Java
jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("n"));
$149 ==> "10"

jshell> LocalDateTime.of(2019,10,1,0,0,12,10).format(DateTimeFormatter.ofPattern("N"));
$150 ==> "12000000010"
```

# v vs V
`v`は一般的なタイムゾーンの名前を表現し、`V`はタイムゾーンIDを表します。
`V`については1つだけだとエラーとなってしまうため、`VV`と指定は2つ必要となります。

``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("v"));
$159 ==> "GMT+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("VV"));
$160 ==> "Asia/Tokyo"
```

これらはタイムゾーンを表現したいので、タイムゾーンの概念がない`LocalDate`系ではエラーとなります。注意が必要です。

``` Java
jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("VV"));
|  例外java.time.DateTimeException: Unable to extract ZoneId from temporal 2019-01-16T17:33:36.488440
|        at DateTimePrintContext.getValue (DateTimePrintContext.java:289)
|        at DateTimeFormatterBuilder$ZoneIdPrinterParser.format (DateTimeFormatterBuilder.java:4170)
|        at DateTimeFormatterBuilder$CompositePrinterParser.format (DateTimeFormatterBuilder.java:2335)
|        at DateTimeFormatter.formatTo (DateTimeFormatter.java:1843)
|        at DateTimeFormatter.format (DateTimeFormatter.java:1817)
|        at LocalDateTime.format (LocalDateTime.java:1752)
|        at (#161:1)

jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("v"));
|  例外java.time.DateTimeException: Unable to extract ZoneId from temporal 2019-01-16T17:33:39.394345
|        at DateTimePrintContext.getValue (DateTimePrintContext.java:289)
|        at DateTimeFormatterBuilder$ZoneTextPrinterParser.format (DateTimeFormatterBuilder.java:4066)
|        at DateTimeFormatterBuilder$CompositePrinterParser.format (DateTimeFormatterBuilder.java:2335)
|        at DateTimeFormatter.formatTo (DateTimeFormatter.java:1843)
|        at DateTimeFormatter.format (DateTimeFormatter.java:1817)
|        at LocalDateTime.format (LocalDateTime.java:1752)
|        at (#162:1)
```

ちなみに日本の日付を表してくれる`JapaneseDate`もエラーになります。

``` Java
jshell> import java.time.chrono.*

jshell> JapaneseDate.now().format(DateTimeFormatter.ofPattern("v"));
|  例外java.time.DateTimeException: Unable to extract ZoneId from temporal Japanese Heisei 31-01-16
|        at DateTimePrintContext.getValue (DateTimePrintContext.java:289)
|        at DateTimeFormatterBuilder$ZoneTextPrinterParser.format (DateTimeFormatterBuilder.java:4066)
|        at DateTimeFormatterBuilder$CompositePrinterParser.format (DateTimeFormatterBuilder.java:2335)
|        at DateTimeFormatter.formatTo (DateTimeFormatter.java:1843)
|        at DateTimeFormatter.format (DateTimeFormatter.java:1817)
|        at ChronoLocalDate.format (ChronoLocalDate.java:642)
|        at (#164:1)

jshell> JapaneseDate.now().format(DateTimeFormatter.ofPattern("VV"));
|  例外java.time.DateTimeException: Unable to extract ZoneId from temporal Japanese Heisei 31-01-16
|        at DateTimePrintContext.getValue (DateTimePrintContext.java:289)
|        at DateTimeFormatterBuilder$ZoneIdPrinterParser.format (DateTimeFormatterBuilder.java:4170)
|        at DateTimeFormatterBuilder$CompositePrinterParser.format (DateTimeFormatterBuilder.java:2335)
|        at DateTimeFormatter.formatTo (DateTimeFormatter.java:1843)
|        at DateTimeFormatter.format (DateTimeFormatter.java:1817)
|        at ChronoLocalDate.format (ChronoLocalDate.java:642)
|        at (#165:1)
```

# z vs Z
`z`はタイムゾーン名を表現し、`Z`はzone-offsetを表します。
一般的が付く付かないの話はつっこみはじめると大変な事態を招きそうと思ったので、ここでは割愛します。

``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("z"));
$166 ==> "JST"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("Z"));
$167 ==> "+0900"
```

`z`と`Z`についても指定個数によって表示が変わりますので、指定してみたいと思います。

``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("z"));
$174 ==> "JST"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("zz"));
$175 ==> "JST"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("zzz"));
$176 ==> "JST"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("zzzz"));
$177 ==> "日本標準時"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("zzzzz"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: z
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1737)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#178:1)
```

``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("Z"));
$180 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("ZZ"));
$181 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("ZZZ"));
$182 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("ZZZZ"));
$183 ==> "GMT+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("ZZZZZ"));
$184 ==> "+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("ZZZZZZ"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: Z
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1764)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#185:1)
```

`z`と`Z`は`SimpleDateFormatter`と挙動が変わるので、その点は注意です。

``` Java
jshell> new SimpleDateFormat("Z").format(new Date());
$219 ==> "+0900"

jshell> new SimpleDateFormat("ZZ").format(new Date());
$220 ==> "+0900"

jshell> new SimpleDateFormat("ZZZ").format(new Date());
$221 ==> "+0900"

jshell> new SimpleDateFormat("ZZZZ").format(new Date());
$222 ==> "+0900"

jshell> new SimpleDateFormat("ZZZZZ").format(new Date());
$223 ==> "+0900"

jshell> new SimpleDateFormat("ZZZZZZ").format(new Date());
$224 ==> "+0900"
```

``` Java
jshell> new SimpleDateFormat("z").format(new Date());
$210 ==> "JST"

jshell> new SimpleDateFormat("zz").format(new Date());
$211 ==> "JST"

jshell> new SimpleDateFormat("zzz").format(new Date());
$212 ==> "JST"

jshell> new SimpleDateFormat("zzzz").format(new Date());
$213 ==> "日本標準時"

jshell> new SimpleDateFormat("zzzzz").format(new Date());
$214 ==> "日本標準時"

jshell> new SimpleDateFormat("zzzzzz").format(new Date());
$215 ==> "日本標準時"

jshell> new SimpleDateFormat("zzzzzzz").format(new Date());
$216 ==> "日本標準時"

jshell> new SimpleDateFormat("zzzzzzzz").format(new Date());
$217 ==> "日本標準時"
```

ちなみに数は結構指定してみましたが、エラーにはなりませんでした。

# x vs X
`x`と`X`の両方ともzone-offsetを表現するものです、UTC以外については同じ動作をするようです。
`Z`との違いは、1個と4個指定した際の表示が変わるだけでした。


``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("x"));
$186 ==> "+09"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("X"));
$187 ==> "+09
```


``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("x"));
$188 ==> "+09"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("xx"));
$189 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("xxx"));
$190 ==> "+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("xxxx"));
$191 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("xxxxx"));
$192 ==> "+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("xxxxxx"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: x
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1781)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#193:1)
```

``` Java
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("X"));
$194 ==> "+09"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("XX"));
$195 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("XXX"));
$196 ==> "+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("XXXX"));
$197 ==> "+0900"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("XXXXX"));
$198 ==> "+09:00"

jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("XXXXXX"));
|  例外java.lang.IllegalArgumentException: Too many pattern letters: X
|        at DateTimeFormatterBuilder.parsePattern (DateTimeFormatterBuilder.java:1776)
|        at DateTimeFormatterBuilder.appendPattern (DateTimeFormatterBuilder.java:1702)
|        at DateTimeFormatter.ofPattern (DateTimeFormatter.java:564)
|        at (#199:1)
```

ちなみに`x`は`SimpleDateFormatter`には存在しておらず、`DateTimeFormatter`のみとなります。

``` Java
jshell> new SimpleDateFormat("x").format(new Date());
|  例外java.lang.IllegalArgumentException: Illegal pattern character 'x'
|        at SimpleDateFormat.compile (SimpleDateFormat.java:845)
|        at SimpleDateFormat.initialize (SimpleDateFormat.java:653)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:624)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:599)
|        at (#205:1)

```

`X`についても動作が異なるため注意が必要です。

``` Java
jshell> new SimpleDateFormat("X").format(new Date());
$206 ==> "+09"

jshell> new SimpleDateFormat("XX").format(new Date());
$207 ==> "+0900"

jshell> new SimpleDateFormat("XXX").format(new Date());
$208 ==> "+09:00"

jshell> new SimpleDateFormat("XXXX").format(new Date());
|  例外java.lang.IllegalArgumentException: invalid ISO 8601 format: length=4
|        at SimpleDateFormat.encode (SimpleDateFormat.java:884)
|        at SimpleDateFormat.compile (SimpleDateFormat.java:865)
|        at SimpleDateFormat.initialize (SimpleDateFormat.java:653)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:624)
|        at SimpleDateFormat.<init> (SimpleDateFormat.java:599)
|        at (#209:1)
```

UTC時にだけ発生する違いについては普段はJSTでの時間を出力することが多いので意識しないでしょうが、UTCだけ気をつけようだけ覚えておけば良いと思います。
実装の中まで見てやっと理解しました。

```
jshell> ZonedDateTime.of(2019,1,1,1,1,1,1,ZoneId.of(ZoneOffset.UTC.toString())).format(DateTimeFormatter.ofPattern("X"));
$228 ==> "Z"

jshell> ZonedDateTime.of(2019,1,1,1,1,1,1,ZoneId.of(ZoneOffset.UTC.toString())).format(DateTimeFormatter.ofPattern("x"));
$229 ==> "+00"
```


# さいごに
`SimpleDateFormatter`との挙動の違いだったり、タイムゾーン周りが怪しかったりとはまるポイントがいくつもあり思ったより怖い存在であることが分かりました。
正直、そこまで使い込んでいる機能ではなかったので、何が正しいかは今のところはわかってませんが、暇あったらJava側の実装も見てみたいと思います。