PHPでテキスト中の日付をN日後に置換

(1/1)
今週の予定表テキストを丸ごと1週間後にコピーしようとするとき、月や年またぎがあるときは、単純な置換では日付を1週間後にすることができない。そこで今回は、正規表現とカレンダー計算を組み合わせ、入力したテキスト中にある日付をN日後に置換するプログラムを作ってみることにする

目次

サンプル・プログラムの実行例

PHPでテキスト中の日付をN日後に置換

サンプル・プログラム

プログラムの方針

入力したテキスト中にある日付文字列を取り出し、指定した差分だけ加算/減算した日付に置き換える。
差分は、年、月、日の3種類とし、同時に3つ指定できるものとする。正数を指定すると加算(未来日)、負数を指定すると減算(過去日)になるようにする。
桁合わせのために "2018/02/03" のように数字の頭にゼロを付ける(ゼロ・サフィックス)されたデータがあるが、たとえばソースが "2018/11/24" のようなデータはゼロ・サフィックスを行うべきかどうか自動判断できない。そこで、ゼロ・サフィックスを手動指定できるようチェックボックスを設ける。

解説:差分計算(日)

 209: /**
 210:  * 指定年月日から$diff日だけ離れた年月日を取得する
 211:  * @param   int $year  西暦年
 212:  * @param   int $month 月
 213:  * @param   int $day   日
 214:  * @param   int $diff  差分日数
 215:  * @return  array(年,月,日)
 216: */
 217: function diffymd($year, $month, $day, $diff) {
 218:     $dm = getDaysInMonth($year, $month);
 219: 
 220:     $day +$diff;
 221:     //月の繰り下がり
 222:     while ($day < 1) {
 223:         $month--;
 224:         if ($month < 1)  $year--;
 225:         $dm = getDaysInMonth($year, $month);
 226:         $day +$dm;
 227:     }
 228:     //月の繰り上がり
 229:     while ($day > $dm) {
 230:         $dm = getDaysInMonth($year, $month);
 231:         $day -$dm;
 232:         $month++;
 233:         if ($month > 12) {
 234:             $year++;
 235:             $month = 1;
 236:         }
 237:     }
 238: 
 239:     return array($year, $month, $day);
 240: }

$year$month$day日から $diff日だけ離れた年月日を取得するユーザー関数 diffymd であるが、まず、$day$diffを加算する。
加算結果が1未満となった場合、月の繰り下がりが発生する。$day が1以上になるまで、while ループを回して繰り下がりを処理し続ける。

月の日数を取得するユーザー関数は getDaysInMonth である。うるう年の2月は29が返るようにしてある。

解説:差分計算(年・月)

 242: /**
 243:  * 指定年月日から$yy年$mm月だけ離れた年月日を取得する
 244:  * @param   int $year  西暦年
 245:  * @param   int $month 月
 246:  * @param   int $day   日
 247:  * @param   int $yy    差分年数
 248:  * @param   int $mm    差分月数
 249:  * @return  array(年,月,日)
 250: */
 251: function diffymd_ym($year, $month, $day, $yy, $mm) {
 252:     $year  +$yy;
 253:     $month +$mm;
 254: 
 255:     //年の繰り下がり
 256:     while ($month < 1) {
 257:         $year--;
 258:         $month +12;
 259:     }
 260:     //年の繰り上がり
 261:     while ($month > 12) {
 262:         $year++;
 263:         $month -12;
 264:     }
 265: 
 266:     return array($year, $month, $day);
 267: }

$year$month$day日から $yy$mm月だけ離れた年月日を取得するユーザー関数 diffymd_ym は、diffymd より簡単だ。月は1~12固定であるため、年の繰り下がりや繰り上がりの計算が単純化できる。

解説:テキスト中の日付をN日後に置換

 269: /**
 270:  * テキスト中の日付をN日後に置換
 271:  * @param   string $sour  オリジナル・テキスト
 272:  * @return  string 変換後テキスト
 273: */
 274: function dayafter($sour) {
 275:     //検索パターン
 276:     $pat = '/(([0-9]+)([  年\/\-]+))(([0-9]+)([  月\/\-]+))(([0-9]+)([  日])*)/iums';
 277: 
 278:     return preg_replace_callback($pat,
 279:         //置換関数
 280:         function ($mat) {
 281:             global $Diff;
 282:             if (! isset($mat[9]))   $mat[9] = '';
 283:             list($year, $month, $day) = diffymd((int)$mat[2], (int)$mat[5], (int)$mat[8], $Diff['day']);
 284:             list($year, $month, $day) = diffymd_ym($year, $month, $day, $Diff['year'], $Diff['month']);
 285:             //ゼロ・プレフィックス
 286:             if ($Diff['prefix']) {
 287:                 $res = ($mat[2!''? sprintf('%04d%s', $year, $mat[3]) : '';
 288:                 $res .sprintf('%02d%s%02d%s', $month, $mat[6], $day, $mat[9]);
 289:             //なし
 290:             } else {
 291:                 $res = ($mat[2!''? sprintf('%d%s', $year, $mat[3]) : '';
 292:                 $res .sprintf('%d%s%d%s', $month, $mat[6], $day, $mat[9]);
 293:         }
 294:         return $res;
 295:     },  $sour);
 296: }

テキスト $sour にあらわれる日付をN日後に置換するユーザー関数が dayafter である。
 preg_replace_callback  を利用して、日付を表す部分文字列にマッチングさせ、コールバック関数の中で切り出した年・月・日に対して差分計算する。

日付にマッチさせる正規表現は次の通り。

(([0-9]+)([  年\/\-]+))?(([0-9]+)([  月\/\-]+))(([0-9]+)([  日]*))

年月日のセパレータは「年・月・日・/・-」を想定している。数字との間に空白文字が入ることも許容している。また、年は必ずしも必要ないが、年月は必須である。
マッチングに成功すると、配列要素として次のような値が入り、コールバック関数に渡される。
要素内容
1年+セパレータ(空文字あり)
2年(空文字あり)
3年のセパレータ(空文字あり)
4月+セパレータ
5
6月のセパレータ
7日+セパレータ
8
9日のセパレータ(空文字あり)
コールバック関数では、前述の diffymd, diffymd_ym の2つの関数で差分置換を行い、 sprintf  を使ってゼロ・サフィックスを行うとともに、オリジナルのセパレータをそのまま戻している。

なお、PHPには、 strtotime  や  date_diff  という関数で差分日付の計算が可能だが、1901年(明治34年)12月13日から2038年(令和20年)1月19日までしか計算できないという制約があるため、今回、あえてユーザー関数として自作した。
(この項おわり)
header