JavaScript按两月增量循环生成日期区间出现偏移问题求助
JavaScript的Date对象存在两套API,分别对应本地时区和UTC时区,同时不同的Date构造方式默认采用的时区规则不同,是所有问题的核心诱因:
- 构造函数规则差异:
- 传入
YYYY-MM-DD格式的ISO字符串时,new Date()会默认按UTC时区解析,生成的时间为对应日期的UTC 0点。 - 传入
new Date(年, 月, 日)多参数形式时,会默认按本地时区解析,生成的时间为对应日期的本地时区0点。
- 传入
- 方法规则差异:
getFullYear()/getMonth()/getDate()/setMonth()/setDate()等不带UTC前缀的方法,都是操作本地时区的时间字段。getUTCFullYear()/getUTCMonth()/getUTCDate()/setUTCMonth()/setUTCDate()等带UTC前缀的方法,操作的是UTC时区的时间字段。toISOString()永远输出UTC时区的时间字符串,和本地时区存在时差时,日期会出现偏移。
疑问1解答
datePointer是new Date('2017-08-01')生成的UTC时间2017-08-01T00:00:00.000Z。而datePointerTest是通过new Date(datePointer.getFullYear(), datePointer.getMonth(), datePointer.getDate())生成:getFullYear()/getMonth()/getDate()取的是datePointer对应的本地时区的年月日,再用多参数构造函数生成本地时区0点的时间,两者对应的UTC时间自然不同,输出的ISO字符串就会不一致。
疑问2解答
datePointer对应的本地时区时间实际是2017年7月31日(取决于你所在时区和UTC的时差),调用getMonth()拿到的是7月对应的序号6,getFullYear()拿到2017,因此new Date(2017,6,1)生成本地时区2017年7月1日的时间,转ISO字符串后就得到2017-07-01。
首次代码日期变2号的原因
你混用了UTC解析、本地时间操作、UTC输出三个环节:初始日期按UTC解析,加月份、设日期用的是本地时间方法,最后输出又用了输出UTC时间的toISOString(),时差叠加后就出现了日期多1天的问题。
核心原则是全程统一时区,不要混用UTC和本地时间的API,不要依赖toISOString()拆分日期,推荐两种实现方案:
方案1:全程使用UTC时区
所有操作统一使用UTC相关API,避免时区偏移:
const createDateRange = (endDate) => { const dateArray = []; // 按UTC创建初始日期,月份从0开始计数,7对应8月 let datePointer = new Date(Date.UTC(2017, 7, 1)); const endUTC = new Date(Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())); while (endUTC > datePointer) { // 直接拼接UTC年月日,无需拆分toISOString const year = datePointer.getUTCFullYear(); const month = String(datePointer.getUTCMonth() + 1).padStart(2, '0'); const day = String(datePointer.getUTCDate()).padStart(2, '0'); dateArray.push(`${year}-${month}-${day}`); // 用UTC方法更新月份、日期 datePointer.setUTCMonth(datePointer.getUTCMonth() + 2); datePointer.setUTCDate(1); } // 处理结束日期 const endYear = endUTC.getUTCFullYear(); const endMonth = String(endUTC.getUTCMonth() + 1).padStart(2, '0'); const endDay = String(endUTC.getUTCDate()).padStart(2, '0'); dateArray.push(`${endYear}-${endMonth}-${endDay} end`); return dateArray; }
方案2:全程使用本地时区
所有操作统一使用本地时区相关API,避免跨时区转换:
const createDateRange = (endDate) => { const dateArray = []; // 按本地时区创建初始日期,月份从0开始计数,7对应8月 let datePointer = new Date(2017, 7, 1); while (endDate > datePointer) { // 直接拼接本地时区年月日 const year = datePointer.getFullYear(); const month = String(datePointer.getMonth() + 1).padStart(2, '0'); const day = String(datePointer.getDate()).padStart(2, '0'); dateArray.push(`${year}-${month}-${day}`); // 更新月份、日期 datePointer.setMonth(datePointer.getMonth() + 2); datePointer.setDate(1); } // 处理结束日期 const endYear = endDate.getFullYear(); const endMonth = String(endDate.getMonth() + 1).padStart(2, '0'); const endDay = String(endDate.getDate()).padStart(2, '0'); dateArray.push(`${endYear}-${endMonth}-${endDay} end`); return dateArray; }
内容的提问来源于stack exchange,提问作者Nicholas Brush

