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
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
-
ob_start、ob_get_contents関数でPHPの標準出力をバッファリング・変数に代入
標準出力をバッファリングし変数に代入することができるob_start()関数の解説。include()の処理をバッファリングすることで自由な場所に処理を記述することが可能。
-
-
ECCUBE mtb_constants initパラメータ設定の項目を追加する方法
ECCUBEのパラメータ設定で設定できる項目を追加する方法を説明します。
-
-
PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う
PHPには日時をオブジェクトとして生成する関数「DateTimeImmutable」「DateTime」が用意されている。これを利用して1ヶ月後、月初日、月末日、5日後などを指定して日付を取得できる。
-
-
ECCUBEの問い合わせフォームに任意の値を引数として渡す方法
ECCUBEのお問い合わせフォームに値を固有の情報を送りそれに基づいて処理をする方法を解説。ボタンの設置、受け取り側のテンプレート、プログラムのサンプルソースを提供。
-
-
カオナビAPI Ver.2.0のtokenを取得する PHP+cURLのサンプルプログラム
PHPのcURLを使用してカオナビAPIのアクセスに必要なtoken、シートの情報を取得するサンプルプログラムを作成。cURLの処理は関数化しているため他のAPIでも流用できる。
-
-
Basic認証の.htaccess、.htpasswd生成ツールと解説
Basic認証を設定する際に必要となる.htaccess、.htpasswdファイルを生成するツール。ID、PASS、.htpasswdへのパスを入力することで編集する情報を生成します。
-
-
ECCUBEのポイント設定、ポイント付与率を一括で変更する方法解説
ECCUBEの商品個別に設定してあるポイントを一括で変更する方法を解説。ECCUBEには商品個別のポイントを一括して変更する機能がありません。SQLを作成して一括置換!
-
-
ECCUBEでアップロードできない。upload_max_filesizeを設定する場所
テンプレートをアップロードする際に発生するエラー「テンプレートファイルがアップロードされていません」の対処方法。これはファイル容量の制限に引っかかっています。
-
-
ECCUBE2.13.3で商品規格の在庫数が無制限から変更できないバグがある
2.13.3固有のバグである商品規格の在庫数の入力エリアがアクティブにならない不具合を解消する解説です。product_class.tplの2行を修正するだけの簡単対応です。
-
-
ECCUBEの商品一覧ページのSEO対策!rel=”next” rel=”prev”を設定
Googleは関連あるページはその旨明示するよう求めています。ECCUBEの商品一覧ページでその求めに応じるための「rel=”next”」「rel=”prev”」について解説します。