+function getMondaysForMonth(int $year, int $monthNum) {
+ $dt = DateTimeImmutable::createFromFormat('Y n', "$year $monthNum");
+ $m = $dt->format('F');
+ $y = $dt->format('Y');
+ $fifthMonday = (int)(new DateTimeImmutable("fifth monday of $m $y"))->format('d');
+ $mondays = [
+ (int)(new DateTimeImmutable("first monday of $m $y"))->format('d'),
+ (int)(new DateTimeImmutable("second monday of $m $y"))->format('d'),
+ (int)(new DateTimeImmutable("third monday of $m $y"))->format('d'),
+ (int)(new DateTimeImmutable("fourth monday of $m $y"))->format('d'),
+ ];
+
+ return array_merge(
+ $mondays,
+ $fifthMonday > $mondays[3] ? [$fifthMonday] : []
+ );
+}
+
+function getDayNumber(int $year, int $month, int $day) {
+ $potentialMondays = array_filter(
+ getMondaysForMonth($year, $month),
+ function($monday) use ($day) {
+ return $monday <= $day;
+ }
+ );
+
+ return $potentialMondays
+ ? closest($day, $potentialMondays)
+ : array_values(
+ array_slice(
+ getMondaysForMonth(
+ getYearWeekBeginsIn($year, $month, $day),
+ getMonthWeekBeginsIn($year, $month, $day)
+ ),
+ -1
+ )
+ )[0];
+}
+
+function getWeekNumber(int $year, int $month, int $day) {
+ $potentialMondays = array_filter(
+ getMondaysForMonth($year, $month),
+ function($monday) use ($day) {
+ return $monday <= $day;
+ }
+ );
+
+ return $potentialMondays
+ ? closestIndex($day, $potentialMondays) + 1
+ : count(
+ getMondaysForMonth(
+ getYearWeekBeginsIn($year, $month, $day),
+ getMonthWeekBeginsIn($year, $month, $day)
+ )
+ );
+}
+
+function getMonthWeekBeginsIn(int $year, int $month, int $day) {
+ return array_merge([12], range(1,11), [12])[
+ $month - ($day < getMondaysForMonth($year, $month)[0] ? 1 : 0)
+ ];
+}
+
+function getYearWeekBeginsIn(int $year, int $month, int $day) {
+ return $year - (int)($month == 1 && getMonthWeekBeginsIn($year, $month, $day) == 12);
+}
+