From e7eb56f32943cc1394e71ecf5f430e164ab9af48 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 19 Sep 2024 14:28:44 +0200 Subject: [PATCH] refactor(material-date-fns-adapter): implement new methods Implements the new methods in the `date-fns` adapter. --- .../adapter/date-fns-adapter.spec.ts | 144 ++++++++++++++++-- .../adapter/date-fns-adapter.ts | 43 ++++++ .../adapter/date-fns-formats.ts | 3 + 3 files changed, 180 insertions(+), 10 deletions(-) diff --git a/src/material-date-fns-adapter/adapter/date-fns-adapter.spec.ts b/src/material-date-fns-adapter/adapter/date-fns-adapter.spec.ts index f3fa6fc48b83..dcdc466b2bf2 100644 --- a/src/material-date-fns-adapter/adapter/date-fns-adapter.spec.ts +++ b/src/material-date-fns-adapter/adapter/date-fns-adapter.spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TestBed, waitForAsync} from '@angular/core/testing'; +import {TestBed} from '@angular/core/testing'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; import {Locale} from 'date-fns'; -import {ja, enUS, da, de} from 'date-fns/locale'; +import {ja, enUS, da, de, fi} from 'date-fns/locale'; import {DateFnsModule} from './index'; const JAN = 0, @@ -20,14 +20,11 @@ const JAN = 0, describe('DateFnsAdapter', () => { let adapter: DateAdapter; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [DateFnsModule], - }); - + beforeEach(() => { + TestBed.configureTestingModule({imports: [DateFnsModule]}); adapter = TestBed.inject(DateAdapter); adapter.setLocale(enUS); - })); + }); it('should get year', () => { expect(adapter.getYear(new Date(2017, JAN, 1))).toBe(2017); @@ -452,19 +449,146 @@ describe('DateFnsAdapter', () => { it('should create invalid date', () => { assertValidDate(adapter, adapter.invalid(), false); }); + + it('should get hours', () => { + expect(adapter.getHours(new Date(2024, JAN, 1, 14))).toBe(14); + }); + + it('should get minutes', () => { + expect(adapter.getMinutes(new Date(2024, JAN, 1, 14, 53))).toBe(53); + }); + + it('should get seconds', () => { + expect(adapter.getSeconds(new Date(2024, JAN, 1, 14, 53, 42))).toBe(42); + }); + + it('should set the time of a date', () => { + const target = new Date(2024, JAN, 1, 0, 0, 0); + const result = adapter.setTime(target, 14, 53, 42); + expect(adapter.getHours(result)).toBe(14); + expect(adapter.getMinutes(result)).toBe(53); + expect(adapter.getSeconds(result)).toBe(42); + }); + + it('should throw when passing in invalid hours to setTime', () => { + expect(() => adapter.setTime(adapter.today(), -1, 0, 0)).toThrowError( + 'Invalid hours "-1". Hours value must be between 0 and 23.', + ); + expect(() => adapter.setTime(adapter.today(), 51, 0, 0)).toThrowError( + 'Invalid hours "51". Hours value must be between 0 and 23.', + ); + }); + + it('should throw when passing in invalid minutes to setTime', () => { + expect(() => adapter.setTime(adapter.today(), 0, -1, 0)).toThrowError( + 'Invalid minutes "-1". Minutes value must be between 0 and 59.', + ); + expect(() => adapter.setTime(adapter.today(), 0, 65, 0)).toThrowError( + 'Invalid minutes "65". Minutes value must be between 0 and 59.', + ); + }); + + it('should throw when passing in invalid seconds to setTime', () => { + expect(() => adapter.setTime(adapter.today(), 0, 0, -1)).toThrowError( + 'Invalid seconds "-1". Seconds value must be between 0 and 59.', + ); + expect(() => adapter.setTime(adapter.today(), 0, 0, 65)).toThrowError( + 'Invalid seconds "65". Seconds value must be between 0 and 59.', + ); + }); + + it('should parse a 24-hour time string', () => { + adapter.setLocale(da); + const result = adapter.parseTime('14:52', 'p')!; + expect(result).toBeTruthy(); + expect(adapter.isValid(result)).toBe(true); + expect(adapter.getHours(result)).toBe(14); + expect(adapter.getMinutes(result)).toBe(52); + expect(adapter.getSeconds(result)).toBe(0); + }); + + it('should parse a 12-hour time string', () => { + const result = adapter.parseTime('2:52 PM', 'p')!; + expect(result).toBeTruthy(); + expect(adapter.isValid(result)).toBe(true); + expect(adapter.getHours(result)).toBe(14); + expect(adapter.getMinutes(result)).toBe(52); + expect(adapter.getSeconds(result)).toBe(0); + }); + + it('should parse a padded time string', () => { + const result = adapter.parseTime('03:04:05 AM', 'pp')!; + expect(result).toBeTruthy(); + expect(adapter.isValid(result)).toBe(true); + expect(adapter.getHours(result)).toBe(3); + expect(adapter.getMinutes(result)).toBe(4); + expect(adapter.getSeconds(result)).toBe(5); + }); + + it('should parse a time string that uses dot as a separator', () => { + adapter.setLocale(fi); + const result = adapter.parseTime('14.52', 'p')!; + expect(result).toBeTruthy(); + expect(adapter.isValid(result)).toBe(true); + expect(adapter.getHours(result)).toBe(14); + expect(adapter.getMinutes(result)).toBe(52); + expect(adapter.getSeconds(result)).toBe(0); + }); + + it('should return an invalid date when parsing invalid time string', () => { + expect(adapter.isValid(adapter.parseTime('abc', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('123', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime(' ', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime(true, 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime(undefined, 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('14:52 PM', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('24:05', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('00:61:05', 'p')!)).toBe(false); + expect(adapter.isValid(adapter.parseTime('14:52:78', 'p')!)).toBe(false); + }); + + it('should compare times', () => { + const base = [2024, JAN, 1] as const; + + expect( + adapter.compareTime(new Date(...base, 12, 0, 0), new Date(...base, 13, 0, 0)), + ).toBeLessThan(0); + expect( + adapter.compareTime(new Date(...base, 12, 50, 0), new Date(...base, 12, 51, 0)), + ).toBeLessThan(0); + expect(adapter.compareTime(new Date(...base, 1, 2, 3), new Date(...base, 1, 2, 3))).toBe(0); + expect( + adapter.compareTime(new Date(...base, 13, 0, 0), new Date(...base, 12, 0, 0)), + ).toBeGreaterThan(0); + expect( + adapter.compareTime(new Date(...base, 12, 50, 11), new Date(...base, 12, 50, 10)), + ).toBeGreaterThan(0); + expect( + adapter.compareTime(new Date(...base, 13, 0, 0), new Date(...base, 10, 59, 59)), + ).toBeGreaterThan(0); + }); + + it('should add milliseconds to a date', () => { + const amount = 1234567; + const initial = new Date(2024, JAN, 1, 12, 34, 56); + const result = adapter.addMilliseconds(initial, amount); + expect(result).not.toBe(initial); + expect(result.getTime() - initial.getTime()).toBe(amount); + }); }); describe('DateFnsAdapter with MAT_DATE_LOCALE override', () => { let adapter: DateAdapter; - beforeEach(waitForAsync(() => { + beforeEach(() => { TestBed.configureTestingModule({ imports: [DateFnsModule], providers: [{provide: MAT_DATE_LOCALE, useValue: da}], }); adapter = TestBed.inject(DateAdapter); - })); + }); it('should take the default locale id from the MAT_DATE_LOCALE injection token', () => { const date = adapter.format(new Date(2017, JAN, 2), 'PP'); diff --git a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts index d9306454aa2d..0cef3728d8fc 100644 --- a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts +++ b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts @@ -14,11 +14,16 @@ import { getYear, getDate, getDay, + getHours, + getMinutes, + getSeconds, + set, getDaysInMonth, formatISO, addYears, addMonths, addDays, + addMilliseconds, isValid, isDate, format, @@ -241,4 +246,42 @@ export class DateFnsAdapter extends DateAdapter { invalid(): Date { return new Date(NaN); } + + override setTime(target: Date, hours: number, minutes: number, seconds: number): Date { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + if (hours < 0 || hours > 23) { + throw Error(`Invalid hours "${hours}". Hours value must be between 0 and 23.`); + } + + if (minutes < 0 || minutes > 59) { + throw Error(`Invalid minutes "${minutes}". Minutes value must be between 0 and 59.`); + } + + if (seconds < 0 || seconds > 59) { + throw Error(`Invalid seconds "${seconds}". Seconds value must be between 0 and 59.`); + } + } + + return set(this.clone(target), {hours, minutes, seconds}); + } + + override getHours(date: Date): number { + return getHours(date); + } + + override getMinutes(date: Date): number { + return getMinutes(date); + } + + override getSeconds(date: Date): number { + return getSeconds(date); + } + + override parseTime(value: any, parseFormat: string | string[]): Date | null { + return this.parse(value, parseFormat); + } + + override addMilliseconds(date: Date, amount: number): Date { + return addMilliseconds(date, amount); + } } diff --git a/src/material-date-fns-adapter/adapter/date-fns-formats.ts b/src/material-date-fns-adapter/adapter/date-fns-formats.ts index b1ffeac2885c..fa99a6d4823d 100644 --- a/src/material-date-fns-adapter/adapter/date-fns-formats.ts +++ b/src/material-date-fns-adapter/adapter/date-fns-formats.ts @@ -11,11 +11,14 @@ import {MatDateFormats} from '@angular/material/core'; export const MAT_DATE_FNS_FORMATS: MatDateFormats = { parse: { dateInput: 'P', + timeInput: 'p', }, display: { dateInput: 'P', + timeInput: 'p', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', monthYearA11yLabel: 'LLLL uuuu', + timeOptionLabel: 'p', }, };