fix(common): do not round factional seconds (#24831)

fixes #24384

PR Close #24831
This commit is contained in:
Victor Berchet 2018-07-10 16:46:54 -07:00 committed by Miško Hevery
parent 265489518e
commit 0746485136
2 changed files with 52 additions and 25 deletions

View File

@ -29,7 +29,7 @@ enum DateType {
Hours, Hours,
Minutes, Minutes,
Seconds, Seconds,
Milliseconds, FractionalSeconds,
Day Day
} }
@ -57,8 +57,6 @@ enum TranslationType {
* If not specified, host system settings are used. * If not specified, host system settings are used.
* *
* See {@link DatePipe} for more details. * See {@link DatePipe} for more details.
*
*
*/ */
export function formatDate( export function formatDate(
value: string | number | Date, format: string, locale: string, timezone?: string): string { value: string | number | Date, format: string, locale: string, timezone?: string): string {
@ -195,6 +193,22 @@ function padNumber(
return neg + strNum; return neg + strNum;
} }
/**
* Trim a fractional part to `digits` number of digits.
* Right pads with "0" to fit the requested number of digits if needed.
*
* @param num The fractional part value
* @param digits The width of the output
*/
function trimRPadFractional(num: number, digits: number): string {
let strNum = String(num);
// Add padding at the end
while (strNum.length < digits) {
strNum = strNum + 0;
}
return strNum.substr(0, digits);
}
/** /**
* Returns a date formatter that transforms a date into its locale digit representation * Returns a date formatter that transforms a date into its locale digit representation
*/ */
@ -202,20 +216,26 @@ function dateGetter(
name: DateType, size: number, offset: number = 0, trim = false, name: DateType, size: number, offset: number = 0, trim = false,
negWrap = false): DateFormatter { negWrap = false): DateFormatter {
return function(date: Date, locale: string): string { return function(date: Date, locale: string): string {
let part = getDatePart(name, date, size); let part = getDatePart(name, date);
if (offset > 0 || part > -offset) { if (offset > 0 || part > -offset) {
part += offset; part += offset;
} }
if (name === DateType.Hours && part === 0 && offset === -12) {
part = 12; if (name === DateType.Hours) {
if (part === 0 && offset === -12) {
part = 12;
}
} else if (name === DateType.FractionalSeconds) {
return trimRPadFractional(part, size);
} }
return padNumber(
part, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim, negWrap); const localeMinus = getLocaleNumberSymbol(locale, NumberSymbol.MinusSign);
return padNumber(part, size, localeMinus, trim, negWrap);
}; };
} }
function getDatePart(name: DateType, date: Date, size: number): number { function getDatePart(part: DateType, date: Date): number {
switch (name) { switch (part) {
case DateType.FullYear: case DateType.FullYear:
return date.getFullYear(); return date.getFullYear();
case DateType.Month: case DateType.Month:
@ -228,13 +248,12 @@ function getDatePart(name: DateType, date: Date, size: number): number {
return date.getMinutes(); return date.getMinutes();
case DateType.Seconds: case DateType.Seconds:
return date.getSeconds(); return date.getSeconds();
case DateType.Milliseconds: case DateType.FractionalSeconds:
const div = size === 1 ? 100 : (size === 2 ? 10 : 1); return date.getMilliseconds();
return Math.round(date.getMilliseconds() / div);
case DateType.Day: case DateType.Day:
return date.getDay(); return date.getDay();
default: default:
throw new Error(`Unknown DateType value "${name}".`); throw new Error(`Unknown DateType value "${part}".`);
} }
} }
@ -561,16 +580,15 @@ function getDateFormatter(format: string): DateFormatter|null {
formatter = dateGetter(DateType.Seconds, 2); formatter = dateGetter(DateType.Seconds, 2);
break; break;
// Fractional second padded (0-9) // Fractional second
case 'S': case 'S':
formatter = dateGetter(DateType.Milliseconds, 1); formatter = dateGetter(DateType.FractionalSeconds, 1);
break; break;
case 'SS': case 'SS':
formatter = dateGetter(DateType.Milliseconds, 2); formatter = dateGetter(DateType.FractionalSeconds, 2);
break; break;
// = millisecond
case 'SSS': case 'SSS':
formatter = dateGetter(DateType.Milliseconds, 3); formatter = dateGetter(DateType.FractionalSeconds, 3);
break; break;

View File

@ -52,7 +52,7 @@ describe('Format date', () => {
// Check the transformation of a date into a pattern // Check the transformation of a date into a pattern
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void { function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
expect(formatDate(date, pattern, defaultLocale)).toEqual(output); expect(formatDate(date, pattern, defaultLocale)).toEqual(output, `pattern: "${pattern}"`);
} }
beforeAll(() => { beforeAll(() => {
@ -105,7 +105,7 @@ describe('Format date', () => {
mm: '03', mm: '03',
s: '1', s: '1',
ss: '01', ss: '01',
S: '6', S: '5',
SS: '55', SS: '55',
SSS: '550', SSS: '550',
a: 'AM', a: 'AM',
@ -233,7 +233,6 @@ describe('Format date', () => {
Object.keys(dateFixtures).forEach((pattern: string) => { Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]); expectDateFormatAs(date, pattern, dateFixtures[pattern]);
}); });
}); });
it('should format with pattern aliases', () => { it('should format with pattern aliases', () => {
@ -266,14 +265,13 @@ describe('Format date', () => {
() => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale)) () => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale))
.toEqual('Jan 20, 2017')); .toEqual('Jan 20, 2017'));
// test for the following bugs:
// https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524
// https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524
it('should format correctly with iso strings that contain time', it('should format correctly with iso strings that contain time',
() => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale)) () => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale))
.toMatch(/07-05-2017 \d{2}:\d{2}/)); .toMatch(/07-05-2017 \d{2}:\d{2}/));
// test for issue https://github.com/angular/angular/issues/21491 // https://github.com/angular/angular/issues/21491
it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => { it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => {
// this test only works if the timezone is not in UTC // this test only works if the timezone is not in UTC
// which is the case for BrowserStack when we test Safari // which is the case for BrowserStack when we test Safari
@ -283,7 +281,6 @@ describe('Format date', () => {
} }
}); });
// test for the following bugs:
// https://github.com/angular/angular/issues/16624 // https://github.com/angular/angular/issues/16624
// https://github.com/angular/angular/issues/17478 // https://github.com/angular/angular/issues/17478
it('should show the correct time when the timezone is fixed', () => { it('should show the correct time when the timezone is fixed', () => {
@ -311,5 +308,17 @@ describe('Format date', () => {
expect(() => formatDate(date, 'b', 'de')) expect(() => formatDate(date, 'b', 'de'))
.toThrowError(/Missing extra locale data for the locale "de"/); .toThrowError(/Missing extra locale data for the locale "de"/);
}); });
// https://github.com/angular/angular/issues/24384
it('should not round fractional seconds', () => {
expect(formatDate(3999, 'm:ss', 'en')).toEqual('0:03');
expect(formatDate(3999, 'm:ss.S', 'en')).toEqual('0:03.9');
expect(formatDate(3999, 'm:ss.SS', 'en')).toEqual('0:03.99');
expect(formatDate(3999, 'm:ss.SSS', 'en')).toEqual('0:03.999');
expect(formatDate(3000, 'm:ss', 'en')).toEqual('0:03');
expect(formatDate(3000, 'm:ss.S', 'en')).toEqual('0:03.0');
expect(formatDate(3000, 'm:ss.SS', 'en')).toEqual('0:03.00');
expect(formatDate(3000, 'm:ss.SSS', 'en')).toEqual('0:03.000');
});
}); });
}); });