はじめに
JavaのDateTimeFormatterは決められた半角英字を用いて、どのような文字列とするかを決めます。
その中でy
とY
のように大文字と小文字だけの違いで別物を表現しようとしている文字列が幾つもあります。
ここではその違いについてまとめました。
環境
Java11を使用しています。
今回のように短いコードを描く場合はJshellが便利なので、JShell上で試しています。
起動後、使用するであろうクラスをimportしておきます、。
tasogarei $ jshell
| JShellへようこそ -- バージョン11.0.1
| 概要については、次を入力してください: /help intro
jshell> import java.time.*
jshell> import java.time.format.DateTimeFormatter
g vs G
g
はユリウス通日を表示し、G
は紀元を表します。
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つ記述すると実行時にエラーとなります。
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」とあるように月ベースの日付を表示してくれます。
私たちが一般的に使用する日付ですね。
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("dd"));
$3 ==> "02"
それに対してD
は「day-of-year」となっており、こちらは年ベースの日付を表示してくれます。
1年のはじめである1月1日からの日数ですね。
jshell> LocalDate.of(2019,2,2).format(DateTimeFormatter.ofPattern("DDD"));
$4 ==> "033"
y vs Y
まずこの2つの違いを見る前に指定する個数によって表示形式が異なる点についてコードの結果から見ていきましょう。
一般的には2つ指定して下2桁か4つ指定して全部表示してもらうかのどちらを使用することになるかと思います。
あまり4桁以外の年を表示するケースはないと思うので、ここでは4桁のみ見てみます。
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
についても年を表現するためにあるので、個数での指定については変わりありません。
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"
そして違いですが、まずは下記のコードを見てみてください。
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
は紀元に対しての年を表示するため、紀元が西暦となっていなければ思った値にならない可能性があります。
デフォルトでは紀元は西暦となっています(ロケール変更したらまでは確認していません)。
jshell> LocalDate.of(2018,12,30).format(DateTimeFormatter.ofPattern("G"));
$46 ==> "西暦"
あまり問題にはならないと思いますが、正確にはu
が年そのまま表示する際に使用するとありますので、u
を使うのが正解かもしれません。
考え方とかによると思うので、なんとも言えないところです。
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
はその月の何周目かを表示するという違いがあります。
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
を表現するものです。
jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("s"));
$57 ==> "24"
jshell> LocalDateTime.now().format(DateTimeFormatter.ofPattern("S"));
$58 ==> "7"
S
はなるほどよくわからんなので、複数指定して動作の違いについて見てみました。
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
は珍しく同じ意味で四半期の何回目であるかを表します。
なぜ多くの文字の意味が違うのにこれだけ一緒なのかは知りません。
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
は一般的な曜日を表示します。
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
は指定した個数によって表示が変わるという面白い動作をしますので、指定個数を変えて実行してみます。
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)
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で文字列を扱うからのようです。
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
からの登場なので注意が必要です。
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経過したかを表します。
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経過したかを表します。
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つ必要となります。
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("v"));
$159 ==> "GMT+09:00"
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("VV"));
$160 ==> "Asia/Tokyo"
これらはタイムゾーンを表現したいので、タイムゾーンの概念がないLocalDate
系ではエラーとなります。注意が必要です。
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
もエラーになります。
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を表します。
一般的が付く付かないの話はつっこみはじめると大変な事態を招きそうと思ったので、ここでは割愛します。
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("z"));
$166 ==> "JST"
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("Z"));
$167 ==> "+0900"
z
とZ
についても指定個数によって表示が変わりますので、指定してみたいと思います。
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)
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
と挙動が変わるので、その点は注意です。
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"
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個指定した際の表示が変わるだけでした。
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("x"));
$186 ==> "+09"
jshell> ZonedDateTime.now().format(DateTimeFormatter.ofPattern("X"));
$187 ==> "+09
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)
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
のみとなります。
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
についても動作が異なるため注意が必要です。
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側の実装も見てみたいと思います。