Skip to content

Commit

Permalink
Date: Dynamically augment skeleton (3/3)
Browse files Browse the repository at this point in the history
  • Loading branch information
rxaviers committed Mar 14, 2017
1 parent c91e911 commit 030691a
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 219 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -7,4 +7,3 @@ before_install:
install:
- npm install
- bower install
sudo: false
206 changes: 46 additions & 160 deletions src/date/expand-pattern.js
@@ -1,8 +1,9 @@
define([
"../common/format-message",
"../common/create-error/invalid-parameter-value",
"./pattern-re"
], function( formatMessage, createErrorInvalidParameterValue, datePatternRe ) {
"./expand-pattern/get-best-match-pattern"
], function( formatMessage, createErrorInvalidParameterValue,
dateExpandPatternGetBestMatchPattern ) {

/**
* expandPattern( options, cldr )
Expand All @@ -25,9 +26,11 @@ define([
* - { datetime: "full" } returns "EEEE, MMMM d, y 'at' h:mm:ss a zzzz";
* - { raw: "dd/mm" } returns "dd/mm";
*/

return function( options, cldr ) {
var dateSkeleton, result, skeleton, timeSkeleton, type, dateTimeSkeleton;
var dateSkeleton, result, skeleton, timeSkeleton, type,

// Using easier to read variables.
getBestMatchPattern = dateExpandPatternGetBestMatchPattern;

function combineDateTime( type, datePattern, timePattern ) {
return formatMessage(
Expand All @@ -39,171 +42,54 @@ return function( options, cldr ) {
);
}

function getBestMatchPattern( path, skeleton ) {
var availableFormats, ratedFormats, format, pattern;

pattern = cldr.main([ path, skeleton ]);

if ( skeleton && !pattern ) {
availableFormats = cldr.main([ path ]);
ratedFormats = [];

for ( format in availableFormats ) {
ratedFormats.push({
format: format,
pattern: availableFormats[format],
rate: compareFormats( skeleton, format )
});
}
switch ( true ) {
case "skeleton" in options:
skeleton = options.skeleton;

ratedFormats = ratedFormats
.filter( function( format ) {
return format.rate > -1;
} )
.sort( function( formatA, formatB ) {
return formatA.rate - formatB.rate;
});
// Preferred hour (j).
skeleton = skeleton.replace( /j/g, cldr.supplemental.timeData.preferred() );

if ( ratedFormats.length ) {
pattern = augmentFormat( skeleton, ratedFormats[0].pattern );
// Try direct map (note that getBestMatchPattern handles it).
// ... or, try to "best match" the whole skeleton.
result = getBestMatchPattern(
cldr,
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
skeleton
);
if ( result ) {
break;
}
}

return pattern;
}

function repeatStr( str, count ) {
var i, result = "";
for ( i = 0; i < count; i++ ) {
result = result + str;
}
return result;
}

function normalizePatternType( char ) {
switch ( char ) {
case "e":
case "E":
case "c":
return "e";

case "M":
case "L":
return "L";

default:
return char;
}
}
// ... or, try to "best match" the date and time parts individually.
timeSkeleton = skeleton.split( /[^hHKkmsSAzZOvVXx]/ ).slice( -1 )[ 0 ];
dateSkeleton = skeleton.split( /[^GyYuUrQqMLlwWdDFgEec]/ )[ 0 ];
dateSkeleton = getBestMatchPattern(
cldr,
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
dateSkeleton
);
timeSkeleton = getBestMatchPattern(
cldr,
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
timeSkeleton
);

function compareFormats( formatA, formatB ) {
var distance,
typeA,
typeB,
matchFound,
i,
j;

if ( formatA === formatB ) {
return 0;
}

formatA = formatA.match( datePatternRe );
formatB = formatB.match( datePatternRe );
if ( formatA.length === formatB.length ) {
distance = 1;
for ( i = 0; i < formatA.length; i++ ) {
typeA = normalizePatternType( formatA[i].charAt( 0 ) );
typeB = null;
matchFound = false;
for ( j = 0; j < formatB.length; j++ ) {
typeB = normalizePatternType( formatB[j].charAt( 0 ) );
if ( typeA === typeB ) {
break;
} else {
typeB = null;
}
}
if ( null === typeB ) {
return -1;
}
distance = distance + Math.abs( formatA[i].length - formatB[j].length );
if ( formatA[i].charAt( 0 ) !== formatB[j].charAt( 0 ) ) {
distance = distance + 1;
}
if ( /(MMMM|LLLL).*[Ec]/.test( dateSkeleton ) ) {
type = "full";
} else if ( /MMMM|LLLL/.test( dateSkeleton ) ) {
type = "long";
} else if ( /MMM|LLL/.test( dateSkeleton ) ) {
type = "medium";
} else {
type = "short";
}
return distance;
}
return -1;
}

function augmentFormat( requestedSkeleton, bestMatchFormat ) {
var i, j, matchedType, matchedLength, requestedType, requestedLength;

requestedSkeleton = requestedSkeleton.match( datePatternRe );
bestMatchFormat = 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;
if (
normalizePatternType( matchedType ) === normalizePatternType( requestedType ) &&
matchedLength < requestedLength
) {
bestMatchFormat[i] = repeatStr( matchedType, requestedLength );
}
if ( dateSkeleton && timeSkeleton ) {
result = combineDateTime( type, dateSkeleton, timeSkeleton );
} else {
result = dateSkeleton || timeSkeleton;
}
}

return bestMatchFormat.join( "" );
}

switch ( true ) {
case "skeleton" in options:
skeleton = options.skeleton;
result = cldr.main([
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
skeleton
]);
if ( !result ) {
timeSkeleton = skeleton.split( /[^hHKkmsSAzZOvVXx]/ ).slice( -1 )[ 0 ];
dateSkeleton = skeleton.split( /[^GyYuUrQqMLlwWdDFgEec]/ )[ 0 ];
dateTimeSkeleton = getBestMatchPattern(
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
skeleton
);
if ( dateTimeSkeleton ) {
result = dateTimeSkeleton;
} else {
dateSkeleton = getBestMatchPattern(
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
dateSkeleton
);
timeSkeleton = getBestMatchPattern(
"dates/calendars/gregorian/dateTimeFormats/availableFormats",
timeSkeleton
);

if ( /(MMMM|LLLL).*[Ec]/.test( dateSkeleton ) ) {
type = "full";
} else if ( /MMMM/g.test( dateSkeleton ) ) {
type = "long";
} else if ( /MMM/g.test( dateSkeleton ) || /LLL/g.test( dateSkeleton ) ) {
type = "medium";
} else {
type = "short";
}

if ( dateSkeleton && timeSkeleton ) {
result = combineDateTime( type, dateSkeleton, timeSkeleton );
} else {
result = dateSkeleton || timeSkeleton;
}
}
}
break;

case "date" in options:
Expand Down
33 changes: 33 additions & 0 deletions src/date/expand-pattern/augment-format.js
@@ -0,0 +1,33 @@
define([
"./normalize-pattern-type",
"../pattern-re",
"../../util/string/repeat"
], function( dateExpandPatternNormalizePatternType, datePatternRe, stringRepeat ) {

return function( requestedSkeleton, bestMatchFormat ) {
var i, j, matchedType, matchedLength, requestedType, requestedLength,

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

requestedSkeleton = requestedSkeleton.match( datePatternRe );
bestMatchFormat = 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;
if ( normalizePatternType( matchedType ) === normalizePatternType( requestedType ) &&
matchedLength < requestedLength
) {
bestMatchFormat[i] = stringRepeat( matchedType, requestedLength );
}
}
}

return bestMatchFormat.join( "" );
};

});
45 changes: 45 additions & 0 deletions src/date/expand-pattern/compare-formats.js
@@ -0,0 +1,45 @@
define([
"./normalize-pattern-type",
"../pattern-re"
], function( dateExpandPatternNormalizePatternType, datePatternRe ) {

return function( formatA, formatB ) {
var distance, typeA, typeB, matchFound, i, j,

// Using easier to read variables.
normalizePatternType = dateExpandPatternNormalizePatternType;

if ( formatA === formatB ) {
return 0;
}

formatA = formatA.match( datePatternRe );
formatB = formatB.match( datePatternRe );
if ( formatA.length === formatB.length ) {
distance = 1;
for ( i = 0; i < formatA.length; i++ ) {
typeA = normalizePatternType( formatA[i].charAt( 0 ) );
typeB = null;
matchFound = false;
for ( j = 0; j < formatB.length; j++ ) {
typeB = normalizePatternType( formatB[j].charAt( 0 ) );
if ( typeA === typeB ) {
break;
} else {
typeB = null;
}
}
if ( typeB === null ) {
return -1;
}
distance = distance + Math.abs( formatA[i].length - formatB[j].length );
if ( formatA[i].charAt( 0 ) !== formatB[j].charAt( 0 ) ) {
distance = distance + 1;
}
}
return distance;
}
return -1;
};

});
43 changes: 43 additions & 0 deletions src/date/expand-pattern/get-best-match-pattern.js
@@ -0,0 +1,43 @@
define([
"./augment-format",
"./compare-formats"
], function( dateExpandPatternAugmentFormat, dateExpandPatternCompareFormats ) {

return function( cldr, path, skeleton ) {
var availableFormats, format, pattern, ratedFormats,

// Using easier to read variables.
augmentFormat = dateExpandPatternAugmentFormat,
compareFormats = dateExpandPatternCompareFormats;

pattern = cldr.main([ path, skeleton ]);

if ( skeleton && !pattern ) {
availableFormats = cldr.main([ path ]);
ratedFormats = [];

for ( format in availableFormats ) {
ratedFormats.push({
format: format,
pattern: availableFormats[format],
rate: compareFormats( skeleton, format )
});
}

ratedFormats = ratedFormats
.filter( function( format ) {
return format.rate > -1;
} )
.sort( function( formatA, formatB ) {
return formatA.rate - formatB.rate;
});

if ( ratedFormats.length ) {
pattern = augmentFormat( skeleton, ratedFormats[0].pattern );
}
}

return pattern;
};

});
9 changes: 9 additions & 0 deletions src/date/expand-pattern/normalize-pattern-type.js
@@ -0,0 +1,9 @@
define([
"./similar-fields-map"
], function( dateExpandPatternSimilarFieldsMap ) {

return function( character ) {
return dateExpandPatternSimilarFieldsMap[ character ] || character;
};

});
16 changes: 16 additions & 0 deletions src/date/expand-pattern/similar-fields-map.js
@@ -0,0 +1,16 @@
define([
"../../util/object/invert"
], function( objectInvert ) {

// Invert key and values, e.g., {"e": "eEc"} ==> {"e": "e", "E": "e", "c": "e"}.
return objectInvert({
"e": "eEc",
"L": "ML"
}, function( object, key, value ) {
value.split( "" ).forEach(function( field ) {
object[ field ] = key;
});
return object;
});

});

0 comments on commit 030691a

Please sign in to comment.