import { Inject , Injectable , InjectionToken , Optional } from '@angular/core' ; import { DateAdapter , MAT_DATE_LOCALE } from '@angular/material/core' ; import { addDays , addMonths , addYears , format , getDate , getDay , getDaysInMonth , getMonth , getYear , Locale , parse , parseISO , setDay , setMonth , toDate , parseJSON , } from 'date-fns' ; import { zonedTimeToUtc } from 'date-fns-tz' ; import { enUS } from 'date-fns/locale' ; import { NGX_MAT_DATEFNS_LOCALES } from './ngx-mat-datefns-locales' ; export interface NgxDateFnsDateAdapterOptions { useUtc ? : boolean ; } export const NGX_MAT_DATEFNS_DATE_ADAPTER_OPTIONS = new InjectionToken < NgxDateFnsDateAdapterOptions > ( 'NGX_MAT_DATEFNS_DATE_ADAPTER_OPTIONS' , { providedIn : 'root' , factory : NGX_MAT_DATEFNS_DATE_ADAPTER_OPTIONS_FACTORY , } ) ; export function NGX_MAT_DATEFNS_DATE_ADAPTER_OPTIONS_FACTORY ( ) { return { useUtc : false , } ; } function range ( start , end ) { const arr = [ ] ; for ( let i = start ; i <= end ; i ++ ) { arr . push ( i ) ; } return arr ; } const UTC_TIMEZONE = 'UTC' ; @ Injectable ( ) export class NgxDateFnsDateAdapter extends DateAdapter < Date > { private _dateFnsLocale ! : Locale ; private getLocale = ( localeCodeOrLocale ) : Locale => { if ( localeCodeOrLocale && ( localeCodeOrLocale as Locale ) . code ) { return localeCodeOrLocale as Locale ; } if ( ! this . locales || ! this . locales . length ) { throw new Error ( 'locales array does not provided or is empty. Provide it via the NGX_MAT_DATEFNS_LOCALES token.' ) ; } const locale = this . locales . find ( ( item ) => item . code === localeCodeOrLocale ) ; if ( ! locale ) { throw new Error ( `locale '${ localeCodeOrLocale }' does not exist in locales array. Add it to the NGX_MAT_DATEFNS_LOCALES token.` ) ; } return locale ; } ; constructor ( @ Optional ( ) @ Inject ( MAT_DATE_LOCALE ) dateLocale , @ Optional ( ) @ Inject ( NGX_MAT_DATEFNS_LOCALES ) private locales , @ Optional ( ) @ Inject ( NGX_MAT_DATEFNS_DATE_ADAPTER_OPTIONS ) private options ? ) { super ( ) ; if ( ! this . locales || this . locales . length === 0 ) { this . locales = [ enUS ] ; } try { this . setLocale ( dateLocale || enUS ) ; } catch ( err ) { this . setLocale ( enUS ) ; } } setLocale ( locale ) { if ( ! locale ) { throw new Error ( 'setLocale should be called with the string locale code or date-fns Locale object' ) ; } this . _dateFnsLocale = this . getLocale ( locale ) ; super . setLocale ( locale ) ; } addCalendarDays ( date , days ) { return addDays ( date , days ) ; } addCalendarMonths ( date , months ) { return addMonths ( date , months ) ; } addCalendarYears ( date , years ) { return addYears ( date , years ) ; } clone ( date ) { return toDate ( date ) ; } createDate ( year , month , date ) { 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 result = this . _createDateWithOverflow ( year , month , date ) ; if ( result . getMonth ( ) !== month ) { throw Error ( `Invalid date "${ date }" for month with index "${ month }".` ) ; } return result ; } deserialize ( value ) { if ( value ) { if ( typeof value === 'string' ) { if ( this . options ?. useUtc ) { return parseJSON ( value ) ; } return parseISO ( value ) ; } if ( typeof value === 'number' ) { return toDate ( value ) ; } if ( value instanceof Date ) { return this . clone ( value as Date ) ; } return null ; } return null ; } format ( date , displayFormat ) { return format ( date , displayFormat , { locale : this . _dateFnsLocale } ) ; } getDate ( date ) { return getDate ( date ) ; } getDateNames ( ) { return range ( 1 , 31 ) . map ( ( day ) => String ( day ) ) ; } getDayOfWeek ( date ) { return getDay ( date ) ; } getDayOfWeekNames ( style ) { const map = { long : 'EEEE' , short : 'EEE' , narrow : 'EEEEE' , } ; const formatStr = map [ style ] ; const date = new Date ( ) ; return range ( 0 , 6 ) . map ( ( day ) => format ( setDay ( date , day ) , formatStr , { locale : this . _dateFnsLocale , } ) ) ; } getFirstDayOfWeek ( ) { return this . _dateFnsLocale . options ?. weekStartsOn ?? 0 ; } getMonth ( date ) { return getMonth ( date ) ; } getMonthNames ( style ) { const map = { long : 'LLLL' , short : 'LLL' , narrow : 'LLLLL' , } ; const formatStr = map [ style ] ; const date = new Date ( ) ; return range ( 0 , 11 ) . map ( ( month ) => format ( setMonth ( date , month ) , formatStr , { locale : this . _dateFnsLocale , } ) ) ; } getNumDaysInMonth ( date ) { return getDaysInMonth ( date ) ; } getYear ( date ) { return getYear ( date ) ; } getYearName ( date ) { return format ( date , 'yyyy' , { locale : this . _dateFnsLocale , } ) ; } invalid ( ) { return new Date ( NaN ) ; } isDateInstance ( obj ) { return obj instanceof Date ; } isValid ( date ) { return date instanceof Date && ! isNaN ( date . getTime ( ) ) ; } parse ( value , parseFormat ) { if ( value ) { if ( typeof value === 'string' ) { if ( this . options ?. useUtc ) { const d = parse ( value . trim ( ) , parseFormat , new Date ( ) , { locale : this . _dateFnsLocale , } ) ; return zonedTimeToUtc ( d , UTC_TIMEZONE ) ; } return parse ( value . trim ( ) , parseFormat , new Date ( ) , { locale : this . _dateFnsLocale , } ) ; } if ( typeof value === 'number' ) { return toDate ( value ) ; } if ( value instanceof Date ) { return this . clone ( value as Date ) ; } return null ; } return null ; } toIso8601 ( date ) { return date . toISOString ( ) ; } today ( ) { const d = new Date ( ) ; return this . _createDateWithOverflow ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) ; } /** Creates a date but allows the month and date to overflow. */ private _createDateWithOverflow ( year , month , date ) { const result = this . _createDateInternal ( year , month , date ) ; if ( year >= 0 && year < 100 ) { result . setFullYear ( this . getYear ( result ) - 1900 ) ; } return result ; } private _createDateInternal ( year , month , date ) { if ( this . options ?. useUtc ) { return zonedTimeToUtc ( new Date ( year , month , date ) , UTC_TIMEZONE ) ; } return new Date ( year , month , date ) ; } }