Skip to content

Commit

Permalink
refactor(material-date-fns-adapter): implement new methods
Browse files Browse the repository at this point in the history
Implements the new methods in the `date-fns` adapter.
  • Loading branch information
crisbeto committed Sep 30, 2024
1 parent 70cc558 commit e7eb56f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 10 deletions.
144 changes: 134 additions & 10 deletions src/material-date-fns-adapter/adapter/date-fns-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,14 +20,11 @@ const JAN = 0,
describe('DateFnsAdapter', () => {
let adapter: DateAdapter<Date, Locale>;

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);
Expand Down Expand Up @@ -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<Date, Locale>;

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');
Expand Down
43 changes: 43 additions & 0 deletions src/material-date-fns-adapter/adapter/date-fns-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import {
getYear,
getDate,
getDay,
getHours,
getMinutes,
getSeconds,
set,
getDaysInMonth,
formatISO,
addYears,
addMonths,
addDays,
addMilliseconds,
isValid,
isDate,
format,
Expand Down Expand Up @@ -241,4 +246,42 @@ export class DateFnsAdapter extends DateAdapter<Date, Locale> {
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);
}
}
3 changes: 3 additions & 0 deletions src/material-date-fns-adapter/adapter/date-fns-formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};

0 comments on commit e7eb56f

Please sign in to comment.