Skip to content
Permalink
Browse files

Date: Add fractional seconds support in formatters (#763)

* Date: Add fractional seconds support in formatters

Follows this algorithm:
http://www.unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons

Fixes #753
  • Loading branch information...
mattyork authored and rxaviers committed Jul 17, 2018
1 parent 13ba130 commit dceb8cd3877c57232e899afe783d8910893dd434
@@ -279,10 +279,8 @@ module.exports = function( grunt ) {
.replace( /define\(\[[^\]]+\]\)[\W\n]+$/, "" ); /* 3 */

// Type b (not as simple as a single return)
if ( [ "util/globalize-date" ].indexOf( id ) !== -1 ) {
contents = "var " + name[ 0 ].toUpperCase() +
name.slice( 1 ) + " = (function() {" +
contents + "}());";
if ( [ "expand-pattern/augment-format" ].indexOf( id ) !== -1 ) {
contents = "var " + name + " = (function() {" + contents + "}());";

// Type a (single return)
} else if ( ( /\// ).test( id ) ) {
@@ -4,30 +4,53 @@ define([
"../../util/string/repeat"
], function( dateExpandPatternNormalizePatternType, datePatternRe, stringRepeat ) {

return function( requestedSkeleton, bestMatchFormat ) {
var i, j, matchedType, matchedLength, requestedType, requestedLength,
function expandBestMatchFormat( skeletonWithoutFractionalSeconds, bestMatchFormat ) {
var i, j, bestMatchFormatParts, matchedType, matchedLength, requestedType,
requestedLength, requestedSkeletonParts,

// Using an easier to read variable.
normalizePatternType = dateExpandPatternNormalizePatternType;

requestedSkeleton = requestedSkeleton.match( datePatternRe );
bestMatchFormat = bestMatchFormat.match( datePatternRe );
requestedSkeletonParts = skeletonWithoutFractionalSeconds.match( datePatternRe );
bestMatchFormatParts = bestMatchFormat.match( datePatternRe );

for ( i = 0; i < bestMatchFormat.length; i++ ) {
matchedType = bestMatchFormat[i].charAt( 0 );
matchedLength = bestMatchFormat[i].length;
for ( j = 0; j < requestedSkeleton.length; j++ ) {
requestedType = requestedSkeleton[j].charAt( 0 );
requestedLength = requestedSkeleton[j].length;
for ( i = 0; i < bestMatchFormatParts.length; i++ ) {
matchedType = bestMatchFormatParts[i].charAt( 0 );
matchedLength = bestMatchFormatParts[i].length;
for ( j = 0; j < requestedSkeletonParts.length; j++ ) {
requestedType = requestedSkeletonParts[j].charAt( 0 );
requestedLength = requestedSkeletonParts[j].length;
if ( normalizePatternType( matchedType ) === normalizePatternType( requestedType ) &&
matchedLength < requestedLength
) {
bestMatchFormat[i] = stringRepeat( matchedType, requestedLength );
bestMatchFormatParts[i] = stringRepeat( matchedType, requestedLength );
}
}
}

return bestMatchFormat.join( "" );
return bestMatchFormatParts.join( "" );
}

// See: http://www.unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
return function( requestedSkeleton, bestMatchFormat, decimalSeparator ) {
var countOfFractionalSeconds, fractionalSecondMatch, lastSecondIdx,
skeletonWithoutFractionalSeconds;

fractionalSecondMatch = requestedSkeleton.match( /S/g );
countOfFractionalSeconds = fractionalSecondMatch ? fractionalSecondMatch.length : 0;
skeletonWithoutFractionalSeconds = requestedSkeleton.replace( /S/g, "" );

bestMatchFormat = expandBestMatchFormat( skeletonWithoutFractionalSeconds, bestMatchFormat );

lastSecondIdx = bestMatchFormat.lastIndexOf( "s" );
if ( lastSecondIdx !== -1 && countOfFractionalSeconds !== 0 ) {
bestMatchFormat =
bestMatchFormat.slice( 0, lastSecondIdx + 1 ) +
decimalSeparator +
stringRepeat( "S", countOfFractionalSeconds ) +
bestMatchFormat.slice( lastSecondIdx + 1 );
}
return bestMatchFormat;
};

});
@@ -1,10 +1,11 @@
define([
"./augment-format",
"./compare-formats"
], function( dateExpandPatternAugmentFormat, dateExpandPatternCompareFormats ) {
"./compare-formats",
"../../number/symbol"
], function( dateExpandPatternAugmentFormat, dateExpandPatternCompareFormats, numberSymbol ) {

return function( cldr, askedSkeleton ) {
var availableFormats, pattern, ratedFormats, skeleton,
var availableFormats, decimalSeparator, pattern, ratedFormats, skeleton,
path = "dates/calendars/gregorian/dateTimeFormats/availableFormats",

// Using easier to read variables.
@@ -34,7 +35,8 @@ return function( cldr, askedSkeleton ) {
});

if ( ratedFormats.length ) {
pattern = augmentFormat( askedSkeleton, ratedFormats[0].pattern );
decimalSeparator = numberSymbol( "decimal", cldr );
pattern = augmentFormat( askedSkeleton, ratedFormats[0].pattern, decimalSeparator );
}
}

@@ -30,6 +30,7 @@ module.exports = {
{ formatter: Globalize.dateFormatter({ skeleton: "GyMMMEd" }), args: [ date ] },
{ formatter: Globalize.dateFormatter({ skeleton: "dhms" }), args: [ date ] },
{ formatter: Globalize.dateFormatter({ skeleton: "GyMMMEdhms" }), args: [ date ] },
{ formatter: Globalize.dateFormatter({ skeleton: "GyMMMEdhmsSSS" }), args: [ date ] },
{ formatter: Globalize.dateFormatter({ skeleton: "Ems" }), args: [ date ] },
{ formatter: Globalize.dateFormatter({ skeleton: "yQQQhm" }), args: [ date ] },

@@ -89,6 +89,7 @@ QUnit.test( "should return a formatter", function( assert ) {
assert.equal( Globalize.dateFormatter({ skeleton: "GyMMMEd" })( date ), "Wed, Sep 15, 2010 AD" );
assert.equal( Globalize.dateFormatter({ skeleton: "dhms" })( date ), "15, 5:35:07 PM" );
assert.equal( Globalize.dateFormatter({ skeleton: "GyMMMEdhms" })( date ), "Wed, Sep 15, 2010 AD, 5:35:07 PM" );
assert.equal( Globalize.dateFormatter({ skeleton: "GyMMMEdhmsSSS" })( date ), "Wed, Sep 15, 2010 AD, 5:35:07.369 PM" );
assert.equal( Globalize.dateFormatter({ skeleton: "Ems" })( date ), "Wed, 35:07" );
assert.equal( Globalize.dateFormatter({ skeleton: "yQQQhm" })( date ), "Q3 2010, 5:35 PM" );
});
@@ -144,6 +144,13 @@ QUnit.test( "should parse skeleton", function( assert ) {
date = startOf( date, "second" );
assertParseDate( assert, "Wed 5:35:07 PM", { skeleton: "Ehms" }, date );

date = new Date();
date.setHours( 17 );
date.setMinutes( 35 );
date.setSeconds( 7 );
date.setMilliseconds(734);
assertParseDate( assert, "Wed 5:35:07.734 PM", { skeleton: "EhmsSSS" }, date );

date = new Date( 2010, 8, 15 );
date = startOf( date, "day" );
assertParseDate( assert, "Wed, Sep 15, 2010 AD", { skeleton: "GyMMMEd" }, date );
@@ -47,6 +47,7 @@ QUnit.test( "should expand {skeleton: \"<skeleton>\"}", function( assert ) {
assert.expandPattern( en, { skeleton: "hhmm" }, "hh:mm a" );
assert.expandPattern( en, { skeleton: "HHmm" }, "HH:mm" );
assert.expandPattern( en, { skeleton: "EHmss" }, "E HH:mm:ss" );
assert.expandPattern( en, { skeleton: "hhmmssSS" }, "hh:mm:ss.SS a" );
assert.expandPattern( en, { skeleton: "yy" }, "yy" );
assert.expandPattern( de, { skeleton: "yMMMMd" }, "d. MMMM y" );
assert.expandPattern( de, { skeleton: "MMMM" }, "LLLL" );
@@ -56,6 +57,7 @@ QUnit.test( "should expand {skeleton: \"<skeleton>\"}", function( assert ) {
assert.expandPattern( de, { skeleton: "MMMMEEEEd" }, "EEEE, d. MMMM" );
assert.expandPattern( de, { skeleton: "MMMMccccd" }, "EEEE, d. MMMM" );
assert.expandPattern( de, { skeleton: "HHmm" }, "HH:mm" );
assert.expandPattern( de, { skeleton: "HHmmssSS" }, "HH:mm:ss,SS" );
assert.expandPattern( de, { skeleton: "EEEEHHmm" }, "EEEE, HH:mm" );
assert.expandPattern( de, { skeleton: "EEEEHmm" }, "EEEE, HH:mm" );
assert.expandPattern( de, { skeleton: "ccccHmm" }, "EEEE, HH:mm" );
@@ -5,29 +5,31 @@ define([
QUnit.module( "Date Expand Pattern Augment Format" );

QUnit.test( "should augment date skeletons", function( assert ) {
assert.equal( augmentFormat( "yy", "y" ), "yy" );
assert.equal( augmentFormat( "yMMdd", "M/d/y" ), "MM/dd/y" );
assert.equal( augmentFormat( "yy", "y", "." ), "yy" );
assert.equal( augmentFormat( "yMMdd", "M/d/y", "." ), "MM/dd/y" );

assert.equal( augmentFormat( "MMMMd", "MMM d" ), "MMMM d" );
assert.equal( augmentFormat( "MMMMd", "M d" ), "MMMM d" );
assert.equal( augmentFormat( "MMMMd", "'M' M d" ), "'M' MMMM d" );
assert.equal( augmentFormat( "MMMMd", "MMM d", "." ), "MMMM d" );
assert.equal( augmentFormat( "MMMMd", "M d", "." ), "MMMM d" );
assert.equal( augmentFormat( "MMMMd", "'M' M d", "." ), "'M' MMMM d" );

assert.equal( augmentFormat( "LLLL", "LLL" ), "LLLL" );
assert.equal( augmentFormat( "LLLLd", "MMM d" ), "MMMM d" );
assert.equal( augmentFormat( "LLLL", "LLL", "." ), "LLLL" );
assert.equal( augmentFormat( "LLLLd", "MMM d", "." ), "MMMM d" );

assert.equal( augmentFormat( "EEEE", "ccc" ), "cccc" );
assert.equal( augmentFormat( "EEEEd", "d E" ), "d EEEE" );
assert.equal( augmentFormat( "EEEE", "ccc", "." ), "cccc" );
assert.equal( augmentFormat( "EEEEd", "d E", "."), "d EEEE" );

assert.equal( augmentFormat( "cccc", "ccc" ), "cccc" );
assert.equal( augmentFormat( "ccccd", "d E" ), "d EEEE" );
assert.equal( augmentFormat( "cccc", "ccc", "." ), "cccc" );
assert.equal( augmentFormat( "ccccd", "d E", "." ), "d EEEE" );
});

QUnit.test( "should augment time skeletons", function( assert ) {
assert.equal( augmentFormat( "hhmm", "h:mm a" ), "hh:mm a" );
assert.equal( augmentFormat( "hhmm", "h:mm a", "." ), "hh:mm a" );
assert.equal( augmentFormat( "hhmmsS", "hh:mm:ss a", "." ), "hh:mm:ss.S a" );
assert.equal( augmentFormat( "hhmmssSSS", "hh:mm:ss a", "," ), "hh:mm:ss,SSS a" );
});

QUnit.test( "should augment datetime skeletons", function( assert ) {
assert.equal( augmentFormat( "EEEhhmmss", "E h:mm:ss a" ), "EEE hh:mm:ss a" );
assert.equal( augmentFormat( "EEEhhmmss", "E h:mm:ss a", "." ), "EEE hh:mm:ss a" );
});

});
@@ -31,6 +31,7 @@ QUnit.test( "should get best match pattern", function( assert ) {
assert.equal( getBestMatchPattern( en, "ccccd" ), "d EEEE" );

assert.equal( getBestMatchPattern( en, "hhmms" ), "hh:mm:ss a" );
assert.equal( getBestMatchPattern( en, "hhmmsS" ), "hh:mm:ss.S a" );
});

QUnit.test( "should be order-proof", function( assert ) {

0 comments on commit dceb8cd

Please sign in to comment.
You can’t perform that action at this time.