37
votes

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 .

17
i answered it here : stackoverflow.com/questions/37495089/… (works but its raw)Noé KlK
i answered it here its simple and quick : stackoverflow.com/questions/37495089/…Noé KlK

17 Answers

41
votes

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 },
    ...
],
14
votes

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 { }
8
votes

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);
  }
5
votes

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 } } 
    ] 
})
3
votes

@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;
  }
1
votes

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.

1
votes

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.

1
votes

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');
0
votes

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 .

0
votes

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
votes

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');
0
votes

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

0
votes

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.

0
votes

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
votes

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.

0
votes

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.

-1
votes

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.