tiseza_oss_live/Scripts/globalize/date.js

3139 lines
74 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Globalize v1.4.2
*
* http://github.com/jquery/globalize
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2019-03-07T13:47Z
*/
/*!
* Globalize v1.4.2 2019-03-07T13:47Z Released under the MIT license
* http://git.io/TrdQbw
*/
(function( root, factory ) {
// UMD returnExports
if ( typeof define === "function" && define.amd ) {
// AMD
define([
"cldr",
"../globalize",
"./number",
"cldr/event",
"cldr/supplemental"
], factory );
} else if ( typeof exports === "object" ) {
// Node, CommonJS
module.exports = factory( require( "cldrjs" ), require( "../globalize" ) );
} else {
// Extend global
factory( root.Cldr, root.Globalize );
}
}(this, function( Cldr, Globalize ) {
var createError = Globalize._createError,
createErrorUnsupportedFeature = Globalize._createErrorUnsupportedFeature,
formatMessage = Globalize._formatMessage,
isPlainObject = Globalize._isPlainObject,
looseMatching = Globalize._looseMatching,
numberNumberingSystemDigitsMap = Globalize._numberNumberingSystemDigitsMap,
numberSymbol = Globalize._numberSymbol,
regexpEscape = Globalize._regexpEscape,
removeLiteralQuotes = Globalize._removeLiteralQuotes,
runtimeBind = Globalize._runtimeBind,
stringPad = Globalize._stringPad,
validate = Globalize._validate,
validateCldr = Globalize._validateCldr,
validateDefaultLocale = Globalize._validateDefaultLocale,
validateParameterPresence = Globalize._validateParameterPresence,
validateParameterType = Globalize._validateParameterType,
validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject,
validateParameterTypeString = Globalize._validateParameterTypeString;
var validateParameterTypeDate = function( value, name ) {
validateParameterType( value, name, value === undefined || value instanceof Date, "Date" );
};
var createErrorInvalidParameterValue = function( name, value ) {
return createError( "E_INVALID_PAR_VALUE", "Invalid `{name}` value ({value}).", {
name: name,
value: value
});
};
/**
* Create a map between the skeleton fields and their positions, e.g.,
* {
* G: 0
* y: 1
* ...
* }
*/
var validateSkeletonFieldsPosMap = "GyYuUrQqMLlwWEecdDFghHKkmsSAzZOvVXx".split( "" ).reduce(function( memo, item, i ) {
memo[ item ] = i;
return memo;
}, {});
/**
* validateSkeleton( skeleton )
*
* skeleton: Assume `j` has already been converted into a localized hour field.
*/
var validateSkeleton = function validateSkeleton( skeleton ) {
var last,
// Using easier to read variable.
fieldsPosMap = validateSkeletonFieldsPosMap;
// "The fields are from the Date Field Symbol Table in Date Format Patterns"
// Ref: http://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
// I.e., check for invalid characters.
skeleton.replace( /[^GyYuUrQqMLlwWEecdDFghHKkmsSAzZOvVXx]/, function( field ) {
throw createError(
"E_INVALID_OPTIONS", "Invalid field `{invalidField}` of skeleton `{value}`",
{
invalidField: field,
type: "skeleton",
value: skeleton
}
);
});
// "The canonical order is from top to bottom in that table; that is, yM not My".
// http://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
// I.e., check for invalid order.
skeleton.split( "" ).every(function( field ) {
if ( fieldsPosMap[ field ] < last ) {
throw createError(
"E_INVALID_OPTIONS", "Invalid order `{invalidField}` of skeleton `{value}`",
{
invalidField: field,
type: "skeleton",
value: skeleton
}
);
}
last = fieldsPosMap[ field ];
return true;
});
};
/**
* Returns a new object created by using `object`'s values as keys, and the keys as values.
*/
var objectInvert = function( object, fn ) {
fn = fn || function( object, key, value ) {
object[ value ] = key;
return object;
};
return Object.keys( object ).reduce(function( newObject, key ) {
return fn( newObject, key, object[ key ] );
}, {});
};
// Invert key and values, e.g., {"e": "eEc"} ==> {"e": "e", "E": "e", "c": "e"}.
var dateExpandPatternSimilarFieldsMap = objectInvert({
"e": "eEc",
"L": "ML"
}, function( object, key, value ) {
value.split( "" ).forEach(function( field ) {
object[ field ] = key;
});
return object;
});
var dateExpandPatternNormalizePatternType = function( character ) {
return dateExpandPatternSimilarFieldsMap[ character ] || character;
};
var datePatternRe = ( /([a-z])\1*|'([^']|'')+'|''|./ig );
var stringRepeat = function( str, count ) {
var i, result = "";
for ( i = 0; i < count; i++ ) {
result = result + str;
}
return result;
};
function expandBestMatchFormat( skeletonWithoutFractionalSeconds, bestMatchFormat ) {
var i, j, bestMatchFormatParts, matchedType, matchedLength, requestedType,
requestedLength, requestedSkeletonParts,
// Using an easier to read variable.
normalizePatternType = dateExpandPatternNormalizePatternType;
requestedSkeletonParts = skeletonWithoutFractionalSeconds.match( datePatternRe );
bestMatchFormatParts = bestMatchFormat.match( datePatternRe );
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
) {
bestMatchFormatParts[i] = stringRepeat( matchedType, requestedLength );
}
}
}
return bestMatchFormatParts.join( "" );
}
// See: http://www.unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
var dateExpandPatternAugmentFormat = 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;
};
var dateExpandPatternCompareFormats = function( formatA, formatB ) {
var a, b, distance, lenA, lenB, typeA, typeB, 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 ) {
return -1;
}
distance = 1;
for ( i = 0; i < formatA.length; i++ ) {
a = formatA[ i ].charAt( 0 );
typeA = normalizePatternType( a );
typeB = null;
for ( j = 0; j < formatB.length; j++ ) {
b = formatB[ j ].charAt( 0 );
typeB = normalizePatternType( b );
if ( typeA === typeB ) {
break;
} else {
typeB = null;
}
}
if ( typeB === null ) {
return -1;
}
lenA = formatA[ i ].length;
lenB = formatB[ j ].length;
distance = distance + Math.abs( lenA - lenB );
// Most symbols have a small distance from each other, e.g., M ≅ L; E ≅ c; a ≅ b ≅ B;
// H ≅ k ≅ h ≅ K; ...
if ( a !== b ) {
distance += 1;
}
// Numeric (l<3) and text fields (l>=3) are given a larger distance from each other.
if ( ( lenA < 3 && lenB >= 3 ) || ( lenA >= 3 && lenB < 3 ) ) {
distance += 20;
}
}
return distance;
};
var dateExpandPatternGetBestMatchPattern = function( cldr, askedSkeleton ) {
var availableFormats, decimalSeparator, pattern, ratedFormats, skeleton,
path = "dates/calendars/gregorian/dateTimeFormats/availableFormats",
// Using easier to read variables.
augmentFormat = dateExpandPatternAugmentFormat,
compareFormats = dateExpandPatternCompareFormats;
pattern = cldr.main([ path, askedSkeleton ]);
if ( askedSkeleton && !pattern ) {
availableFormats = cldr.main([ path ]);
ratedFormats = [];
for ( skeleton in availableFormats ) {
ratedFormats.push({
skeleton: skeleton,
pattern: availableFormats[ skeleton ],
rate: compareFormats( askedSkeleton, skeleton )
});
}
ratedFormats = ratedFormats
.filter( function( format ) {
return format.rate > -1;
} )
.sort( function( formatA, formatB ) {
return formatA.rate - formatB.rate;
});
if ( ratedFormats.length ) {
decimalSeparator = numberSymbol( "decimal", cldr );
pattern = augmentFormat( askedSkeleton, ratedFormats[0].pattern, decimalSeparator );
}
}
return pattern;
};
/**
* expandPattern( options, cldr )
*
* @options [Object] if String, it's considered a skeleton. Object accepts:
* - skeleton: [String] lookup availableFormat;
* - date: [String] ( "full" | "long" | "medium" | "short" );
* - time: [String] ( "full" | "long" | "medium" | "short" );
* - datetime: [String] ( "full" | "long" | "medium" | "short" );
* - raw: [String] For more info see datetime/format.js.
*
* @cldr [Cldr instance].
*
* Return the corresponding pattern.
* Eg for "en":
* - "GyMMMd" returns "MMM d, y G";
* - { skeleton: "GyMMMd" } returns "MMM d, y G";
* - { date: "full" } returns "EEEE, MMMM d, y";
* - { time: "full" } returns "h:mm:ss a zzzz";
* - { datetime: "full" } returns "EEEE, MMMM d, y 'at' h:mm:ss a zzzz";
* - { raw: "dd/mm" } returns "dd/mm";
*/
var dateExpandPattern = function( options, cldr ) {
var dateSkeleton, result, skeleton, timeSkeleton, type,
// Using easier to read variables.
getBestMatchPattern = dateExpandPatternGetBestMatchPattern;
function combineDateTime( type, datePattern, timePattern ) {
return formatMessage(
cldr.main([
"dates/calendars/gregorian/dateTimeFormats",
type
]),
[ timePattern, datePattern ]
);
}
switch ( true ) {
case "skeleton" in options:
skeleton = options.skeleton;
// Preferred hour (j).
skeleton = skeleton.replace( /j/g, function() {
return cldr.supplemental.timeData.preferred();
});
validateSkeleton( skeleton );
// Try direct map (note that getBestMatchPattern handles it).
// ... or, try to "best match" the whole skeleton.
result = getBestMatchPattern(
cldr,
skeleton
);
if ( result ) {
break;
}
// ... 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,
dateSkeleton
);
timeSkeleton = getBestMatchPattern(
cldr,
timeSkeleton
);
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";
}
if ( dateSkeleton && timeSkeleton ) {
result = combineDateTime( type, dateSkeleton, timeSkeleton );
} else {
result = dateSkeleton || timeSkeleton;
}
break;
case "date" in options:
case "time" in options:
result = cldr.main([
"dates/calendars/gregorian",
"date" in options ? "dateFormats" : "timeFormats",
( options.date || options.time )
]);
break;
case "datetime" in options:
result = combineDateTime( options.datetime,
cldr.main([ "dates/calendars/gregorian/dateFormats", options.datetime ]),
cldr.main([ "dates/calendars/gregorian/timeFormats", options.datetime ])
);
break;
case "raw" in options:
result = options.raw;
break;
default:
throw createErrorInvalidParameterValue({
name: "options",
value: options
});
}
return result;
};
var dateWeekDays = [ "sun", "mon", "tue", "wed", "thu", "fri", "sat" ];
/**
* firstDayOfWeek
*/
var dateFirstDayOfWeek = function( cldr ) {
return dateWeekDays.indexOf( cldr.supplemental.weekData.firstDay() );
};
/**
* getTimeZoneName( length, type )
*/
var dateGetTimeZoneName = function( length, type, timeZone, cldr ) {
var metaZone, result;
if ( !timeZone ) {
return;
}
result = cldr.main([
"dates/timeZoneNames/zone",
timeZone,
length < 4 ? "short" : "long",
type
]);
if ( result ) {
return result;
}
// The latest metazone data of the metazone array.
// TODO expand to support the historic metazones based on the given date.
metaZone = cldr.supplemental([
"metaZones/metazoneInfo/timezone", timeZone, 0,
"usesMetazone/_mzone"
]);
return cldr.main([
"dates/timeZoneNames/metazone",
metaZone,
length < 4 ? "short" : "long",
type
]);
};
/**
* timezoneHourFormatShortH( hourFormat )
*
* @hourFormat [String]
*
* Unofficial deduction of the short hourFormat given time zone `hourFormat` element.
* Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
*
* Example:
* - "+HH.mm;-HH.mm" => "+H;-H"
* - "+HH:mm;-HH:mm" => "+H;-H"
* - "+HH:mm;HH:mm" => "+H;H" (Note MINUS SIGN \u2212)
* - "+HHmm;-HHmm" => "+H:-H"
*/
var dateTimezoneHourFormatH = function( hourFormat ) {
return hourFormat
.split( ";" )
.map(function( format ) {
return format.slice( 0, format.indexOf( "H" ) + 1 );
})
.join( ";" );
};
/**
* timezoneHourFormatLongHm( hourFormat )
*
* @hourFormat [String]
*
* Unofficial deduction of the short hourFormat given time zone `hourFormat` element.
* Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
*
* Example (hFormat === "H"): (used for short Hm)
* - "+HH.mm;-HH.mm" => "+H.mm;-H.mm"
* - "+HH:mm;-HH:mm" => "+H:mm;-H:mm"
* - "+HH:mm;HH:mm" => "+H:mm;H:mm" (Note MINUS SIGN \u2212)
* - "+HHmm;-HHmm" => "+Hmm:-Hmm"
*
* Example (hFormat === "HH": (used for long Hm)
* - "+HH.mm;-HH.mm" => "+HH.mm;-HH.mm"
* - "+HH:mm;-HH:mm" => "+HH:mm;-HH:mm"
* - "+H:mm;-H:mm" => "+HH:mm;-HH:mm"
* - "+HH:mm;HH:mm" => "+HH:mm;HH:mm" (Note MINUS SIGN \u2212)
* - "+HHmm;-HHmm" => "+HHmm:-HHmm"
*/
var dateTimezoneHourFormatHm = function( hourFormat, hFormat ) {
return hourFormat
.split( ";" )
.map(function( format ) {
var parts = format.split( /H+/ );
parts.splice( 1, 0, hFormat );
return parts.join( "" );
})
.join( ";" );
};
var runtimeCacheDataBind = function( key, data ) {
var fn = function() {
return data;
};
fn.dataCacheKey = key;
return fn;
};
/**
* properties( pattern, cldr )
*
* @pattern [String] raw pattern.
* ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
*
* @cldr [Cldr instance].
*
* Return the properties given the pattern and cldr.
*
* TODO Support other calendar types.
*/
var dateFormatProperties = function( pattern, cldr, timeZone ) {
var properties = {
numberFormatters: {},
pattern: pattern,
timeSeparator: numberSymbol( "timeSeparator", cldr )
},
widths = [ "abbreviated", "wide", "narrow" ];
function setNumberFormatterPattern( pad ) {
properties.numberFormatters[ pad ] = stringPad( "", pad );
}
if ( timeZone ) {
properties.timeZoneData = runtimeCacheDataBind( "iana/" + timeZone, {
offsets: cldr.get([ "globalize-iana/zoneData", timeZone, "offsets" ]),
untils: cldr.get([ "globalize-iana/zoneData", timeZone, "untils" ]),
isdsts: cldr.get([ "globalize-iana/zoneData", timeZone, "isdsts" ])
});
}
pattern.replace( datePatternRe, function( current ) {
var aux, chr, daylightTzName, formatNumber, genericTzName, length, standardTzName;
chr = current.charAt( 0 );
length = current.length;
if ( chr === "j" ) {
// Locale preferred hHKk.
// http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
properties.preferredTime = chr = cldr.supplemental.timeData.preferred();
}
// ZZZZ: same as "OOOO".
if ( chr === "Z" && length === 4 ) {
chr = "O";
length = 4;
}
// z...zzz: "{shortRegion}", eg. "PST" or "PDT".
// zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
// e.g., "Pacific Standard Time" or "Pacific Daylight Time".
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
if ( chr === "z" ) {
standardTzName = dateGetTimeZoneName( length, "standard", timeZone, cldr );
daylightTzName = dateGetTimeZoneName( length, "daylight", timeZone, cldr );
if ( standardTzName ) {
properties.standardTzName = standardTzName;
}
if ( daylightTzName ) {
properties.daylightTzName = daylightTzName;
}
// Fall through the "O" format in case one name is missing.
if ( !standardTzName || !daylightTzName ) {
chr = "O";
if ( length < 4 ) {
length = 1;
}
}
}
// v...vvv: "{shortRegion}", eg. "PT".
// vvvv: "{regionName} {Time}" or "{regionName} {Time}",
// e.g., "Pacific Time"
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
if ( chr === "v" ) {
genericTzName = dateGetTimeZoneName( length, "generic", timeZone, cldr );
// Fall back to "V" format.
if ( !genericTzName ) {
chr = "V";
length = 4;
}
}
switch ( chr ) {
// Era
case "G":
properties.eras = cldr.main([
"dates/calendars/gregorian/eras",
length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
]);
break;
// Year
case "y":
// Plain year.
formatNumber = true;
break;
case "Y":
// Year in "Week of Year"
properties.firstDay = dateFirstDayOfWeek( cldr );
properties.minDays = cldr.supplemental.weekData.minDays();
formatNumber = true;
break;
case "u": // Extended year. Need to be implemented.
case "U": // Cyclic year name. Need to be implemented.
throw createErrorUnsupportedFeature({
feature: "year pattern `" + chr + "`"
});
// Quarter
case "Q":
case "q":
if ( length > 2 ) {
if ( !properties.quarters ) {
properties.quarters = {};
}
if ( !properties.quarters[ chr ] ) {
properties.quarters[ chr ] = {};
}
properties.quarters[ chr ][ length ] = cldr.main([
"dates/calendars/gregorian/quarters",
chr === "Q" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
} else {
formatNumber = true;
}
break;
// Month
case "M":
case "L":
if ( length > 2 ) {
if ( !properties.months ) {
properties.months = {};
}
if ( !properties.months[ chr ] ) {
properties.months[ chr ] = {};
}
properties.months[ chr ][ length ] = cldr.main([
"dates/calendars/gregorian/months",
chr === "M" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
} else {
formatNumber = true;
}
break;
// Week - Week of Year (w) or Week of Month (W).
case "w":
case "W":
properties.firstDay = dateFirstDayOfWeek( cldr );
properties.minDays = cldr.supplemental.weekData.minDays();
formatNumber = true;
break;
// Day
case "d":
case "D":
case "F":
formatNumber = true;
break;
case "g":
// Modified Julian day. Need to be implemented.
throw createErrorUnsupportedFeature({
feature: "Julian day pattern `g`"
});
// Week day
case "e":
case "c":
if ( length <= 2 ) {
properties.firstDay = dateFirstDayOfWeek( cldr );
formatNumber = true;
break;
}
/* falls through */
case "E":
if ( !properties.days ) {
properties.days = {};
}
if ( !properties.days[ chr ] ) {
properties.days[ chr ] = {};
}
if ( length === 6 ) {
// If short day names are not explicitly specified, abbreviated day names are
// used instead.
// http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
// http://unicode.org/cldr/trac/ticket/6790
properties.days[ chr ][ length ] = cldr.main([
"dates/calendars/gregorian/days",
chr === "c" ? "stand-alone" : "format",
"short"
]) || cldr.main([
"dates/calendars/gregorian/days",
chr === "c" ? "stand-alone" : "format",
"abbreviated"
]);
} else {
properties.days[ chr ][ length ] = cldr.main([
"dates/calendars/gregorian/days",
chr === "c" ? "stand-alone" : "format",
widths[ length < 3 ? 0 : length - 3 ]
]);
}
break;
// Period (AM or PM)
case "a":
properties.dayPeriods = {
am: cldr.main(
"dates/calendars/gregorian/dayPeriods/format/wide/am"
),
pm: cldr.main(
"dates/calendars/gregorian/dayPeriods/format/wide/pm"
)
};
break;
// Hour
case "h": // 1-12
case "H": // 0-23
case "K": // 0-11
case "k": // 1-24
// Minute
case "m":
// Second
case "s":
case "S":
case "A":
formatNumber = true;
break;
// Zone
case "v":
if ( length !== 1 && length !== 4 ) {
throw createErrorUnsupportedFeature({
feature: "timezone pattern `" + pattern + "`"
});
}
properties.genericTzName = genericTzName;
break;
case "V":
if ( length === 1 ) {
throw createErrorUnsupportedFeature({
feature: "timezone pattern `" + pattern + "`"
});
}
if ( timeZone ) {
if ( length === 2 ) {
properties.timeZoneName = timeZone;
break;
}
var timeZoneName,
exemplarCity = cldr.main([
"dates/timeZoneNames/zone", timeZone, "exemplarCity"
]);
if ( length === 3 ) {
if ( !exemplarCity ) {
exemplarCity = cldr.main([
"dates/timeZoneNames/zone/Etc/Unknown/exemplarCity"
]);
}
timeZoneName = exemplarCity;
}
if ( exemplarCity && length === 4 ) {
timeZoneName = formatMessage(
cldr.main(
"dates/timeZoneNames/regionFormat"
),
[ exemplarCity ]
);
}
if ( timeZoneName ) {
properties.timeZoneName = timeZoneName;
break;
}
}
if ( current === "v" ) {
length = 1;
}
/* falls through */
case "O":
// O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
// OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
properties.gmtFormat = cldr.main( "dates/timeZoneNames/gmtFormat" );
properties.gmtZeroFormat = cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
// Unofficial deduction of the hourFormat variations.
// Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
aux = cldr.main( "dates/timeZoneNames/hourFormat" );
properties.hourFormat = length < 4 ?
[ dateTimezoneHourFormatH( aux ), dateTimezoneHourFormatHm( aux, "H" ) ] :
dateTimezoneHourFormatHm( aux, "HH" );
/* falls through */
case "Z":
case "X":
case "x":
setNumberFormatterPattern( 1 );
setNumberFormatterPattern( 2 );
break;
}
if ( formatNumber ) {
setNumberFormatterPattern( length );
}
});
return properties;
};
var dateFormatterFn = function( dateToPartsFormatter ) {
return function dateFormatter( value ) {
return dateToPartsFormatter( value ).map( function( part ) {
return part.value;
}).join( "" );
};
};
/**
* parseProperties( cldr )
*
* @cldr [Cldr instance].
*
* @timeZone [String] FIXME.
*
* Return parser properties.
*/
var dateParseProperties = function( cldr, timeZone ) {
var properties = {
preferredTimeData: cldr.supplemental.timeData.preferred()
};
if ( timeZone ) {
properties.timeZoneData = runtimeCacheDataBind( "iana/" + timeZone, {
offsets: cldr.get([ "globalize-iana/zoneData", timeZone, "offsets" ]),
untils: cldr.get([ "globalize-iana/zoneData", timeZone, "untils" ]),
isdsts: cldr.get([ "globalize-iana/zoneData", timeZone, "isdsts" ])
});
}
return properties;
};
var ZonedDateTime = (function() {
function definePrivateProperty(object, property, value) {
Object.defineProperty(object, property, {
value: value
});
}
function getUntilsIndex(original, untils) {
var index = 0;
var originalTime = original.getTime();
// TODO Should we do binary search for improved performance?
while (index < untils.length - 1 && originalTime >= untils[index]) {
index++;
}
return index;
}
function setWrap(fn) {
var offset1 = this.getTimezoneOffset();
var ret = fn();
this.original.setTime(new Date(this.getTime()));
var offset2 = this.getTimezoneOffset();
if (offset2 - offset1) {
this.original.setMinutes(this.original.getMinutes() + offset2 - offset1);
}
return ret;
}
var ZonedDateTime = function(date, timeZoneData) {
definePrivateProperty(this, "original", new Date(date.getTime()));
definePrivateProperty(this, "local", new Date(date.getTime()));
definePrivateProperty(this, "timeZoneData", timeZoneData);
definePrivateProperty(this, "setWrap", setWrap);
if (!(timeZoneData.untils && timeZoneData.offsets && timeZoneData.isdsts)) {
throw new Error("Invalid IANA data");
}
this.setTime(this.local.getTime() - this.getTimezoneOffset() * 60 * 1000);
};
ZonedDateTime.prototype.clone = function() {
return new ZonedDateTime(this.original, this.timeZoneData);
};
// Date field getters.
["getFullYear", "getMonth", "getDate", "getDay", "getHours", "getMinutes",
"getSeconds", "getMilliseconds"].forEach(function(method) {
// Corresponding UTC method, e.g., "getUTCFullYear" if method === "getFullYear".
var utcMethod = "getUTC" + method.substr(3);
ZonedDateTime.prototype[method] = function() {
return this.local[utcMethod]();
};
});
// Note: Define .valueOf = .getTime for arithmetic operations like date1 - date2.
ZonedDateTime.prototype.valueOf =
ZonedDateTime.prototype.getTime = function() {
return this.local.getTime() + this.getTimezoneOffset() * 60 * 1000;
};
ZonedDateTime.prototype.getTimezoneOffset = function() {
var index = getUntilsIndex(this.original, this.timeZoneData.untils);
return this.timeZoneData.offsets[index];
};
// Date field setters.
["setFullYear", "setMonth", "setDate", "setHours", "setMinutes", "setSeconds", "setMilliseconds"].forEach(function(method) {
// Corresponding UTC method, e.g., "setUTCFullYear" if method === "setFullYear".
var utcMethod = "setUTC" + method.substr(3);
ZonedDateTime.prototype[method] = function(value) {
var local = this.local;
// Note setWrap is needed for seconds and milliseconds just because
// abs(value) could be >= a minute.
return this.setWrap(function() {
return local[utcMethod](value);
});
};
});
ZonedDateTime.prototype.setTime = function(time) {
return this.local.setTime(time);
};
ZonedDateTime.prototype.isDST = function() {
var index = getUntilsIndex(this.original, this.timeZoneData.untils);
return Boolean(this.timeZoneData.isdsts[index]);
};
ZonedDateTime.prototype.inspect = function() {
var index = getUntilsIndex(this.original, this.timeZoneData.untils);
var abbrs = this.timeZoneData.abbrs;
return this.local.toISOString().replace(/Z$/, "") + " " +
(abbrs && abbrs[index] + " " || (this.getTimezoneOffset() * -1) + " ") +
(this.isDST() ? "(daylight savings)" : "");
};
ZonedDateTime.prototype.toDate = function() {
return new Date(this.getTime());
};
// Type cast getters.
["toISOString", "toJSON", "toUTCString"].forEach(function(method) {
ZonedDateTime.prototype[method] = function() {
return this.toDate()[method]();
};
});
return ZonedDateTime;
}());
/**
* isLeapYear( year )
*
* @year [Number]
*
* Returns an indication whether the specified year is a leap year.
*/
var dateIsLeapYear = function( year ) {
return new Date( year, 1, 29 ).getMonth() === 1;
};
/**
* lastDayOfMonth( date )
*
* @date [Date]
*
* Return the last day of the given date's month
*/
var dateLastDayOfMonth = function( date ) {
return new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
};
/**
* startOf changes the input to the beginning of the given unit.
*
* For example, starting at the start of a day, resets hours, minutes
* seconds and milliseconds to 0. Starting at the month does the same, but
* also sets the date to 1.
*
* Returns the modified date
*/
var dateStartOf = function( date, unit ) {
date = date instanceof ZonedDateTime ? date.clone() : new Date( date.getTime() );
switch ( unit ) {
case "year":
date.setMonth( 0 );
/* falls through */
case "month":
date.setDate( 1 );
/* falls through */
case "day":
date.setHours( 0 );
/* falls through */
case "hour":
date.setMinutes( 0 );
/* falls through */
case "minute":
date.setSeconds( 0 );
/* falls through */
case "second":
date.setMilliseconds( 0 );
}
return date;
};
/**
* Differently from native date.setDate(), this function returns a date whose
* day remains inside the month boundaries. For example:
*
* setDate( FebDate, 31 ): a "Feb 28" date.
* setDate( SepDate, 31 ): a "Sep 30" date.
*/
var dateSetDate = function( date, day ) {
var lastDay = new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
date.setDate( day < 1 ? 1 : day < lastDay ? day : lastDay );
};
/**
* Differently from native date.setMonth(), this function adjusts date if
* needed, so final month is always the one set.
*
* setMonth( Jan31Date, 1 ): a "Feb 28" date.
* setDate( Jan31Date, 8 ): a "Sep 30" date.
*/
var dateSetMonth = function( date, month ) {
var originalDate = date.getDate();
date.setDate( 1 );
date.setMonth( month );
dateSetDate( date, originalDate );
};
var outOfRange = function( value, low, high ) {
return value < low || value > high;
};
/**
* parse( value, tokens, properties )
*
* @value [String] string date.
*
* @tokens [Object] tokens returned by date/tokenizer.
*
* @properties [Object] output returned by date/tokenizer-properties.
*
* ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
*/
var dateParse = function( value, tokens, properties ) {
var amPm, day, daysOfYear, month, era, hour, hour12, timezoneOffset, valid,
YEAR = 0,
MONTH = 1,
DAY = 2,
HOUR = 3,
MINUTE = 4,
SECOND = 5,
MILLISECONDS = 6,
date = new Date(),
truncateAt = [],
units = [ "year", "month", "day", "hour", "minute", "second", "milliseconds" ];
// Create globalize date with given timezone data.
if ( properties.timeZoneData ) {
date = new ZonedDateTime( date, properties.timeZoneData() );
}
if ( !tokens.length ) {
return null;
}
valid = tokens.every(function( token ) {
var century, chr, value, length;
if ( token.type === "literal" ) {
// continue
return true;
}
chr = token.type.charAt( 0 );
length = token.type.length;
if ( chr === "j" ) {
// Locale preferred hHKk.
// http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
chr = properties.preferredTimeData;
}
switch ( chr ) {
// Era
case "G":
truncateAt.push( YEAR );
era = +token.value;
break;
// Year
case "y":
value = token.value;
if ( length === 2 ) {
if ( outOfRange( value, 0, 99 ) ) {
return false;
}
// mimic dojo/date/locale: choose century to apply, according to a sliding
// window of 80 years before and 20 years after present year.
century = Math.floor( date.getFullYear() / 100 ) * 100;
value += century;
if ( value > date.getFullYear() + 20 ) {
value -= 100;
}
}
date.setFullYear( value );
truncateAt.push( YEAR );
break;
case "Y": // Year in "Week of Year"
throw createErrorUnsupportedFeature({
feature: "year pattern `" + chr + "`"
});
// Quarter (skip)
case "Q":
case "q":
break;
// Month
case "M":
case "L":
if ( length <= 2 ) {
value = token.value;
} else {
value = +token.value;
}
if ( outOfRange( value, 1, 12 ) ) {
return false;
}
// Setting the month later so that we have the correct year and can determine
// the correct last day of February in case of leap year.
month = value;
truncateAt.push( MONTH );
break;
// Week (skip)
case "w": // Week of Year.
case "W": // Week of Month.
break;
// Day
case "d":
day = token.value;
truncateAt.push( DAY );
break;
case "D":
daysOfYear = token.value;
truncateAt.push( DAY );
break;
case "F":
// Day of Week in month. eg. 2nd Wed in July.
// Skip
break;
// Week day
case "e":
case "c":
case "E":
// Skip.
// value = arrayIndexOf( dateWeekDays, token.value );
break;
// Period (AM or PM)
case "a":
amPm = token.value;
break;
// Hour
case "h": // 1-12
value = token.value;
if ( outOfRange( value, 1, 12 ) ) {
return false;
}
hour = hour12 = true;
date.setHours( value === 12 ? 0 : value );
truncateAt.push( HOUR );
break;
case "K": // 0-11
value = token.value;
if ( outOfRange( value, 0, 11 ) ) {
return false;
}
hour = hour12 = true;
date.setHours( value );
truncateAt.push( HOUR );
break;
case "k": // 1-24
value = token.value;
if ( outOfRange( value, 1, 24 ) ) {
return false;
}
hour = true;
date.setHours( value === 24 ? 0 : value );
truncateAt.push( HOUR );
break;
case "H": // 0-23
value = token.value;
if ( outOfRange( value, 0, 23 ) ) {
return false;
}
hour = true;
date.setHours( value );
truncateAt.push( HOUR );
break;
// Minute
case "m":
value = token.value;
if ( outOfRange( value, 0, 59 ) ) {
return false;
}
date.setMinutes( value );
truncateAt.push( MINUTE );
break;
// Second
case "s":
value = token.value;
if ( outOfRange( value, 0, 59 ) ) {
return false;
}
date.setSeconds( value );
truncateAt.push( SECOND );
break;
case "A":
date.setHours( 0 );
date.setMinutes( 0 );
date.setSeconds( 0 );
/* falls through */
case "S":
value = Math.round( token.value * Math.pow( 10, 3 - length ) );
date.setMilliseconds( value );
truncateAt.push( MILLISECONDS );
break;
// Zone
case "z":
case "Z":
case "O":
case "v":
case "V":
case "X":
case "x":
if ( typeof token.value === "number" ) {
timezoneOffset = token.value;
}
break;
}
return true;
});
if ( !valid ) {
return null;
}
// 12-hour format needs AM or PM, 24-hour format doesn't, ie. return null
// if amPm && !hour12 || !amPm && hour12.
if ( hour && !( !amPm ^ hour12 ) ) {
return null;
}
if ( era === 0 ) {
// 1 BC = year 0
date.setFullYear( date.getFullYear() * -1 + 1 );
}
if ( month !== undefined ) {
dateSetMonth( date, month - 1 );
}
if ( day !== undefined ) {
if ( outOfRange( day, 1, dateLastDayOfMonth( date ) ) ) {
return null;
}
date.setDate( day );
} else if ( daysOfYear !== undefined ) {
if ( outOfRange( daysOfYear, 1, dateIsLeapYear( date.getFullYear() ) ? 366 : 365 ) ) {
return null;
}
date.setMonth( 0 );
date.setDate( daysOfYear );
}
if ( hour12 && amPm === "pm" ) {
date.setHours( date.getHours() + 12 );
}
if ( timezoneOffset !== undefined ) {
date.setMinutes( date.getMinutes() + timezoneOffset - date.getTimezoneOffset() );
}
// Truncate date at the most precise unit defined. Eg.
// If value is "12/31", and pattern is "MM/dd":
// => new Date( <current Year>, 12, 31, 0, 0, 0, 0 );
truncateAt = Math.max.apply( null, truncateAt );
date = dateStartOf( date, units[ truncateAt ] );
// Get date back from globalize date.
if ( date instanceof ZonedDateTime ) {
date = date.toDate();
}
return date;
};
/**
* tokenizer( value, numberParser, properties )
*
* @value [String] string date.
*
* @numberParser [Function]
*
* @properties [Object] output returned by date/tokenizer-properties.
*
* Returns an Array of tokens, eg. value "5 o'clock PM", pattern "h 'o''clock' a":
* [{
* type: "h",
* lexeme: "5"
* }, {
* type: "literal",
* lexeme: " "
* }, {
* type: "literal",
* lexeme: "o'clock"
* }, {
* type: "literal",
* lexeme: " "
* }, {
* type: "a",
* lexeme: "PM",
* value: "pm"
* }]
*
* OBS: lexeme's are always String and may return invalid ranges depending of the token type.
* Eg. "99" for month number.
*
* Return an empty Array when not successfully parsed.
*/
var dateTokenizer = function( value, numberParser, properties ) {
var digitsRe, valid,
tokens = [],
widths = [ "abbreviated", "wide", "narrow" ];
digitsRe = properties.digitsRe;
value = looseMatching( value );
valid = properties.pattern.match( datePatternRe ).every(function( current ) {
var aux, chr, length, numeric, tokenRe,
token = {};
function hourFormatParse( tokenRe, numberParser ) {
var aux, isPositive,
match = value.match( tokenRe );
numberParser = numberParser || function( value ) {
return +value;
};
if ( !match ) {
return false;
}
isPositive = match[ 1 ];
// hourFormat containing H only, e.g., `+H;-H`
if ( match.length < 6 ) {
aux = isPositive ? 1 : 3;
token.value = numberParser( match[ aux ] ) * 60;
// hourFormat containing H and m, e.g., `+HHmm;-HHmm`
} else if ( match.length < 10 ) {
aux = isPositive ? [ 1, 3 ] : [ 5, 7 ];
token.value = numberParser( match[ aux[ 0 ] ] ) * 60 +
numberParser( match[ aux[ 1 ] ] );
// hourFormat containing H, m, and s e.g., `+HHmmss;-HHmmss`
} else {
aux = isPositive ? [ 1, 3, 5 ] : [ 7, 9, 11 ];
token.value = numberParser( match[ aux[ 0 ] ] ) * 60 +
numberParser( match[ aux[ 1 ] ] ) +
numberParser( match[ aux[ 2 ] ] ) / 60;
}
if ( isPositive ) {
token.value *= -1;
}
return true;
}
function oneDigitIfLengthOne() {
if ( length === 1 ) {
// Unicode equivalent to /\d/
numeric = true;
return tokenRe = digitsRe;
}
}
function oneOrTwoDigitsIfLengthOne() {
if ( length === 1 ) {
// Unicode equivalent to /\d\d?/
numeric = true;
return tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
}
}
function oneOrTwoDigitsIfLengthOneOrTwo() {
if ( length === 1 || length === 2 ) {
// Unicode equivalent to /\d\d?/
numeric = true;
return tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
}
}
function twoDigitsIfLengthTwo() {
if ( length === 2 ) {
// Unicode equivalent to /\d\d/
numeric = true;
return tokenRe = new RegExp( "^(" + digitsRe.source + "){2}" );
}
}
// Brute-force test every locale entry in an attempt to match the given value.
// Return the first found one (and set token accordingly), or null.
function lookup( path ) {
var array = properties[ path.join( "/" ) ];
if ( !array ) {
return null;
}
// array of pairs [key, value] sorted by desc value length.
array.some(function( item ) {
var valueRe = item[ 1 ];
if ( valueRe.test( value ) ) {
token.value = item[ 0 ];
tokenRe = item[ 1 ];
return true;
}
});
return null;
}
token.type = current;
chr = current.charAt( 0 );
length = current.length;
if ( chr === "Z" ) {
// Z..ZZZ: same as "xxxx".
if ( length < 4 ) {
chr = "x";
length = 4;
// ZZZZ: same as "OOOO".
} else if ( length < 5 ) {
chr = "O";
length = 4;
// ZZZZZ: same as "XXXXX"
} else {
chr = "X";
length = 5;
}
}
if ( chr === "z" ) {
if ( properties.standardOrDaylightTzName ) {
token.value = null;
tokenRe = properties.standardOrDaylightTzName;
}
}
// v...vvv: "{shortRegion}", eg. "PT".
// vvvv: "{regionName} {Time}" or "{regionName} {Time}",
// e.g., "Pacific Time"
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
if ( chr === "v" ) {
if ( properties.genericTzName ) {
token.value = null;
tokenRe = properties.genericTzName;
// Fall back to "V" format.
} else {
chr = "V";
length = 4;
}
}
if ( chr === "V" && properties.timeZoneName ) {
token.value = length === 2 ? properties.timeZoneName : null;
tokenRe = properties.timeZoneNameRe;
}
switch ( chr ) {
// Era
case "G":
lookup([
"gregorian/eras",
length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
]);
break;
// Year
case "y":
case "Y":
numeric = true;
// number l=1:+, l=2:{2}, l=3:{3,}, l=4:{4,}, ...
if ( length === 1 ) {
// Unicode equivalent to /\d+/.
tokenRe = new RegExp( "^(" + digitsRe.source + ")+" );
} else if ( length === 2 ) {
// Lenient parsing: there's no year pattern to indicate non-zero-padded 2-digits
// year, so parser accepts both zero-padded and non-zero-padded for `yy`.
//
// Unicode equivalent to /\d\d?/
tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
} else {
// Unicode equivalent to /\d{length,}/
tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + ",}" );
}
break;
// Quarter
case "Q":
case "q":
// number l=1:{1}, l=2:{2}.
// lookup l=3...
oneDigitIfLengthOne() || twoDigitsIfLengthTwo() ||
lookup([
"gregorian/quarters",
chr === "Q" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
break;
// Month
case "M":
case "L":
// number l=1:{1,2}, l=2:{2}.
// lookup l=3...
//
// Lenient parsing: skeleton "yMd" (i.e., one M) may include MM for the pattern,
// therefore parser accepts both zero-padded and non-zero-padded for M and MM.
// Similar for L.
oneOrTwoDigitsIfLengthOneOrTwo() || lookup([
"gregorian/months",
chr === "M" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
break;
// Day
case "D":
// number {l,3}.
if ( length <= 3 ) {
// Equivalent to /\d{length,3}/
numeric = true;
tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + ",3}" );
}
break;
case "W":
case "F":
// number l=1:{1}.
oneDigitIfLengthOne();
break;
// Week day
case "e":
case "c":
// number l=1:{1}, l=2:{2}.
// lookup for length >=3.
if ( length <= 2 ) {
oneDigitIfLengthOne() || twoDigitsIfLengthTwo();
break;
}
/* falls through */
case "E":
if ( length === 6 ) {
// Note: if short day names are not explicitly specified, abbreviated day
// names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
lookup([
"gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
"short"
]) || lookup([
"gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
"abbreviated"
]);
} else {
lookup([
"gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
widths[ length < 3 ? 0 : length - 3 ]
]);
}
break;
// Period (AM or PM)
case "a":
lookup([
"gregorian/dayPeriods/format/wide"
]);
break;
// Week
case "w":
// number l1:{1,2}, l2:{2}.
oneOrTwoDigitsIfLengthOne() || twoDigitsIfLengthTwo();
break;
// Day, Hour, Minute, or Second
case "d":
case "h":
case "H":
case "K":
case "k":
case "j":
case "m":
case "s":
// number l1:{1,2}, l2:{2}.
//
// Lenient parsing:
// - skeleton "hms" (i.e., one m) always includes mm for the pattern, i.e., it's
// impossible to use a different skeleton to parse non-zero-padded minutes,
// therefore parser accepts both zero-padded and non-zero-padded for m. Similar
// for seconds s.
// - skeleton "hms" (i.e., one h) may include h or hh for the pattern, i.e., it's
// impossible to use a different skeleton to parser non-zero-padded hours for some
// locales, therefore parser accepts both zero-padded and non-zero-padded for h.
// Similar for d (in skeleton yMd).
oneOrTwoDigitsIfLengthOneOrTwo();
break;
case "S":
// number {l}.
// Unicode equivalent to /\d{length}/
numeric = true;
tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + "}" );
break;
case "A":
// number {l+5}.
// Unicode equivalent to /\d{length+5}/
numeric = true;
tokenRe = new RegExp( "^(" + digitsRe.source + "){" + ( length + 5 ) + "}" );
break;
// Zone
case "v":
case "V":
case "z":
if ( tokenRe && tokenRe.test( value ) ) {
break;
}
if ( chr === "V" && length === 2 ) {
break;
}
/* falls through */
case "O":
// O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
// OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
if ( value === properties[ "timeZoneNames/gmtZeroFormat" ] ) {
token.value = 0;
tokenRe = properties[ "timeZoneNames/gmtZeroFormatRe" ];
} else {
aux = properties[ "timeZoneNames/hourFormat" ].some(function( hourFormatRe ) {
if ( hourFormatParse( hourFormatRe, numberParser ) ) {
tokenRe = hourFormatRe;
return true;
}
});
if ( !aux ) {
return null;
}
}
break;
case "X":
// Same as x*, except it uses "Z" for zero offset.
if ( value === "Z" ) {
token.value = 0;
tokenRe = /^Z/;
break;
}
/* falls through */
case "x":
// x: hourFormat("+HH[mm];-HH[mm]")
// xx: hourFormat("+HHmm;-HHmm")
// xxx: hourFormat("+HH:mm;-HH:mm")
// xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
// xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
aux = properties.x.some(function( hourFormatRe ) {
if ( hourFormatParse( hourFormatRe ) ) {
tokenRe = hourFormatRe;
return true;
}
});
if ( !aux ) {
return null;
}
break;
case "'":
token.type = "literal";
tokenRe = new RegExp( "^" + regexpEscape( removeLiteralQuotes( current ) ) );
break;
default:
token.type = "literal";
tokenRe = new RegExp( "^" + regexpEscape( current ) );
}
if ( !tokenRe ) {
return false;
}
// Get lexeme and consume it.
value = value.replace( tokenRe, function( lexeme ) {
token.lexeme = lexeme;
if ( numeric ) {
token.value = numberParser( lexeme );
}
return "";
});
if ( !token.lexeme ) {
return false;
}
if ( numeric && isNaN( token.value ) ) {
return false;
}
tokens.push( token );
return true;
});
if ( value !== "" ) {
valid = false;
}
return valid ? tokens : [];
};
var dateParserFn = function( numberParser, parseProperties, tokenizerProperties ) {
return function dateParser( value ) {
var tokens;
validateParameterPresence( value, "value" );
validateParameterTypeString( value, "value" );
tokens = dateTokenizer( value, numberParser, tokenizerProperties );
return dateParse( value, tokens, parseProperties ) || null;
};
};
var objectFilter = function( object, testRe ) {
var key,
copy = {};
for ( key in object ) {
if ( testRe.test( key ) ) {
copy[ key ] = object[ key ];
}
}
return copy;
};
/**
* tokenizerProperties( pattern, cldr )
*
* @pattern [String] raw pattern.
*
* @cldr [Cldr instance].
*
* Return Object with data that will be used by tokenizer.
*/
var dateTokenizerProperties = function( pattern, cldr, timeZone ) {
var digitsReSource,
properties = {
pattern: looseMatching( pattern )
},
timeSeparator = numberSymbol( "timeSeparator", cldr ),
widths = [ "abbreviated", "wide", "narrow" ];
digitsReSource = numberNumberingSystemDigitsMap( cldr );
digitsReSource = digitsReSource ? "[" + digitsReSource + "]" : "\\d";
properties.digitsRe = new RegExp( digitsReSource );
// Transform:
// - "+H;-H" -> /\+(\d\d?)|-(\d\d?)/
// - "+HH;-HH" -> /\+(\d\d)|-(\d\d)/
// - "+HHmm;-HHmm" -> /\+(\d\d)(\d\d)|-(\d\d)(\d\d)/
// - "+HH:mm;-HH:mm" -> /\+(\d\d):(\d\d)|-(\d\d):(\d\d)/
//
// If gmtFormat is GMT{0}, the regexp must fill {0} in each side, e.g.:
// - "+H;-H" -> /GMT\+(\d\d?)|GMT-(\d\d?)/
function hourFormatRe( hourFormat, gmtFormat, digitsReSource, timeSeparator ) {
var re;
if ( !digitsReSource ) {
digitsReSource = "\\d";
}
if ( !gmtFormat ) {
gmtFormat = "{0}";
}
re = hourFormat
.replace( "+", "\\+" )
// Unicode equivalent to (\\d\\d)
.replace( /HH|mm|ss/g, "((" + digitsReSource + "){2})" )
// Unicode equivalent to (\\d\\d?)
.replace( /H|m/g, "((" + digitsReSource + "){1,2})" );
if ( timeSeparator ) {
re = re.replace( /:/g, timeSeparator );
}
re = re.split( ";" ).map(function( part ) {
return gmtFormat.replace( "{0}", part );
}).join( "|" );
return new RegExp( "^" + re );
}
function populateProperties( path, value ) {
// Skip
var skipRe = /(timeZoneNames\/zone|supplemental\/metaZones|timeZoneNames\/metazone|timeZoneNames\/regionFormat|timeZoneNames\/gmtFormat)/;
if ( skipRe.test( path ) ) {
return;
}
if ( !value ) {
return;
}
// The `dates` and `calendars` trim's purpose is to reduce properties' key size only.
path = path.replace( /^.*\/dates\//, "" ).replace( /calendars\//, "" );
// Specific filter for "gregorian/dayPeriods/format/wide".
if ( path === "gregorian/dayPeriods/format/wide" ) {
value = objectFilter( value, /^am|^pm/ );
}
// Transform object into array of pairs [key, /value/], sort by desc value length.
if ( isPlainObject( value ) ) {
value = Object.keys( value ).map(function( key ) {
return [ key, new RegExp( "^" + regexpEscape( looseMatching( value[ key ] ) ) ) ];
}).sort(function( a, b ) {
return b[ 1 ].source.length - a[ 1 ].source.length;
});
// If typeof value === "string".
} else {
value = looseMatching( value );
}
properties[ path ] = value;
}
function regexpSourceSomeTerm( terms ) {
return "(" + terms.filter(function( item ) {
return item;
}).reduce(function( memo, item ) {
return memo + "|" + item;
}) + ")";
}
cldr.on( "get", populateProperties );
pattern.match( datePatternRe ).forEach(function( current ) {
var aux, chr, daylightTzName, gmtFormat, length, standardTzName;
chr = current.charAt( 0 );
length = current.length;
if ( chr === "Z" ) {
if ( length < 5 ) {
chr = "O";
length = 4;
} else {
chr = "X";
length = 5;
}
}
// z...zzz: "{shortRegion}", eg. "PST" or "PDT".
// zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
// e.g., "Pacific Standard Time" or "Pacific Daylight Time".
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
if ( chr === "z" ) {
standardTzName = dateGetTimeZoneName( length, "standard", timeZone, cldr );
daylightTzName = dateGetTimeZoneName( length, "daylight", timeZone, cldr );
if ( standardTzName ) {
standardTzName = regexpEscape( looseMatching( standardTzName ) );
}
if ( daylightTzName ) {
daylightTzName = regexpEscape( looseMatching( daylightTzName ) );
}
if ( standardTzName || daylightTzName ) {
properties.standardOrDaylightTzName = new RegExp(
"^" + regexpSourceSomeTerm([ standardTzName, daylightTzName ])
);
}
// Fall through the "O" format in case one name is missing.
if ( !standardTzName || !daylightTzName ) {
chr = "O";
if ( length < 4 ) {
length = 1;
}
}
}
// v...vvv: "{shortRegion}", eg. "PT".
// vvvv: "{regionName} {Time}" or "{regionName} {Time}",
// e.g., "Pacific Time"
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
if ( chr === "v" ) {
if ( length !== 1 && length !== 4 ) {
throw createErrorUnsupportedFeature({
feature: "timezone pattern `" + pattern + "`"
});
}
var genericTzName = dateGetTimeZoneName( length, "generic", timeZone, cldr );
if ( genericTzName ) {
properties.genericTzName = new RegExp(
"^" + regexpEscape( looseMatching( genericTzName ) )
);
chr = "O";
// Fall back to "V" format.
} else {
chr = "V";
length = 4;
}
}
switch ( chr ) {
// Era
case "G":
cldr.main([
"dates/calendars/gregorian/eras",
length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
]);
break;
// Year
case "u": // Extended year. Need to be implemented.
case "U": // Cyclic year name. Need to be implemented.
throw createErrorUnsupportedFeature({
feature: "year pattern `" + chr + "`"
});
// Quarter
case "Q":
case "q":
if ( length > 2 ) {
cldr.main([
"dates/calendars/gregorian/quarters",
chr === "Q" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
}
break;
// Month
case "M":
case "L":
// number l=1:{1,2}, l=2:{2}.
// lookup l=3...
if ( length > 2 ) {
cldr.main([
"dates/calendars/gregorian/months",
chr === "M" ? "format" : "stand-alone",
widths[ length - 3 ]
]);
}
break;
// Day
case "g":
// Modified Julian day. Need to be implemented.
throw createErrorUnsupportedFeature({
feature: "Julian day pattern `g`"
});
// Week day
case "e":
case "c":
// lookup for length >=3.
if ( length <= 2 ) {
break;
}
/* falls through */
case "E":
if ( length === 6 ) {
// Note: if short day names are not explicitly specified, abbreviated day
// names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
cldr.main([
"dates/calendars/gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
"short"
]) || cldr.main([
"dates/calendars/gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
"abbreviated"
]);
} else {
cldr.main([
"dates/calendars/gregorian/days",
[ chr === "c" ? "stand-alone" : "format" ],
widths[ length < 3 ? 0 : length - 3 ]
]);
}
break;
// Period (AM or PM)
case "a":
cldr.main(
"dates/calendars/gregorian/dayPeriods/format/wide"
);
break;
// Zone
case "V":
if ( length === 1 ) {
throw createErrorUnsupportedFeature({
feature: "timezone pattern `" + pattern + "`"
});
}
if ( timeZone ) {
if ( length === 2 ) {
// Skip looseMatching processing since timeZone is a canonical posix value.
properties.timeZoneName = timeZone;
properties.timeZoneNameRe = new RegExp( "^" + regexpEscape( timeZone ) );
break;
}
var timeZoneName,
exemplarCity = cldr.main([
"dates/timeZoneNames/zone", timeZone, "exemplarCity"
]);
if ( length === 3 ) {
if ( !exemplarCity ) {
exemplarCity = cldr.main([
"dates/timeZoneNames/zone/Etc/Unknown/exemplarCity"
]);
}
timeZoneName = exemplarCity;
}
if ( exemplarCity && length === 4 ) {
timeZoneName = formatMessage(
cldr.main(
"dates/timeZoneNames/regionFormat"
),
[ exemplarCity ]
);
}
if ( timeZoneName ) {
timeZoneName = looseMatching( timeZoneName );
properties.timeZoneName = timeZoneName;
properties.timeZoneNameRe = new RegExp(
"^" + regexpEscape( timeZoneName )
);
}
}
if ( current === "v" ) {
length = 1;
}
/* falls through */
case "z":
case "O":
gmtFormat = cldr.main( "dates/timeZoneNames/gmtFormat" );
cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
cldr.main( "dates/timeZoneNames/hourFormat" );
properties[ "timeZoneNames/gmtZeroFormatRe" ] =
new RegExp( "^" + regexpEscape( properties[ "timeZoneNames/gmtZeroFormat" ] ) );
aux = properties[ "timeZoneNames/hourFormat" ];
properties[ "timeZoneNames/hourFormat" ] = (
length < 4 ?
[ dateTimezoneHourFormatHm( aux, "H" ), dateTimezoneHourFormatH( aux ) ] :
[ dateTimezoneHourFormatHm( aux, "HH" ) ]
).map(function( hourFormat ) {
return hourFormatRe(
hourFormat,
gmtFormat,
digitsReSource,
timeSeparator
);
});
/* falls through */
case "X":
case "x":
// x: hourFormat("+HH[mm];-HH[mm]")
// xx: hourFormat("+HHmm;-HHmm")
// xxx: hourFormat("+HH:mm;-HH:mm")
// xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
// xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
properties.x = [
[ "+HHmm;-HHmm", "+HH;-HH" ],
[ "+HHmm;-HHmm" ],
[ "+HH:mm;-HH:mm" ],
[ "+HHmmss;-HHmmss", "+HHmm;-HHmm" ],
[ "+HH:mm:ss;-HH:mm:ss", "+HH:mm;-HH:mm" ]
][ length - 1 ].map(function( hourFormat ) {
return hourFormatRe( hourFormat );
});
}
});
cldr.off( "get", populateProperties );
return properties;
};
/**
* dayOfWeek( date, firstDay )
*
* @date
*
* @firstDay the result of `dateFirstDayOfWeek( cldr )`
*
* Return the day of the week normalized by the territory's firstDay [0-6].
* Eg for "mon":
* - return 0 if territory is GB, or BR, or DE, or FR (week starts on "mon");
* - return 1 if territory is US (week starts on "sun");
* - return 2 if territory is EG (week starts on "sat");
*/
var dateDayOfWeek = function( date, firstDay ) {
return ( date.getDay() - firstDay + 7 ) % 7;
};
/**
* distanceInDays( from, to )
*
* Return the distance in days between from and to Dates.
*/
var dateDistanceInDays = function( from, to ) {
var inDays = 864e5;
return ( to.getTime() - from.getTime() ) / inDays;
};
/**
* dayOfYear
*
* Return the distance in days of the date to the begin of the year [0-d].
*/
var dateDayOfYear = function( date ) {
return Math.floor( dateDistanceInDays( dateStartOf( date, "year" ), date ) );
};
// Invert key and values, e.g., {"year": "yY"} ==> {"y": "year", "Y": "year"}
var dateFieldsMap = objectInvert({
"era": "G",
"year": "yY",
"quarter": "qQ",
"month": "ML",
"week": "wW",
"day": "dDF",
"weekday": "ecE",
"dayperiod": "a",
"hour": "hHkK",
"minute": "m",
"second": "sSA",
"zone": "zvVOxX"
}, function( object, key, value ) {
value.split( "" ).forEach(function( symbol ) {
object[ symbol ] = key;
});
return object;
});
/**
* millisecondsInDay
*/
var dateMillisecondsInDay = function( date ) {
// TODO Handle daylight savings discontinuities
return date - dateStartOf( date, "day" );
};
/**
* hourFormat( date, format, timeSeparator, formatNumber )
*
* Return date's timezone offset according to the format passed.
* Eg for format when timezone offset is 180:
* - "+H;-H": -3
* - "+HHmm;-HHmm": -0300
* - "+HH:mm;-HH:mm": -03:00
* - "+HH:mm:ss;-HH:mm:ss": -03:00:00
*/
var dateTimezoneHourFormat = function( date, format, timeSeparator, formatNumber ) {
var absOffset,
offset = date.getTimezoneOffset();
absOffset = Math.abs( offset );
formatNumber = formatNumber || {
1: function( value ) {
return stringPad( value, 1 );
},
2: function( value ) {
return stringPad( value, 2 );
}
};
return format
// Pick the correct sign side (+ or -).
.split( ";" )[ offset > 0 ? 1 : 0 ]
// Localize time separator
.replace( ":", timeSeparator )
// Update hours offset.
.replace( /HH?/, function( match ) {
return formatNumber[ match.length ]( Math.floor( absOffset / 60 ) );
})
// Update minutes offset and return.
.replace( /mm/, function() {
return formatNumber[ 2 ]( Math.floor( absOffset % 60 ) );
})
// Update minutes offset and return.
.replace( /ss/, function() {
return formatNumber[ 2 ]( Math.floor( absOffset % 1 * 60 ) );
});
};
/**
* format( date, properties )
*
* @date [Date instance].
*
* @properties
*
* TODO Support other calendar types.
*
* Disclosure: this function borrows excerpts of dojo/date/locale.
*/
var dateFormat = function( date, numberFormatters, properties ) {
var parts = [];
var timeSeparator = properties.timeSeparator;
// create globalize date with given timezone data
if ( properties.timeZoneData ) {
date = new ZonedDateTime( date, properties.timeZoneData() );
}
properties.pattern.replace( datePatternRe, function( current ) {
var aux, dateField, type, value,
chr = current.charAt( 0 ),
length = current.length;
if ( chr === "j" ) {
// Locale preferred hHKk.
// http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
chr = properties.preferredTime;
}
if ( chr === "Z" ) {
// Z..ZZZ: same as "xxxx".
if ( length < 4 ) {
chr = "x";
length = 4;
// ZZZZ: same as "OOOO".
} else if ( length < 5 ) {
chr = "O";
length = 4;
// ZZZZZ: same as "XXXXX"
} else {
chr = "X";
length = 5;
}
}
// z...zzz: "{shortRegion}", e.g., "PST" or "PDT".
// zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
// e.g., "Pacific Standard Time" or "Pacific Daylight Time".
if ( chr === "z" ) {
if ( date.isDST ) {
value = date.isDST() ? properties.daylightTzName : properties.standardTzName;
}
// Fall back to "O" format.
if ( !value ) {
chr = "O";
if ( length < 4 ) {
length = 1;
}
}
}
switch ( chr ) {
// Era
case "G":
value = properties.eras[ date.getFullYear() < 0 ? 0 : 1 ];
break;
// Year
case "y":
// Plain year.
// The length specifies the padding, but for two letters it also specifies the
// maximum length.
value = date.getFullYear();
if ( length === 2 ) {
value = String( value );
value = +value.substr( value.length - 2 );
}
break;
case "Y":
// Year in "Week of Year"
// The length specifies the padding, but for two letters it also specifies the
// maximum length.
// yearInWeekofYear = date + DaysInAWeek - (dayOfWeek - firstDay) - minDays
value = new Date( date.getTime() );
value.setDate(
value.getDate() + 7 -
dateDayOfWeek( date, properties.firstDay ) -
properties.firstDay -
properties.minDays
);
value = value.getFullYear();
if ( length === 2 ) {
value = String( value );
value = +value.substr( value.length - 2 );
}
break;
// Quarter
case "Q":
case "q":
value = Math.ceil( ( date.getMonth() + 1 ) / 3 );
if ( length > 2 ) {
value = properties.quarters[ chr ][ length ][ value ];
}
break;
// Month
case "M":
case "L":
value = date.getMonth() + 1;
if ( length > 2 ) {
value = properties.months[ chr ][ length ][ value ];
}
break;
// Week
case "w":
// Week of Year.
// woy = ceil( ( doy + dow of 1/1 ) / 7 ) - minDaysStuff ? 1 : 0.
// TODO should pad on ww? Not documented, but I guess so.
value = dateDayOfWeek( dateStartOf( date, "year" ), properties.firstDay );
value = Math.ceil( ( dateDayOfYear( date ) + value ) / 7 ) -
( 7 - value >= properties.minDays ? 0 : 1 );
break;
case "W":
// Week of Month.
// wom = ceil( ( dom + dow of `1/month` ) / 7 ) - minDaysStuff ? 1 : 0.
value = dateDayOfWeek( dateStartOf( date, "month" ), properties.firstDay );
value = Math.ceil( ( date.getDate() + value ) / 7 ) -
( 7 - value >= properties.minDays ? 0 : 1 );
break;
// Day
case "d":
value = date.getDate();
break;
case "D":
value = dateDayOfYear( date ) + 1;
break;
case "F":
// Day of Week in month. eg. 2nd Wed in July.
value = Math.floor( date.getDate() / 7 ) + 1;
break;
// Week day
case "e":
case "c":
if ( length <= 2 ) {
// Range is [1-7] (deduced by example provided on documentation)
// TODO Should pad with zeros (not specified in the docs)?
value = dateDayOfWeek( date, properties.firstDay ) + 1;
break;
}
/* falls through */
case "E":
value = dateWeekDays[ date.getDay() ];
value = properties.days[ chr ][ length ][ value ];
break;
// Period (AM or PM)
case "a":
value = properties.dayPeriods[ date.getHours() < 12 ? "am" : "pm" ];
break;
// Hour
case "h": // 1-12
value = ( date.getHours() % 12 ) || 12;
break;
case "H": // 0-23
value = date.getHours();
break;
case "K": // 0-11
value = date.getHours() % 12;
break;
case "k": // 1-24
value = date.getHours() || 24;
break;
// Minute
case "m":
value = date.getMinutes();
break;
// Second
case "s":
value = date.getSeconds();
break;
case "S":
value = Math.round( date.getMilliseconds() * Math.pow( 10, length - 3 ) );
break;
case "A":
value = Math.round( dateMillisecondsInDay( date ) * Math.pow( 10, length - 3 ) );
break;
// Zone
case "z":
break;
case "v":
// v...vvv: "{shortRegion}", eg. "PT".
// vvvv: "{regionName} {Time}",
// e.g., "Pacific Time".
if ( properties.genericTzName ) {
value = properties.genericTzName;
break;
}
/* falls through */
case "V":
//VVVV: "{explarCity} {Time}", e.g., "Los Angeles Time"
if ( properties.timeZoneName ) {
value = properties.timeZoneName;
break;
}
if ( current === "v" ) {
length = 1;
}
/* falls through */
case "O":
// O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
// OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
if ( date.getTimezoneOffset() === 0 ) {
value = properties.gmtZeroFormat;
} else {
// If O..OOO and timezone offset has non-zero minutes, show minutes.
if ( length < 4 ) {
aux = date.getTimezoneOffset();
aux = properties.hourFormat[ aux % 60 - aux % 1 === 0 ? 0 : 1 ];
} else {
aux = properties.hourFormat;
}
value = dateTimezoneHourFormat(
date,
aux,
timeSeparator,
numberFormatters
);
value = properties.gmtFormat.replace( /\{0\}/, value );
}
break;
case "X":
// Same as x*, except it uses "Z" for zero offset.
if ( date.getTimezoneOffset() === 0 ) {
value = "Z";
break;
}
/* falls through */
case "x":
// x: hourFormat("+HH[mm];-HH[mm]")
// xx: hourFormat("+HHmm;-HHmm")
// xxx: hourFormat("+HH:mm;-HH:mm")
// xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
// xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
aux = date.getTimezoneOffset();
// If x and timezone offset has non-zero minutes, use xx (i.e., show minutes).
if ( length === 1 && aux % 60 - aux % 1 !== 0 ) {
length += 1;
}
// If (xxxx or xxxxx) and timezone offset has zero seconds, use xx or xxx
// respectively (i.e., don't show optional seconds).
if ( ( length === 4 || length === 5 ) && aux % 1 === 0 ) {
length -= 2;
}
value = [
"+HH;-HH",
"+HHmm;-HHmm",
"+HH:mm;-HH:mm",
"+HHmmss;-HHmmss",
"+HH:mm:ss;-HH:mm:ss"
][ length - 1 ];
value = dateTimezoneHourFormat( date, value, ":" );
break;
// timeSeparator
case ":":
value = timeSeparator;
break;
// ' literals.
case "'":
value = removeLiteralQuotes( current );
break;
// Anything else is considered a literal, including [ ,:/.@#], chinese, japonese, and
// arabic characters.
default:
value = current;
}
if ( typeof value === "number" ) {
value = numberFormatters[ length ]( value );
}
dateField = dateFieldsMap[ chr ];
type = dateField ? dateField : "literal";
// Concat two consecutive literals
if ( type === "literal" && parts.length && parts[ parts.length - 1 ].type === "literal" ) {
parts[ parts.length - 1 ].value += value;
return;
}
parts.push( { type: type, value: value } );
});
return parts;
};
var dateToPartsFormatterFn = function( numberFormatters, properties ) {
return function dateToPartsFormatter( value ) {
validateParameterPresence( value, "value" );
validateParameterTypeDate( value, "value" );
return dateFormat( value, numberFormatters, properties );
};
};
function optionsHasStyle( options ) {
return options.skeleton !== undefined ||
options.date !== undefined ||
options.time !== undefined ||
options.datetime !== undefined ||
options.raw !== undefined;
}
function validateRequiredCldr( path, value ) {
validateCldr( path, value, {
skip: [
/dates\/calendars\/gregorian\/dateTimeFormats\/availableFormats/,
/dates\/calendars\/gregorian\/days\/.*\/short/,
/dates\/timeZoneNames\/zone/,
/dates\/timeZoneNames\/metazone/,
/globalize-iana/,
/supplemental\/metaZones/,
/supplemental\/timeData\/(?!001)/,
/supplemental\/weekData\/(?!001)/
]
});
}
function validateOptionsPreset( options ) {
validateOptionsPresetEach( "date", options );
validateOptionsPresetEach( "time", options );
validateOptionsPresetEach( "datetime", options );
}
function validateOptionsPresetEach( type, options ) {
var value = options[ type ];
validate(
"E_INVALID_OPTIONS",
"Invalid `{{type}: \"{value}\"}`.",
value === undefined || [ "short", "medium", "long", "full" ].indexOf( value ) !== -1,
{ type: type, value: value }
);
}
function validateOptionsSkeleton( pattern, skeleton ) {
validate(
"E_INVALID_OPTIONS",
"Invalid `{skeleton: \"{value}\"}` based on provided CLDR.",
skeleton === undefined || ( typeof pattern === "string" && pattern ),
{ type: "skeleton", value: skeleton }
);
}
function validateRequiredIana( timeZone ) {
return function( path, value ) {
if ( !/globalize-iana/.test( path ) ) {
return;
}
validate(
"E_MISSING_IANA_TZ",
"Missing required IANA timezone content for `{timeZone}`: `{path}`.",
value,
{
path: path.replace( /globalize-iana\//, "" ),
timeZone: timeZone
}
);
};
}
/**
* .loadTimeZone( json )
*
* @json [JSON]
*
* Load IANA timezone data.
*/
Globalize.loadTimeZone = function( json ) {
var customData = {
"globalize-iana": json
};
validateParameterPresence( json, "json" );
validateParameterTypePlainObject( json, "json" );
Cldr.load( customData );
};
/**
* .dateFormatter( options )
*
* @options [Object] see date/expand_pattern for more info.
*
* Return a date formatter function (of the form below) according to the given options and the
* default/instance locale.
*
* fn( value )
*
* @value [Date]
*
* Return a function that formats a date according to the given `format` and the default/instance
* locale.
*/
Globalize.dateFormatter =
Globalize.prototype.dateFormatter = function( options ) {
var args, dateToPartsFormatter, returnFn;
validateParameterTypePlainObject( options, "options" );
options = options || {};
if ( !optionsHasStyle( options ) ) {
options.skeleton = "yMd";
}
args = [ options ];
dateToPartsFormatter = this.dateToPartsFormatter( options );
returnFn = dateFormatterFn( dateToPartsFormatter );
runtimeBind( args, this.cldr, returnFn, [ dateToPartsFormatter ] );
return returnFn;
};
/**
* .dateToPartsFormatter( options )
*
* @options [Object] see date/expand_pattern for more info.
*
* Return a date formatter function (of the form below) according to the given options and the
* default/instance locale.
*
* fn( value )
*
* @value [Date]
*
* Return a function that formats a date to parts according to the given `format`
* and the default/instance
* locale.
*/
Globalize.dateToPartsFormatter =
Globalize.prototype.dateToPartsFormatter = function( options ) {
var args, cldr, numberFormatters, pad, pattern, properties, returnFn,
timeZone, ianaListener;
validateParameterTypePlainObject( options, "options" );
cldr = this.cldr;
options = options || {};
if ( !optionsHasStyle( options ) ) {
options.skeleton = "yMd";
}
validateOptionsPreset( options );
validateDefaultLocale( cldr );
timeZone = options.timeZone;
validateParameterTypeString( timeZone, "options.timeZone" );
args = [ options ];
cldr.on( "get", validateRequiredCldr );
if ( timeZone ) {
ianaListener = validateRequiredIana( timeZone );
cldr.on( "get", ianaListener );
}
pattern = dateExpandPattern( options, cldr );
validateOptionsSkeleton( pattern, options.skeleton );
properties = dateFormatProperties( pattern, cldr, timeZone );
cldr.off( "get", validateRequiredCldr );
if ( ianaListener ) {
cldr.off( "get", ianaListener );
}
// Create needed number formatters.
numberFormatters = properties.numberFormatters;
delete properties.numberFormatters;
for ( pad in numberFormatters ) {
numberFormatters[ pad ] = this.numberFormatter({
raw: numberFormatters[ pad ]
});
}
returnFn = dateToPartsFormatterFn( numberFormatters, properties );
runtimeBind( args, cldr, returnFn, [ numberFormatters, properties ] );
return returnFn;
};
/**
* .dateParser( options )
*
* @options [Object] see date/expand_pattern for more info.
*
* Return a function that parses a string date according to the given `formats` and the
* default/instance locale.
*/
Globalize.dateParser =
Globalize.prototype.dateParser = function( options ) {
var args, cldr, numberParser, parseProperties, pattern, returnFn, timeZone,
tokenizerProperties;
validateParameterTypePlainObject( options, "options" );
cldr = this.cldr;
options = options || {};
if ( !optionsHasStyle( options ) ) {
options.skeleton = "yMd";
}
validateOptionsPreset( options );
validateDefaultLocale( cldr );
timeZone = options.timeZone;
validateParameterTypeString( timeZone, "options.timeZone" );
args = [ options ];
cldr.on( "get", validateRequiredCldr );
if ( timeZone ) {
cldr.on( "get", validateRequiredIana( timeZone ) );
}
pattern = dateExpandPattern( options, cldr );
validateOptionsSkeleton( pattern, options.skeleton );
tokenizerProperties = dateTokenizerProperties( pattern, cldr, timeZone );
parseProperties = dateParseProperties( cldr, timeZone );
cldr.off( "get", validateRequiredCldr );
if ( timeZone ) {
cldr.off( "get", validateRequiredIana( timeZone ) );
}
numberParser = this.numberParser({ raw: "0" });
returnFn = dateParserFn( numberParser, parseProperties, tokenizerProperties );
runtimeBind( args, cldr, returnFn, [ numberParser, parseProperties, tokenizerProperties ] );
return returnFn;
};
/**
* .formatDate( value, options )
*
* @value [Date]
*
* @options [Object] see date/expand_pattern for more info.
*
* Formats a date or number according to the given options string and the default/instance locale.
*/
Globalize.formatDate =
Globalize.prototype.formatDate = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeDate( value, "value" );
return this.dateFormatter( options )( value );
};
/**
* .formatDateToParts( value, options )
*
* @value [Date]
*
* @options [Object] see date/expand_pattern for more info.
*
* Formats a date or number to parts according to the given options and the default/instance locale.
*/
Globalize.formatDateToParts =
Globalize.prototype.formatDateToParts = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeDate( value, "value" );
return this.dateToPartsFormatter( options )( value );
};
/**
* .parseDate( value, options )
*
* @value [String]
*
* @options [Object] see date/expand_pattern for more info.
*
* Return a Date instance or null.
*/
Globalize.parseDate =
Globalize.prototype.parseDate = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeString( value, "value" );
return this.dateParser( options )( value );
};
return Globalize;
}));