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

fix(timepicker): fix date overflow on short months #6480

Closed
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
182 changes: 169 additions & 13 deletions src/timepicker/testing/timepicker.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ function getDebugElements(fixture: any, selector: string) {
return fixture.debugElement.queryAll(By.css(selector));
}

function testTime(hours?: number, minutes?: number, seconds?: number) {
const time = new Date();
time.setHours(hours || 0);
time.setMinutes(minutes || 0);
time.setSeconds(seconds || 0);
function testDateTime(date: Date, hours?: number, minutes?: number, seconds?: number) {
const datetime = new Date(date);
datetime.setHours(hours || 0);
datetime.setMinutes(minutes || 0);
datetime.setSeconds(seconds || 0);

return time;
return datetime;
}

function testTime(hours?: number, minutes?: number, seconds?: number) {
return testDateTime(new Date(), hours, minutes, seconds);
}

describe('Component: TimepickerComponent', () => {
Expand Down Expand Up @@ -1076,7 +1080,7 @@ describe('Component: TimepickerComponent', () => {
component.updateHours(inputHours);

expect(component.invalidHours).toEqual(false);
expect(component._updateTime).toHaveBeenCalled();
expect(component._updateTime).toHaveBeenCalled();
});

it('should clear model if minute input is invalid', () => {
Expand Down Expand Up @@ -1214,7 +1218,6 @@ describe('Component: TimepickerComponent', () => {
it('should use \'ss\' for seconds placeholder', () => {
expect(inputSeconds.getAttribute('placeholder')).toEqual('ss');
});

});

describe('date part', () => {
Expand Down Expand Up @@ -1289,7 +1292,6 @@ describe('Component: TimepickerComponent', () => {
// Still compare epoch value for millisecond precision
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});

}));

it('should preserve day when hour crosses down from 00 to 23', fakeAsync(() => {
Expand Down Expand Up @@ -1334,7 +1336,6 @@ describe('Component: TimepickerComponent', () => {
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});

}));

// Case for #3139
Expand Down Expand Up @@ -1389,7 +1390,6 @@ describe('Component: TimepickerComponent', () => {
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});

}));

// Case for #3139
Expand Down Expand Up @@ -1443,7 +1443,6 @@ describe('Component: TimepickerComponent', () => {
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});

}));

// Case for #3139
Expand Down Expand Up @@ -1506,7 +1505,6 @@ describe('Component: TimepickerComponent', () => {
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});

}));

// Case for #3139
Expand Down Expand Up @@ -1568,8 +1566,166 @@ describe('Component: TimepickerComponent', () => {
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});
}));

it('should preserve day when hour crosses down from 00 to 23 on the 1st of a month that is shorter than the preceeding month', fakeAsync(() => {
const hourA = 0;
const hourAstr = '00';

let componentDateTime: Date;
component.registerOnChange((newDateTime: Date) => {
componentDateTime = newDateTime;

return newDateTime;
});

// @ts-ignore
expect(componentDateTime).toBeUndefined();

const testedDate = new Date(2000, 1, 1);
const testedTime = testDateTime(testedDate, hourA);
component.writeValue(testedTime);

fixture.detectChanges();

// @ts-ignore
if (!componentDateTime) {
return expect(void 0).toBeDefined();
}

expect(componentDateTime.getHours()).toBe(hourA);
expect(inputHours.value).toBe(hourAstr);

// Record date part before changing hour
const expectedDate = _getDateOnly(componentDateTime);

fireEvent(buttngOnChangess[3], 'click'); // Hour decrement button
const hourB = 23;
const hourBstr = '23';

fixture.detectChanges();
fixture.whenStable().then(() => {
expect(componentDateTime.getHours()).toBe(hourB);
expect(inputHours.value).toBe(hourBstr);

const actualDate = _getDateOnly(componentDateTime);
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});
}));

// Case for #3139
it('should preserve day when minutes cross down from 00:01 to 23:56 on the 1st of a month that is shorter than the preceeding month', fakeAsync(() => {
const hourA = 0;
const hourAstr = '00';
const minutesA = 1;
const minutesAstr = '01';

let componentDateTime: Date;
component.registerOnChange((newDateTime: Date) => {
componentDateTime = newDateTime;

return newDateTime;
});
// @ts-ignore
expect(componentDateTime).toBeUndefined();

const testedDate = new Date(2000, 1, 1);
const testedTime = testDateTime(testedDate, hourA, minutesA);
component.writeValue(testedTime);

fixture.detectChanges();

// @ts-ignore
if (!componentDateTime) {
return expect(void 0).toBeDefined();
}

expect(componentDateTime.getHours()).toBe(hourA);
expect(inputHours.value).toBe(hourAstr);
expect(componentDateTime.getMinutes()).toBe(minutesA);
expect(inputMinutes.value).toBe(minutesAstr);

// Record date part before changing hour
const expectedDate = _getDateOnly(componentDateTime);

fireEvent(buttngOnChangess[4], 'click'); // Minutes decrement button
const hourB = 23;
const hourBstr = '23';
const minutesB = 56;
const minutesBstr = '56';

fixture.detectChanges();
fixture.whenStable().then(() => {
expect(componentDateTime.getHours()).toBe(hourB);
expect(inputHours.value).toBe(hourBstr);
expect(componentDateTime.getMinutes()).toBe(minutesB);
expect(inputMinutes.value).toBe(minutesBstr);

const actualDate = _getDateOnly(componentDateTime);
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});
}));

it('should preserve day when seconds cross down from 00:00:01 to 23:59:51 on the 1st of a month that is shorter than the preceeding month', fakeAsync(() => {
const hourA = 0;
const hourAstr = '00';
const minutesA = 0;
const minutesAstr = '00';
const secondsA = 1;
const secondsAstr = '01';

let componentDateTime: Date;
component.registerOnChange((newDateTime: Date) => {
componentDateTime = newDateTime;

return newDateTime;
});
// @ts-ignore
expect(componentDateTime).toBeUndefined();

const testedDate = new Date(2000, 1, 1);
const testedTime = testDateTime(testedDate, hourA, minutesA, secondsA);
component.writeValue(testedTime);

fixture.detectChanges();
// @ts-ignore
if (!componentDateTime) {
return expect(void 0).toBeDefined();
}

expect(componentDateTime.getHours()).toBe(hourA);
expect(inputHours.value).toBe(hourAstr);
expect(componentDateTime.getMinutes()).toBe(minutesA);
expect(inputMinutes.value).toBe(minutesAstr);
expect(componentDateTime.getSeconds()).toBe(secondsA);
expect(inputSeconds.value).toBe(secondsAstr);

// Record date part before changing hour
const expectedDate = _getDateOnly(componentDateTime);

fireEvent(buttngOnChangess[5], 'click'); // Seconds decrement button
const hourB = 23;
const hourBstr = '23';
const minutesB = 59;
const minutesBstr = '59';
const secondsB = 51;
const secondsBstr = '51';

fixture.detectChanges();
fixture.whenStable().then(() => {
expect(componentDateTime.getHours()).toBe(hourB);
expect(inputHours.value).toBe(hourBstr);
expect(componentDateTime.getMinutes()).toBe(minutesB);
expect(inputMinutes.value).toBe(minutesBstr);
expect(componentDateTime.getSeconds()).toBe(secondsB);
expect(inputSeconds.value).toBe(secondsBstr);

const actualDate = _getDateOnly(componentDateTime);
expect(actualDate.toString()).toBe(expectedDate.toString());
expect(actualDate.valueOf()).toBe(expectedDate.valueOf());
});
}));
});
});
15 changes: 9 additions & 6 deletions src/timepicker/timepicker.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,22 @@ export function createDate(
minutes: number,
seconds: number
): Date {
const year = value.getFullYear();
const month = value.getMonth();
const day = value.getDate();

const newValue = new Date(
value.getFullYear(),
value.getMonth(),
value.getDate(),
year,
month,
day,
hours,
minutes,
seconds,
value.getMilliseconds()
);

// #3139 ensure date part remains unchanged
newValue.setFullYear(value.getFullYear());
newValue.setMonth(value.getMonth());
newValue.setDate(value.getDate());
newValue.setFullYear(year, month, day);

return newValue;
}
Expand Down
Loading