Angular Material日期选择器为何发送日期为前一天?
这个问题本质是时区差异导致的:当你选择日期时,Angular Material Datepicker获取的是你本地时区的「当天0点」,但发送到服务器时会被序列化为UTC时间,而Mongoose存储的是UTC时间,这就出现了本地时间和UTC时间的日期偏移。
举个例子:如果你在东二区(+02:00)选择2018-01-18,本地时间是2018-01-18T00:00:00+02:00,转成UTC就是2018-01-17T22:00:00.000Z——服务器存储这个UTC时间后,按UTC日期分组时,自然会把它归到1月17日;而Angular的日期管道默认用本地时区解析UTC时间,所以显示出来还是1月18日,这就造成了「显示正常但分组错误」的矛盾。
下面给你几个可行的解决方案,按需选择:
方案一:前端提交时转换为UTC当天0点
在表单提交前,手动把选择的日期调整为UTC时区的「当天0点」,这样服务器收到的就是和你选择的日期一致的UTC时间:
// 假设你在提交表单的方法里处理 onSubmit() { const dateControl = this.item.get('date'); if (dateControl.value) { const localDate = new Date(dateControl.value); // 构造UTC当天0点的日期 const utcDate = new Date(Date.UTC( localDate.getFullYear(), localDate.getMonth(), localDate.getDate() )); dateControl.setValue(utcDate); } // 然后执行提交逻辑 this.http.post('/your-api', this.item.value).subscribe(...); }
这样服务器收到的就是2018-01-18T00:00:00.000Z,Mongoose存储后,无论按UTC还是本地时区分组,日期都和你选择的一致。
方案二:后端Mongoose存储时转换日期
如果不想修改前端代码,可以在Mongoose的Schema里配置set函数,自动把传入的日期转换为UTC当天0点:
const yourSchema = new mongoose.Schema({ // 其他字段... date: { type: Date, set: function(value) { // 把传入的日期(本地时间)转换为UTC当天0点 return new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate())); } } });
这样不管前端发送的是本地时间还是UTC时间,Mongoose都会统一存储为UTC当天0点的日期,分组时就不会出现跨天问题。
方案三:自定义Angular Datepicker适配器(全局生效)
如果你的项目中有很多日期选择器,想统一处理时区,可以自定义一个UTC日期适配器,让Datepicker全程使用UTC时间:
1. 创建UTC日期适配器
import { NativeDateAdapter } from '@angular/material/core'; export class UtcDateAdapter extends NativeDateAdapter { // 解析日期时,转换为UTC当天0点 override parse(value: any): Date | null { if (typeof value === 'string' && !value.includes('T')) { const [year, month, day] = value.split('-').map(Number); return new Date(Date.UTC(year, month - 1, day)); } const date = super.parse(value); if (date) { return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); } return null; } // 格式化日期时,基于UTC日期显示 override format(date: Date, displayFormat: Object): string { return new Date( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() ).toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-'); } }
2. 在模块中配置适配器
import { NgModule } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; import { UtcDateAdapter } from './utc-date-adapter'; // 自定义日期格式,和你的需求匹配 const MY_DATE_FORMATS = { parse: { dateInput: 'yyyy-MM-dd', }, display: { dateInput: 'yyyy-MM-dd', monthYearLabel: 'MMM yyyy', dateA11yLabel: 'LL', monthYearA11yLabel: 'MMMM yyyy', }, }; @NgModule({ providers: [ { provide: DateAdapter, useClass: UtcDateAdapter }, { provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS }, ], }) export class DatePickerConfigModule {}
把这个模块导入到你的主模块后,所有Datepicker都会使用UTC时间处理,提交的日期自然就是正确的UTC当天0点,从根源上解决时区问题。
内容的提问来源于stack exchange,提问作者Виталий Мудрый

