PAYATHON 2026

PHP month payment filter:按日期范围筛选付款记录

支付小周

结论

可以。让用户通过两个 <input type="date"> 分别提交开始日期和结束日期,再用日期条件筛选 payments 表。

应使用预处理语句,避免把用户提交的日期直接拼接到 SQL 中。如果数据库字段 date 的类型是 DATE,查询条件可以写成:

WHERE `date` BETWEEN ? AND ?

如果 date 是 DATETIME 或 TIMESTAMP,建议使用左闭右开的日期范围:

WHERE `date` >= ? AND `date` < DATE_ADD(?, INTERVAL 1 DAY)

这种写法可以查到结束日期当天的所有记录。

完整示例

下面的代码保留了原有的记录列表和收支汇总,并加入日期范围筛选:

<?php
include 'db-connect.php';

$startDate = $_GET['start_date'] ?? '';
$endDate   = $_GET['end_date'] ?? '';
$error     = '';
$result    = false;

function isValidDate(string $date): bool
{
    $value = DateTime::createFromFormat('!Y-m-d', $date);

    return $value !== false && $value->format('Y-m-d') === $date;
}

if ($startDate === '' && $endDate === '') {
    // 未选择日期时显示全部记录
    $result = mysqli_query(
        $con,
        "SELECT title, `date`, incoming, outgoing, notes
         FROM payments
         ORDER BY `date` ASC"
    );
} elseif (
    !isValidDate($startDate) ||
    !isValidDate($endDate)
) {
    $error = '请选择有效的开始日期和结束日期。';
} elseif ($startDate > $endDate) {
    $error = '开始日期不能晚于结束日期。';
} else {
    /*
     * 适用于 DATE、DATETIME 或 TIMESTAMP 字段。
     * 结束日期加一天,并使用 <,可包含结束日期当天的全部记录。
     */
    $stmt = mysqli_prepare(
        $con,
        "SELECT title, `date`, incoming, outgoing, notes
         FROM payments
         WHERE `date` >= ?
           AND `date` < DATE_ADD(?, INTERVAL 1 DAY)
         ORDER BY `date` ASC"
    );

    if ($stmt) {
        mysqli_stmt_bind_param($stmt, 'ss', $startDate, $endDate);
        mysqli_stmt_execute($stmt);
        $result = mysqli_stmt_get_result($stmt);
    } else {
        $error = '无法创建数据库查询。';
    }
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>付款记录</title>
</head>
<body>

<form method="get">
    <label for="start_date">开始日期:</label>
    <input
        type="date"
        id="start_date"
        name="start_date"
        value="<?= htmlspecialchars($startDate, ENT_QUOTES, 'UTF-8') ?>"
    >

    <label for="end_date">结束日期:</label>
    <input
        type="date"
        id="end_date"
        name="end_date"
        value="<?= htmlspecialchars($endDate, ENT_QUOTES, 'UTF-8') ?>"
    >

    <button type="submit">筛选</button>
    <a href="<?= htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8') ?>">
        清除筛选
    </a>
</form>

<?php if ($error !== ''): ?>

    <p><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>

<?php elseif ($result === false): ?>

    <p>查询失败:<?= htmlspecialchars(mysqli_error($con), ENT_QUOTES, 'UTF-8') ?></p>

<?php else: ?>

    <?php
    $totalIncoming = 0.0;
    $totalOutgoing = 0.0;
    ?>

    <table>
        <thead>
        <tr>
            <th>Title</th>
            <th>Date</th>
            <th>Incoming</th>
            <th>Outgoing</th>
            <th>Notes</th>
        </tr>
        </thead>
        <tbody>

        <?php if (mysqli_num_rows($result) === 0): ?>
            <tr>
                <td colspan="5">所选日期范围内没有付款记录。</td>
            </tr>
        <?php else: ?>
            <?php while ($row = mysqli_fetch_assoc($result)): ?>
                <?php
                $totalIncoming += (float) $row['incoming'];
                $totalOutgoing += (float) $row['outgoing'];
                ?>
                <tr>
                    <td><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></td>
                    <td><?= htmlspecialchars($row['date'], ENT_QUOTES, 'UTF-8') ?></td>
                    <td>£<?= number_format((float) $row['incoming'], 2) ?></td>
                    <td>£<?= number_format((float) $row['outgoing'], 2) ?></td>
                    <td><?= htmlspecialchars($row['notes'], ENT_QUOTES, 'UTF-8') ?></td>
                </tr>
            <?php endwhile; ?>
        <?php endif; ?>

        </tbody>
    </table>

    <?php $profit = $totalIncoming - $totalOutgoing; ?>

    <div class="payment-total">
        <h1>Profit: £<?= number_format($profit, 2) ?></h1>
    </div>

<?php endif; ?>

<?php
if (isset($stmt)) {
    mysqli_stmt_close($stmt);
}

mysqli_close($con);
?>

</body>
</html>

工作方式

表单通过 GET 提交。选择日期后,浏览器会生成类似这样的地址:

payments.php?start_date=2026-08-01&end_date=2026-08-31

PHP 从 $_GET 读取这两个值,检查它们是否为有效的 Y-m-d 日期,然后通过预处理语句传给数据库。查询结果只包含指定日期范围内的记录,后续计算出的 incoming、outgoing 和 profit 也只统计这些记录。

如果只需要按月份筛选

如果用户只需选择月份,不需要设置任意日期范围,可以改用:

<form method="get">
    <label for="month">月份:</label>
    <input type="month" id="month" name="month" required>
    <button type="submit">筛选</button>
</form>

收到 2026-08 这样的值后,计算该月的起始日期和下个月的起始日期:

$month = $_GET['month'] ?? '';
$monthObject = DateTime::createFromFormat('!Y-m', $month);

if ($monthObject && $monthObject->format('Y-m') === $month) {
    $startDate = $monthObject->format('Y-m-d');
    $endDate = (clone $monthObject)
        ->modify('first day of next month')
        ->format('Y-m-d');

    $stmt = mysqli_prepare(
        $con,
        "SELECT title, `date`, incoming, outgoing, notes
         FROM payments
         WHERE `date` >= ? AND `date` < ?
         ORDER BY `date` ASC"
    );

    mysqli_stmt_bind_param($stmt, 'ss', $startDate, $endDate);
    mysqli_stmt_execute($stmt);
    $result = mysqli_stmt_get_result($stmt);
}

不建议写成 WHERE MONTH(date) = ?。对列使用 MONTH() 或 DATE_FORMAT(),通常会影响数据库使用普通日期索引,而且只比较月份时还可能查到其他年份的数据。

注意事项

确认日期字段类型

数据库中的 date 字段应使用 DATE、DATETIME 或 TIMESTAMP,不要用普通字符串保存日期。例如:

ALTER TABLE payments MODIFY `date` DATE NOT NULL;

转换字段前,应先确认现有数据都能转换为合法日期,并做好备份。

结束日期的边界

如果字段类型是 DATE,下面的写法通常可以正常工作:

WHERE `date` BETWEEN ? AND ?

如果字段还保存了时间,例如 2026-08-31 15:30:00,直接将结束值绑定为 2026-08-31,可能会漏掉当天零点之后的数据。因此,示例使用以下条件:

WHERE `date` >= ?
  AND `date` < DATE_ADD(?, INTERVAL 1 DAY)

输出需要转义

数据库内容也可能包含 HTML 特殊字符。页面输出 title、notes 等字段时,应使用:

htmlspecialchars($value, ENT_QUOTES, 'UTF-8')

这样可以防止浏览器把数据当作 HTML 执行。

为日期字段建立索引

记录较多时,可以为日期字段添加索引:

CREATE INDEX idx_payments_date ON payments (`date`);

日期范围查询通常能够使用该索引,从而减少需要扫描的记录数量。

备注:内容仅供参考。