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

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

目次

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

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

サンプル・プログラム

プログラムの方針

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

解説:差分計算(日)

0209: /**
0210:  * 指定年月日から$diff日だけ離れた年月日を取得する
0211:  * @param   int $year  西暦年
0212:  * @param   int $month月
0213:  * @param   int $day   日
0214:  * @param   int $diff  差分日数
0215:  * @return  array(年,月,日)
0216: */
0217: function diffymd($year$month$day$diff) {
0218:     $dm = getDaysInMonth($year$month);
0219: 
0220:     $day += $diff;
0221:     //月の繰り下がり
0222:     while ($day < 1) {
0223:         $month--;
0224:         if ($month < 1) $year--;
0225:         $dm = getDaysInMonth($year$month);
0226:         $day += $dm;
0227:     }
0228:     //月の繰り上がり
0229:     while ($day > $dm) {
0230:         $dm = getDaysInMonth($year$month);
0231:         $day -= $dm;
0232:         $month++;
0233:         if ($month > 12) {
0234:             $year++;
0235:             $month = 1;
0236:         }
0237:     }
0238: 
0239:     return array($year$month$day);
0240: }

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

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

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

0242: /**
0243:  * 指定年月日から$yy年$mm月だけ離れた年月日を取得する
0244:  * @param   int $year  西暦年
0245:  * @param   int $month月
0246:  * @param   int $day   日
0247:  * @param   int $yy    差分年数
0248:  * @param   int $mm    差分月数
0249:  * @return  array(年,月,日)
0250: */
0251: function diffymd_ym($year$month$day$yy$mm) {
0252:     $year  += $yy;
0253:     $month += $mm;
0254: 
0255:     //年の繰り下がり
0256:     while ($month < 1) {
0257:         $year--;
0258:         $month += 12;
0259:     }
0260:     //年の繰り上がり
0261:     while ($month > 12) {
0262:         $year++;
0263:         $month -= 12;
0264:     }
0265: 
0266:     return array($year$month$day);
0267: }

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

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

0269: /**
0270:  * テキスト中の日付をN日後に置換
0271:  * @param   string $sour  オリジナル・テキスト
0272:  * @return  string変換後テキスト
0273: */
0274: function dayafter($sour) {
0275:     //検索パターン
0276:     $pat = '/(([0-9]+)([  年\/\-]+))(([0-9]+)([  月\/\-]+))(([0-9]+)([  日])*)/iums';
0277: 
0278:     return preg_replace_callback($pat,
0279:         //置換関数
0280:         function ($mat) {
0281:             global $Diff;
0282:             if (! isset($mat[9]))    $mat[9] = '';
0283:             list($year$month$day) = diffymd((int)$mat[2], (int)$mat[5], (int)$mat[8]$Diff['day']);
0284:             list($year$month$day) = diffymd_ym($year$month$day$Diff['year'], $Diff['month']);
0285:             //ゼロ・プレフィックス
0286:             if ($Diff['prefix']) {
0287:                 $res = ($mat[2] != '') ? sprintf('%04d%s', $year$mat[3]) : '';
0288:                 $res .= sprintf('%02d%s%02d%s', $month$mat[6]$day$mat[9]);
0289:             //なし
0290:             } else {
0291:                 $res = ($mat[2] != '') ? sprintf('%d%s', $year$mat[3]) : '';
0292:                 $res .= sprintf('%d%s%d%s', $month$mat[6]$day$mat[9]);
0293:         }
0294:         return $res;
0295:     },  $sour);
0296: }

テキスト $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