Angular Material日期选择器中的日期错误

52
当我选择一个日期时,我在字段中看到了正确的日期,但是当我保存时,日期选择器会发送我所选日期的前一天(偏移3小时)。 我正在使用angular响应式表单和MatMomentDateModule进行日期选择器。 问题与时区有关,但我只想将用户输入的相同日期保存到数据库中。 代码示例:https://stackblitz.com/edit/angular-material-moment-adapter-example-kdk9nk?file=app%2Fapp.module.ts

issue on stackblitz

在 GitHub 上与此相关的问题:

https://github.com/angular/material2/issues/7167

非常感谢您的帮助,我认为很多开发者需要一个解决方案。


我在这里回答了这个问题: https://dev59.com/dZffa4cB1Zd3GeqP8n-r#56561931(可以工作,但是很原始) - Noé KlK
我在这里回答了,很简单快捷:https://dev59.com/dZffa4cB1Zd3GeqP8n-r#56561931 - Noé KlK
19个回答

46
两天前,在https://github.com/angular/material2/issues/7167,Silthus发布了他的解决方案,覆盖了MomentJsDataAdapter。我尝试了一下,它在全球任何地方都能完美运行。
首先,他添加了一个扩展MomentDateAdapter的MomentUtcDateAdapter。
import { Inject, Injectable, Optional } from '@angular/core';
import { MAT_DATE_LOCALE } from '@angular/material';   
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { Moment } from 'moment';
import * as moment from 'moment';

@Injectable()
export class MomentUtcDateAdapter extends MomentDateAdapter {

  constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string) {
    super(dateLocale);
  }

  createDate(year: number, month: number, date: number): Moment {
    // Moment.js will create an invalid date if any of the components are out of bounds, but we
    // explicitly check each case so we can throw more descriptive errors.
    if (month < 0 || month > 11) {
      throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`);
    }

    if (date < 1) {
      throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
    }

    let result = moment.utc({ year, month, date }).locale(this.locale);

    // If the result isn't valid, the date must have been out of bounds for this month.
    if (!result.isValid()) {
      throw Error(`Invalid date "${date}" for month with index "${month}".`);
    }

    return result;
  }
}

然后在AppModule组件中,你需要做如下操作:

providers: [
    ...
    { provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
    { provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
    { provide: DateAdapter, useClass: MomentUtcDateAdapter },
    ...
],

3
这里有一个 StackBlitz 代码示例,用于解决问题。 https://stackblitz.com/edit/angular-material-moment-adapter-example-kdk9nk?file=app%2Fapp.module.ts - Motaz Homsi
1
非常感谢,这解决了我的问题,按预期工作。 - Md. Sabbir Ahamed
4
当用户在文本框中手动输入日期时,该方法无法使用。它不会触发该函数。 - Johnathan Li
1
当您选择日期时,它可以正常工作,但如果日期已经来自提供者,则会延迟一天。 - Luis Ruiz Figueroa
针对@JohnathanLi提出的问题,请查看Pias Uddin Jibon的答案。 - Deitsch
显示剩余2条评论

27

只需在MatMomentDateAdapter中使用选项useUtc: true

import { MatMomentDateModule, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';

@NgModule({
  exports: [
    MatMomentDateModule,
    // ...
  ],
  providers: [
    { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
  ],
})
export class CustomModule { }

2
不错,它运作正常了。但是MatMoment数据模块必须单独安装,因为它不是材料模块的一部分。以下内容应该有所帮助。npm install @angular/material-moment-adapter参考:https://material.angular.io/components/datepicker/overview - Senthilkumar Ramasamy
1
这是正确而简单的答案。它有效。但如果有人能解释为什么会发生这种情况,以及如果我们添加MatMomentDateAdapter会发生什么背后的情况,那将不胜感激。 - PrazSam
1
如果您同时将MomentDateAdapter作为DateAdapter提供,则还需要在那里注入该选项对象: { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] } - Tomek
2
Moment已不再进行活跃的开发。这并非理想的持续解决方案。 - toxaq

9

https://github.com/angular/material2/issues/7167#issuecomment-402061126

你可以通过提供MAT_MOMENT_DATA_ADAPTER_OPTIONS并将其设置为useUtc:true来更改默认行为以将日期解析为UTC。

@NgModule({ 
    imports: [MatDatepickerModule, MatMomentDateModule], 
    providers: [ 
        { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } 
    ] 
})

1
当使用例如 { provide: MAT_DATE_LOCALE, useValue: 'en-GB' } 时,这仍然可以节省一小时。 - jenson-button-event

9
也许对某些人有帮助。这是我的示例方法,我从组件日期中删除了时区偏移量。
  addPriceListPeriod(priceListId: number, periodDateFrom: Date) {

    let UTCDate = Date.UTC(periodDateFrom.getFullYear(), periodDateFrom.getMonth(), periodDateFrom.getDate()) - periodDateFrom.getTimezoneOffset();

    periodDateFrom = new Date(UTCDate);

    const tempObject = {
      priceListId,
      fromDate: periodDateFrom
    }
    return this.httpClient.post('PriceLists/addPriceListPeriod', tempObject);
  }

5

@Bruno_Cerecetto的代码工作得很好,但有一个小问题。当用户在文本框中输入日期而不是使用日期选择器选择日期时,他的代码不能正常工作,它会再次减少一天。为了使上述代码正常工作,你需要覆盖解析方法。每当用户在文本框中输入时,解析方法都会被调用。以下是对我有效的代码。我认为它可能会帮助某些人。

parse(value: any, parseFormat: string | string[]): Moment | null {
    console.log(value)
    if (value && typeof value === 'string') {
      return moment.utc(value, parseFormat, this.locale, true);
    }
    return value ? moment.utc(value).locale(this.locale) : null;
  }

不错,应该排得更靠前! - Deitsch

3

我知道这是一个比较老的问题,但是由于我没有找到一个简单的答案,所以我会提供我成功的解决方法:

[ { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS,
    useValue: { useUtc: true } },
  { provide: DateAdapter, useClass: MomentDateAdapter,
    deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] } ]

您还需要在dateAdapterdeps中指定适配器选项。这就是让我成功的原因。


谢谢,这是我缺失的部分(也要在依赖项中提供它)。 - Serkan Sipahi

2
请参考此链接。更简单的方法是尝试以下步骤。
var d = new Date('yourDate');
d.setMinutes( d.getMinutes() + d.getTimezoneOffset() );

d现在应该是正确的日期。


1
这个是我唯一正确的解决方案。其他的都不起作用,即使使用了moment和MAT_MOMENT_DATE_ADAPTER_OPTIONS。 - Hello

2

以上所有解决方案都很好,但没有一个是一行代码的解决方法。如果您只想获取所选本地日期并将该数据作为日期对象发送到后端,请尝试以下方法:

框架:Angular,使用FormGroup和Formcontrol

let newDate = new Date(moment(this.date.value).format('YYYY-MM-DD'))

这不会将您的本地日期修改为字符串,它将保持为Date对象,并允许您以一致的方式使用数据类型,而且这是一个简单的修复。


1

选项1: 我遇到了同样的问题,并在后端(Java)中解决了它。你能在你的后端代码中解决吗?
以下是Java代码,如果有人需要相同的帮助,概念对于其他服务器端技术也应该是类似的。

模拟了相同的错误,以下是分析。

通过JSON打印 | “startdate”:“2018-02-08T18:30:00.000Z”。

提交之前打印 >>Fri Feb 09 2018 00:00:00 GMT+0530 (IST)

public static String dateConversion(String dt) {

        Instant timestamp = Instant.parse(dt);
        ZonedDateTime isttime = timestamp.atZone(ZoneId.of("Asia/Kolkata"));
        System.out.println(isttime);

        System.out.println(DateTimeFormatter.ofPattern("dd-MM-yyyy").format(isttime));
        return DateTimeFormatter.ofPattern("dd-MM-yyyy").format(isttime);
    }

选项2:(前端解决方案) 我没有尝试过这个选项,但是文档似乎非常清晰。
https://maggiepint.com/2016/05/14/moment-js-shows-the-wrong-date/ 看一下。


我正在使用PHP,我认为将时区日期转换为时间戳,然后再将其转换为任何其他所需格式是正确的。 但是我正在寻找一种解决方案,可以在将日期输入添加到MySQL之前,适用于所有应用程序而无需转换日期输入,因为我有很多带有日期输入的视图, 如果我找不到全局解决方案来处理此问题,我想我会采用这种方法。 感谢您的回答 :-) - Motaz Homsi

1

目前,Angular Material日期选择器不支持不同的时区,并且它会给出一个UTC时间偏移量。但对我来说这不是问题,因为我将输出字符串保存到数据库中,当我将其返回给用户时,浏览器会显示正确的日期时间对象。 在控制台尝试此操作(我在+01区),您将看到所选日期:

new Date('2018-02-08T23:00:00.000Z')
Fri Feb 09 2018 00:00:00 GMT+0100 (CET)

另一种解决方法是在保存到数据库之前修改您的日期对象,但这并不是最佳实践。
最后,如果您的应用程序中使用Moment.js,您可以尝试使用MomentDateAdapter,并查看它是否有帮助。只需按照此处所述将MatMomentDateModule添加到您的应用程序中即可。

1
太好了,我在MySQL中将记录保存为日期时间,所以每当我更新日期时,都会得到前一天的日期。现在将记录保存为字符串解决了我的问题,非常感谢。 - Motaz Homsi
实际上我尝试使用MatMomentDateModule来解决这个问题,但是我无法将它按照我想要的方式工作。如果你看到我在主要问题中复制的代码,你就可以找到它并且编辑它进行尝试。我认为如果您能给出一个例子,那将是真正的解决方案,因为很多开发人员需要将日期保存为“datetime”以供数据库查询目的。 - Motaz Homsi
我不确定它是否有效。通常在Angular应用程序中我们不需要Moment。无论如何,我认为我们也可以将生成的日期保存为数据库中的日期时间格式。我会在周一尝试并告诉你结果。 - pouyada
非常感谢 @pouyada ,我很感激你的帮助,我会等待你的测试 :-) - Motaz Homsi
我尝试将这个日期格式插入到mysql中,但它不被视为dateTime,所以你需要进行转换,但这并不推荐。关于MomentDateAdapter,它在较旧版本的angular material中不受支持。你应该使用更新的版本(推荐)或从这里安装它。 - pouyada
我在我的真实应用程序中使用Angular Material 5.0.3,按照文档这里的说明,包括MatMomentDateModule以包含MomentDateAdapter。 我认为我必须使用此适配器的解析方法,使用moment js将其更改为UTC或任何其他格式,以便mysql将其作为datetime接受而不在后端进行更改,但是我尝试了很多次都没有成功, 现在我正在尝试使用ControlValueAccessor来更改datepicker的输出值。 - Motaz Homsi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接