52

When I select a date I see the correct date in the field but, when I save, the datepicker send the day before the date I have selected ( 3 hours offset ) i am using angular reactive form and MatMomentDateModule for date picker .

the problem is related to timezones but i just want to save the same date that the user enter to database .

Code repreduced here : https://stackblitz.com/edit/angular-material-moment-adapter-example-kdk9nk?file=app%2Fapp.module.ts

issue on stackblitz

issue related to this on githup :

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

Any help is appreciated , and i think a lot of developers need a solution for this .

Motaz Homsi
  • 701
  • 1
  • 6
  • 9
  • i answered it here : https://stackoverflow.com/questions/37495089/angular-material-datepicker-returns-one-day-before-the-exact-date/56561931#56561931][1] (works but its raw) – Noé KlK Jun 12 '19 at 12:14
  • i answered it here its simple and quick : https://stackoverflow.com/questions/37495089/angular-material-datepicker-returns-one-day-before-the-exact-date/56561931#56561931 – Noé KlK Jun 12 '19 at 12:16

19 Answers19

46

Two days ago, at https://github.com/angular/material2/issues/7167, Silthus posted his workaround that overrides the MomentJsDataAdapter. I tried it and it worked as a charm from anywhere in the globe.

First he added a MomentUtcDateAdapter that extends MomentDateAdapter

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;
  }
}

And then in the AppModule component, you have to do this:

providers: [
    ...
    { provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
    { provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
    { provide: DateAdapter, useClass: MomentUtcDateAdapter },
    ...
],
Bruno Cerecetto
  • 780
  • 8
  • 17
27

Just use option useUtc: true for MatMomentDateAdapter:

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 { }
Je Suis Alrick
  • 3,404
  • 2
  • 16
  • 23
Interloper
  • 790
  • 7
  • 20
  • 2
    Nice. It worked. But the MatMoment Data Module has to be installed separately as this is not a part of material module. The below should help. npm install @angular/material-moment-adapter reference: https://material.angular.io/components/datepicker/overview – Senthilkumar Ramasamy Aug 03 '21 at 04:08
  • 1
    This is the correct and simple answer. It works. But It's much appreciated if someone can explain why this happens and what happens behind the scene if we add MatMomentDateAdapter? – PrazSam Nov 23 '21 at 03:41
  • 1
    If you're also providing `MomentDateAdapter` as `DateAdapter` then you need to inject that options object there too: `{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] }` – Tomek Apr 19 '22 at 11:51
  • 2
    Moment is no longer in active development. Not the ideal ongoing solution. – toxaq May 31 '22 at 04:22
9

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

You can change the default behaviour to parse dates as UTC by providing the MAT_MOMENT_DATA_ADAPTER_OPTIONS and setting it to useUtc: true.

@NgModule({ 
    imports: [MatDatepickerModule, MatMomentDateModule], 
    providers: [ 
        { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } 
    ] 
})
Küzdi Máté
  • 661
  • 8
  • 12
9

Maybe it will be helpful for someone. It is my example method in which i erase TimeZone OffSet from component date

  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);
  }
Wzorzec
  • 301
  • 3
  • 5
5

@Bruno_Cerecetto's code works fine but there is small issue. When a user type date in text box instead of choosing date with datepicker then his code does't work it reduce one day again. To work properly with the above code you need to override parse method also. Parse method called every time when a user type in the text box. Here is the code that works for me. I think it may help someone.

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;
  }
2

Refer to the link here. The easier way is to try below

var d = new Date('yourDate');
d.setMinutes( d.getMinutes() + d.getTimezoneOffset() );

d should be the correct date now.

Vision
  • 548
  • 1
  • 7
  • 20
  • 1
    This one is the only correct solution for me. All thew others are not working even using moment and MAT_MOMENT_DATE_ADAPTER_OPTIONS. – Hello Apr 21 '22 at 15:37
2

I know this is quite an old question, but since i didn't find a simple answer I will provide what worked for me:

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

You have to also specify the adapter options in the deps of the dateAdapter. That's what made it work for me.

Jawa
  • 2,336
  • 6
  • 34
  • 39
Loris Nico
  • 37
  • 1
2

All the above solutions are great, but none is a one line fix If you want to just get the local date you select and post that data to your backend as a date object, try this:

Framework: Angular, Using FormGroup and Formcontrol

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

This will not modify you local date to a string, it will stay as Date object and allows you to use the data type in a consist way and also its a easy fix.

AlphaMega
  • 21
  • 1
1

OPTION 1: Had same issue and solved it in backed (Java) Can you solve this in your backend code.
Presenting java code in case if anyone needs the same help. concept should be similar for other server side technologies too.

Simulated the same bug and following are the analysis.

PRINT Via JSON | "startdate": "2018-02-08T18:30:00.000Z" .

PRINT BEFORE SUBMIT >>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);
    }

OPTION 2: (solution in frontend) I have not tried this option but the documentation seems very clear.
https://maggiepint.com/2016/05/14/moment-js-shows-the-wrong-date/ Check this out.

Sivadinesh
  • 157
  • 2
  • 5
  • 15
  • i am using PHP and i think its right to convert timezoned date to timestamp and then convert it again to any other required format but i am searching for a solution for all app without convert date inputs before i add it to mysql because i have a lot of views with date inputs , i think i will go with this if i did not found a global solution for mt2 datepicker to handle this . and thanks for your answer :-) – Motaz Homsi Feb 09 '18 at 17:31
1

Angular material date picker does not support different time zones at the moment and it gives you an UTC time offset. But to me it is not a problem as i save the output string to my database and when i return it back to user the browser shows the correct date time object. Try this in your console(im in +01 zone) and you will see the selected date:

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

Another solution is to modify your date object before saving to database which is not the best practice.

Lastly, If you are using Moment.js in your app you could give the MomentDateAdapter a try and see if it helps you. Just add the MatMomentDateModule to your application as described here.

pouyada
  • 341
  • 3
  • 15
  • 1
    Great , i was saving the record as datetime in mysql , so when ever i update the date i get the previous day , now saving the record as string solve my problem , thanks a lot – Motaz Homsi Feb 10 '18 at 05:46
  • actually i tried to use MatMomentDateModule to solve this but i can't get it to work as i want , if you see the code that i repreduced in the main question you will find it and you can edit it to try it , i think that would be the real solution if you can give an example because a lot of developers need to save the date as "datetime" for DB quering purposes . – Motaz Homsi Feb 10 '18 at 05:50
  • Im not sure that it works. I didn't try as normally we don't need Moment in an angular app. Anyway i think we can save the produced date also as a datetime in DB. I will try it Monday and let you know. – pouyada Feb 10 '18 at 09:38
  • Thanks a lot @pouyada , i am appreciate your help , i will wait for your test :-) – Motaz Homsi Feb 10 '18 at 15:41
  • I tried to insert this date format to mysql but it does not accept it as a dateTime, so you need to convert it which is not recommended. Regarding to `MomentDateAdapter`, it is not suppoeted in older versions of angular material. you should use newer version ( recommended ) or install it from [HERE](https://github.com/angular/material2-moment-adapter-builds) – pouyada Feb 12 '18 at 10:52
  • i am using angular material 5.0.3 in my real app , i am including MatMomentDateModule for MomentDateAdapter to be included as the docs explain [here](https://material.angular.io/components/datepicker/overview#choosing-a-date-implementation-and-date-format-settings) . i think i have to use parse method of this adapter to change it using moment js to be in UTC or any other format the mysql accept it as datetime without changing it in backend , but i tried a lot without any success , now i am trying using ControlValueAccessor to change output value of datepicker . – Motaz Homsi Feb 12 '18 at 15:04
1

If you want to use it in your component, you can simply do

pipe = new DatePipe('en-US'); // Use your own locale

Now, you can simply use its transform method, which will be

const now = Date.now();
const myFormattedDate = this.pipe.transform(now, 'short');
Sanjiv Rajput
  • 145
  • 1
  • 13
0

thanks to @ankit-raonka answer here : https://stackoverflow.com/a/48761312/6423656

using ControlValueAccessor solved my issue and here is a working example : https://stackblitz.com/edit/angular-dlxnmx?file=app%2Fcva-date.component.ts

Thanks for every one for answers , i think this issue could also resolved using parse method of moment date adapter but i like the ControlValueAccessor try as i control all the input aspects + reduce the code written when i want to use it in my html .

Motaz Homsi
  • 701
  • 1
  • 6
  • 9
0

The solution stackblitz works great, but if you are not storing the timestamp in Database, then you need to convert back the date to UTC format while retrieving it from database before assigning to the datepicker.

let dbDate = new Date('2018-02-09T00:00:00.000Z');

let updateDate = new Date(
      Date.UTC(dbDate.getFullYear(), dbDate.getMonth(), dbDate.getDate())
    ).toISOString(); // 2018-02-08T00:00:00.000Z

now assign the updatedDate to the datepicker, hope this helps.

Naresh Chennuri
  • 1,133
  • 11
  • 10
0

If You using Reactive Forms and do this.form.get("dateField").value you must cast it as a Date before submit.

It'll be something like:

save() {
  let obj = new MyObj();
  obj.myDate = new Date(this.form.get("mydateField").value);
  this.service.save(obj);
}
0

If the hour of the date is equal to 0, it means we are in the same timezone and you don't need to do something.

If the hour of the date is greater than 12, it means the date is the day of yesterday and you need to add a day.

If the hour of the date is lower than 12, it means the date is the day tomorrow and you need to correct it.

Below, a small function that may helps you as it helps me

let date = new Date(dateReceived);
//We use dayCorrector to remove the timezone. We want brut date without any timezone
let dayCorrector = (date.getHours()>12) ? (1) : (date.getHours()<=12) ? (-1) : (0);
date.setDate(date.getDate()+dayCorrector);
let dateFinal = (String(date.getFullYear()) +'-'+ String(date.getMonth()+1) +'-'+ String(date.getDate())+' 00:00:00Z');
phenric
  • 307
  • 1
  • 8
  • 15
0

This worked for me:

const date = new Date(dateFromDatepicker);
date.toLocaleString();   // displays the correct date that was chosen in the datepicker

I'm using reactive forms. dateFromDatepicker is the value I got from the Angular material datepicker formControl.

Source: https://github.com/angular-ui/ui-date/issues/88

Sofía
  • 784
  • 10
  • 24
0

You can modify your date format before sending to the server:

public saveYourDate(date: Date): Observable<any> {
        const formattedDate = date.toLocaleDateString();
        return this.http.post(`${this.yourPathVariable}/date`, formattedDate);
    }

In this case you don't specify the time so the date is being sent as a string in the format depending on locale conventions like this: '10/30/2020'.

0
  providers: 
  [
    {provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } },
    {provide: MAT_DATE_LOCALE, useValue: 'fr-FR'},
    {
        provide: DateAdapter,
        useClass: MomentDateAdapter,
        deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]
    },
  ]
0

if you just want to get today's date without time


get myDateToday(): Date {
        return new Date(moment(new Date()).format('YYYY-MM-DD'));
}

let beginDate = this.myDateToday;