PHPにおける日付時刻の処理大全

日付時刻って重要だけど、そんなに頻度高くなくて、毎回調べちゃってたのでまとめておきます
コメントアウトしているコードでも動きます！エルビス演算子``?:``かnull合体演算子``??``かの違い
#目次
[西暦から和暦へ変換](#西暦から和暦へ変換)
[生年月日から年齢へ変換](#生年月日から年齢へ変換)
[日付が存在するか確認する](#日付が存在するか確認する)
[曜日の求め方](#曜日の求め方)
[月末日の求め方](#月末日の求め方)
[締め日の求め方](#締め日の求め方)
[第三月曜日の求め方](#第三月曜日の求め方)
[年度の求め方](#年度の求め方)
[日付セレクトメニューの表示](#日付セレクトメニューの表示)
[日付の表示形式の変換と加減算](#日付の表示形式の変換と加減算)
[グレゴリウス日をユリウス積算日に変換](#グレゴリウス日をユリウス積算日に変換)

#西暦から和暦へ変換
西暦が元号の期間に含まれているか確認し、改元された年から何年目なのか求める


|元号|改元日|
|:----:|:----:|
|明治|1968/01/25|
|大正|1912/07/30|
|昭和|1926/12/25|
|平成|1989/01/08|
|令和|2019/05/01|


```example.php
<?php
$year = 2020;
$month = 10;
$day = 30;
//$res = convertToJapaneseDate($year, $month, $day) ?? '不正な日時です';
$res = convertToJapaneseDate($year, $month, $day) ?: '不正な日時です';
echo $res;
//明治6年より前は返さない
//function convertToJapaneseDate(int $year, int $month, int $day): ?string
function convertToJapaneseDate(int $year, int $month, int $day): string
{
    $REIWA = 20190501;
    $HEISEI = 19890108;
    $SHOWA = 19261225;
    $TAISHO = 19120730;
    //$MEIJI = 19680125;
    //if (!checkdate($month, $day, $year) || $year < 1873) return null;
    if (!checkdate($month, $day, $year) || $year < 1873) return false;
    $date = intval(sprintf('%04%02d%02d', $year, $month, $day));
    switch ($date) {
        case $date >= $REIWA:
            $era = '令和';
            $first_year = 2018;
            break;
        case $date >= $HEISEI:
            $era = '平成';
            $first_year = 1988;
            break;
        case $date >= $SHOWA:
            $era = '昭和';
            $first_year = 1925;
            break;
        case $date >= $TAISHO:
            $era = '大正';
            $first_year = 1911;
            break;
        default:
            $era = '明治';
            $first_year = 1868;
    }
    $year -= $first_year;
    $japanese_year = ($year == 1) ? $era . '元年' : $era . $year . '年';
    return $japanese_year . $month . '月' . $day . '日';
}
```

#生年月日から年齢へ変換

```example.php
floor((基準とする日付 - 生年月日) / 10000) //(int)/intval()でも可
```

これは日付を数値に変更する必要がある
たとえば``explode()``関数を使うとか``implode()``関数を使うとか、この二つを使えば大体なんとかなる
気が向いたら他の記事で解説したい笑
タイムスタンプから計算する場合は``$birthday = (int)date('Ymd', $timestamp)``みたいな感じかな

#日付が存在するか確認する

```example.php
checkdate(int $month, int $day, int $year): boolean
```
第三引数の年は``1~32767``まで

#曜日の求め方
``getDate()``か``date()``を使う
0が日曜で6が土曜

```getdate.php
<?php
$timestamp = mktime(0, 0, 0, 10, 1, 2020);
$date = getdate($timestamp);
$wday = $date['wday'];

$weekday_label = ['日', '月', '火', '水', '木', '金', '土',];
echo date('Y/m/d', $timestamp); //2020/10/01
echo $wday . '(' . $weekday_label[$wday] . ')'; //1(木)
```

```date.php
<?php
$timestamp = mktime(0, 0, 0, 10, 1, 2020);
$wday = intval(date('w', $timestamp));
echo $wday; //4
```

#月末日の求め方
``mktime()``か``date()``を使う
``mktime()``の場合
年に取得したい年月の年、
月に取得したい年月の月+1、
日には0を指定する

```mktime.php
<?php
$year = 2020;
$month = 12;
$timestamp = mktime(0, 0, 0, $month + 1, $year);
echo $year . '年' . $month . '月の末日' . date('Y/m/d', $timestamp);
```

``date()``の場合
第一引数にtを指定する
戻り値を文字列で返すので``intval()``で数値に変換する必要がある
オプション
- `Y`：年（4桁表記）
  - `y`：年（2桁表記）
- `m`：月（2桁表記）
  - `n`：月（先頭にゼロつけない）
- `d`：日（2桁表記）
- `H`：時間（24時間単位）
  - `h`：時間（12時間単位）
- `i`：分
- `s`：秒
- `t`：指定した月の日数
- `w`：曜日番号（0[日曜]から6[土曜]の値）
- `.`：文字列連結
- `\n`：改行

```date.php
<?php
$year = 2018;
$month = 4;
//2018/04/10のタイムスタンプ
$timestamp = mktime(0, 0, 0, $month, 10, $year);
//第一引数のtで第二引数で指定した月の日数の取得
$last_day = intval(date('t', $timestamp));
echo $year . '年' . $month . '月の末日' . $last_day;
```

#締め日の求め方

```example.php
<?php
$cutoffDay = 25;
$date = mktime(0, 0, 0, 10, 30, 2020);
echo '締め日' . $cutoffDay;
echo '計算したい日' . date('Y/m/d', $date);

$ret = getCutoffDate($cutoffDay, $date);
echo $ret == true ? date('Y/m/d', $ret) : 'invalid date';
//第一引数に締め日を指定(1~31)
//第二引数には計算したい日のタイムスタンプ省略した場合は現在の日時を指定
function getCutoffDate(int $cutoffDay, int $timestamp = null): int
{
	$timestamp = $timestamp ?? time();
	if ($cutoffDay < 1 || $cutoffDay > 31) return false;
	$date = getdate($timestamp);
	$year = $date['year'];
	$month = $date['mon'];
	$day = $date['mday'];
	$endOfMonth = intval(date('t', $timestamp));

	$cutoffDay = modifyCutoffDay($endOfMonth, $cutoffDay);
	//計算した日が締め日を過ぎている場合翌月の締め日を取得
	if ($day > $cutoffDay) {
		$month++;
		//締め日と翌月末日を比較
		$endOfNextMonth = intval(date('t', mktime(0, 0, 0, $month, 1, $year)));
		$cutoffDay = modifyCutoffDay($endOfNextMonth, $cutoffDay);
	}
	return mktime(0, 0, 0, $month, $cutoffDay, $year);
}
//月末日が締め日よりも前の場合月末日を締め日にする
//min()関数を使って値の判定と代入を行う
function modifyCutoffDay(int $endOfMonth, int $cutoffDay): int
{
	return min($endOfMonth, $cutoffDay);
}
```

#第三月曜日の求め方
第一月曜日を求めその三週間後を求めればいい

```example.php
<?php
$year = 2020;
$month = 10;
$week = 3;
$weekday = 1; //Monday
//$res = getNthDay($year, $month, $week, $weekday) ?: '該当する日にちはありませんでした';
$res = getNthDay($year, $month, $week, $weekday) ?? '該当する日にちはありませんでした';
echo $res;
//第X番目のX曜日の日付を返す
//第三引数には週番号、第四引数には曜日を数値で指定
//function getNthDay(int $year, int $month, int $week, int $weekday): int
function getNthDay(int $year, int $month, int $week, int $weekday): ?int
{
    //if ($week < 1 || $week > 5) return false;
    //if ($weekday < 0 || $weekday > 6) return false;
    if ($week < 1 || $week > 5) return null;
    if ($weekday < 0 || $weekday > 6) return null;
    //月初の曜日を取得
    $firstWeekday = intval(date('w', mktime(0, 0, 0, $month, 1, $year)));
    //第一の日付を求める
    $firstDay = $weekday - $firstWeekday + 1;
    if ($firstDay <=  0) $firstDay += 7;
    //7の倍数を加算する
    $targetDay = $firstDay + 7 * ($week - 1);
    //存在する日付か確認
    //if (!checkdate($month, $targetDay, $year)) return false;
    if (!checkdate($month, $targetDay, $year)) return null;
    return $targetDay;
}
```
 
#年度の求め方

```example.php
<?php
$time = mktime(0, 0, 0, 10, 30, 2020);
$startMonth = 4;
$res = getYear($startMonth, $time) ?: 'エラー';
echo $res;

function getYear(int $startMonth = 4, int $time = null): string
{
    if ($startMonth < 1 || $startMonth > 12) return false;
    $date = ($time == null) ? getdate() : getdate($time);
    $year = $date['year'];
    $month = $date['mon'] - ($startMonth - 1);
    $res = getdate(mktime(0, 0, 0, $month, 1, $year));
    return $res['year'] . '年度';
}
/* これでも可
$res = getYear($startMonth, $time) ?? 'エラー';
echo $res;
function getYear(int $startMonth = 4, int $time = null): ?string
{
    if ($startMonth < 1 || $startMonth > 12) return null;
    $date = ($time == null) ? getdate() : getdate($time);
    $year = $date['year'];
    $month = $date['mon'] - ($startMonth - 1);
    $res = getdate(mktime(0, 0, 0, $month, 1, $year));
    return $res['year'] . '年度';
}
 */
```

#日付セレクトメニューの表示

```selectYear.php
<?php
$from = intval(date('Y'));
$to = $from + 5; //5years
//<select>
for ($i = $from; $i <= $to; $i++):
//<option value=\"<?= $i\; ?>"><?= $i; ?></option>
endfor;
//</select>年
```

```selectYearMonth.php
<?php
$from = mktime(0, 0, 0, 10, 1, 2020);
$to = mktime(0, 0, 0, 10, 1, 2030);
//<select>
while ($from <= $to) {
    $label = date('Y年 m月', $from);
    $value = date('Ym', $from);
    //<option value=\"<?= $value\; ?>"><?= $label; ?></option>
    $date = getdate($from);
    $from = mktime(0, 0, 0, $date['mon'] + 1, 1, $date['year'];
}
//</select>
```

#日付の表示形式の変換と加減算
``DateTime``クラスを使うとインスタンスを作成し、``format()``メソッドで指定したフォーマット文字列に基づいて日時文字列を取得できる

```example.php
<?php
$now = new DateTime();
$now->add(DateInterval::createFromDateString('任意の数 加減算する単位')); //ex) 1 day , 1 year
echo $now->format('表示形式'); //Y-m-d H:i:sなど
//ｎ,j=1, m,d=01, H=23, h=11
$past = new DateTime('2020-10-30');
$past->getTimestamp();
$past->add(DateInterval::createFromDateString('1 day'));
echo $past->format('Y/m/d');
```

#グレゴリウス日をユリウス積算日に変換
``gregoriantojd(int $month, int $day, int $year): int``を使う
ユリウス積算日をユリウス暦に変換するときは``jdtojulian(int $julianday) : string``

```example.php
<?php
$jd = gregoriantojd(10, 11, 1970); //2440871
$gregorian = jdtogregorian($jd); //10/11/1970
```

##intval()関数の注意

```php:注意
整数型への変換が失敗した際に0や1を返してしまうことがある
全て文字列だった場合、 0が返される
配列だった場合には要素が存在する場合には1、空配列の場合には0が返される
真偽値だった場合はtrue=1, false=0が返される
もちろんfloat型の場合は切り捨てられる

数値型だった場合のみ変換したいときはis_numericで判定を行うといい
```