GETDATE() 返回当前服务器时间而非当天零点,直接用于日期计算会导致偏差;正确做法是先截断为DATE再加天数,并注意datepart拼写、业务语义(含/不含首日)、时区及月末边界处理。

GETDATE() 返回的是当前时间,不是“今天零点”
很多人误以为 GETDATE() 会返回当天的起始时间(比如 2024-06-15 00:00:00.000),其实它返回的是精确到毫秒的当前服务器时间(如 2024-06-15 14:23:08.123)。如果直接用它做“从今天开始加30天”的逻辑,结果会包含当前时刻的偏移,容易导致过期判断偏差。
常见错误写法:SELECT DATEADD(day, 30, GETDATE()) AS ExpiryDate
这实际是“当前时刻 + 30 天”,不是“今天日期 + 30 天”。对需要按日粒度判定过期(比如“会员有效期30天,从注册当天起算”)的场景,会导致用户在注册当天下午注册,第30天凌晨就过期。
正确做法是先截断时间部分:SELECT DATEADD(day, 30, CAST(CAST(GETDATE() AS DATE) AS DATETIME)) AS ExpiryDate
或更简洁(SQL Server 2008+):SELECT DATEADD(day, 30, CONVERT(DATE, GETDATE())) AS ExpiryDate
DATEADD 的 datepart 参数必须用标准关键字,不能拼错
DATEADD 第一个参数是时间单位,SQL Server 对大小写不敏感,但拼写必须准确。常见错误包括:
- 写成 days(正确是 day)
- 写成 monthes(正确是 month)
- 混用英文和中文(如 天)——直接报错:Msg 155, Level 15, State 1: '天' is not a recognized dateadd option.
常用合法值:year、quarter、month、day、hour、minute、second、millisecond。
注意:week 表示“周”,不是“星期”,加 1 表示加 7 天;weekday 不是有效参数(SQL Server 不支持)。
计算过期时间时,要考虑业务语义是“含首日”还是“不含首日”
比如用户注册当天是否算作有效期第一天?这个逻辑直接影响 DATEADD 的数值选择:
- 若注册当天算第1天(含首日),则30天有效期截止时间为:注册日期 + 29 天(即第30天的23:59:59.997)
- 若注册当天不算(次日才算第1天),则截止时间为:注册日期 + 30 天
更稳妥的做法是统一用日期比较,而非时间戳比较:WHERE ExpiryDate >= GETDATE() —— 过期判断
但要注意 ExpiryDate 字段类型:如果是 DATE 类型,它自动归零为 00:00:00,那么 >= GETDATE() 在当天凌晨0点后就会失效;
建议存储为 DATETIME 或 DATETIME2,并明确设置时间部分(如设为 23:59:59.997)。
跨月/跨年时 DATEADD 自动处理边界,但要注意闰年与月末天数
DATEADD 是智能的:加一个月不会简单加30天,而是按日历推进。例如:SELECT DATEADD(month, 1, '2024-01-31') → 2024-02-29(闰年2月有29天)SELECT DATEADD(month, 1, '2023-01-31') → 2023-02-28(非闰年)SELECT DATEADD(month, 1, '2024-03-31') → 2024-04-30(4月只有30天)
这意味着:如果业务要求“固定每月某日到期”(比如每月15号),不能依赖 DATEADD(month, n, @start),而应改用 DATEFROMPARTS + 手动构造,否则遇到31号起始日期时会意外提前。
真正容易被忽略的是:SQL Server 默认语言环境会影响 DATEADD 对 weekday 等参数的解析(虽然它不支持该参数),但更隐蔽的问题是 GETDATE() 依赖服务器时区,而应用层可能用 UTC 时间比对——时区不一致会导致过期判断早/晚整整一天。

















