Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date adapter changes #29799

Merged
merged 6 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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});
mmalerba marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
mmalerba marked this conversation as resolved.
Show resolved Hide resolved
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',
},
};
Loading
Loading