UNPKG

globalize

Version:

A JavaScript library for internationalization and localization that leverages the official Unicode CLDR JSON data.

2,063 lines (1,676 loc) 75.8 kB
/** * Globalize v1.7.0 * * https://github.com/globalizejs/globalize * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2021-08-02T11:53Z */ /*! * Globalize v1.7.0 2021-08-02T11:53Z 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, partsJoin = Globalize._partsJoin, partsPush = Globalize._partsPush, 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 partsJoin( dateToPartsFormatter( value )); }; }; /** * 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; }; /* eslint-disable no-unused-expressions */ /** * 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[ ke