Last active
October 26, 2019 00:20
-
-
Save rovercoder/d223ff977194bc8ca389d853ad1ac81c to your computer and use it in GitHub Desktop.
ng-pick-datetime: date-fns implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { NgModule } from '@angular/core'; | |
| import { FormsModule } from '@angular/forms'; | |
| import { BrowserModule } from '@angular/platform-browser'; | |
| import { NoopAnimationsModule } from '@angular/platform-browser/animations'; | |
| import { DateTimeAdapter, OwlDateTimeModule, OwlNativeDateTimeModule, OWL_DATE_TIME_FORMATS } from 'ng-pick-datetime'; | |
| import { DateFnsDateTimeAdapter } from 'src/@global/date/date-fns-date-time-adapter.class'; | |
| import { AppComponent } from './app.component'; | |
| import { DateExampleComponent } from './date-example/date-example.component'; | |
| const DATEFNS_FORMATS_EN_LOCALE = { | |
| parseInput: 'dd/MM/yyyy HH:mm || dd/MM/yyyy', // multiple date input types separated by || | |
| fullPickerInput: 'dd/MM/yyyy HH:mm', | |
| datePickerInput: 'dd/MM/yyyy', | |
| timePickerInput: 'HH:mm', | |
| monthYearLabel: 'MMM yyyy', | |
| dateA11yLabel: 'dd/MM/yyyy', | |
| monthYearA11yLabel: 'MMMM yyyy', | |
| }; | |
| @NgModule({ | |
| declarations: [ | |
| AppComponent, | |
| DateExampleComponent | |
| ], | |
| imports: [ | |
| BrowserModule, | |
| FormsModule, | |
| NoopAnimationsModule, | |
| OwlDateTimeModule, | |
| OwlNativeDateTimeModule | |
| ], | |
| providers: [ | |
| { provide: OWL_DATE_TIME_LOCALE, useValue: 'en-GB' }, // default: 'en-US' | |
| { provide: DateTimeAdapter, useClass: DateFnsDateTimeAdapter }, | |
| { provide: OWL_DATE_TIME_FORMATS, useValue: DATEFNS_FORMATS_EN_LOCALE }, | |
| { provide: OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS, useValue: <OwlDateFnsDateTimeAdapterOptions>{ locale: enGB } } // default: enUS. | |
| ], | |
| bootstrap: [AppComponent] | |
| }) | |
| export class AppModule { } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <input [(ngModel)]="testDate" [owlDateTime]="dt2" placeholder="Date Time"> | |
| <button [owlDateTimeTrigger]="dt2"><i class="fa fa-calendar"></i></button> | |
| <owl-date-time #dt2 pickerType="both"></owl-date-time> | |
| {{testDate | date:'long'}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Component } from '@angular/core'; | |
| @Component({ | |
| selector: 'app-date-example', | |
| templateUrl: './date-example.component.html' | |
| }) | |
| export class DateExampleComponent { | |
| testDate = new Date(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * date-fns-date-time-adapter.class | |
| */ | |
| import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; | |
| import { addDays, addMonths, addYears, differenceInCalendarDays, format, getDate, getDay, getDaysInMonth, getHours, getMinutes, getMonth, getSeconds, getTime, getYear, isDate, isEqual, isSameDay, isValid, Locale, parse, parseISO, setHours, setMinutes, setSeconds, toDate } from 'date-fns'; | |
| import { enUS } from 'date-fns/locale'; | |
| import { DateTimeAdapter, OWL_DATE_TIME_LOCALE } from 'ng-pick-datetime'; | |
| /** | |
| * Matches strings that have the form of a valid RFC 3339 string | |
| * (https://tools.ietf.org/html/rfc3339). Note that the string may not actually be a valid date | |
| * because the regex will match strings an with out of bounds month, date, etc. | |
| */ | |
| const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))?)?$/; | |
| export interface DateFnsOptions { | |
| locale?: Locale; | |
| weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; | |
| firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7; | |
| useAdditionalWeekYearTokens?: boolean; | |
| useAdditionalDayOfYearTokens?: boolean; | |
| } | |
| /** Configurable options for {@see DateFnsDateAdapter}. */ | |
| export interface OwlDateFnsDateTimeAdapterOptions extends DateFnsOptions { | |
| getInstance: Function; | |
| } | |
| export const getOwlDateFnsDateTimeAdapterOptionsInstance = function (): DateFnsOptions { | |
| var _this = Object.assign({}, this); | |
| delete _this.getInstance; | |
| return _this; | |
| }; | |
| /** InjectionToken for date-fns date adapter to configure options. */ | |
| export const OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS = new InjectionToken<OwlDateFnsDateTimeAdapterOptions>( | |
| 'OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS', { | |
| providedIn: 'root', | |
| factory: OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS_FACTORY | |
| } | |
| ); | |
| /** @docs-private */ | |
| export function OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS_FACTORY(): OwlDateFnsDateTimeAdapterOptions { | |
| return { | |
| locale: enUS, | |
| getInstance: getOwlDateFnsDateTimeAdapterOptionsInstance | |
| }; | |
| } | |
| /** Creates an array and fills it with values. */ | |
| function range<T>(length: number, valueFunction: (index: number) => T): T[] { | |
| const valuesArray = Array(length); | |
| for (let i = 0; i < length; i++) { | |
| valuesArray[i] = valueFunction(i); | |
| } | |
| return valuesArray; | |
| } | |
| @Injectable() | |
| export class DateFnsDateTimeAdapter extends DateTimeAdapter<Date> { | |
| private _localeData: { | |
| longMonths: string[], | |
| shortMonths: string[], | |
| narrowMonths: string[], | |
| longDaysOfWeek: string[], | |
| shortDaysOfWeek: string[], | |
| narrowDaysOfWeek: string[], | |
| dates: string[], | |
| }; | |
| constructor(@Optional() @Inject(OWL_DATE_TIME_LOCALE) private owlDateTimeLocale: string, | |
| @Optional() @Inject(OWL_DATEFNS_DATE_TIME_ADAPTER_OPTIONS) private options: OwlDateFnsDateTimeAdapterOptions) { | |
| super(); | |
| this.setLocale(owlDateTimeLocale, options); | |
| } | |
| public setLocale(locale: string, options?: OwlDateFnsDateTimeAdapterOptions) { | |
| super.setLocale(locale); | |
| if (!!options) { | |
| if (!this.options) { | |
| this.options = options; | |
| } else { | |
| for (const key in options) { | |
| this.options[key] = options[key]; | |
| } | |
| } | |
| } | |
| this.options.getInstance = getOwlDateFnsDateTimeAdapterOptionsInstance; | |
| const dateFnsLocaleData = this.options.locale.localize; | |
| this._localeData = { | |
| longMonths: range(12, (i) => dateFnsLocaleData.month(i, { width: 'wide' })), | |
| shortMonths: range(12, (i) => dateFnsLocaleData.month(i, { width: 'abbreviated' })), | |
| narrowMonths: range(12, (i) => dateFnsLocaleData.month(i, { width: 'narrow' })), | |
| longDaysOfWeek: range(7, (i) => dateFnsLocaleData.day(i, { width: 'wide' })), | |
| shortDaysOfWeek: range(7, (i) => dateFnsLocaleData.day(i, { width: 'abbreviated' })), | |
| narrowDaysOfWeek: range(7, (i) => dateFnsLocaleData.day(i, { width: 'short' })), | |
| dates: range(31, (i) => String(this.getDate(this.createDate(2017, 0, i + 1)))), | |
| }; | |
| } | |
| public getYear(date: Date): number { | |
| return getYear(this.clone(date)); | |
| } | |
| public getMonth(date: Date): number { | |
| return getMonth(this.clone(date)); | |
| } | |
| public getDay(date: Date): number { | |
| return getDay(this.clone(date)); | |
| } | |
| public getDate(date: Date): number { | |
| return getDate(this.clone(date)); | |
| } | |
| public getHours(date: Date): number { | |
| return getHours(this.clone(date)); | |
| } | |
| public getMinutes(date: Date): number { | |
| return getMinutes(this.clone(date)); | |
| } | |
| public getSeconds(date: Date): number { | |
| return getSeconds(this.clone(date)); | |
| } | |
| public getTime(date: Date): number { | |
| return getTime(this.clone(date)); | |
| } | |
| public getNumDaysInMonth(date: Date): number { | |
| return getDaysInMonth(this.clone(date)); | |
| } | |
| public differenceInCalendarDays(dateLeft: Date, dateRight: Date): number { | |
| return differenceInCalendarDays(dateLeft, dateRight); | |
| } | |
| public getYearName(date: Date): string { | |
| return String(format(date, 'yyyy', this.options.getInstance())); | |
| } | |
| public getMonthNames(style: 'long' | 'short' | 'narrow'): string[] { | |
| switch (style) { | |
| case 'narrow': return this._localeData.narrowMonths; | |
| case 'short': return this._localeData.shortMonths; | |
| default: return this._localeData.longMonths; | |
| } | |
| } | |
| public getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] { | |
| switch (style) { | |
| case 'narrow': return this._localeData.narrowDaysOfWeek; | |
| case 'short': return this._localeData.shortDaysOfWeek; | |
| default: return this._localeData.longDaysOfWeek; | |
| } | |
| } | |
| public getDateNames(): string[] { | |
| return this._localeData.dates; | |
| } | |
| public toIso8601(date: Date): string { | |
| return this.clone(date).toISOString(); | |
| } | |
| public isEqual(dateLeft: Date, dateRight: Date): boolean { | |
| if (dateLeft && dateRight) { | |
| return isEqual(this.clone(dateLeft), this.clone(dateRight)); | |
| } | |
| return dateLeft === dateRight; | |
| } | |
| public isSameDay(dateLeft: Date, dateRight: Date): boolean { | |
| if (dateLeft && dateRight) { | |
| return isSameDay(this.clone(dateLeft), this.clone(dateRight)); | |
| } | |
| return dateLeft === dateRight; | |
| } | |
| public isValid(date: Date): boolean { | |
| return isValid(this.clone(date)); | |
| } | |
| public invalid(): Date { | |
| return new Date(NaN); | |
| } | |
| public isDateInstance(obj: any): boolean { | |
| return isDate(obj); | |
| } | |
| public addCalendarYears(date: Date, amount: number): Date { | |
| return addYears(this.clone(date), amount); | |
| } | |
| public addCalendarMonths(date: Date, amount: number): Date { | |
| return addMonths(this.clone(date), amount); | |
| } | |
| public addCalendarDays(date: Date, amount: number): Date { | |
| return addDays(this.clone(date), amount); | |
| } | |
| public setHours(date: Date, amount: number): Date { | |
| return setHours(this.clone(date), amount); | |
| } | |
| public setMinutes(date: Date, amount: number): Date { | |
| return setMinutes(this.clone(date), amount); | |
| } | |
| public setSeconds(date: Date, amount: number): Date { | |
| return setSeconds(this.clone(date), amount); | |
| } | |
| public createDate(year: number, month: number, date: number): Date; | |
| public createDate(year: number, month: number, date: number, hours: number = 0, minutes: number = 0, seconds: number = 0): Date { | |
| if (month < 0 || month > 11) { | |
| throw Error(`Invalid month index "${month}". Month index should be between 0 and 11.`); | |
| } | |
| if (date < 1) { | |
| throw Error(`Invalid date "${date}". Date should be greater than 0.`); | |
| } | |
| if (hours < 0 || hours > 23) { | |
| throw Error(`Invalid hours "${hours}". Hours should be between 0 and 23.`); | |
| } | |
| if (minutes < 0 || minutes > 59) { | |
| throw Error(`Invalid minutes "${minutes}". Minutes should between 0 and 59.`); | |
| } | |
| if (seconds < 0 || seconds > 59) { | |
| throw Error(`Invalid seconds "${seconds}". Seconds should be between 0 and 59.`); | |
| } | |
| const result = this.createDateWithOverflow(year, month, date, hours, minutes, seconds); | |
| // If the result isn't valid, the date must have been out of bounds for this month. | |
| if (!this.isValid(result)) { | |
| throw Error(`Invalid date "${date}" for month with index "${month}".`); | |
| } | |
| return result; | |
| } | |
| public clone(date: Date | number | string): Date { | |
| return typeof date === 'string' ? parseISO(date) : toDate(date); | |
| } | |
| public now(): Date { | |
| return new Date(); | |
| } | |
| public format(date: Date, displayFormat: any): string { | |
| date = this.clone(date); | |
| if (!this.isValid(date)) { | |
| throw Error('DateFnsDateTimeAdapter: Cannot format invalid date.'); | |
| } | |
| return format(date, displayFormat, this.options.getInstance()); | |
| } | |
| public parse(value: any, parseFormat: any): Date | null { | |
| if (value && typeof value === 'string' && parseFormat) { | |
| if (typeof parseFormat === 'string') { | |
| parseFormat = parseFormat.split('||'); | |
| } | |
| if (!Array.isArray(parseFormat)) { | |
| throw Error('DateFnsDateTimeAdapter: Invalid date parse format string set: ' + JSON.stringify(parseFormat)); | |
| } | |
| for (let i = 0; i < parseFormat.length; i++) { | |
| const parsedDate = parse(value, parseFormat[i].trim(), new Date(), this.options.getInstance()); | |
| if (this.isValid(parsedDate)) { | |
| return parsedDate; | |
| } | |
| } | |
| } | |
| return value ? this.clone(value) : null; | |
| } | |
| /** | |
| * Returns the given value if given a valid DateFns or null. Deserializes valid ISO 8601 strings | |
| * (https://www.ietf.org/rfc/rfc3339.txt) and valid Date objects into valid DateFnss and empty | |
| * string into null. Returns an invalid date for all other values. | |
| */ | |
| deserialize(value: any): Date | null { | |
| let date; | |
| if (typeof value === 'string') { | |
| if (!value) { | |
| return null; | |
| } | |
| date = this.parse(value, null); | |
| } else { | |
| date = this.clone(value); | |
| } | |
| if (date && this.isValid(date)) { | |
| return date; | |
| } | |
| return super.deserialize(value); | |
| } | |
| /** | |
| * Creates a date but allows the month and date to overflow. | |
| * @param {number} year | |
| * @param {number} month | |
| * @param {number} date | |
| * @param {number} hours -- default 0 | |
| * @param {number} minutes -- default 0 | |
| * @param {number} seconds -- default 0 | |
| * @returns The new date, or null if invalid. | |
| * */ | |
| private createDateWithOverflow(year: number, month: number, date: number, hours: number = 0, minutes: number = 0, seconds: number = 0): Date { | |
| const result = new Date(year, month, date, hours, minutes, seconds); | |
| if (year >= 0 && year < 100) { | |
| result.setFullYear(this.getYear(result) - 1900); | |
| } | |
| return result; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@rovercoder Just a note regarding the
parsefunction in your adapter class. When initialising with a ISO 8601 string it was setting the value to null as thedate-fnsparsemethod was returningInvalid Date.I've updated my local copy and tested it now seems to be working well with the following
parseimplementation: