首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 取得當週、當月日期範圍工具函式:支援上週、上月、自訂偏移

     編輯:PHP 相關知識

報表查詢、統計分析常需「本週」、「上月」等動態日期範圍。本文提供可重用的 PHP 函式庫,支援週/月範圍計算、任意偏移、時區處理與資料庫查詢整合。

核心工具類

<?php
class DateRange {
    private DateTimeZone $timezone;

    public function __construct(string $timezone = 'Asia/Taipei') {
        $this->timezone = new DateTimeZone($timezone);
    }

    /**
     * 取得指定日期所在週的範圍(週一至週日)
     * @param DateTime|string $date 基準日期
     * @return array ['start' => DateTime, 'end' => DateTime]
     */
    public function getWeekRange(DateTime|string $date): array {
        $dt = $this->normalizeDate($date);
        // ISO-8601: 週一=1, 週日=7
        $dayOfWeek = (int)$dt->format('N');
        
        $start = clone $dt;
        $start->modify("-" . ($dayOfWeek - 1) . " days");
        $start->setTime(0, 0, 0);

        $end = clone $start;
        $end->modify('+6 days');
        $end->setTime(23, 59, 59);

        return ['start' => $start, 'end' => $end];
    }

    /**
     * 取得指定日期所在月的範圍
     */
    public function getMonthRange(DateTime|string $date): array {
        $dt = $this->normalizeDate($date);
        
        $start = clone $dt;
        $start->modify('first day of this month');
        $start->setTime(0, 0, 0);

        $end = clone $dt;
        $end->modify('last day of this month');
        $end->setTime(23, 59, 59);

        return ['start' => $start, 'end' => $end];
    }

    /**
     * 取得常用預設範圍
     * @param int $type 1=昨天 2=本週 3=上週 4=上上週 5=本月 6=上月 7=本年 8=上年
     */
    function getPresetRange(int $type): array {
        $now = new DateTime('now', $this->timezone);
        
        return match ($type) {
            1 => $this->getDayRange($now->modify('-1 day')),
            2 => $this->getWeekRange($now),
            3 => $this->getWeekRange($now->modify('-1 week')),
            4 => $this->getWeekRange($now->modify('-2 weeks')),
            5 => $this->getMonthRange($now),
            6 => $this->getMonthRange($now->modify('-1 month')),
            7 => $this->getYearRange($now),
            8 => $this->getYearRange($now->modify('-1 year')),
            default => throw new InvalidArgumentException("Invalid preset type: $type")
        };
    }

    private function getDayRange(DateTime $date): array {
        $start = clone $date;
        $start->setTime(0, 0, 0);
        $end = clone $date;
        $end->setTime(23, 59, 59);
        return ['start' => $start, 'end' => $end];
    }

    private function getYearRange(DateTime $date): array {
        $start = clone $date;
        $start->modify('first day of January');
        $start->setTime(0, 0, 0);
        $end = clone $date;
        $end->modify('last day of December');
        $end->setTime(23, 59, 59);
        return ['start' => $start, 'end' => $end];
    }

    private function normalizeDate(DateTime|string $date): DateTime {
        if ($date instanceof DateTime) {
            return clone $date;
        }
        return new DateTime($date, $this->timezone);
    }
}
?>

資料庫查詢整合

PDO 參數綁定範例

<?php
$range = new DateRange('Asia/Taipei');

// 本週訂單統計
$week = $range->getPresetRange(2); // 本週
$stmt = $pdo->prepare("
    SELECT COUNT(*) as cnt, SUM(amount) as total 
    FROM orders 
    WHERE created_at BETWEEN :start AND :end
");
$stmt->execute([
    'start' => $week['start']->format('Y-m-d H:i:s'),
    'end' => $week['end']->format('Y-m-d H:i:s')
]);
$stats = $stmt->fetch();

// 上月會員註冊趨勢(每日)
$month = $range->getPresetRange(6); // 上月
$stmt = $pdo->prepare("
    SELECT DATE(created_at) as day, COUNT(*) as cnt
    FROM users
    WHERE created_at BETWEEN :start AND :end
    GROUP BY DATE(created_at)
    ORDER BY day
");
$stmt->execute([
    'start' => $month['start']->format('Y-m-d H:i:s'),
    'end' => $month['end']->format('Y-m-d H:i:s')
]);
$daily = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
?>

動態 WHERE 子句建構器

<?php
function buildDateRangeWhere(string $column, array $range, string $alias = ''): array {
    $prefix = $alias ? "$alias." : "";
    $where = "{$prefix}$column BETWEEN :{$alias}start AND :{$alias}end";
    $params = [
        "{$alias}start" => $range['start']->format('Y-m-d H:i:s'),
        "{$alias}end" => $range['end']->format('Y-m-d H:i:s')
    ];
    return [$where, $params];
}

// 使用
[$where, $params] = buildDateRangeWhere('created_at', $weekRange, 'o');
// $where: "o.created_at BETWEEN :ostart AND :oend"
// $params: ['ostart' => '2023-12-18 00:00:00', 'oend' => '2023-12-24 23:59:59']
?>

前端日期選擇器整合

Flatpickr 範例

<input type="text" id="date-range" placeholder="選擇日期範圍">

<script>
flatpickr('#date-range', {
    mode: 'range',
    dateFormat: 'Y-m-d',
    locale: 'zh_tw',
    onChange: function(selectedDates) {
        if (selectedDates.length === 2) {
            const [start, end] = selectedDates;
            // 送往後端 API
            fetch('/api/report', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    start: start.toISOString().split('T')[0],
                    end: end.toISOString().split('T')[0]
                })
            });
        }
    }
});
</script>

後端 API 接收

<?php
// API 端點
$data = json_decode(file_get_contents('php://input'), true);
$range = new DateRange();

$start = $data['start'] ?? null;
$end = $data['end'] ?? null;

if ($start && $end) {
    $startDt = (new DateTime($start, $range->timezone))->setTime(0, 0, 0);
    $endDt = (new DateTime($end, $range->timezone))->setTime(23, 59, 59);
    $range = ['start' => $startDt, 'end' => $endDt];
} else {
    // 預設本月
    $range = $range->getPresetRange(5);
}

// 執行查詢...
?>

進階:自訂財務週/月

<?php
class FiscalDateRange extends DateRange {
    private int $fiscalYearStartMonth; // 1-12

    public function __construct(string $timezone, int $fiscalYearStartMonth = 4) {
        parent::__construct($timezone);
        $this->fiscalYearStartMonth = $fiscalYearStartMonth;
    }

    public function getFiscalYearRange(DateTime|string $date): array {
        $dt = $this->normalizeDate($date);
        $year = (int)$dt->format('Y');
        $month = (int)$dt->format('n');
        
        $fiscalYear = $month >= $this->fiscalYearStartMonth ? $year : $year - 1;
        
        $start = new DateTime("{$fiscalYear}-{$this->fiscalYearStartMonth}-01", $this->timezone);
        $start->setTime(0, 0, 0);
        
        $end = clone $start;
        $end->modify('+1 year -1 second'); // 下一年第一天前一秒
        
        return ['start' => $start, 'end' => $end];
    }

    public function getFiscalQuarterRange(DateTime|string $date): array {
        $dt = $this->normalizeDate($date);
        $month = (int)$dt->format('n');
        $year = (int)$dt->format('Y');
        
        // 計算財務季度 (Q1 從 fiscalYearStartMonth 開始)
        $offset = ($month - $this->fiscalYearStartMonth + 12) % 12;
        $quarter = (int)($offset / 3) + 1;
        $quarterStartMonth = (($quarter - 1) * 3 + $this->fiscalYearStartMonth - 1) % 12 + 1;
        $quarterYear = $quarterStartMonth > $month ? $year - 1 : $year;
        
        $start = new DateTime("{$quarterYear}-{$quarterStartMonth}-01", $this->timezone);
        $start->setTime(0, 0, 0);
        $end = clone $start;
        $end->modify('+3 months -1 second');
        
        return ['start' => $start, 'end' => $end];
    }
}
?>

單元測試範例

<?php
// tests/DateRangeTest.php
use PHPUnit\Framework\TestCase;

class DateRangeTest extends TestCase {
    private DateRange $range;

    protected function setUp(): void {
        $this->range = new DateRange('UTC');
    }

    public function testWeekRange(): void {
        // 2023-12-25 是週一
        $result = $this->range->getWeekRange('2023-12-25');
        $this->assertEquals('2023-12-25 00:00:00', $result['start']->format('Y-m-d H:i:s'));
        $this->assertEquals('2023-12-31 23:59:59', $result['end']->format('Y-m-d H:i:s'));
    }

    public function testMonthRange(): void {
        $result = $this->range->getMonthRange('2023-02-15');
        $this->assertEquals('2023-02-01 00:00:00', $result['start']->format('Y-m-d H:i:s'));
        $this->assertEquals('2023-02-28 23:59:59', $result['end']->format('Y-m-d H:i:s'));
    }

    public function testLeapYearFebruary(): void {
        $result = $this->range->getMonthRange('2024-02-15');
        $this->assertEquals('2024-02-29 23:59:59', $result['end']->format('Y-m-d H:i:s'));
    }
}
?>

這組工具函式解決了日期範圍計算的常見痛點:時區一致性、週起始日定義、月份長度差異、閏年處理。建議專案統一引用此類別,避免分散在各處的 `strtotime`、`date` 組合邏輯導致不一致錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved