報表、統計、排程常需「本週一至週日」、「本月首尾日」、「上季度」、「下半年」等區間。本文彙整 DateTime 實例方法(AddDays、AddMonths、AddYears)組合技巧,並提供**可直接複製的靜態工具類**,涵蓋週、月、季、年、上下 N 期、中文星期顯示,一勞永逸。
using System;
public static class DateRangeHelper
{
// ====== 基準:今日 ======
public static DateTime Today => DateTime.Today; // 去除時間部分
// ====== 本週 (週一~週日) ======
public static DateTime ThisWeekStart() // 週一
{
var today = Today;
int diff = today.DayOfWeek - DayOfWeek.Monday;
if (diff < 0) diff += 7;
return today.AddDays(-diff);
}
public static DateTime ThisWeekEnd() => ThisWeekStart().AddDays(6); // 週日
// ====== 本月 ======
public static DateTime ThisMonthStart() => new DateTime(Today.Year, Today.Month, 1);
public static DateTime ThisMonthEnd() => ThisMonthStart().AddMonths(1).AddDays(-1);
// ====== 本季 ======
public static DateTime ThisQuarterStart()
{
var m = Today.Month;
int qStartMonth = (m - 1) / 3 * 3 + 1;
return new DateTime(Today.Year, qStartMonth, 1);
}
public static DateTime ThisQuarterEnd() => ThisQuarterStart().AddMonths(3).AddDays(-1);
// ====== 本年 ======
public static DateTime ThisYearStart() => new DateTime(Today.Year, 1, 1);
public static DateTime ThisYearEnd() => new DateTime(Today.Year, 12, 31);
// ====== 通用:上/下 N 期 ======
public static DateTime PrevWeekStart() => ThisWeekStart().AddDays(-7);
public static DateTime NextWeekStart() => ThisWeekStart().AddDays(7);
public static DateTime PrevMonthStart() => ThisMonthStart().AddMonths(-1);
public static DateTime NextMonthStart() => ThisMonthStart().AddMonths(1);
public static DateTime PrevQuarterStart() => ThisQuarterStart().AddMonths(-3);
public static DateTime NextQuarterStart() => ThisQuarterStart().AddMonths(3);
public static DateTime PrevYearStart() => ThisYearStart().AddYears(-1);
public static DateTime NextYearStart() => ThisYearStart().AddYears(1);
// ====== 區間 Tuple 回傳 (C# 7+) ======
public static (DateTime Start, DateTime End) ThisWeek() => (ThisWeekStart(), ThisWeekEnd());
public static (DateTime Start, DateTime End) ThisMonth() => (ThisMonthStart(), ThisMonthEnd());
public static (DateTime Start, DateTime End) ThisQuarter() => (ThisQuarterStart(), ThisQuarterEnd());
public static (DateTime Start, DateTime End) ThisYear() => (ThisYearStart(), ThisYearEnd());
// ====== 中文星期顯示 ======
private static readonly string[] ChineseWeek = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
public static string ToChineseWeek(this DateTime dt) => ChineseWeek[(int)dt.DayOfWeek];
}
var (start, end) = DateRangeHelper.ThisMonth();
Console.WriteLine($"本月:{start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}");
// 本月:2024-01-01 ~ 2024-01-31
Console.WriteLine(DateTime.Today.ToChineseWeek()); // 星期一
Console.WriteLine(DateRangeHelper.PrevQuarterStart():yyyy-MM-dd); // 2023-10-01
| 需求 | 格式字串 | 範例輸出 |
|---|---|---|
| 月首日 | yyyy-MM-01 | 2024-01-01 |
| 月末日 | 無單一格式,需 AddMonths(1).AddDays(-1) | 2024-01-31 |
| 年首日 | yyyy-01-01 | 2024-01-01 |
| 年末日 | yyyy-12-31 | 2024-12-31 |
DayOfWeek 列舉值:Sunday=0,週一起算需自行調整(見 ThisWeekStart)。(Month - 1) / 3 得到 0-based 季索引,乘 3 + 1 得起始月。DateTime.Today vs DateTime.Now:前者時間歸零,適合日期邏輯;後者含毫秒,適合時間戳記。ISOWeek (NET Core 3.0+)。.NET 8+ 引入 TimeProvider 抽象系統時間,利於單元測試;複雜時區、曆法建議引入 NodaTime,避免 DateTime 眾多已知缺陷。
將零散的 AddDays/AddMonths 組合封裝為靜態工具類,配合 Tuple 回傳區間、擴充方法顯示中文星期,即可在專案中統一、正確、可測試地處理所有日期區間需求。複製 DateRangeHelper 到 Common 庫,下次寫報表查詢直接 var (s, e) = DateRangeHelper.ThisQuarter(); 即可專心寫 SQL。