Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set timeZone for DatePicker from Angular 2?

Is there any way to set the time zone on the datepicker from angular material 2?

Component:

  <mat-form-field>
    <input matInput [matDatepicker]="picker" placeholder="Choose a date">
    <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
  </mat-form-field>
like image 250
Miguel Frias Avatar asked Oct 25 '17 15:10

Miguel Frias


2 Answers

To set the timezone on the datepicker you need to create a custom DateAdapter as mentioned by @lee-richardson.

The following implementation is working on angular 11.0.0.

https://stackblitz.com/edit/angular-zbmhw5-n955ki

How I created this stackblitz:

  1. I forked the stackblitz provided by the Angular Material documentation illustrating the basic datepicker

  2. I Installed the moment-timezone dependency

  3. I Created a custom-moment-date-adapter.ts and inherit from MomentDateAdapter provided by Angular/Component. Then I have overrided createDate, deserialize, parse and today methods.

For the exemple I hardcoded the timezone but you can adapt the code to get the timezone from where you like.

import { Inject, Injectable, Optional } from "@angular/core";
import { MomentDateAdapter } from "@angular/material-moment-adapter";
import { MAT_DATE_LOCALE } from "@angular/material/core";
import moment from "moment-timezone";

@Injectable()
export class CustomMomentDateAdapter extends MomentDateAdapter {
  constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string) {
    super(dateLocale);
  }

  static TIMEZONE = "Asia/Sakhalin";

  createDate(year: number, month: number, date: number): moment.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.`);
    }

    const monthString = ("0" + (month + 1)).slice(-2);
    const yearSting = ("0" + date).slice(-2);
    const dateString = `${year}-${monthString}-${yearSting} 00:00`;
    const result = moment.tz(dateString, CustomMomentDateAdapter.TIMEZONE);

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

  /**
   * Returns the given value if given a valid Moment or null. Deserializes valid ISO 8601 strings
   * (https://www.ietf.org/rfc/rfc3339.txt) and valid Date objects into valid Moments and empty
   * string into null. Returns an invalid date for all other values.
   */
  deserialize(value: any): moment.Moment | null {
    let date;
    if (value instanceof Date) {
      date = this._createMoment2(value).locale(this.locale);
    } else if (this.isDateInstance(value)) {
      // Note: assumes that cloning also sets the correct locale.
      return this.clone(value);
    }
    if (typeof value === "string") {
      if (!value) {
        return null;
      }
      date = this._createMoment2(value, moment.ISO_8601).locale(this.locale);
    }
    if (date && this.isValid(date)) {
      return this._createMoment2(date).locale(this.locale);
    }
    return super.deserialize(value);
  }

  parse(value: any, parseFormat: string | string[]): moment.Moment | null {
    if (value && typeof value === "string") {
      return this._createMoment2(value, parseFormat, this.locale);
    }
    return value ? this._createMoment2(value).locale(this.locale) : null;
  }

  today(): moment.Moment {
    return moment()
      .utc()
      .tz(CustomMomentDateAdapter.TIMEZONE)
      .local(this.locale);
  }

  /** Creates a Moment instance while respecting the current UTC settings. */
  private _createMoment2(
    date: moment.MomentInput,
    format?: moment.MomentFormatSpecification,
    locale?: string
  ): moment.Moment {
    const date2 = moment(date, format, locale).format("YYYY-MM-DD");
    return moment.tz(date2, CustomMomentDateAdapter.TIMEZONE);
  }
}
  1. In the module (DemoMaterialModule in my stackblitz exemple): 2.1) I Imported MatMomentDateModule 2.2) I Injected the newly created CustomMomentDateAdapter
providers: [{ provide: DateAdapter, useClass: CustomMomentDateAdapter }]
  1. I updated datepicker-overview-example component for the purpose of this exemple.

Other references I used to find this solution:

  • https://material.angular.io/components/datepicker/overview#choosing-a-date-implementation-and-date-format-settings
  • Angular Material custom datepicker format throws error "format.replace is not a function"
like image 131
Yanal-Yves Fargialla Avatar answered Sep 21 '22 10:09

Yanal-Yves Fargialla


Technically I think the correct answer to your question is to create a custom DateAdapter

This is accomplished by subclassing DateAdapter and providing your subclass as the DateAdapter implementation.

However, if the underlying problem is that you're using the MomentDateAdapter and seeing odd time's in your date like 2019-05-01 23:00:00 when you actually clicked May 2, 2019 (which I see when I switch my timezone to London), then the solution is to set the moment date adapter to use UTC like:

{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }

Then you should see May 2, 2019 go through as 2019-05-02 00:00:00, as you would expect.

like image 23
Lee Richardson Avatar answered Sep 23 '22 10:09

Lee Richardson