報表、統計、排程常需將日期區間按**自然週 (週一~週日)** 或**自然月 (1號~月末)** 切分。封裝可復用工具類,處理邊界、閏年、跨年等細節。
<?php
class DateRange {
/**
* 取得日期所屬自然週範圍 (週一 00:00:00 ~ 週日 23:59:59)
* @param string $date 'Y-m-d'
* @return array ['start' => 'Y-m-d', 'end' => 'Y-m-d']
*/
public static function weekRange(string $date): array {
$ts = strtotime($date);
if ($ts === false) throw new InvalidArgumentException("Invalid date: $date");
// ISO-8601: 1=週一 ... 7=週日
$dow = (int)date('N', $ts);
$start = date('Y-m-d', $ts - ($dow - 1) * 86400);
$end = date('Y-m-d', $ts + (7 - $dow) * 86400);
return ['start' => $start, 'end' => $end];
}
/**
* 取得日期所屬自然月範圍 (1號 00:00:00 ~ 月末 23:59:59)
*/
public static function monthRange(string $date): array {
$ts = strtotime($date);
if ($ts === false) throw new InvalidArgumentException("Invalid date: $date");
$start = date('Y-m-01', $ts);
$end = date('Y-m-t', $ts); // 't' = 月份天數
return ['start' => $start, 'end' => $end];
}
/**
* 取得區間內所有自然週 (不重疊、連續覆蓋)
* @param string $startDate 'Y-m-d'
* @param string $endDate 'Y-m-d'
* @return array [[start,end], ...]
*/
public static function weeksInRange(string $startDate, string $endDate): array {
$startTs = strtotime($startDate);
$endTs = strtotime($endDate);
if ($startTs > $endTs) throw new InvalidArgumentException('Start date after end date');
$weeks = [];
$current = $startDate;
while (true) {
$range = self::weekRange($current);
$weeks[] = $range;
if ($range['end'] >= $endDate) break;
// 移至下週一
$current = date('Y-m-d', strtotime($range['end'] . ' +1 day'));
}
return $weeks;
}
/**
* 取得區間內所有自然月
*/
public static function monthsInRange(string $startDate, string $endDate): array {
$startTs = strtotime($startDate);
$endTs = strtotime($endDate);
if ($startTs > $endTs) throw new InvalidArgumentException('Start date after end date');
$months = [];
$current = $startDate;
while (true) {
$range = self::monthRange($current);
$months[] = $range;
if ($range['end'] >= $endDate) break;
// 移至下月 1 號
$current = date('Y-m-d', strtotime($range['start'] . ' +1 month'));
}
return $months;
}
/** 格式化輸出 "m-d ~ m-d" */
public static function formatRange(array $range): string {
return date('m-d', strtotime($range['start'])) . ' ~ ' . date('m-d', strtotime($range['end']));
}
}
?>
<?php
require 'DateRange.php';
// 1. 單一日期查詢
$week = DateRange::weekRange('2024-11-05');
echo "Week: " . DateRange::formatRange($week) . "\n"; // 11-04 ~ 11-10
$month = DateRange::monthRange('2024-02-15');
echo "Month: " . DateRange::formatRange($month) . "\n"; // 02-01 ~ 02-29 (閏年)
// 2. 區間查詢
$weeks = DateRange::weeksInRange('2024-11-01', '2024-11-30');
echo "November weeks:\n";
foreach ($weeks as $w) {
echo " " . DateRange::formatRange($w) . "\n";
}
// 輸出:
// 10-28 ~ 11-03
// 11-04 ~ 11-10
// 11-11 ~ 11-17
// 11-18 ~ 11-24
// 11-25 ~ 12-01 (跨月週正確包含)
$months = DateRange::monthsInRange('2024-01-15', '2024-03-20');
echo "Months:\n";
foreach ($months as $m) {
echo " " . DateRange::formatRange($m) . "\n";
}
// 01-01 ~ 01-31
// 02-01 ~ 02-29
// 03-01 ~ 03-31
?>
// 產生週期間迭代器 (每週一)
$period = new DatePeriod(
new DateTime('2024-11-01'),
new DateInterval('P1W'), // 1 週
(new DateTime('2024-11-30'))->modify('+1 day') // 結束開區間
);
foreach ($period as $dt) {
echo $dt->format('Y-m-d') . "\n"; // 每週一日期
}
此工具類解決「日期區間按週/月切分」的通用需求,代碼精簡、無依賴、易測試,可直接納入專案共用庫。