專案常需列出某日期區間內的每一天,並標註該月為大月(31天)或小月(30天/28/29天)。本文提供一個純 PHP 實作,無需資料庫即可完成。
<?php
/**
* 列出從今天倒推至指定結束日期的所有日期,並標註大小月
*
* @param string $end_date 結束日期 (YYYY-MM-DD)
* @return void 直接輸出 HTML
*/
function list_dates_to($end_date) {
$end_ts = strtotime($end_date);
if ($end_ts === false) {
echo "結束日期格式錯誤,請使用 YYYY-MM-DD";
return;
}
$current_ts = time(); // 今天 00:00:00
$current_ts = strtotime(date('Y-m-d', $current_ts)); // 歸零時分秒
if ($current_ts < $end_ts) {
echo "結束日期不能晚於今天";
return;
}
echo "";
while ($current_ts >= $end_ts) {
$date_str = date('Y-m-d', $current_ts);
$month = (int)date('m', $current_ts);
$year = (int)date('Y', $current_ts);
// 判斷大小月
$month_type = get_month_type($month, $year);
echo "- $date_str ($month_type)
";
// 遞減一天
$current_ts -= 86400; // 24*60*60
}
echo "
";
}
/**
* 判斷月份類型
* @return string '大月' | '小月' | '閏年二月' | '平年二月'
*/
function get_month_type($month, $year) {
if ($month === 2) {
return is_leap_year($year) ? '閏年二月(29天)' : '平年二月(28天)';
}
// 1-7月:奇大偶小;8-12月:偶大奇小
$is_big = ($month <= 7) ? ($month % 2 === 1) : ($month % 2 === 0);
return $is_big ? '大月(31天)' : '小月(30天)';
}
function is_leap_year($year) {
return ($year % 4 === 0 && $year % 100 !== 0) || ($year % 400 === 0);
}
// 使用範例
list_dates_to('2024-01-01');
?>
2024-12-20 (大月(31天)) 2024-12-19 (大月(31天)) ... 2024-02-29 (閏年二月(29天)) 2024-02-28 (閏年二月(29天)) ... 2024-01-01 (大月(31天))
<?php
class DateRangeIterator implements Iterator {
private $start_ts;
private $end_ts;
private $current_ts;
private $step; // +86400 或 -86400
private $format;
public function __construct($start_date, $end_date, $format = 'Y-m-d') {
$this->start_ts = strtotime($start_date);
$this->end_ts = strtotime($end_date);
$this->format = $format;
if ($this->start_ts <= $this->end_ts) {
$this->current_ts = $this->start_ts;
$this->step = 86400;
} else {
$this->current_ts = $this->start_ts;
$this->step = -86400;
}
}
public function current() {
return [
'date' => date($this->format, $this->current_ts),
'timestamp' => $this->current_ts,
'month_type' => $this->getMonthType(),
'is_weekend' => in_array(date('N', $this->current_ts), [6,7]),
];
}
public function next() { $this->current_ts += $this->step; }
public function key() { return $this->current_ts; }
public function valid() {
return ($this->step > 0) ? ($this->current_ts <= $this->end_ts) : ($this->current_ts >= $this->end_ts);
}
public function rewind() {
$this->current_ts = ($this->step > 0) ? $this->start_ts : $this->start_ts;
}
private function getMonthType() {
$m = (int)date('m', $this->current_ts);
$y = (int)date('Y', $this->current_ts);
if ($m === 2) return is_leap_year($y) ? 'leap_feb' : 'common_feb';
$big = ($m <= 7) ? ($m % 2 === 1) : ($m % 2 === 0);
return $big ? 'big' : 'small';
}
}
function is_leap_year($y) {
return ($y % 4 === 0 && $y % 100 !== 0) || ($y % 400 === 0);
}
// 使用
foreach (new DateRangeIterator('2024-01-01', '2024-12-31') as $info) {
echo "{$info['date']} [{$info['month_type']}]" . ($info['is_weekend'] ? ' ⚡' : '') . "
";
}
?>
<?php
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-12-31');
$end->modify('+1 day'); // 包含結束日
$interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt) {
$m = (int)$dt->format('m');
$y = (int)$dt->format('Y');
$type = ($m === 2)
? (($y % 4 === 0 && $y % 100 !== 0) || ($y % 400 === 0) ? '閏年二月' : '平年二月')
: ((($m <= 7 && $m % 2 === 1) || ($m >= 8 && $m % 2 === 0)) ? '大月' : '小月');
echo $dt->format('Y-m-d') . " ($type)
";
}
?>
利用 strtotime()、date() 與簡單數學運算即可實現日期區間遍歷與大小月判斷。現代專案建議使用 DateTime、DatePeriod 物件導向介面,更直觀且自動處理時區、閏秒等邊界情況。記得:2月單獨判斷閏年,1-7月奇大偶小,8-12月偶大奇小 是核心邏輯。