From a527c695aa0ad433797e488d3cf74cd8c590d8ca Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 10 Jul 2018 16:46:54 -0700 Subject: [PATCH] fix(common): do not round factional seconds (#24831) fixes #24384 PR Close #24831 --- packages/common/src/i18n/format_date.ts | 56 ++++++++++++------- packages/common/test/i18n/format_date_spec.ts | 21 +++++-- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/packages/common/src/i18n/format_date.ts b/packages/common/src/i18n/format_date.ts index df8841957c..48fb90838b 100644 --- a/packages/common/src/i18n/format_date.ts +++ b/packages/common/src/i18n/format_date.ts @@ -29,7 +29,7 @@ enum DateType { Hours, Minutes, Seconds, - Milliseconds, + FractionalSeconds, Day } @@ -57,8 +57,6 @@ enum TranslationType { * If not specified, host system settings are used. * * See {@link DatePipe} for more details. - * - * */ export function formatDate( value: string | number | Date, format: string, locale: string, timezone?: string): string { @@ -195,6 +193,22 @@ function padNumber( 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 */ @@ -202,20 +216,26 @@ function dateGetter( name: DateType, size: number, offset: number = 0, trim = false, negWrap = false): DateFormatter { return function(date: Date, locale: string): string { - let part = getDatePart(name, date, size); + let part = getDatePart(name, date); if (offset > 0 || 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 { - switch (name) { +function getDatePart(part: DateType, date: Date): number { + switch (part) { case DateType.FullYear: return date.getFullYear(); case DateType.Month: @@ -228,13 +248,12 @@ function getDatePart(name: DateType, date: Date, size: number): number { return date.getMinutes(); case DateType.Seconds: return date.getSeconds(); - case DateType.Milliseconds: - const div = size === 1 ? 100 : (size === 2 ? 10 : 1); - return Math.round(date.getMilliseconds() / div); + case DateType.FractionalSeconds: + return date.getMilliseconds(); case DateType.Day: return date.getDay(); 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); break; - // Fractional second padded (0-9) + // Fractional second case 'S': - formatter = dateGetter(DateType.Milliseconds, 1); + formatter = dateGetter(DateType.FractionalSeconds, 1); break; case 'SS': - formatter = dateGetter(DateType.Milliseconds, 2); + formatter = dateGetter(DateType.FractionalSeconds, 2); break; - // = millisecond case 'SSS': - formatter = dateGetter(DateType.Milliseconds, 3); + formatter = dateGetter(DateType.FractionalSeconds, 3); break; diff --git a/packages/common/test/i18n/format_date_spec.ts b/packages/common/test/i18n/format_date_spec.ts index a57e3929ca..dbac3fdd7d 100644 --- a/packages/common/test/i18n/format_date_spec.ts +++ b/packages/common/test/i18n/format_date_spec.ts @@ -52,7 +52,7 @@ describe('Format date', () => { // Check the transformation of a date into a pattern 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(() => { @@ -105,7 +105,7 @@ describe('Format date', () => { mm: '03', s: '1', ss: '01', - S: '6', + S: '5', SS: '55', SSS: '550', a: 'AM', @@ -233,7 +233,6 @@ describe('Format date', () => { Object.keys(dateFixtures).forEach((pattern: string) => { expectDateFormatAs(date, pattern, dateFixtures[pattern]); }); - }); it('should format with pattern aliases', () => { @@ -266,14 +265,13 @@ describe('Format date', () => { () => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale)) .toEqual('Jan 20, 2017')); - // test for the following bugs: // https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524 it('should format correctly with iso strings that contain time', () => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale)) .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', () => { // this test only works if the timezone is not in UTC // 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/17478 it('should show the correct time when the timezone is fixed', () => { @@ -311,5 +308,17 @@ describe('Format date', () => { expect(() => formatDate(date, 'b', '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'); + }); }); });