to_char(date, 'd') 默认以周日为1,因遵循locale设定(如en_us中周日为首日);需稳定逻辑应改用'id'(iso标准,周一=1)或extract(isodow from date)。

TO_CHAR(date, 'D') 返回的星期几为什么从1开始且周日是1?
PostgreSQL 的 TO_CHAR 函数用 'D' 模板返回的是「当前区域设置(locale)下该周的第几天」,默认以周日为一周起点,值为 1;周一为 2,依此类推。这和很多人直觉中「周一为1」不同,也和 ISO 标准不一致。
常见错误现象:SELECT TO_CHAR('2024-06-02'::DATE, 'D'); 返回 '1'(因为 2024-06-02 是周日),但用户误以为是周一,导致调度或分组逻辑出错。
- 依赖
'D'前务必确认SHOW lc_time;结果,不同 locale 可能改变周起始日(如en_US.utf8固定周日开头,de_DE.utf8仍为周一开头) - 若需稳定行为,不要依赖
'D',改用'ID'(ISO 标准:周一=1,周日=7)或'DY'(缩写名称,如'Sun') -
'ID'不受 locale 影响,推荐用于计算、条件判断等逻辑场景
想返回中文星期名,不能只靠 TO_CHAR('DY')
TO_CHAR(date, 'DY') 默认返回英文缩写(如 'Mon'),即使数据库 locale 设为 zh_CN.utf8,也仅影响 to_char 对月份/星期全称('DAY')的翻译,而 'DY' 缩写在多数 PostgreSQL 版本中仍固定为英文。
真正可行的做法是显式映射:
SELECT
d,
CASE TO_CHAR(d, 'ID')
WHEN '1' THEN '星期一'
WHEN '2' THEN '星期二'
WHEN '3' THEN '星期三'
WHEN '4' THEN '星期四'
WHEN '5' THEN '星期五'
WHEN '6' THEN '星期六'
WHEN '7' THEN '星期日'
END AS week_cn
FROM (VALUES ('2024-06-03'::DATE)) t(d);
- 避免用
TO_CHAR(d, 'DAY')+TRIM拼中文——它返回带空格的全名(如'Monday '),且 locale 未生效时仍是英文 - 如果大量使用,可封装为 SQL 函数,参数为
date,返回TEXT - 注意
'ID'是数字字符串,不是整数,所以CASE中要用单引号包裹
WHERE 条件里按星期筛选,别在 WHERE 中调用 TO_CHAR
写 WHERE TO_CHAR(created_at, 'DY') = 'Mon' 看似简洁,但会导致索引失效(函数作用于列),尤其在大表上性能骤降。
- 正确做法是换算成日期范围:例如查「所有周一的数据」,用
WHERE created_at >= '2024-01-01' AND EXTRACT(ISODOW FROM created_at) = 1 -
EXTRACT(ISODOW FROM date)直接返回整数(周一=1,周日=0),支持索引(如果字段上有函数索引:CREATE INDEX idx_created_isodow ON tbl (EXTRACT(ISODOW FROM created_at));) - 若已有 B-tree 索引在
created_at上,也可用范围查询模拟:周一 =created_at::DATE - ((created_at::DATE - '2000-01-03'::DATE) % 7),但可读性差,优先选EXTRACT(ISODOW ...)
格式化模板大小写敏感,'dy' 和 'DY' 结果不同
TO_CHAR 的模板字母严格区分大小写:'dy' 返回小写缩写('mon'),'DY' 返回大写('MON'),'Day' 返回首字母大写其余小写('Mon')。这不是 bug,是设计行为。
容易踩的坑:
- 写
WHERE TO_CHAR(d, 'dy') = 'mon'却忘了自己实际插入的是'Mon',结果查不到数据 - 前端展示用
'Day',后端逻辑用'DY',同一日期得到两个不同字符串,导致缓存或比对失败 - 模板中混用(如
'YYYY-MM-DD DY')没问题,但不要假设大小写可忽略——PostgreSQL 不做隐式归一化
最稳妥的方式是在逻辑层统一用 EXTRACT(ISODOW FROM ...) 得整数,再由应用决定显示为何种文本,避开格式化歧义。










