先记住这个答案
Date 内部只有一个时间戳,setHours 和 setUTCHours 都是改完某个字段后重新算这个时间戳,区别在于改的是哪块表。setHours 先把时间戳换成本地钟点,改小时,再按本地时区换回去;setUTCHours 在 UTC 钟点上改,不经过本地时区。所以在东八区把同一个时间设成 0 点,两者差 8 小时。在有夏令时的地区,setHours 还可能碰上某个钟点不存在或出现两次,改出来的时间差和字段差对不上。
setHours改本地钟点,setUTCHours改 UTC 钟点- 东八区里两者设成 0 点,结果差 8 小时
- 跨夏令时,本地加 2 小时不一定等于过 2 小时
- setter 原地修改 Date,返回的是时间戳不是 Date
最常见的场景:把时间设成当天零点
「查今天的订单」「按天统计」都要先算出当天零点。很多人随手写 setUTCHours(0, 0, 0, 0),在国内就会出问题。
// 以下注释是在东八区(Asia/Shanghai)运行的结果
const now = new Date('2026-09-10T15:30:00+08:00') // 北京下午 3 点半
const local = new Date(now)
local.setHours(0, 0, 0, 0) // 北京时间 9 月 10 日 00:00
console.log(local.toISOString()) // 2026-09-09T16:00:00.000Z
const utc = new Date(now)
utc.setUTCHours(0, 0, 0, 0) // UTC 的 9 月 10 日 00:00
console.log(utc.toString()) // Thu Sep 10 2026 08:00:00 GMT+0800setHours(0, 0, 0, 0) 得到的是北京的零点,换成 UTC 是前一天 16:00。setUTCHours(0, 0, 0, 0) 得到的是 UTC 零点,在北京已经是早上 8 点。如果你拿后者去查「今天」的数据,凌晨 0 点到 8 点下的单全都漏掉了。
更隐蔽的是,如果当前时间是北京早上 5 点,这时 UTC 还是前一天,setUTCHours(0) 会得到前一天早上 8 点,连日期都错了。按用户的「今天」算,用 setHours;按 UTC 日期算,比如和后端约定了 UTC 日切,才用 setUTCHours。
跨夏令时,本地加 2 小时不等于过 2 小时
东八区没有夏令时,上面的差距是固定的 8 小时。在有夏令时的地区,setHours 还会多一层麻烦。纽约 2024 年 11 月 3 日凌晨 2 点时钟拨回 1 点,01:00 到 01:59 走了两遍。
// 以下注释是在 America/New_York 时区运行的结果
const start = new Date('2024-11-03T00:30:00-04:00') // 本地 00:30
const local = new Date(start)
const utc = new Date(start)
local.setHours(local.getHours() + 2) // 本地钟点改成 02:30
utc.setUTCHours(utc.getUTCHours() + 2) // UTC 钟点加 2
console.log((local - start) / 3600000) // 3
console.log((utc - start) / 3600000) // 2本地钟面从 00:30 走到 02:30,中间 01:00 到 02:00 这一段走了两遍,所以实际过了 3 小时。setUTCHours 在没有夏令时的 UTC 时间轴上加 2,严格过了 2 小时,只是本地钟面显示成了 01:30。
两个结果都没错,回答的是不同问题。缓存 TTL、倒计时要的是真实经过的时长,用时间戳或 UTC 方法。「每天早上 9 点」这种排班要的是本地钟点,用 setHours 或 setDate。
两个方法共同的坑:原地修改和返回值
setHours 和 setUTCHours 都会直接改调用它的那个对象,返回值是新的毫秒时间戳。这两点在 React 状态、函数参数里很容易出事。
const base = new Date('2026-09-10T00:00:00Z')
const ret = base.setUTCHours(25) // 25 点会进位到第二天 1 点
console.log(typeof ret, ret === base.getTime()) // 返回值是时间戳
console.log(base.toISOString()) // 原对象已经变了查看输出与解释
number true
2026-09-11T01:00:00.000Zret 是数字,拿它继续调 .toISOString() 会报错。base 本身被改了,如果它是函数参数或者 React state 里的对象,调用方手里的日期也跟着变了。需要保留原值时,先用 new Date(base) 复制一份再改。另外可以看到,传 25 不会报错,会进位成第二天的 1 点。
容易答错的地方
- 以为加 1 小时永远等于加 3600000 毫秒
- 用
setUTCHours或直接加毫秒时成立。用setHours跨过夏令时切换点时就不一定了。比如秋季回拨那天,本地 01:30 是夏令时的那一次时,把钟点改成 2 点,实际过了 2 小时。 - 以为东八区用
setUTCHours(0)也能拿到今天零点 - 拿到的是北京早上 8 点,凌晨时段还可能落到前一天。按用户本地日期算,要用
setHours(0, 0, 0, 0)。
面试官还会怎么问?
怎么给一个时间加 90 分钟?
new Date(date.getTime() + 90 * 60 * 1000)。直接在时间戳上加,表达的就是「经过 90 分钟」,不受本地时区和夏令时影响,也不会改原对象。
怎么拿到用户时区里的「明天同一时间」?
复制一份,然后 setDate(d.getDate() + 1)。它按本地日历加一天,钟点保持不变,跨月、跨年会自动进位。碰上夏令时切换,这一天可能是 23 或 25 小时,这正是日历语义要的结果。
setHours 能一次设置分钟和秒吗?
能。完整签名是 setHours(hours, minutes, seconds, ms),后三个可选。setHours(0, 0, 0, 0) 一次把时分秒毫秒都清零,比连调四个 setter 简洁,setUTCHours 也一样。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。