日付処理を扱う上で、carbonに非常に助けられている今日この頃だが、思ったより簡単に書けない処理があり、暫定で作ったものを備忘録として残す。それもっと簡単に行けるで、という情報があれば是非教えていただければと思う。
前提
日曜始まり土曜終わりを1週として、日曜から順に0,1,2...と曜日と数字が対応している。
['sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6,]
1週目、2週目、、、5週目とn週目とnが対応している。
['1週目' => 1, '2週目' => 2, '3週目' => 3, '4週目' => 4, '5週目' => 5, '6週目' => 6]
要件
n週目のm曜日を入力値として与えられた時に、日付を返す。
public function getDate($week_of_month, $weekday)
{
// return '2020-10-19'
}
Carbonじゃダメなの?
Carbonにも何週目か判定するメソッドや、曜日を判定するメソッドなどなど便利なメソッドが多々あるが、何週目かの計算が、月曜始まりのものと、単純に7日づつ加算して算出するものしかなく、日曜始まりで扱いやすそうな処理が書けなかったので断念
//月曜始まりで、n週目を計算。
$carbon->weekNumberInMonth;
//7日間づつ計算
$carbon->weekOfMonth;
2020/10/12追記
@vf8974 さんより、標準関数でより簡潔にかける方法をいただいたので追記
public function getDay($params)
{
// DateTimeImmutableインスタンス
$d = new \DateTimeImmutable();
// 年(省略時は今年)
$year = $params['year'] ?? $d->format('Y');
// 月(省略時は今月)
$month = $params['month'] ?? $d->format('m');
// 対象月1日
$firstDay = $d->setDate($year, $month, 1);
// 対象月1日の曜日
$firstDayOfWeek = $firstDay->format('w');
// 対象月の最終日
$lastDay = $firstDay->format('t');
// 第num week曜日の日
$day = 1 + ($params['num'] - 1) * 7 + ($params['week'] - $firstDayOfWeek) % 7;
// $dayが当月範囲内ならYYYY-MM-DDを、範囲外ならfalseを返す
return $day >= 1 && $day <= $lastDay ? sprintf('%04d-%02d-%02d', $year, $month, $day) : false;
}
書いたコード
日曜始まりの簡易的なカレンダーを作成し、曜日加算するという二段構えで実装した。
protected $month_weeks;
public function __construct()
{
$this->month_weeks = $this->createWeekOfMonths($this->argument($target_month))
}
/**
* @param Carbon $start_of_month
* @return array
*/
public function createWeekOfMonths(Carbon $start_of_month): array
{
//start_of_month='2020-11-01'
$target_month_weeks = [];
//月初の曜日取得 start_of_month_weekday=0, (ie, sunday)
$start_of_month_weekday = $start_of_month->weekday();
//対象月の初週の日曜日を、取得
$target_sunday = (new Carbon())->createFromTimestamp($start_of_month->timestamp)->subDays($start_of_month_weekday);
while ($target_sunday->lte($target_month->endOfMonth())) {
array_push($month_weeks, (new Carbon())->createFromTimestamp($target_sunday->timestamp));
$target_sunday->addDays(7);
}
return $month_weeks;
}
//$this->createWeekOfMonths('2020-11-01');
//['2020-11-01','2020-11-08','2020-11-15','2020-11-22','2020-11-29']
こうすると配列のIndexがそのままn-1週目になっている形で取得できるので、あとはこの簡易カレンダーをもとにn週目,n曜日の条件から日付を取得する。
public function getDate($week_of_month, $weekday)
{
$target_date = array_key_exists($week_of_month - 1, $this->month_weeks) //n週目存在チェック
? (new Carbon())
->createFromTimestamp($this->month_weeks[$week_of_month - 1]->timestamp) //n週目の日曜日のCarbonオブジェクト取得
->addDays($weekday) //m曜日のm分加算 (2020-11-01 sun(0) -> 2020-11-03 tue(2))
: null;
return $target_date;
}
まとめ
簡易カレンダー作成にループ使用するのが若干嫌な気がしますが、暫定版なのでひとまずこれで対応。