// 上個月第一天 00:00:00
DateTime firstDayLastMonth = DateTime.Now.AddMonths(-1).AddDays(1 - DateTime.Now.Day);
// 上個月最後一天 23:59:59.999 (或當天 00:00:00 視需求)
DateTime lastDayLastMonth = DateTime.Now.AddDays(-DateTime.Now.Day).AddTicks(-1);
// 或者單純取日期部分
DateTime lastDayLastMonthDate = DateTime.Now.AddDays(-DateTime.Now.Day).Date;
DateTime.Now.Day 取得今天是當月第幾天(如 15)。AddDays(-Day) 退回到「上個月最後一天」(如 4/30)。AddMonths(-1) 退回到「上個月同一天」(如 3/15),再 AddDays(1-Day) 推到「上個月第一天」(3/1)。public static class DateTimeExtensions
{
/// 取得指定日期所在月份的第一天 00:00:00
public static DateTime FirstDayOfMonth(this DateTime dt) => new DateTime(dt.Year, dt.Month, 1);
/// 取得指定日期所在月份的最後一天 23:59:59.999
public static DateTime LastDayOfMonth(this DateTime dt) => dt.FirstDayOfMonth().AddMonths(1).AddTicks(-1);
/// 取得上個月第一天
public static DateTime FirstDayOfLastMonth(this DateTime dt) => dt.AddMonths(-1).FirstDayOfMonth();
/// 取得上個月最後一天
public static DateTime LastDayOfLastMonth(this DateTime dt) => dt.AddMonths(-1).LastDayOfMonth();
/// 取得下個月第一天
public static DateTime FirstDayOfNextMonth(this DateTime dt) => dt.AddMonths(1).FirstDayOfMonth();
/// 取得本月迄今範圍
public static (DateTime Start, DateTime End) MonthToDate(this DateTime dt)
=> (dt.FirstDayOfMonth(), dt.Date.AddDays(1).AddTicks(-1));
}
var now = DateTime.Now;
var start = now.FirstDayOfLastMonth(); // 2024-03-01 00:00:00
var end = now.LastDayOfLastMonth(); // 2024-03-31 23:59:59.999
// SQL 查詢範圍
string sql = "SELECT * FROM Orders WHERE CreateTime >= @start AND CreateTime <= @end";
// 參數化查詢避免注入
| 陷阱 | 說明 | 解決 |
|---|---|---|
| 時區問題 | DateTime.Now 為伺服器本地時間,跨時區部署會不一致 |
統一使用 DateTime.UtcNow,轉換時指定 TimeZoneInfo |
| 月末邊界 | 1/31 加一個月變 2/28/29,非預期 | 用 FirstDayOfMonth().AddMonths(1) 而非直接 AddMonths(1) |
| 資料庫時間精度 | SQL Server datetime 精度 3.33ms,datetime2 100ns |
查詢上界用 < 下個月第一天 而非 <= 最後一天 23:59:59.999 |
| 效能 | 頻繁計算可快取當月首尾 | 靜態快取 Dictionary Key=Year*100+Month |
// 推薦:[start, nextMonthStart) 避免精度問題
DateTime start = DateTime.UtcNow.FirstDayOfLastMonth();
DateTime endExclusive = start.AddMonths(1); // 下個月第一天
// SQL
WHERE CreateTimeUtc >= @start AND CreateTimeUtc < @endExclusive
利用 AddMonths(-1) 結合 Day 屬性,即可以 O(1) 時間取得上個月首尾日期。封裝為擴充方法並採用「半開區間」查詢,能避開時區、精度、閏年等常見坑,程式碼更簡潔、可讀、可維護。