エス技研

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.

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

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

  関連記事

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

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

PHPでスクレイピング。phpQueryとphp-simple-html-dom-parserの比較と設置方法
PHPでスクレイピング。phpQueryとphp-simple-html-dom-parserの比較と設置方法

「PHP スクレイピング」で検索すると「phpQuery」ばかりヒットするが、10年以上も放置されている。なので今も開発が続いている「PHP Simple HTML DOM Parser」をオススメする。

PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う
PHPで1ヵ月前、先月、今月1日、来月末の日付などの算出はDateTimeImmutableを使う

PHPには日時をオブジェクトとして生成する関数「DateTimeImmutable」「DateTime」が用意されている。これを利用して1ヶ月後、月初日、月末日、5日後などを指定して日付を取得できる。

QRコード(二次元バーコード)作成サービスを公開
QRコード(二次元バーコード)作成サービスを公開

QRコード(二次元バーコード)を生成するサービス。QRコードにする文字列を入力するだけで QRコードが簡単に作れる。オプションとして、画像サイズ、余白サイズ、エラー訂正レベルがある。

ECCUBEの注文完了画面注文番号と商品情報を編集するCRITEO(クリテオ)タグを編集

ECCUBEの注文完了画面にクリテオのタグを編集する方法を紹介。標準機能では完了画面に注文IDや購入商品の情報を渡さないため改修が必要です。

フォルダを指定してファイルのパーミッションを変更するプログラム

フォームからフォルダ、パーミッションを指定しパーミッションを変更するサンプルプログラムの解説です。

サーバ移転、PHPバージョンアップでPHPのソースコードが表示される・ショートタグのPHPが動かない

PHPでショートタグを使うのは危険。サーバ移転やバージョンアップで動かなくなる!ソースが丸見え、設定情報流出のリスクが!php.iniのshort_open_tagの設定を再確認。

ob_start、ob_get_contents関数でPHPの標準出力をバッファリング・変数に代入

標準出力をバッファリングし変数に代入することができるob_start()関数の解説。include()の処理をバッファリングすることで自由な場所に処理を記述することが可能。

PHP range関数を使って階乗と重複組み合わせを計算

PHPの range関数を使って階乗と重複組み合わせを計算し、それを元に乱数発生器を作成しました。

Smartyのテンプレート内の処理で計算、加工をする方法

Smartyのテンプレート上で変数を計算する、加工する方法を解説します。