Carbonの subMonth と subMonthsWithOverflow とか
テストを回している時に、テストデータとして先月の情報を入れるはずが今月の情報を入れてしまいテストがコケるという事が起きたのでメモ。
使用している Carbon は ^2.0 です。
SubMonths メソッドという物
先月や数ヶ月前の日付を取り出すのに使用しています。ただ、Carbonには subMonths
メソッド以外にも subMonthsWithOverflow
, subMonthsWithoutOverflow
, subMonthsWithNoOverflow
, subMonthsNoOverflow
とあります。
説明文には「overflow explicitly forbidden」や「overflow explicitly allowed」と言った区別がされています。
とりあえず 2021/03/29 の一ヶ月前を見てみます。
>>> \Carbon\Carbon::create( 2021, 3, 29, 1 )->subMonths(1)
=> Carbon\Carbon @1614528000 {#3827
date: 2021-03-01 01:00:00.0 Asia/Tokyo (+09:00),
}
>>> \Carbon\Carbon::create( 2021, 3, 29, 1 )->subMonthsWithOverflow(1)
=> Carbon\Carbon @1614528000 {#3856
date: 2021-03-01 01:00:00.0 Asia/Tokyo (+09:00),
}
>>> \Carbon\Carbon::create( 2021, 3, 29, 1 )->subMonthsWithoutOverflow(1)
=> Carbon\Carbon @1614441600 {#3860
date: 2021-02-28 01:00:00.0 Asia/Tokyo (+09:00),
}
>>> \Carbon\Carbon::create( 2021, 3, 29, 1 )->subMonthsWithNoOverflow(1)
=> Carbon\Carbon @1614441600 {#3858
date: 2021-02-28 01:00:00.0 Asia/Tokyo (+09:00),
}
>>> \Carbon\Carbon::create( 2021, 3, 29, 1 )->subMonthsNoOverflow(1)
=> Carbon\Carbon @1614441600 {#3861
date: 2021-02-28 01:00:00.0 Asia/Tokyo (+09:00),
}
一ヶ月前を取るなら subMonthsWithoutOverflow
でいいじゃんって言う感じですが、いくつか疑問が出ます。
- 内部(PHP)では -30 日してるみたいだけど何で 3/1 や 2/28 とブレが出る?
-
subMonthsWithOverflow
,subMonthsWithoutOverflow
,subMonthsWithNoOverflow
,subMonthsNoOverflow
のそれぞれの違いは何?
内部(PHP)では -30 日してるみたいだけど何で 3/1 や 2/28 とブレが出る?
Carbon は基本的には PHP の \DateTime
を継承している物です。そして独自メソッドの subMonths
等は Carbon\Traits\Date
とかでゴリゴリ実装している感じ。
で一ヶ月前の解釈違いで、例えば 3/10 の一ヶ月前は 2/10 としたら、 3/30 の一ヶ月前は 2/30 にはならないので翌月の 3/2 にする、という解釈になる。この翌月に持ち越すのを overflow と言っているみたいです。これは年度にも言えて、うるう年などに関係してきます。
subMonthsWithOverflow
,subMonthsWithoutOverflow
,subMonthsWithNoOverflow
,subMonthsNoOverflow
のそれぞれの違いは何?
overflow をするしないかの違いなのですが、なぜに Without
や WithNo
や No
が増えたというアレ。あと久々に見たらえらい見にくくなってたのでソースコード追っかけメモ書き。
ここらへん -- github をみれば大体わかります。
見た限り Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia
と No|With|Without|WithNo
の2種類に分けて、後半の Overflow
に関しては With
を付けるか付けないかのみの違いみたいです。
そして実際の処理は $this->{"${action}Unit"}($unit, $parameters[0] ?? 1, $overflow);
といった感じですごい投げ方してます。 subMonthsWithOverflow
で展開すると $action = 'sub'; $unit = 'Month'; $parameters = []; $overflow = true;
になって subUnit('Month', 1, true);
といった感じになるはず。
そんなわけで Units::subUnit に飛ぶのです が単純に数値を負にするだけのようで、結局は Units::addUnit が処理をします。
すべての add/sub 処理は Units::addUnit で Datetime::modify で実行しています。しかし Units::addUnit の末尾の方で overflow 設定をしている場合は先月末に戻す 処理を入れています。
したがって、 subMonths()
は subMonthsWithOverflow()
は処理内容は同じです。そして subMonthsWithoutOverflow
,subMonthsWithNoOverflow
,subMonthsNoOverflow
ここらは否定の仕方が違うだけで中身は同じです。
参考
同じハマり方 : PHP Carbonで月の足し引きオーバーフローにはまった
Discussion