エス技研

WordPress、CakePHP、PHP、baserCMSなどの Web系システムを中心に情報を提供します!


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月31日」などの月末に関連する日の「1ヶ月後」を算出する際は注意が必要です。
 

 
「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月31日」の「2ヶ月後」「3ヶ月後」...は「31日×2ヶ月分後」「31日×3ヶ月分後」ではない点にも注意が必要ですね。
 
 
ちなみに、(「+2 month」) と (「+1 month」に「+1 month」) とでは異なる結果になります。
 

 
2回目の「$dateImmutable2」に対して「+1 month」する処理では、「3月2日」に対して実行する処理のためですね。
この点も一つ認識をしておく必要があるかと思います。
 
 

PHPで「翌月末」や「1ヶ月後」を算出するサンプルプログラム

 
さてさて。
ここまで説明した内容は、PHPの「1ヶ月後」の定義ですので変えようがない部分です。
では、「1ヶ月後」をどのように算出するか、を考えていきましょう。
 
しかし、算出するにあたっては、まず最初に「1ヶ月後」を定義する必要があります。
この「1ヶ月後」がどういう定義なのか、によって対応方法が変わってきます。
 
 

取得パターン:「翌月末」を算出する方法

 
経理処理などをする際は「『申込日』を基準としてその『翌月末』を支払い日とする」というような定義で日付の算出が必要な場合があるかと思います。
 
そのような場合は、下記のように「+1 month」の前に「last day of」を追加することで翌月末を取得することができます。
 

 
この処理では「月末」を算出する処理ですので、「1月15日」を基準日に処理を実行しても「2月29日」が算出されます。
ただ、「月末」を算出する処理としてはシンプルに記述することができます。
 
 

取得パターン:1ヶ月後を取得するけど、月末の場合は翌月末日を取得する

 
『基本的な処理は「1ヶ月後」を取得するけど、月末の場合は翌月末を取得する』という場合は、「月末」は単純に「+1 month」では対応できませんので、「月末」の場合だけ処理を分岐する下記のような処理をするといいのではないか、と思います。
 

 
上記は閏年の 2月は考慮されていません。
閏年の場合は、「2月28日」も「2月29日」も「3月31日」となります。それで問題がある場合は、閏年か否かを判断して「28日」の取り扱いの分岐を増やしてください。
 
 

取得パターン:1ヶ月後を取得するけど、31日の月の月末の場合のみ翌月末日を取得する

 
『基本的な処理は「1ヶ月後」を取得するけど、31日の月の月末の場合は翌月末を取得する』という場合は、下記のような分岐処理をするといいのではないか、と思います。
 
前項と何が違うかというと、「1月31日」の 1ヵ月後は「2月末」にしたいが、「4月30日」の 1ヶ月後は「5月30日」にしたい、という場合で、前項よりもちょっとだけセコイ契約になる感じでしょうか...。でも、プログラムとしてはこちらの方がシンプル!
 

 
「7月」は 31日までありますが、翌月も 31日までありますので、「3月」などと同じ分岐に入れても、最後の elseの分岐に入れても結果は変わりません。
 
 

取得パターン:1ヶ月を 30日 or 31日と規定している場合

 
「1ヶ月」の定義を「30日 or 31日」と規定している場合は、下記のようにシンプルに算出できます。
 

 
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」を算出すると想定する日付にならない場合がある

 - PHP・Smarty・ECCUBE

GoogleAdwords

GoogleAdwords

最後までお読みいただきましてありがとうございます。
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

下記の空欄を埋めてください。 * Time limit is exhausted. Please reload CAPTCHA.

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

※入力いただいたコメントは管理者の承認後に掲載されます。

  関連記事

sleepの秒指定は整数のみなので1.5秒はsleep、usleepを組み合わせる
sleepの秒指定は整数のみなので1.5秒はsleep、usleepを組み合わせる

PHPのプログラム処理を遅延させる「sleep()」は整数秒単位。「usleep()」は1秒未満のマイクロ秒単位。では「1.5」秒はどう表現するのか。「sleep()」「usleep()」を組み合わせる。その解説。

数値がMySQLのint(11)に保存できない!PHPの変数が本当にint型か確認!
数値がMySQLのint(11)に保存できない!PHPの変数が本当にint型か確認!

PHPでintegerとdoubleが混在するような計算をする場合は要注意!計算結果が整数値であっても途中で使用する変数にdoubleの値が入っているときは計算結果がintegerではない場合があります。

PHP画面が真っ白 header(“Location: $url”);

PHPの開発で header(“Location: $url”);を使うと画面が真っ白になる不具合が出る場合もあります。

PHPで特定の日間の日付を for、strtotimeで表示する

ある特定の間の日付の情報を for文、strtotimeを使って作成し、その解説をしています。

include、requireのパス指定をdirname(__FILE__)、__DIR__と書く理由

include、requireのパスの指定を dirname(__FILE__)、__DIR__で記述する理由に付いて解説。相対パス、絶対パスを直書き、パスを書かない場合は何が問題かを説明。

ECCUBEでアップロードできない。upload_max_filesizeを設定する場所

テンプレートをアップロードする際に発生するエラー「テンプレートファイルがアップロードされていません」の対処方法。これはファイル容量の制限に引っかかっています。

ECCUBEの問い合わせフォームに任意の値を引数として渡す方法

ECCUBEのお問い合わせフォームに値を固有の情報を送りそれに基づいて処理をする方法を解説。ボタンの設置、受け取り側のテンプレート、プログラムのサンプルソースを提供。

WindowsのXAMPPのPHPではstrptimeは使用不可。代替はdate_parse_from_formatを使う
WindowsのXAMPPのPHPではstrptimeは使用不可。代替はdate_parse_from_formatを使う

strptimeはWindowsのPHPには未実装。LinuxとMacで挙動が異なる。PHP8.1で非推奨になる。なので日付のチェックはdate_parse_from_formatを使おう。使い方を詳細解説。

路線・駅検索をPHPで実装する方法解説。GoogleMapsの緯度経度から計算し検索
路線・駅検索をPHPで実装する方法解説。GoogleMapsの緯度経度から計算し検索

路線・駅検索の仕組みの構築は大変。登録する側も最寄り駅が多い場合は大変。なので簡易に実装するため緯度経度に基づき直線距離を計算する処理を考案して実装して、その処理を解説。

路線・駅検索のために緯度経度からPHPで簡易的に距離を計算する処理解説
路線・駅検索のために緯度経度からPHPで2点間の距離を計算する処理解説

路線・駅検索の仕組みの構築は大変。それを簡易に実装するために緯度経度を元に距離計算をする仕組みを考案。まずは2点間の距離を計算する仕組みを解説し、距離計算にまつわる関連技術も紹介。