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 80576641a8
commit a527c695aa
2 changed files with 52 additions and 25 deletions

View File

@ -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;

View File

@ -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');
});
});
});