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:
parent
8f2260a073
commit
03f0b157c1
|
@ -101,6 +101,38 @@ export function formatDate(
|
||||||
return text;
|
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 {
|
function getNamedFormat(locale: string, format: string): string {
|
||||||
const localeId = getLocaleId(locale);
|
const localeId = getLocaleId(locale);
|
||||||
NAMED_FORMATS[localeId] = NAMED_FORMATS[localeId] || {};
|
NAMED_FORMATS[localeId] = NAMED_FORMATS[localeId] || {};
|
||||||
|
@ -362,13 +394,13 @@ function timeZoneGetter(width: ZoneWidth): DateFormatter {
|
||||||
const JANUARY = 0;
|
const JANUARY = 0;
|
||||||
const THURSDAY = 4;
|
const THURSDAY = 4;
|
||||||
function getFirstThursdayOfYear(year: number) {
|
function getFirstThursdayOfYear(year: number) {
|
||||||
const firstDayOfYear = (new Date(year, JANUARY, 1)).getDay();
|
const firstDayOfYear = createDate(year, JANUARY, 1).getDay();
|
||||||
return new Date(
|
return createDate(
|
||||||
year, 0, 1 + ((firstDayOfYear <= THURSDAY) ? THURSDAY : THURSDAY + 7) - firstDayOfYear);
|
year, 0, 1 + ((firstDayOfYear <= THURSDAY) ? THURSDAY : THURSDAY + 7) - firstDayOfYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThursdayThisWeek(datetime: Date) {
|
function getThursdayThisWeek(datetime: Date) {
|
||||||
return new Date(
|
return createDate(
|
||||||
datetime.getFullYear(), datetime.getMonth(),
|
datetime.getFullYear(), datetime.getMonth(),
|
||||||
datetime.getDate() + (THURSDAY - datetime.getDay()));
|
datetime.getDate() + (THURSDAY - datetime.getDay()));
|
||||||
}
|
}
|
||||||
|
@ -720,7 +752,7 @@ export function toDate(value: string|number|Date): Date {
|
||||||
is applied.
|
is applied.
|
||||||
Note: ISO months are 0 for January, 1 for February, ... */
|
Note: ISO months are 0 for January, 1 for February, ... */
|
||||||
const [y, m = 1, d = 1] = value.split('-').map((val: string) => +val);
|
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);
|
const parsedNb = parseFloat(value);
|
||||||
|
|
|
@ -114,7 +114,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
* | | O, OO & OOO | Short localized GMT format | GMT-8 |
|
* | | O, OO & OOO | Short localized GMT format | GMT-8 |
|
||||||
* | | OOOO | Long localized GMT format | GMT-08:00 |
|
* | | 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
|
* ### Format examples
|
||||||
*
|
*
|
||||||
|
|
|
@ -347,6 +347,32 @@ describe('Format date', () => {
|
||||||
.toEqual('10:14 AM');
|
.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',
|
it('should remove bidi control characters',
|
||||||
() => expect(formatDate(date, 'MM/dd/yyyy', ɵDEFAULT_LOCALE_ID)!.length).toEqual(10));
|
() => 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('2013-12-29', 'YYYY', 'en')).toEqual('2014');
|
||||||
expect(formatDate('2010-01-02', 'YYYY', 'en')).toEqual('2009');
|
expect(formatDate('2010-01-02', 'YYYY', 'en')).toEqual('2009');
|
||||||
expect(formatDate('2010-01-04', 'YYYY', 'en')).toEqual('2010');
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue