PHPで月末から1ヶ月後「+1 month」を算出すると想定する日付にならない場合がある
PHPで月末から1ヶ月後「+1 month」を算出すると想定する日付にならない場合がある
「CakePHP4のFrozenDateで1ヵ月前、先月、今月1日、来月末の日付などを算出する方法」
「PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う」
上記の記事では PHPで 1ヶ月後の日付、今月月初の日付、来月末の日付、5か月後の日付などを算出する際は、CakePHPでは「FrozenDate」を使い、PHPでは「DateTime」「DateTimeImmutable」を使うと便利ですよ、という記事を書きました。
※この記事では、日付を生成する関数に「DateTimeImmutable」を使用しています。
「DateTimeImmutable」を「DateTime」に置き換えてもらっても問題ありません。「DateTime」と「DateTimeImmutable」の違いについても下記に書いていますので併せて参考にしてください。
「PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う」
PHPの 1ヶ月後「+1 month」が 1ヶ月後にならない事例
この際、PHPでは 1ヶ月後(「+1 month」「next month」)を算出する際に注意が必要な点があります。
下記のように、月末に関連しない日の「1ヶ月後」は全く問題ありません。
1 2 3 4 5 6 |
// 1月15日の 1ヶ月後は? $dateImmutable = new DateTimeImmutable("2024-01-15"); var_export($dateImmutable->modify('+1 month')); // 結果の日付は下記 // 'date' => '2024-02-15 00:00:00.000000', |
ですが、下記のように「1月31日」などの月末に関連する日の「1ヶ月後」を算出する際は注意が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 1月31日の 1ヶ月後は? $dateImmutable = new DateTimeImmutable("2024-01-31"); var_export($dateImmutable->modify('+1 month')); // 結果の日付は下記 // 'date' => '2024-03-02 00:00:00.000000', // 1月30日の 1ヶ月後は? $dateImmutable = new DateTimeImmutable("2024-01-30"); var_export($dateImmutable->modify('+1 month')); // 結果の日付は下記 // 'date' => '2024-03-01 00:00:00.000000', // 4月30日の 1ヶ月後は? $dateImmutable = new DateTimeImmutable("2024-04-30"); var_export($dateImmutable->modify('+1 month')); // 結果の日付は下記 // 'date' => '2024-05-30 00:00:00.000000', |
「1月31日」の「+1 month(1ヶ月後)」は、1月は 1ヵ月 31日なので 31日後、
「4月30日」の「+1 month(1ヶ月後)」は、4月は 1ヵ月 30日なので 30日後、
という算出がなされます。
1ヶ月後「+1 month」が 1ヶ月後にならないのはバグではなく PHPの仕様
これは、バグではなく、PHPでは「1ヶ月後」を「その月の日数分の日付を進めた日」と定義しているということです。
つまり、
1月、3月のように 31日まである月の 1ヵ月とは 31日後で、
4月、6月のように 30日までの月の 1ヵ月とは 30日後で、
2月のように 28日(閏年は除く)までの月の 1ヵ月とは 28日後です
ということです。
これはバグではなく、PHPの「1ヶ月後」がそう定義されているので仕方がないですよね。
「1ヶ月後」だけではなく「2ヶ月後」「3か月後」を算出するとより分かりやすいかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$dateImmutable = new DateTimeImmutable("2024-01-31"); var_export($dateImmutable->modify("+1 month")); var_export($dateImmutable->modify("+2 month")); var_export($dateImmutable->modify("+3 month")); var_export($dateImmutable->modify("+4 month")); var_export($dateImmutable->modify("+5 month")); // 出力結果は以下のようになります。 // 'date' => '2024-01-31 00:00:00.000000' // 'date' => '2024-03-02 00:00:00.000000' // 31日後(1月分) // 'date' => '2024-03-31 00:00:00.000000' // 29日後(2月分) // 'date' => '2024-05-01 00:00:00.000000' // 31日後(3月分) // 'date' => '2024-05-31 00:00:00.000000' // 30日後(4月分) // 'date' => '2024-07-01 00:00:00.000000' // 31日後(5月分) |
また、上記から分かるように、「1月31日」の「2ヶ月後」「3ヶ月後」...は「31日×2ヶ月分後」「31日×3ヶ月分後」ではない点にも注意が必要ですね。
ちなみに、(「+2 month」) と (「+1 month」に「+1 month」) とでは異なる結果になります。
1 2 3 4 5 6 7 8 9 10 |
$dateImmutable = new DateTimeImmutable("2024-01-31"); var_export($dateImmutable); var_export($dateImmutable2 = $dateImmutable->modify("+1 month")); var_export($dateImmutable3 = $dateImmutable2->modify("+1 month")); // 出力結果は以下のようになります。 // 'date' => '2024-01-31 00:00:00.000000' // 'date' => '2024-03-02 00:00:00.000000' // 31日後(1月分) // 'date' => '2024-04-02 00:00:00.000000' // 31日後(3月分) |
2回目の「$dateImmutable2
」に対して「+1 month」する処理では、「3月2日」に対して実行する処理のためですね。
この点も一つ認識をしておく必要があるかと思います。
PHPで「翌月末」や「1ヶ月後」を算出するサンプルプログラム
さてさて。
ここまで説明した内容は、PHPの「1ヶ月後」の定義ですので変えようがない部分です。
では、「1ヶ月後」をどのように算出するか、を考えていきましょう。
しかし、算出するにあたっては、まず最初に「1ヶ月後」を定義する必要があります。
この「1ヶ月後」がどういう定義なのか、によって対応方法が変わってきます。
取得パターン:「翌月末」を算出する方法
経理処理などをする際は「『申込日』を基準としてその『翌月末』を支払い日とする」というような定義で日付の算出が必要な場合があるかと思います。
そのような場合は、下記のように「+1 month」の前に「last day of」を追加することで翌月末を取得することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$dateImmutable = new DateTimeImmutable("2024-01-31"); var_export($dateImmutable); var_export($dateImmutable->modify("last day of +1 month")); var_export($dateImmutable->modify("last day of +2 month")); var_export($dateImmutable->modify("last day of +3 month")); var_export($dateImmutable->modify("last day of +4 month")); var_export($dateImmutable->modify("last day of +5 month")); // 出力結果は以下のようになります。 // 'date' => '2024-01-31 00:00:00.000000' // 'date' => '2024-02-29 00:00:00.000000' // last day of +1 month // 'date' => '2024-03-31 00:00:00.000000' // last day of +2 month // 'date' => '2024-04-30 00:00:00.000000' // last day of +3 month // 'date' => '2024-05-31 00:00:00.000000' // last day of +4 month // 'date' => '2024-06-30 00:00:00.000000' // last day of +5 month |
この処理では「月末」を算出する処理ですので、「1月15日」を基準日に処理を実行しても「2月29日」が算出されます。
ただ、「月末」を算出する処理としてはシンプルに記述することができます。
取得パターン:1ヶ月後を取得するけど、月末の場合は翌月末日を取得する
『基本的な処理は「1ヶ月後」を取得するけど、月末の場合は翌月末を取得する』という場合は、「月末」は単純に「+1 month」では対応できませんので、「月末」の場合だけ処理を分岐する下記のような処理をするといいのではないか、と思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
$dateImmutable = new DateTimeImmutable("2024-01-31"); if (in_array($dateImmutable->format("m"),[1])){ // 「1月」の対応 if(in_array($dateImmutable->format("d"),[29,30,31])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } elseif (in_array($dateImmutable->format("m"),[2])){ // 「2月」の対応 if(in_array($dateImmutable->format("d"),[28,29])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } elseif (in_array($dateImmutable->format("m"),[3,5,7,8,10,12])){ // 「3月」「5月」「7月」「8月」「10月」「12月」の対応 if(in_array($dateImmutable->format("d"),[31])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } else { // 「4月」「6月」「9月」「11月」の対応 if(in_array($dateImmutable->format("d"),[30])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } |
上記は閏年の 2月は考慮されていません。
閏年の場合は、「2月28日」も「2月29日」も「3月31日」となります。それで問題がある場合は、閏年か否かを判断して「28日」の取り扱いの分岐を増やしてください。
取得パターン:1ヶ月後を取得するけど、31日の月の月末の場合のみ翌月末日を取得する
『基本的な処理は「1ヶ月後」を取得するけど、31日の月の月末の場合は翌月末を取得する』という場合は、下記のような分岐処理をするといいのではないか、と思います。
前項と何が違うかというと、「1月31日」の 1ヵ月後は「2月末」にしたいが、「4月30日」の 1ヶ月後は「5月30日」にしたい、という場合で、前項よりもちょっとだけセコイ契約になる感じでしょうか...。でも、プログラムとしてはこちらの方がシンプル!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$dateImmutable = new DateTimeImmutable("2024-01-30"); if (in_array($dateImmutable->format("m"),[1])){ // 「1月」の対応 if(in_array($dateImmutable->format("d"),[29,30,31])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } elseif (in_array($dateImmutable->format("m"),[3,5,8,10,12])){ // 「3月」「5月」「8月」「10月」「12月」の対応 if(in_array($dateImmutable->format("d"),[31])){ var_export($dateImmutable->modify("last day of +1 month")); } else { var_export($dateImmutable->modify("+1 month")); } } else { // 「2月」「4月」「6月」「7月」「9月」「11月」の対応 var_export($dateImmutable->modify("+1 month")); } |
「7月」は 31日までありますが、翌月も 31日までありますので、「3月」などと同じ分岐に入れても、最後の elseの分岐に入れても結果は変わりません。
取得パターン:1ヶ月を 30日 or 31日と規定している場合
「1ヶ月」の定義を「30日 or 31日」と規定している場合は、下記のようにシンプルに算出できます。
1 2 3 4 5 6 7 |
$dateImmutable = new DateTimeImmutable("2024-01-31"); // 1ヶ月を 30日と規定している場合 var_export($dateImmutable->modify("+30 day")); // 1ヶ月を 31日と規定している場合 var_export($dateImmutable->modify("+31 day")); |
1ヶ月を 30日とする場合は 1年が 360日となりますので、毎年 5日ずつ短くなっていきます。
また、「1月15日」の「1ヶ月後」が「2月14日」になりますので、混乱につながる可能性はあるかと思います。
ただ、それが許容できるのであれば、システムの処理としてはシンプルになります。
ちなみに、この短くなる 5日ですが、そのまま毎年短くなっていく方法もありだと思いますが、1年継続利用した場合は、最終月に無料利用期間の 5日間を追加するので、結果的に 1年後は 365日後になる、みたいな処理(契約)でもいいのかもしれないですね。
PHPで「翌月末」や「1ヶ月後」を算出する処理のまとめ
サンプルプログラムを提示しつつ「翌月末」や「1ヶ月後」を算出しましたが、
「1月31日」の「1ヶ月後」は
「3月2日」なのか?
「2月28日」なのか?
は状況によるので(契約内容によるので)、その条件なしに「これが正解」とは言えないということですね。
それぞれ、システムを作る上で「1ヶ月後」の定義を確認したうえで、それに合わせた適切な処理を検討することになるわけですね。
日付、日時に関する関連記事をこの記事も含めて 3記事書いていますので併せて参考にしてください。
「CakePHP4のFrozenDateで1ヵ月前、先月、今月1日、来月末の日付などを算出する方法」
「PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う」
「PHPで月末から1ヶ月後「+1 month」を算出すると想定する日付にならない場合がある」
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
QRコード作成ライブラリ「cakePHP-QR-Code-Helper」をPHPで使うカスタマイズ
CakePHP2用のQRコード作成ライブラリ「cakePHP-QR-Code-Helper」をプレーンのPHPでも使うためのカスタマイズ方法を解説。1ファイルを設置するだけでQRコードが作れるため使い勝手がいい。
-
include、requireのパス指定をdirname(__FILE__)、__DIR__と書く理由
include、requireのパスの指定を dirname(__FILE__)、__DIR__で記述する理由に付いて解説。相対パス、絶対パスを直書き、パスを書かない場合は何が問題かを説明。
-
PHPのデバッグで使う print_r、var_dump、var_exportの動作の違い
PHPのデバッグ等で変数や配列の中身を確認するために使用する関数print_r、var_dump、var_exportの動作の違い、仕様の違いについて確認した。var_exportがオススメ。
-
PHPで特定の日間の日付を for、strtotimeで表示する
ある特定の間の日付の情報を for文、strtotimeを使って作成し、その解説をしています。
-
ECCUBE2.13.3で商品規格の在庫数が無制限から変更できないバグがある
2.13.3固有のバグである商品規格の在庫数の入力エリアがアクティブにならない不具合を解消する解説です。product_class.tplの2行を修正するだけの簡単対応です。
-
ECCUBEでカード決済NGの受注情報をマイページ購入履歴に表示しない方法解説
ECCUBEでカード決済に失敗しても購入履歴一覧に注文情報(受注情報)が表示される問題への対処方法を解説。受注情報レコードの作成の流れとステイタスについても解説。
-
PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う
PHPには日時をオブジェクトとして生成する関数「DateTimeImmutable」「DateTime」が用意されている。これを利用して1ヶ月後、月初日、月末日、5日後などを指定して日付を取得できる。
-
PHPのソースで見慣れない記号が出てきた・アロー演算子(->)、ダブルアロー演算子(=>)
PHPのプログラムソースには見慣れない記号が出てきます。その意味や調べ方です。
-
SEO対策用タイトル、ディスクリプションの文字数カウントツール
SEO対策に使える文字数カウントツールで文字数の条件の説明も行っています。
-
ECCUBEの問い合わせフォームに任意の値を引数として渡す方法
ECCUBEのお問い合わせフォームに値を固有の情報を送りそれに基づいて処理をする方法を解説。ボタンの設置、受け取り側のテンプレート、プログラムのサンプルソースを提供。