SchwarzePete
SchwarzePete

Reputation: 33

How to format Time zone for angular-material-components/datetime-picker?

I'm using the angular-material-components/datetime-picker as described here in their doc/demo site - https://h2qutc.github.io/angular-material-components/#/datetimepicker.

How can I get the time zone (non-UTC) in the picked date time value? Preferred value have the format: Feb 10, 2024, 06:00 PM CST.

If I go the NgxMatNativeDateModule I specify the following format ->

const INTL_DATE_INPUT_FORMAT = {
  year: 'numeric',
  month: 'short',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  timeZoneName: 'short'
};

const MAT_DATE_FORMATS: NgxMatDateFormats = {
  parse: {
    dateInput: INTL_DATE_INPUT_FORMAT,
  },
  display: {
    dateInput: INTL_DATE_INPUT_FORMAT,
    monthYearLabel: { year: 'numeric', month: 'short' },
    dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
    monthYearA11yLabel: { year: 'numeric', month: 'long' },
  },

This produces the following in the date picker input but it's in UTC rather than the local time zone which is what I'm looking for. I'm not sure how to get around that.

Native Date Module

Otherwise I can use the NgxMatMomentModule and specify a date format like

export const MOMENT_DATETIME_WITH_SECONDS_FORMAT = 'MM/d/y, h:mm A ZZ';

const CUSTOM_MOMENT_FORMATS: NgxMatDateFormats = {
  parse: {
    dateInput: MOMENT_DATETIME_WITH_SECONDS_FORMAT,
  },
  display: {
    dateInput: MOMENT_DATETIME_WITH_SECONDS_FORMAT,
    monthYearLabel: 'MMM YYYY',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};

That produces the following and displays the time zone as an hour offset but I prefer the string like 'CST'. Is there a way to do that? Do I need to make a custom adapter to do that potentially?

Moment Date Module

Upvotes: 3

Views: 593

Answers (2)

SchwarzePete
SchwarzePete

Reputation: 33

As an update to this, ferhado's answer got me on the right track and I eventually ended up going with a custom adapter like the following.

    @Injectable()
    export class CustomDateAdapter extends NgxMatNativeDateAdapter {
    
      constructor(@Inject(MAT_DATE_LOCALE) matDateLocal: string, platform: Platform) {
        super(matDateLocal, platform);
      }
     override format(date: Date, displayFormat: any): string {
       let result = date.toDateString();
       const dateString = date.toLocaleDateString(this.locale, { timeZoneName: 'short' });
       const shortTimeZoneString = dateString.split(' ')[1];
       switch (displayFormat) {
         case 'DD/MM/YYYY':
           result = new DatePipe('en-US').transform(date, 'short') + ' ' + shortTimeZoneString;
           break;
        default:
          result = '';
      }
    
      return result;
    }

This will produce a date string for the input looking like 3/19/24, 12:00 PM CST.

Still working on how to get the picker itself to produce dates in a particular time zone that is not the server's.

Upvotes: 0

ferhado
ferhado

Reputation: 2604

Here is a copy of my custom Date adapter:

import { DatePipe } from '@angular/common';
import { NativeDateAdapter } from '@angular/material/core';

const USE_TIMEZONE = 'UTC';

function range<T>(length: number, valueFunction: (index: number) => T): T[] {
  return Array.from({ length }, (_, i) => valueFunction(i));
}

function createDtf(locale: string, options: Intl.DateTimeFormatOptions) {
  return new Intl.DateTimeFormat(locale, {
    ...options,
    timeZone: USE_TIMEZONE,
  });
}

export class AppDateAdapter extends NativeDateAdapter {
  private formatDate(dtf: Intl.DateTimeFormat, date: Date): string {
    const d = new Date(
      Date.UTC(
        date.getFullYear(),
        date.getMonth(),
        date.getDate(),
        date.getHours(),
        date.getMinutes(),
        date.getSeconds(),
        date.getMilliseconds()
      )
    );
    return dtf.format(d);
  }

  override getFirstDayOfWeek(): number {
    return 1;
  }

  override getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
    const dtf = createDtf(this.locale, { weekday: 'short' });
    return range(7, (i) => this.formatDate(dtf, new Date(2017, 0, i + 1)));
  }

  override getDateNames(): string[] {
    const dtf = createDtf(this.locale, { day: '2-digit' });
    return range(31, (i) => this.formatDate(dtf, new Date(2017, 0, i + 1)));
  }

  override format(date: any, displayFormat: any): any {
    return new DatePipe(this.locale).transform(date, displayFormat);
  }
}

import { DEFAULT_CURRENCY_CODE, LOCALE_ID, Provider } from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MAT_DATE_LOCALE,
} from '@angular/material/core';

// German
import localeDe from '@angular/common/locales/de';
import localeDeExtra from '@angular/common/locales/extra/de';

import { registerLocaleData } from '@angular/common';
registerLocaleData(localeDe, localeDeExtra);

import { AppDateAdapter } from './locale.adapter';

export function provideLocaleConfig(): Provider[] {
  return [
    {
      provide: LOCALE_ID,
      useValue: 'de-DE',
    },

    {
      provide: MAT_DATE_FORMATS,
      useValue: {
        parse: { dateInput: 'mediumDate' },
        display: {
          dateInput: 'mediumDate',
          monthLabel: 'MMMM',
          monthYearLabel: 'MMM YYYY',
          dateA11yLabel: 'LL',
          monthYearA11yLabel: 'MMMM YYYY',
        },
      },
    },
    {
      provide: DEFAULT_CURRENCY_CODE,
      useValue: '€',
    },
    {
      provide: DateAdapter,
      useClass: AppDateAdapter ,
      deps: [MAT_DATE_LOCALE],
    },
  ];
}
// app.config.ts in angular v17+

export const appConfig: ApplicationConfig = {
  providers: [
    //...
    provideLocaleConfig()
    //...
  ],
};

For older version you can just provide it in same way in app.module:

providers: [
    //...
    provideLocaleConfig()
    //...
  ],

Upvotes: 2

Related Questions