fix(common): date is not correctly formatted when year is between 0 and 99 (#40448)

use setFullYear method when parsing date to avoid javascript date factory behaviour

Fixes #40377

PR Close #40448
This commit is contained in:
abarghoud 2021-01-15 11:58:04 +01:00 committed by Alex Rickabaugh
parent 8f2260a073
commit 03f0b157c1
3 changed files with 73 additions and 5 deletions

View File

@ -101,6 +101,38 @@ export function formatDate(
return text;
}
/**
* Create a new Date object with the given date value, and the time set to midnight.
*
* We cannot use `new Date(year, month, date)` because it maps years between 0 and 99 to 1900-1999.
* See: https://github.com/angular/angular/issues/40377
*
* Note that this function returns a Date object whose time is midnight in the current locale's
* timezone. In the future we might want to change this to be midnight in UTC, but this would be a
* considerable breaking change.
*/
function createDate(year: number, month: number, date: number): Date {
// The `newDate` is set to midnight (UTC) on January 1st 1970.
// - In PST this will be December 31st 1969 at 4pm.
// - In GMT this will be January 1st 1970 at 1am.
// Note that they even have different years, dates and months!
const newDate = new Date(0);
// `setFullYear()` allows years like 0001 to be set correctly. This function does not
// change the internal time of the date.
// Consider calling `setFullYear(2019, 8, 20)` (September 20, 2019).
// - In PST this will now be September 20, 2019 at 4pm
// - In GMT this will now be September 20, 2019 at 1am
newDate.setFullYear(year, month, date);
// We want the final date to be at local midnight, so we reset the time.
// - In PST this will now be September 20, 2019 at 12am
// - In GMT this will now be September 20, 2019 at 12am
newDate.setHours(0, 0, 0);
return newDate;
}
function getNamedFormat(locale: string, format: string): string {
const localeId = getLocaleId(locale);
NAMED_FORMATS[localeId] = NAMED_FORMATS[localeId] || {};
@ -362,13 +394,13 @@ function timeZoneGetter(width: ZoneWidth): DateFormatter {
const JANUARY = 0;
const THURSDAY = 4;
function getFirstThursdayOfYear(year: number) {
const firstDayOfYear = (new Date(year, JANUARY, 1)).getDay();
return new Date(
const firstDayOfYear = createDate(year, JANUARY, 1).getDay();
return createDate(
year, 0, 1 + ((firstDayOfYear <= THURSDAY) ? THURSDAY : THURSDAY + 7) - firstDayOfYear);
}
function getThursdayThisWeek(datetime: Date) {
return new Date(
return createDate(
datetime.getFullYear(), datetime.getMonth(),
datetime.getDate() + (THURSDAY - datetime.getDay()));
}
@ -720,7 +752,7 @@ export function toDate(value: string|number|Date): Date {
is applied.
Note: ISO months are 0 for January, 1 for February, ... */
const [y, m = 1, d = 1] = value.split('-').map((val: string) => +val);
return new Date(y, m - 1, d);
return createDate(y, m - 1, d);
}
const parsedNb = parseFloat(value);

View File

@ -114,7 +114,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* | | O, OO & OOO | Short localized GMT format | GMT-8 |
* | | OOOO | Long localized GMT format | GMT-08:00 |
*
* Note that timezone correction is not applied to an ISO string that has no time component, such as "2016-09-19"
*
* ### Format examples
*

View File

@ -347,6 +347,32 @@ describe('Format date', () => {
.toEqual('10:14 AM');
});
// The following test is disabled because backwards compatibility requires that date-only ISO
// strings are parsed with the local timezone.
// it('should create UTC date objects when an ISO string is passed with no time components',
// () => {
// expect(formatDate('2019-09-20', `MMM d, y, h:mm:ss a ZZZZZ`, ɵDEFAULT_LOCALE_ID))
// .toEqual('Sep 20, 2019, 12:00:00 AM Z');
// });
// This test is to ensure backward compatibility for parsing date-only ISO strings.
it('should create local timezone date objects when an ISO string is passed with no time components',
() => {
// Dates created with individual components are evaluated against the local timezone. See
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#Individual_date_and_time_component_values
const localDate = new Date(2019, 8, 20, 0, 0, 0, 0);
expect(formatDate('2019-09-20', `MMM d, y, h:mm:ss a ZZZZZ`, ɵDEFAULT_LOCALE_ID))
.toEqual(formatDate(localDate, `MMM d, y, h:mm:ss a ZZZZZ`, ɵDEFAULT_LOCALE_ID));
});
it('should create local timezone date objects when an ISO string is passed with time components',
() => {
const localDate = new Date(2019, 8, 20, 0, 0, 0, 0);
expect(formatDate('2019-09-20T00:00:00', `MMM d, y, h:mm:ss a ZZZZZ`, ɵDEFAULT_LOCALE_ID))
.toEqual(formatDate(localDate, `MMM d, y, h:mm:ss a ZZZZZ`, ɵDEFAULT_LOCALE_ID));
});
it('should remove bidi control characters',
() => expect(formatDate(date, 'MM/dd/yyyy', ɵDEFAULT_LOCALE_ID)!.length).toEqual(10));
@ -389,6 +415,17 @@ describe('Format date', () => {
expect(formatDate('2013-12-29', 'YYYY', 'en')).toEqual('2014');
expect(formatDate('2010-01-02', 'YYYY', 'en')).toEqual('2009');
expect(formatDate('2010-01-04', 'YYYY', 'en')).toEqual('2010');
expect(formatDate('0049-01-01', 'YYYY', 'en')).toEqual('0048');
expect(formatDate('0049-01-04', 'YYYY', 'en')).toEqual('0049');
});
// https://github.com/angular/angular/issues/40377
it('should format date with year between 0 and 99 correctly', () => {
expect(formatDate('0098-01-11', 'YYYY', ɵDEFAULT_LOCALE_ID)).toEqual('0098');
expect(formatDate('0099-01-11', 'YYYY', ɵDEFAULT_LOCALE_ID)).toEqual('0099');
expect(formatDate('0100-01-11', 'YYYY', ɵDEFAULT_LOCALE_ID)).toEqual('0100');
expect(formatDate('0001-01-11', 'YYYY', ɵDEFAULT_LOCALE_ID)).toEqual('0001');
expect(formatDate('0000-01-11', 'YYYY', ɵDEFAULT_LOCALE_ID)).toEqual('0000');
});
});
});