From 5ba59fed717387c5c54992767d073f3957daaa9b Mon Sep 17 00:00:00 2001 From: Rafael Xavier de Souza Date: Tue, 7 Mar 2017 17:43:40 -0300 Subject: [PATCH] Date: Olson-timezone-support (real z, v, V) and options.timeZone (2/2) --- README.md | 2 +- doc/api/date/date-formatter.md | 35 +- package.json | 1 + src/date-runtime.js | 18 +- src/date.js | 71 ++- src/date/fields-map.js | 2 +- src/date/format-properties.js | 114 ++-- src/date/format.js | 38 +- src/date/parse-properties.js | 16 +- src/date/parse.js | 7 +- src/date/start-of.js | 2 +- src/date/tokenizer-properties.js | 12 +- src/util/globalize-date.js | 153 ++--- test/config.js | 1 + test/functional/date/format-date.js | 30 +- test/functional/date/parse-date.js | 21 +- test/unit.js | 2 +- test/unit/date/format-properties.js | 136 ++-- test/unit/date/format.js | 921 ++++++++-------------------- test/unit/date/globalize-date.js | 305 --------- test/unit/date/parse-properties.js | 17 +- test/unit/date/parse.js | 503 +++++++++------ test/unit/util/globalize-date.js | 121 ++++ 23 files changed, 1142 insertions(+), 1386 deletions(-) delete mode 100644 test/unit/date/globalize-date.js create mode 100644 test/unit/util/globalize-date.js diff --git a/README.md b/README.md index c64cc5a90..c5d5dea04 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ requirements. See table below. |---|---| | Core module | cldr/supplemental/likelySubtags.json | | Currency module | cldr/main/`locale`/currencies.json
cldr/supplemental/currencyData.json
+CLDR JSON files from number module
+CLDR JSON files from plural module for name style support | -| Date module | cldr/main/`locale`/ca-gregorian.json
cldr/main/`locale`/timeZoneNames.json
cldr/supplemental/timeData.json
cldr/supplemental/weekData.json
+CLDR JSON files from number module | +| Date module | cldr/main/`locale`/ca-gregorian.json
cldr/main/`locale`/timeZoneNames.json
cldr/supplemental/metaZones.json
cldr/supplemental/timeData.json
cldr/supplemental/weekData.json
+CLDR JSON files from number module | | Number module | cldr/main/`locale`/numbers.json
cldr/supplemental/numberingSystems.json | | Plural module | cldr/supplemental/plurals.json (for cardinals)
cldr/supplemental/ordinals.json (for ordinals) | | Relative time module | cldr/main/`locale`/dateFields.json
+CLDR JSON files from number and plural modules | diff --git a/doc/api/date/date-formatter.md b/doc/api/date/date-formatter.md index a61c0ce8f..84b3a79c3 100644 --- a/doc/api/date/date-formatter.md +++ b/doc/api/date/date-formatter.md @@ -54,6 +54,12 @@ A JSON object including one of the following. > [raw pattern (anything in the "Sym." column)](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) > eg. `{ raw: "dd/mm" }`. Note this is NOT recommended for i18n in general. > Use `skeleton` instead. +> +> **timeZone** +> +> String based on the time zone names of the [IANA time zone +> database](https://www.iana.org/time-zones), such as `"Asia/Shanghai"`, `"Asia/Kolkata"`, +> `"America/New_York"`. **value** @@ -63,15 +69,12 @@ Date instance to be formatted, eg. `new Date()`; Prior to using any date methods, you must load `cldr/main/{locale}/ca-gregorian.json`, `cldr/main/{locale}/timeZoneNames.json`, -`cldr/supplemental/timeData.json`, `cldr/supplemental/weekData.json`, and the -CLDR content required by the number module. Read [CLDR content][] if you need -more information. +`cldr/supplemental/metaZones.json`, `cldr/supplemental/timeData.json`, +`cldr/supplemental/weekData.json`, and the CLDR content required by the number +module. Read [CLDR content][] if you need more information. [CLDR content]: ../../../README.md#2-cldr-content -You can use the static method `Globalize.dateFormatter()`, which uses the default -locale. - ```javascript var formatter; @@ -183,6 +186,26 @@ hourMinuteSecondFormatter( date ); // > "17:55:00" ``` +Using specific timeZones, i.e., using `options.timezone`. Note that prior to using +it, you must load IANA Timezone Data. + +```js +Globalize.loadIANATimezone( require( "iana-tz-data" ) ); +``` + +```js +Globalize.locale( "en" ); + +Globalize.dateFormatter({ datetime: "medium", timeZone: "America/Los_Angeles" })( new Date() ); +// > "Nov 1, 2010, 12:55:00 PM" + +Globalize.dateFormatter({ datetime: "medium", timeZone: "America/Sao_Paulo" })( new Date() ) +// > "Nov 1, 2010, 5:55:00 PM" + +Globalize.dateFormatter({ datetime: "full", timeZone: "Europe/Berlin" })( new Date() ) +// > "Monday, November 1, 2010 at 8:55:00 PM Central European Standard Time" +``` + For improved performance on iterations, first create the formatter. Then, reuse it on each loop. diff --git a/package.json b/package.json index b3ca45933..1ef712c1b 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "grunt-git-authors": "^3.1.0", "grunt-jscs": "1.8.0", "gzip-js": "0.3.2", + "iana-tz-data": "0.0.4", "matchdep": "0.3.0" }, "commitplease": { diff --git a/src/date-runtime.js b/src/date-runtime.js index ba6fe0782..f642fbe12 100644 --- a/src/date-runtime.js +++ b/src/date-runtime.js @@ -20,6 +20,14 @@ Globalize._dateParser = dateParse; Globalize._dateTokenizer = dateTokenizer; Globalize._validateParameterTypeDate = validateParameterTypeDate; +function optionsHasStyle( options ) { + return options.skeleton !== undefined || + options.date !== undefined || + options.time !== undefined || + options.datetime !== undefined || + options.raw !== undefined; +} + Globalize.dateFormatter = Globalize.prototype.dateFormatter = function( options ) { var formatterFn = this.dateToPartsFormatter( options ); @@ -33,13 +41,19 @@ Globalize.prototype.dateFormatter = function( options ) { Globalize.dateToPartsFormatter = Globalize.prototype.dateToPartsFormatter = function( options ) { - options = options || { skeleton: "yMd" }; + options = options || {}; + if ( !optionsHasStyle( options ) ) { + options.skeleton = "yMd"; + } return Globalize[ runtimeKey( "dateToPartsFormatter", this._locale, [ options ] ) ]; }; Globalize.dateParser = Globalize.prototype.dateParser = function( options ) { - options = options || { skeleton: "yMd" }; + options = options || {}; + if ( !optionsHasStyle( options ) ) { + options.skeleton = "yMd"; + } return Globalize[ runtimeKey( "dateParser", this._locale, [ options ] ) ]; }; diff --git a/src/date.js b/src/date.js index 201197b83..c33ee237e 100644 --- a/src/date.js +++ b/src/date.js @@ -24,11 +24,23 @@ define([ validateParameterTypeString, Globalize, dateExpandPattern, dateToPartsFormatterFn, dateFormatProperties, dateParserFn, dateParseProperties, dateTokenizerProperties ) { +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)/ ] @@ -60,14 +72,33 @@ function validateOptionsSkeleton( pattern, 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 + } + ); + }; +} + /** - * .loadIANA( json ) + * .loadIANATimezone( json ) * * @json [JSON] * - * Load IANA data. + * Load IANA timezone data. */ -Globalize.loadIANA = function( json ) { +Globalize.loadIANATimezone = function( json ) { var customData = { "globalize-iana": json }; @@ -122,26 +153,36 @@ Globalize.prototype.dateFormatter = function( options ) { */ Globalize.dateToPartsFormatter = Globalize.prototype.dateToPartsFormatter = function( options ) { - var args, cldr, numberFormatters, pad, pattern, properties, returnFn, timeZone; + var args, cldr, numberFormatters, pad, pattern, properties, returnFn, + timeZone; validateParameterTypePlainObject( options, "options" ); cldr = this.cldr; - options = options || { skeleton: "yMd" }; + options = options || {}; + if ( !optionsHasStyle( options ) ) { + options.skeleton = "yMd"; + } validateOptionsPreset( options ); validateDefaultLocale( cldr ); timeZone = options.timeZone; - validateParameterTypeString( options.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 ); properties = dateFormatProperties( pattern, cldr, timeZone ); cldr.off( "get", validateRequiredCldr ); + if ( timeZone ) { + cldr.off( "get", validateRequiredIana( timeZone ) ); + } // Create needed number formatters. numberFormatters = properties.numberFormatters; @@ -169,27 +210,37 @@ Globalize.prototype.dateToPartsFormatter = function( options ) { */ Globalize.dateParser = Globalize.prototype.dateParser = function( options ) { - var args, cldr, numberParser, parseProperties, pattern, tokenizerProperties, returnFn, timeZone; + var args, cldr, numberParser, parseProperties, pattern, returnFn, timeZone, + tokenizerProperties; validateParameterTypePlainObject( options, "options" ); cldr = this.cldr; - options = options || { skeleton: "yMd" }; + options = options || {}; + if ( !optionsHasStyle( options ) ) { + options.skeleton = "yMd"; + } validateOptionsPreset( options ); validateDefaultLocale( cldr ); timeZone = options.timeZone; - validateParameterTypeString( options.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 ); - parseProperties = dateParseProperties( cldr ); + parseProperties = dateParseProperties( cldr, timeZone ); cldr.off( "get", validateRequiredCldr ); + if ( timeZone ) { + cldr.off( "get", validateRequiredIana( timeZone ) ); + } numberParser = this.numberParser({ raw: "0" }); diff --git a/src/date/fields-map.js b/src/date/fields-map.js index a5c121c72..411454b35 100644 --- a/src/date/fields-map.js +++ b/src/date/fields-map.js @@ -15,7 +15,7 @@ return objectInvert({ "hour": "hHkK", "minute": "m", "second": "sSA", - "zone": "zOxX" + "zone": "zvVOxX" }, function( object, key, value ) { value.split( "" ).forEach(function( symbol ) { object[ symbol ] = key; diff --git a/src/date/format-properties.js b/src/date/format-properties.js index d4f08807a..08e97ebd9 100644 --- a/src/date/format-properties.js +++ b/src/date/format-properties.js @@ -28,39 +28,55 @@ return function( pattern, cldr, timeZone ) { }, widths = [ "abbreviated", "wide", "narrow" ]; - if ( timeZone ) { - var getTimeZoneData = function( timeZoneData ) { - if ( timeZoneData.name && timeZoneData.name === timeZone ) { - return true; - } - return false; - }; - properties.timeZoneData = cldr.get( "globalize-iana/zones" ).filter( getTimeZoneData )[0]; + function getTimeZoneName( length, type ) { + var metaZone, result; + + 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 + ]); } function setNumberFormatterPattern( pad ) { properties.numberFormatters[ pad ] = stringPad( "", pad ); } + if ( timeZone ) { + properties.timeZoneData = { + 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 formatNumber, chr = current.charAt( 0 ), length = current.length, - metaZone, standardTzName, daylightTzName, genericTzName; - if ( timeZone && ( chr === "v" || chr === "z" )) { - - // 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" - ]); - } - if ( chr === "j" ) { // Locale preferred hHKk. @@ -74,31 +90,22 @@ return function( pattern, cldr, timeZone ) { length = 4; } - // z...zzz: fallback to "O" - // zzzz: fallback to "OOOO" + // 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" ) { - if ( metaZone ) { - - //z...zzz: "{shortRegion}", eg. "PST" or "PDT". - //zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}", - //eg. "Pacific Standard Time" or "Pacific Daylight Time". - standardTzName = cldr.main([ - "dates/timeZoneNames/metazone", - metaZone, - length < 4 ? "short" : "long", - "standard" - ]); - daylightTzName = cldr.main([ - "dates/timeZoneNames/metazone", - metaZone, - length < 4 ? "short" : "long", - "daylight" - ]); + standardTzName = getTimeZoneName( length, "standard" ); + daylightTzName = getTimeZoneName( length, "daylight" ); + if ( standardTzName ) { + properties.standardTzName = standardTzName; + } + if ( daylightTzName ) { + properties.daylightTzName = daylightTzName; } - //fall through "O" format - if ( !metaZone || !standardTzName ) { + // Fall through the "O" format in case one name is missing. + if ( !standardTzName || !daylightTzName ) { chr = "O"; if ( length < 4 ) { length = 1; @@ -106,25 +113,15 @@ return function( pattern, cldr, timeZone ) { } } - // v: fallback to "VVVV" - // vvvv: fallback to "VVVV" + // 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 ( metaZone ) { - - //v...vvv: "{shortRegion}", eg. "PT". - //vvvv: "{regionName} {Time}" or "{regionName} {Time}", - //eg. "Pacific Time" - genericTzName = cldr.main([ - "dates/timeZoneNames/metazone", - metaZone, - length === 1 ? "short" : "long", - "generic" - ]); - } + genericTzName = getTimeZoneName( length, "generic" ); - //fall through "V" format - if ( !metaZone || !genericTzName ) { + // Fall back to "V" format. + if ( !genericTzName ) { chr = "V"; length = 4; } @@ -293,11 +290,6 @@ return function( pattern, cldr, timeZone ) { break; // Zone - case "z": - properties.standardTzName = standardTzName; - properties.daylightTzName = daylightTzName; - break; - case "v": if ( length !== 1 && length !== 4 ) { throw createErrorUnsupportedFeature({ diff --git a/src/date/format.js b/src/date/format.js index 6752129a2..db63d8c61 100644 --- a/src/date/format.js +++ b/src/date/format.js @@ -64,6 +64,23 @@ return function( date, numberFormatters, properties ) { } } + // 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 @@ -216,23 +233,13 @@ return function( date, numberFormatters, properties ) { // Zone case "z": + break; - // z...zzz: "{shortRegion}", eg. "PST" or "PDT". - // zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}", - // eg. "Pacific Standard Time" or "Pacific Daylight Time". - // TODO: date.isDST() FIXME - if ( date.isDST && properties.standardTzName ) { - value = date.isDST() && properties.daylightTzName ? - properties.daylightTzName : properties.standardTzName; - break; - } - - /* falls through */ case "v": - //v...vvv: "{shortRegion}", eg. "PT". - //vvvv: "{regionName} {Time}", - //eg. "Pacific Time" + // v...vvv: "{shortRegion}", eg. "PT". + // vvvv: "{regionName} {Time}", + // e.g., "Pacific Time". if ( properties.genericTzName ) { value = properties.genericTzName; break; @@ -241,8 +248,7 @@ return function( date, numberFormatters, properties ) { /* falls through */ case "V": - //VVVV: "{explarCity} {Time}", - //eg. "Los Angeles Time" + //VVVV: "{explarCity} {Time}", e.g., "Los Angeles Time" if ( properties.timeZoneName ) { value = properties.timeZoneName; break; diff --git a/src/date/parse-properties.js b/src/date/parse-properties.js index a5db8d672..6bb5bcd2c 100644 --- a/src/date/parse-properties.js +++ b/src/date/parse-properties.js @@ -5,12 +5,24 @@ define(function() { * * @cldr [Cldr instance]. * + * @timeZone [String] FIXME. + * * Return parser properties. */ -return function( cldr ) { - return { +return function( cldr, timeZone ) { + var properties = { preferredTimeData: cldr.supplemental.timeData.preferred() }; + + if ( timeZone ) { + properties.timeZoneData = { + 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; }; }); diff --git a/src/date/parse.js b/src/date/parse.js index 83bb39e1f..50546771a 100644 --- a/src/date/parse.js +++ b/src/date/parse.js @@ -34,7 +34,7 @@ return function( value, tokens, properties ) { truncateAt = [], units = [ "year", "month", "day", "hour", "minute", "second", "milliseconds" ]; - // create globalize date with given timezone data + // Create globalize date with given timezone data. if ( properties.timeZoneData ) { date = new GlobalizeDate( date, properties.timeZoneData ); } @@ -287,6 +287,11 @@ return function( value, tokens, properties ) { truncateAt = Math.max.apply( null, truncateAt ); date = dateStartOf( date, units[ truncateAt ] ); + // Get date back from globalize date. + if ( date.isGlobalizeDate ) { + date = new Date( date.getTime() ); // FIXME can we improve this? E.g., toDate() + } + return date; }; diff --git a/src/date/start-of.js b/src/date/start-of.js index 16b5dce4c..ac8b98444 100644 --- a/src/date/start-of.js +++ b/src/date/start-of.js @@ -10,7 +10,7 @@ define(function() { * Returns the modified date */ return function( date, unit ) { - date = new Date( date.getTime() ); + date = date.isGlobalizeDate ? date.clone() : new Date( date.getTime() ); switch ( unit ) { case "year": date.setMonth( 0 ); diff --git a/src/date/tokenizer-properties.js b/src/date/tokenizer-properties.js index b601a58f0..b41454a0d 100644 --- a/src/date/tokenizer-properties.js +++ b/src/date/tokenizer-properties.js @@ -14,23 +14,13 @@ define([ * * Return Object with data that will be used by tokenizer. */ -return function( pattern, cldr, timeZone ) { +return function( pattern, cldr ) { var properties = { pattern: pattern, timeSeparator: numberSymbol( "timeSeparator", cldr ) }, widths = [ "abbreviated", "wide", "narrow" ]; - if ( timeZone ) { - var getTimeZoneData = function( timeZoneData ) { - if ( timeZoneData.name && timeZoneData.name === timeZone ) { - return true; - } - return false; - }; - properties.timeZoneData = cldr.get( "globalize-iana/zones" ).filter( getTimeZoneData )[0]; - } - function populateProperties( path, value ) { // The `dates` and `calendars` trim's purpose is to reduce properties' key size only. diff --git a/src/util/globalize-date.js b/src/util/globalize-date.js index 705e0110e..d7b750669 100644 --- a/src/util/globalize-date.js +++ b/src/util/globalize-date.js @@ -1,128 +1,143 @@ - define( function() { -var GlobalizeDate = function( date, timeZonedata ) { - this.date = new Date( date.getTime() ); - this.timeZoneData = timeZonedata; - this.setTime( this.date.getTime() - this.getTimeZoneAdjustment() * 60 * 1000 ); + +function getUntilsIndex( original, untils ) { + var index = 0, + originalTime = original.getTime(); + + // TODO Should we do binary search for improved performance? + while ( index < untils.length - 1 && originalTime >= untils[ index ] ) { + index++; + } + return index; +} + +var GlobalizeDate = function( date, timeZoneData ) { + this.original = new Date( date.getTime() ); + this.local = new Date( date.getTime() ); + this.isGlobalizeDate = true; + this.timeZoneData = timeZoneData; + if ( !( timeZoneData.untils && timeZoneData.offsets && timeZoneData.isdsts ) ) { + throw new Error( "Invalid IANA data" ); + } + this.setTime( this.local.getTime() - this.getTimezoneOffset() * 60 * 1000 ); }; -GlobalizeDate.prototype.getFullYear = function() { - return this.date.getUTCFullYear(); +GlobalizeDate.prototype.setWrap = function( fn ) { + var offset1 = this.getTimezoneOffset(); + var ret = fn(); + this.original = new Date( this.getTime() ); + var offset2 = this.getTimezoneOffset(); + this.original.setMinutes( this.original.getMinutes() + offset2 - offset1 ); + return ret; }; -GlobalizeDate.prototype.getMonth = function() { - return this.date.getUTCMonth(); +GlobalizeDate.prototype.clone = function() { + return new GlobalizeDate( this.original, this.timeZoneData ); }; -GlobalizeDate.prototype.getDay = function() { - return this.date.getUTCDay(); +GlobalizeDate.prototype.getFullYear = function() { + return this.local.getUTCFullYear(); }; -GlobalizeDate.prototype.getDate = function() { - return this.date.getUTCDate(); +GlobalizeDate.prototype.getMonth = function() { + return this.local.getUTCMonth(); }; -GlobalizeDate.prototype.getMinutes = function() { - return this.date.getUTCMinutes(); +GlobalizeDate.prototype.getDate = function() { + return this.local.getUTCDate(); }; -GlobalizeDate.prototype.getSeconds = function() { - return this.date.getUTCSeconds(); +GlobalizeDate.prototype.getDay = function() { + return this.local.getUTCDay(); }; GlobalizeDate.prototype.getHours = function() { - return this.date.getUTCHours(); + return this.local.getUTCHours(); }; GlobalizeDate.prototype.getMinutes = function() { - return this.date.getUTCMinutes(); + return this.local.getUTCMinutes(); }; GlobalizeDate.prototype.getSeconds = function() { - return this.date.getUTCSeconds(); + return this.local.getUTCSeconds(); }; GlobalizeDate.prototype.getMilliseconds = function() { - return this.date.getUTCMilliseconds(); + return this.local.getUTCMilliseconds(); }; GlobalizeDate.prototype.getTime = function() { - return this.date.getTime(); + return this.local.getTime() + this.getTimezoneOffset() * 60 * 1000; }; -GlobalizeDate.prototype.setFullYear = function( year ) { - return this.date.setUTCFullYear( year ); +GlobalizeDate.prototype.getTimezoneOffset = function() { + var index = getUntilsIndex( this.original, this.timeZoneData.untils ); + return this.timeZoneData.offsets[ index ]; }; -GlobalizeDate.prototype.setMonth = function( month ) { - return this.date.setUTCMonth( month ); +GlobalizeDate.prototype.setFullYear = function( year ) { + var local = this.local; + return this.setWrap(function() { + return local.setUTCFullYear( year ); + }); }; -GlobalizeDate.prototype.setDay = function( date ) { - return this.date.setUTCDay( date ); +GlobalizeDate.prototype.setMonth = function( month ) { + var local = this.local; + return this.setWrap(function() { + return local.setUTCMonth( month ); + }); }; GlobalizeDate.prototype.setDate = function( date ) { - return this.date.setUTCDate( date ); -}; - -GlobalizeDate.prototype.setMinutes = function( minutes ) { - return this.date.setUTCMinutes( minutes ); -}; - -GlobalizeDate.prototype.setSeconds = function( seconds ) { - return this.date.setUTCSeconds( seconds ); + var local = this.local; + return this.setWrap(function() { + return local.setUTCDate( date ); + }); }; GlobalizeDate.prototype.setHours = function( hour ) { - return this.date.setUTCHours( hour ); + var local = this.local; + return this.setWrap(function() { + return local.setUTCHours( hour ); + }); }; GlobalizeDate.prototype.setMinutes = function( minutes ) { - return this.date.setUTCMinutes( minutes ); + var local = this.local; + return this.setWrap(function() { + return local.setUTCMinutes( minutes ); + }); }; GlobalizeDate.prototype.setSeconds = function( seconds ) { - return this.date.setUTCSeconds( seconds ); + var local = this.local; + + // setWrap is needed here just because abs(seconds) could be >= a minute. + return this.setWrap(function() { + return local.setUTCSeconds( seconds ); + }); }; GlobalizeDate.prototype.setMilliseconds = function( milliseconds ) { - return this.date.setUTCMilliseconds( milliseconds ); + var local = this.local; + + // setWrap is needed here just because abs(seconds) could be >= a minute. + return this.setWrap(function() { + return local.setUTCMilliseconds( milliseconds ); + }); }; GlobalizeDate.prototype.setTime = function( time ) { - return this.date.setTime( time ); + return this.local.setTime( time ); }; GlobalizeDate.prototype.isDST = function() { - return this.getStdOffset() !== -this.getTimeZoneAdjustment(); -}; - -GlobalizeDate.prototype.getTimezoneOffset = function() { - return this.getTimeZoneAdjustment(); -}; - -GlobalizeDate.prototype.getStdOffset = function() { - var stdOffset = -1; - if ( this.timeZoneData.offsets > 1 ) { - stdOffset *= Math.max( - this.timeZoneData.offsets[ this.timeZoneData.offsets.length - 1 ], - this.timeZoneData.offsets[ this.timeZoneData.offsets.length - 2 ] - ); - } else { - stdOffset *= this.timeZoneData.offsets[ this.timeZoneData.offsets.length - 1 ]; - } - return stdOffset; -}; - -GlobalizeDate.prototype.getTimeZoneAdjustment = function() { - var index = 0; - while ( index < this.timeZoneData.untils.length - 1 && - this.date.getTime() >= this.timeZoneData.untils[ index ] ) { - index++; - } - return index === 0 ? 0 : this.timeZoneData.offsets[ index ]; + var index = getUntilsIndex( this.original, this.timeZoneData.untils ); + return Boolean( this.timeZoneData.isdsts[ index ] ); }; return GlobalizeDate; + }); diff --git a/test/config.js b/test/config.js index 7addeaaf5..bd6b08c2c 100644 --- a/test/config.js +++ b/test/config.js @@ -4,6 +4,7 @@ var requirejs = { cldr: "../external/cldrjs/dist/cldr", "cldr-data": "../external/cldr-data", globalize: "../dist/globalize", + "iana-tz-data": "../node_modules/iana-tz-data/iana-tz-data", json: "../external/requirejs-plugins/src/json", src: "../src", text: "../external/requirejs-text/text" diff --git a/test/functional/date/format-date.js b/test/functional/date/format-date.js index 84d0c1505..c0539f728 100644 --- a/test/functional/date/format-date.js +++ b/test/functional/date/format-date.js @@ -9,15 +9,17 @@ define([ "json!cldr-data/main/pt/ca-gregorian.json", "json!cldr-data/main/pt/numbers.json", "json!cldr-data/supplemental/likelySubtags.json", + "json!cldr-data/supplemental/metaZones.json", "json!cldr-data/supplemental/numberingSystems.json", "json!cldr-data/supplemental/timeData.json", "json!cldr-data/supplemental/weekData.json", + "json!iana-tz-data.json", "../../util", "globalize/date" ], function( Globalize, arCaGregorian, arNumbers, arTimeZoneNames, enCaGregorian, enNumbers, - enTimeZoneNames, ptCaGregorian, ptNumbers, likelySubtags, numberingSystems, timeData, weekData, - util ) { + enTimeZoneNames, ptCaGregorian, ptNumbers, likelySubtags, metaZones, numberingSystems, timeData, + weekData, ianaTimezoneData, util ) { var ar, date = new Date( 2010, 8, 15, 17, 35, 7, 369 ); @@ -30,12 +32,14 @@ function extraSetup() { enCaGregorian, enNumbers, enTimeZoneNames, + metaZones, numberingSystems, ptCaGregorian, ptNumbers, timeData, weekData ); + Globalize.loadIANATimezone( ianaTimezoneData ); } QUnit.module( ".formatDate( value, options )", { @@ -158,4 +162,26 @@ QUnit.test( "should format raw patterns", function( assert ) { assert.equal( Globalize.formatDate( date, { raw: "E, MMM d, y G" } ), "Wed, Sep 15, 2010 AD" ); }); +QUnit.test( "should format date in various timezones", function( assert ) { + var date = new Date( "2010-09-15T16:35:07.000Z" ); + extraSetup(); + + assert.equal( + Globalize.formatDate( date, { datetime: "long", timeZone: "Etc/UTC" } ), + "September 15, 2010 at 4:35:07 PM GMT" + ); + assert.equal( + Globalize.formatDate( date, { datetime: "long", timeZone: "Europe/Berlin" } ), + "September 15, 2010 at 6:35:07 PM GMT+2" + ); + assert.equal( + Globalize.formatDate( date, { datetime: "long", timeZone: "America/Sao_Paulo" } ), + "September 15, 2010 at 1:35:07 PM GMT-3" + ); + assert.equal( + Globalize.formatDate( date, { datetime: "long", timeZone: "America/Los_Angeles" } ), + "September 15, 2010 at 9:35:07 AM PDT" + ); +}); + }); diff --git a/test/functional/date/parse-date.js b/test/functional/date/parse-date.js index 65af57310..eefaa09ea 100644 --- a/test/functional/date/parse-date.js +++ b/test/functional/date/parse-date.js @@ -13,12 +13,13 @@ define([ "json!cldr-data/supplemental/numberingSystems.json", "json!cldr-data/supplemental/timeData.json", "json!cldr-data/supplemental/weekData.json", + "json!iana-tz-data.json", "../../util", "globalize/date" ], function( Globalize, startOf, arCaGregorian, arNumbers, arTimeZoneNames, enCaGregorian, enNumbers, enTimeZoneNames, ptCaGregorian, ptNumbers, likelySubtags, numberingSystems, timeData, - weekData, util ) { + weekData, ianaTimezoneData, util ) { var ar, date; @@ -36,6 +37,7 @@ function extraSetup() { timeData, weekData ); + Globalize.loadIANATimezone( ianaTimezoneData ); } QUnit.module( ".parseDate( value, options )", { @@ -86,6 +88,9 @@ QUnit.test( "should validate parameters", function( assert ) { assert.throws(function() { Globalize.parseDate( "15", { skeleton: "invalid-stuff" }); }, /E_INVALID_OPTIONS.*skeleton.*invalid-stuff/ ); + + // FIXME: Test passing {timeZone} only. + // FIXME: Test passing invalid {timeZone}s. }); QUnit.test( "should validate CLDR content", function( assert ) { @@ -204,6 +209,20 @@ QUnit.test( "should parse a formatted date (reverse operation test)", function( assert.deepEqual( Globalize.parseDate( Globalize.formatDate( date, { datetime: "full" } ), { datetime: "full" } ), date ); assert.deepEqual( ar.parseDate( ar.formatDate( date, { datetime: "full" } ), { datetime: "full" } ), date ); + // Testing DST edge cases... + // Note we can't reliably parse overlapping times (daylight to standard cases). For example, we + // can't reliably parse "2/18/2017 11:00 PM" for America/Sao_Paulo into + // "2017-02-19T01:00:00.000Z" or "2017-02-19T02:00:00.000Z" without providing the zone string, + // e.g., 11:00 PM BRT or 11:00 PM BRST (both times are valid). Therefore, formatting either one + // should return back the parsed string. + assert.deepEqual( + Globalize.formatDate( + Globalize.parseDate( "2/18/2017 11:00 PM", { raw: "M/d/y h:mm a", timeZone: "America/Sao_Paulo" } ), + { raw: "M/d/y h:mm a", timeZone: "America/Sao_Paulo" } + ), + "2/18/2017 11:00 PM" + ); + // Test #689 - special test when target date and today are in different DST rules. // Note it was arbitrarily chosen O, other timezone patterns are supposed to pass too. // date1 = a DST date (or vice-versa depending on the running environment). diff --git a/test/unit.js b/test/unit.js index 7c08cbac1..a3d0447d4 100644 --- a/test/unit.js +++ b/test/unit.js @@ -2,6 +2,7 @@ require([ "qunit", // util + "./unit/util/globalize-date", "./unit/util/object/invert", "./unit/util/regexp/escape", @@ -26,7 +27,6 @@ require([ "./unit/date/format-properties", "./unit/date/parse-properties", "./unit/date/tokenizer-properties", - "./unit/date/globalize-date", "./unit/date/format", "./unit/date/tokenizer", diff --git a/test/unit/date/format-properties.js b/test/unit/date/format-properties.js index 215c21d16..5e97adcb7 100644 --- a/test/unit/date/format-properties.js +++ b/test/unit/date/format-properties.js @@ -2,25 +2,38 @@ define([ "cldr", "src/date/format-properties", "json!cldr-data/main/en/ca-gregorian.json", + "json!cldr-data/main/en/timeZoneNames.json", + "json!cldr-data/main/en-GB/ca-gregorian.json", + "json!cldr-data/main/en-GB/timeZoneNames.json", "json!cldr-data/supplemental/likelySubtags.json", "json!cldr-data/supplemental/timeData.json", "json!cldr-data/supplemental/weekData.json", "json!cldr-data/supplemental/metaZones.json", + "json!iana-tz-data.json", "cldr/event", "cldr/supplemental" -], function( Cldr, properties, enCaGregorian, likelySubtags, timeData, weekData, metaZones ) { +], function( Cldr, properties, enCaGregorian, enTimeZoneNames, enGbCaGregorian, enGbTimeZoneNames, + likelySubtags, timeData, weekData, metaZones, ianaTimezoneData ) { var cldr; Cldr.load( enCaGregorian, + enTimeZoneNames, + enGbCaGregorian, + enGbTimeZoneNames, likelySubtags, timeData, weekData, metaZones ); +// Needed for globalizeDate. +Cldr.load({ + "globalize-iana": ianaTimezoneData +}); + cldr = new Cldr( "en" ); QUnit.module( "Date Format Properties" ); @@ -124,54 +137,93 @@ QUnit.test( "should return dayPeriods property for period (a)", function( assert * Zone */ -QUnit.test( "should return standardTzName and daylightTzName properties for zone (z|zz|zzz|zzzz|zzzzz)", +QUnit.test( "should return standardTzName and daylightTzName properties for zone (z|zz|zzz|zzzz)", function( assert ) { - [ "z", "zz", "zzz", "zzzz", "zzzzz" ].forEach(function( pattern ) { - var timeZone = "America/Los_Angeles", - length = pattern.length; - assert.ok( "standardTzName" in properties( pattern, cldr, timeZone ) ); - assert.ok( "daylightTzName" in properties( pattern, cldr, timeZone ) ); - - if ( length < 4 ) { - assert.equal( "PST", properties( pattern, cldr, timeZone ).standardTzName ); - assert.equal( "PDT", properties( pattern, cldr, timeZone ).daylightTzName ); - } else { - assert.equal( "Pacific Standard Time", properties( pattern, cldr, timeZone ).standardTzName ); - assert.equal( "Pacific Daylight Time", properties( pattern, cldr, timeZone ).daylightTzName ); - } + var timeZone, + enGb = new Cldr( "en-GB" ); + + timeZone = "America/Los_Angeles"; + [ "z", "zz", "zzz" ].forEach(function( pattern ) { + assert.equal( properties( pattern, cldr, timeZone ).standardTzName, "PST" ); + assert.equal( properties( pattern, cldr, timeZone ).daylightTzName, "PDT" ); + }); + [ "zzzz" ].forEach(function( pattern ) { + assert.equal( properties( pattern, cldr, timeZone ).standardTzName, "Pacific Standard Time" ); + assert.equal( properties( pattern, cldr, timeZone ).daylightTzName, "Pacific Daylight Time" ); }); -}); -QUnit.test( "should return standardTzName and daylightTzName properties for zone (v|vvvv)", - function( assert ) { - [ "v", "vvvv" ].forEach(function( pattern ) { - var timeZone = "America/Los_Angeles", - length = pattern.length; - assert.ok( "genericTzName" in properties( pattern, cldr, timeZone ) ); + // Test for ??: + timeZone = "Asia/Dubai"; + [ "z", "zz", "zzz" ].forEach(function( pattern ) { + var formatProperties = properties( pattern, cldr, timeZone ); + assert.ok( !( "standardTzName" in formatProperties ) ); + assert.ok( !( "daylightTzName" in formatProperties ) ); + assert.ok( "gmtFormat" in formatProperties ); + assert.ok( "gmtZeroFormat" in formatProperties ); + assert.ok( "tzLongHourFormat" in formatProperties ); + }); + [ "zzzz" ].forEach(function( pattern ) { + var formatProperties = properties( pattern, cldr, timeZone ); + assert.equal( formatProperties.standardTzName, "Gulf Standard Time" ); + assert.ok( !( "daylightTzName" in formatProperties ) ); + assert.ok( "gmtFormat" in formatProperties ); + assert.ok( "gmtZeroFormat" in formatProperties ); + assert.ok( "tzLongHourFormat" in formatProperties ); + }); - if ( length === 1 ) { - assert.equal( "PT", properties( pattern, cldr, timeZone ).genericTzName ); - } else if ( length === 4 ) { - assert.equal( "Pacific Time", properties( pattern, cldr, timeZone ).genericTzName ); - } + // Test for two things: + // - daylightTzName using the zone data (primary), not the metazone (secondary try); + // - standardTzName being undefined, therefore requiring the O fallback properties; + timeZone = "Europe/London"; + [ "z", "zz", "zzz" ].forEach(function( pattern ) { + var formatProperties = properties( pattern, enGb, timeZone ); + assert.ok( !( "standardTzName" in formatProperties ) ); + assert.equal( formatProperties.daylightTzName, "BST" ); + assert.ok( "gmtFormat" in formatProperties ); + assert.ok( "gmtZeroFormat" in formatProperties ); + assert.ok( "tzLongHourFormat" in formatProperties ); + }); + [ "zzzz" ].forEach(function( pattern ) { + var formatProperties = properties( pattern, enGb, timeZone ); + assert.ok( !( "standardTzName" in formatProperties ) ); + assert.equal( formatProperties.daylightTzName, "British Summer Time" ); + assert.ok( "gmtFormat" in formatProperties ); + assert.ok( "gmtZeroFormat" in formatProperties ); + assert.ok( "tzLongHourFormat" in formatProperties ); }); }); -QUnit.test( "should return standardTzName and daylightTzName properties for zone (VV|VVV|VVVV)", - function( assert ) { - [ "VV", "VVV", "VVVV" ].forEach(function( pattern ) { - var timeZone = "America/Los_Angeles", - length = pattern.length; - assert.ok( "timeZoneName" in properties( pattern, cldr, timeZone ) ); - - if ( length === 2 ) { - assert.equal( "America/Los_Angeles", properties( pattern, cldr, timeZone ).timeZoneName ); - } else if ( length === 3 ) { - assert.equal( "Los Angeles", properties( pattern, cldr, timeZone ).timeZoneName ); - } else if ( length === 4 ) { - assert.equal( "Los Angeles Time", properties( pattern, cldr, timeZone ).timeZoneName ); - } - }); +QUnit.test( "should return genericTzName property for zone (v|vvvv)", function( assert ) { + var pattern, + timeZone = "America/Los_Angeles"; + + pattern = "v"; + assert.equal( properties( pattern, cldr, timeZone ).genericTzName, "PT" ); + + pattern = "vvvv"; + assert.equal( properties( pattern, cldr, timeZone ).genericTzName, "Pacific Time" ); +}); + +QUnit.test( "should return timeZoneName properties for zone (VV|VVV|VVVV)", function( assert ) { + var pattern, + timeZone = "America/Los_Angeles"; + + pattern = "VV"; + assert.equal( properties( pattern, cldr, timeZone ).timeZoneName, "America/Los_Angeles" ); + + pattern = "VVV"; + assert.equal( properties( pattern, cldr, timeZone ).timeZoneName, "Los Angeles" ); + + pattern = "VVVV"; + assert.equal( properties( pattern, cldr, timeZone ).timeZoneName, "Los Angeles Time" ); +}); + +QUnit.test( "should return timeZoneData properties when using timeZone for any pattern", function( assert ) { + var formatProperties = properties( "d", cldr, "America/Los_Angeles" ); + assert.ok( "timeZoneData" in formatProperties ); + assert.ok( "offsets" in formatProperties.timeZoneData ); + assert.ok( "untils" in formatProperties.timeZoneData ); + assert.ok( "isdsts" in formatProperties.timeZoneData ); }); }); diff --git a/test/unit/date/format.js b/test/unit/date/format.js index 2eec4faa0..567038f99 100644 --- a/test/unit/date/format.js +++ b/test/unit/date/format.js @@ -7,6 +7,7 @@ define([ "json!cldr-data/main/en/ca-gregorian.json", "json!cldr-data/main/en/timeZoneNames.json", "json!cldr-data/main/en-GB/ca-gregorian.json", + "json!cldr-data/main/en-GB/timeZoneNames.json", "json!cldr-data/main/en-IN/ca-gregorian.json", "json!cldr-data/main/pt/ca-gregorian.json", "json!cldr-data/main/ru/ca-gregorian.json", @@ -14,12 +15,13 @@ define([ "json!cldr-data/supplemental/timeData.json", "json!cldr-data/supplemental/weekData.json", "json!cldr-data/supplemental/metaZones.json", + "json!iana-tz-data.json", "cldr/event", "cldr/supplemental" ], function( Cldr, format, formatProperties, stringPad, deCaGregorian, enCaGregorian, - enTimeZoneNames, enGbCaGregorian, enInCaGregorian, ptCaGregorian, ruCaGregorian, likelySubtags, - timeData, weekData, metaZones ) { + enTimeZoneNames, enGbCaGregorian, enGbTimeZoneNames, enInCaGregorian, ptCaGregorian, + ruCaGregorian, likelySubtags, timeData, weekData, metaZones, ianaTimezoneData ) { var cldr, year0 = new Date( -62167190400000 ), @@ -47,6 +49,7 @@ Cldr.load( deCaGregorian, enCaGregorian, enGbCaGregorian, + enGbTimeZoneNames, enInCaGregorian, enTimeZoneNames, likelySubtags, @@ -59,12 +62,6 @@ Cldr.load( cldr = new Cldr( "en" ); -// test exemplerCity when metaZones not found for a given timeZone -// zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}" -// z...zzz: fall back to "O" format -// vvvv: "{regionName} {Time}" -// v...vvv: fall back to "O" format - Cldr.load({ "main": { "en": { @@ -72,7 +69,7 @@ Cldr.load({ "timeZoneNames": { "zone": { "Foo":{ - "Bar":{ + "Baz":{ "exemplarCity": "Foo City" } } @@ -83,579 +80,25 @@ Cldr.load({ } }); -// needed for magicDate +// Needed for globalizeDate. Cldr.load({ + "globalize-iana": ianaTimezoneData +}, { "globalize-iana": { - "zones": [ - { - "name": "America/Los_Angeles", - "abbrs": [ - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PWT", - "PPT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST", - "PDT", - "PST" - ], - "untils": [ - -1633269600000, - -1615129200000, - -1601820000000, - -1583679600000, - -880207200000, - -769395600000, - -765385200000, - -687967140000, - -662655600000, - -620838000000, - -608137200000, - -589388400000, - -576082800000, - -557938800000, - -544633200000, - -526489200000, - -513183600000, - -495039600000, - -481734000000, - -463590000000, - -450284400000, - -431535600000, - -418230000000, - -400086000000, - -386780400000, - -368636400000, - -355330800000, - -337186800000, - -323881200000, - -305737200000, - -292431600000, - -273682800000, - -260982000000, - -242233200000, - -226508400000, - -210783600000, - -195058800000, - -179334000000, - -163609200000, - -147884400000, - -131554800000, - -116434800000, - -100105200000, - -84376800000, - -68655600000, - -52927200000, - -37206000000, - -21477600000, - -5756400000, - 9972000000, - 25693200000, - 41421600000, - 57747600000, - 73476000000, - 89197200000, - 104925600000, - 120646800000, - 126698400000, - 152096400000, - 162381600000, - 183546000000, - 199274400000, - 215600400000, - 230724000000, - 247050000000, - 262778400000, - 278499600000, - 294228000000, - 309949200000, - 325677600000, - 341398800000, - 357127200000, - 372848400000, - 388576800000, - 404902800000, - 420026400000, - 436352400000, - 452080800000, - 467802000000, - 483530400000, - 499251600000, - 514980000000, - 530701200000, - 544615200000, - 562150800000, - 576064800000, - 594205200000, - 607514400000, - 625654800000, - 638964000000, - 657104400000, - 671018400000, - 688554000000, - 702468000000, - 720003600000, - 733917600000, - 752058000000, - 765367200000, - 783507600000, - 796816800000, - 814957200000, - 828871200000, - 846406800000, - 860320800000, - 877856400000, - 891770400000, - 909306000000, - 923220000000, - 941360400000, - 954669600000, - 972810000000, - 986119200000, - 1004259600000, - 1018173600000, - 1035709200000, - 1049623200000, - 1067158800000, - 1081072800000, - 1099213200000, - 1112522400000, - 1130662800000, - 1143972000000, - 1162112400000, - 1173607200000, - 1194166800000, - 1205056800000, - 1225616400000, - 1236506400000, - 1257066000000, - 1268560800000, - 1289120400000, - 1300010400000, - 1320570000000, - 1331460000000, - 1352019600000, - 1362909600000, - 1383469200000, - 1394359200000, - 1414918800000, - 1425808800000, - 1446368400000, - 1457863200000, - 1478422800000, - 1489312800000, - 1509872400000, - 1520762400000, - 1541322000000, - 1552212000000, - 1572771600000, - 1583661600000, - 1604221200000, - 1615716000000, - 1636275600000, - 1647165600000, - 1667725200000, - 1678615200000, - 1699174800000, - 1710064800000, - 1730624400000, - 1741514400000, - 1762074000000, - 1772964000000, - 1793523600000, - 1805018400000, - 1825578000000, - 1836468000000, - 1857027600000, - 1867917600000, - 1888477200000, - 1899367200000, - 1919926800000, - 1930816800000, - 1951376400000, - 1962871200000, - 1983430800000, - 1994320800000, - 2014880400000, - 2025770400000, - 2046330000000, - 2057220000000, - 2077779600000, - 2088669600000, - 2109229200000, - 2120119200000, - 2140678800000, - null - ], - "offsets": [ - 480, - 420, - 480, - 420, - 480, - 420, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480, - 420, - 480 - ], - "population": 15058000 + "zoneData": { + "Foo": { + "Bar": { + offsets: ianaTimezoneData.zoneData.UTC.offsets, + untils: ianaTimezoneData.zoneData.UTC.untils, + isdsts: ianaTimezoneData.zoneData.UTC.isdsts + }, + "Baz": { + offsets: ianaTimezoneData.zoneData.UTC.offsets, + untils: ianaTimezoneData.zoneData.UTC.untils, + isdsts: ianaTimezoneData.zoneData.UTC.isdsts + } } - ] + } } }); @@ -1581,110 +1024,266 @@ QUnit.test( "should format various milliseconds (A+)", function( assert ) { * Zone */ QUnit.test( "should format timezone (z)", function( assert ) { + var date, + enGb = new Cldr( "en-GB" ); - //Test for country with Daylight Savings and Standard time - //eg. Pacific Standard Time and Pacific Daylight Time - var date = new Date( 2017, 0, 1 ); - assert.dateFormatWithTimezone( date, "z", "America/Los_Angeles", cldr, "PST" ); - assert.dateFormatWithTimezone( date, "zz", "America/Los_Angeles", cldr, "PST" ); - assert.dateFormatWithTimezone( date, "zzz", "America/Los_Angeles", cldr, "PST" ); - assert.dateFormatWithTimezone( date, "zzzz", "America/Los_Angeles", cldr, "Pacific Standard Time" ); + // Test for country with Daylight Savings and Standard time, e.g., Pacific Standard Time and + // Pacific Daylight Time. + date = new Date( 2017, 0, 1 ); + assert.dateFormatWithTimezone( date, "z", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PST" + }]); + assert.dateFormatWithTimezone( date, "zz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PST" + }]); + assert.dateFormatWithTimezone( date, "zzz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PST" + }]); + assert.dateFormatWithTimezone( date, "zzzz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "Pacific Standard Time" + }]); date = new Date( 2017, 6, 1 ); - assert.dateFormatWithTimezone( date, "z", "America/Los_Angeles", cldr, "PDT" ); - assert.dateFormatWithTimezone( date, "zz", "America/Los_Angeles", cldr, "PDT" ); - assert.dateFormatWithTimezone( date, "zzz", "America/Los_Angeles", cldr, "PDT" ); - assert.dateFormatWithTimezone( date, "zzzz", "America/Los_Angeles", cldr, "Pacific Daylight Time" ); - - - //Test for country with only standard time - //eg. long: Indian Standard Time - //This test also covers the case where timezone name is undefined - //like short timezone name for Asia/Calcutta and should fall through 'O' format - date = new FakeDate( 0 ); - date.isDST = function(){ - return false; - }; - assert.dateFormatWithTimezone( date, "z", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zz", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzz", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzzz", "Asia/Calcutta", cldr, "India Standard Time" ); + assert.dateFormatWithTimezone( date, "z", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PDT" + }]); + assert.dateFormatWithTimezone( date, "zz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PDT" + }]); + assert.dateFormatWithTimezone( date, "zzz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PDT" + }]); + assert.dateFormatWithTimezone( date, "zzzz", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "Pacific Daylight Time" + }]); - date.isDST = function(){ - return true; - }; - assert.dateFormatWithTimezone( date, "z", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zz", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzz", "Asia/Calcutta", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzzz", "Asia/Calcutta", cldr, "India Standard Time" ); - - //fall through to 'O' format - //using "America/Argentina/Buenos_Aires" (https://en.wikipedia.org/wiki/America/Argentina/Buenos_Aires) - //which is deprecated in CLDR json and thus no metaZones data - date = new FakeDate( 0 ); - assert.dateFormatWithTimezone( date, "z", "America/Argentina/Buenos_Aires", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zz", "America/Argentina/Buenos_Aires", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzz", "America/Argentina/Buenos_Aires", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "zzzz", "America/Argentina/Buenos_Aires", cldr, "GMT" ); -}); + date = new Date( 2017, 0, 1 ); + assert.dateFormatWithTimezone( date, "zzzz", "Asia/Dubai", cldr, [{ + type: "zone", + value: "Gulf Standard Time" + }]); -QUnit.test( "should format timezone (v)", function( assert ) { - try { - var date = new Date( 2017, 5, 1 ); - assert.dateFormatWithTimezone( date, "v", "America/Los_Angeles", cldr, "PT" ); - assert.dateFormatWithTimezone( date, "vvvv", "America/Los_Angeles", cldr, "Pacific Time" ); + date = new Date( 2017, 6, 1 ); + assert.dateFormatWithTimezone( date, "zzzz", "Asia/Dubai", cldr, [{ + type: "zone", + value: "Gulf Standard Time" + }]); - //fall through 'VVVV' format - assert.dateFormatWithTimezone( date, "v", "Foo/Bar", cldr, "Foo City Time" ); - assert.dateFormatWithTimezone( date, "vvvv", "Foo/Bar", cldr, "Foo City Time" ); + // Test for two things: + // - daylightTzName using the zone data (primary), not the metazone (secondary try); + // - standardTzName being undefined, therefore requiring the O fallback properties; + date = new Date( "2015-06-01T12:32:46" ); + assert.dateFormatWithTimezone( date, "z", "Europe/London", enGb, [{ + type: "zone", + value: "BST" + }]); + assert.dateFormatWithTimezone( date, "zz", "Europe/London", enGb, [{ + type: "zone", + value: "BST" + }]); + assert.dateFormatWithTimezone( date, "zzz", "Europe/London", enGb, [{ + type: "zone", + value: "BST" + }]); + assert.dateFormatWithTimezone( date, "zzzz", "Europe/London", enGb, [{ + type: "zone", + value: "British Summer Time" + }]); - //fall through to 'VVVV' format with "Unknown" exemplarCity - //using "America/Argentina/Buenos_Aires" (https://en.wikipedia.org/wiki/America/Argentina/Buenos_Aires) - //which is deprecated in CLDR json and thus no metaZones data - date = new FakeDate( 0 ); - assert.dateFormatWithTimezone( date, "v", "America/Argentina/Buenos_Aires", cldr, "GMT" ); - assert.dateFormatWithTimezone( date, "vvvv", "America/Argentina/Buenos_Aires", cldr, "GMT" ); - } catch ( e ) { + date = new Date( "2015-01-01T12:32:46" ); + assert.dateFormatWithTimezone( date, "z", "Europe/London", enGb, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date, "zz", "Europe/London", enGb, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date, "zzz", "Europe/London", enGb, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date, "zzzz", "Europe/London", enGb, [{ + type: "zone", + value: "GMT" + }]); - /* jshint ignore:start */ - console.log ( "Error: ", e.stack); + // Test for country with only standard time, e.g., long: Indian Standard Time. + // This test also covers the case where timezone name is undefined like short timezone name for + // Asia/Calcutta and should fall through 'O' format. + // FIXME need date that isDST === false. + date = new Date( 2017, 0, 1 ); + assert.dateFormatWithTimezone( date1, "z", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zzz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zzzz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "India Standard Time" + }]); - /* jshint ignore:end */ + // FIXME need date that isDST === true. + date = new Date( 1943, 0, 1 ); + assert.dateFormatWithTimezone( date1, "z", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zzz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "GMT+5" + }]); + assert.dateFormatWithTimezone( date1, "zzzz", "Asia/Calcutta", cldr, [{ + type: "zone", + value: "India Standard Time" + }]); - } + // Fall through to 'O' format + assert.dateFormatWithTimezone( date1, "z", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date1, "zz", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date1, "zzz", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date1, "zzzz", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); +}); + +QUnit.test( "should format timezone (v)", function( assert ) { + var date = new Date( 2017, 5, 1 ); + assert.dateFormatWithTimezone( date, "v", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "PT" + }]); + assert.dateFormatWithTimezone( date, "vvvv", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "Pacific Time" + }]); + + // Fall through 'VVVV' format. + assert.dateFormatWithTimezone( date, "v", "Foo/Baz", cldr, [{ + type: "zone", + value: "Foo City Time" + }]); + assert.dateFormatWithTimezone( date, "vvvv", "Foo/Baz", cldr, [{ + type: "zone", + value: "Foo City Time" + }]); + + // Fall through to 'VVVV' format with "Unknown" exemplarCity. + assert.dateFormatWithTimezone( date, "v", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormatWithTimezone( date, "vvvv", "Foo/Bar", cldr, [{ + type: "zone", + value: "GMT" + }]); }); QUnit.test( "should format timezone (V)", function( assert ) { var date = new Date( 2017, 5, 1 ); - assert.dateFormatWithTimezone( date, "VV", "America/Los_Angeles", cldr, "America/Los_Angeles" ); - assert.dateFormatWithTimezone( date, "VVV", "America/Los_Angeles", cldr, "Los Angeles" ); - assert.dateFormatWithTimezone( date, "VVVV", "America/Los_Angeles", cldr, "Los Angeles Time" ); - - //fall through to 'VVVV' format with "Unknown" exemplarCity - //using "America/Argentina/Buenos_Aires" (https://en.wikipedia.org/wiki/America/Argentina/Buenos_Aires) - //which is deprecated in CLDR json and thus no metaZones data - date = new FakeDate( 0 ); - assert.dateFormatWithTimezone( date, "VVV", "America/Argentina/Buenos_Aires", cldr, "Unknown City" ); + assert.dateFormatWithTimezone( date, "VV", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "America/Los_Angeles" + }]); + assert.dateFormatWithTimezone( date, "VVV", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "Los Angeles" + }]); + assert.dateFormatWithTimezone( date, "VVVV", "America/Los_Angeles", cldr, [{ + type: "zone", + value: "Los Angeles Time" + }]); + + // Fall through to 'VVVV' format with "Unknown" exemplarCity. + assert.dateFormatWithTimezone( date, "VVV", "Foo/Bar", cldr, [{ + type: "zone", + value: "Unknown City" + }]); }); QUnit.test( "should format timezone (O)", function( assert ) { var date = new FakeDate( 0 ); - assert.dateFormat( date, "O", cldr, "GMT" ); - assert.dateFormat( date, "OO", cldr, "GMT" ); - assert.dateFormat( date, "OOO", cldr, "GMT" ); - assert.dateFormat( date, "OOOO", cldr, "GMT" ); + assert.dateFormat( date, "O", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormat( date, "OO", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormat( date, "OOO", cldr, [{ + type: "zone", + value: "GMT" + }]); + assert.dateFormat( date, "OOOO", cldr, [{ + type: "zone", + value: "GMT" + }]); date = new FakeDate( -3 ); - assert.dateFormat( date, "O", cldr, "GMT-3" ); - assert.dateFormat( date, "OO", cldr, "GMT-3" ); - assert.dateFormat( date, "OOO", cldr, "GMT-3" ); - assert.dateFormat( date, "OOOO", cldr, "GMT-03:00" ); + assert.dateFormat( date, "O", cldr, [{ + type: "zone", + value: "GMT-3" + }]); + assert.dateFormat( date, "OO", cldr, [{ + type: "zone", + value: "GMT-3" + }]); + assert.dateFormat( date, "OOO", cldr, [{ + type: "zone", + value: "GMT-3" + }]); + assert.dateFormat( date, "OOOO", cldr, [{ + type: "zone", + value: "GMT-03:00" + }]); date = new FakeDate( 11 ); - assert.dateFormat( date, "O", cldr, "GMT+11" ); - assert.dateFormat( date, "OO", cldr, "GMT+11" ); - assert.dateFormat( date, "OOO", cldr, "GMT+11" ); - assert.dateFormat( date, "OOOO", cldr, "GMT+11:00" ); + assert.dateFormat( date, "O", cldr, [{ + type: "zone", + value: "GMT+11" + }]); + assert.dateFormat( date, "OO", cldr, [{ + type: "zone", + value: "GMT+11" + }]); + assert.dateFormat( date, "OOO", cldr, [{ + type: "zone", + value: "GMT+11" + }]); + assert.dateFormat( date, "OOOO", cldr, [{ + type: "zone", + value: "GMT+11:00" + }]); }); QUnit.test( "should format timezone (Z)", function( assert ) { diff --git a/test/unit/date/globalize-date.js b/test/unit/date/globalize-date.js deleted file mode 100644 index 1d13db49c..000000000 --- a/test/unit/date/globalize-date.js +++ /dev/null @@ -1,305 +0,0 @@ -define([ - "src/util/globalize-date" -], function( GlobalizeDate ) { - -var date = new Date(Date.UTC(2017, 1, 19, 2)), - date1 = new Date(Date.UTC(2017, 1, 19, 1)); - -QUnit.assert.magicDate = function( date, tzdata, expected ) { - var globalizeDate = new GlobalizeDate( date, tzdata ); - this.equal( globalizeDate.getDate(), expected.Date ); - this.equal( globalizeDate.getMonth(), expected.Month ); - this.equal( globalizeDate.getFullYear(), expected.Year ); - this.equal( globalizeDate.getHours(), expected.Hours ); - this.equal( globalizeDate.getMinutes(), expected.Minutes ); - this.equal( globalizeDate.getSeconds(), expected.Seconds ); -}; - -QUnit.test( "should test magic date with trasitions", function( assert ) { - var TzData_Sao_Paulo = { - "name": "America/Sao_Paulo", - "untils": [ - -1767214412000, - -1206957600000, - -1191362400000, - -1175374800000, - -1159826400000, - -633819600000, - -622069200000, - -602283600000, - -591832800000, - -570747600000, - -560210400000, - -539125200000, - -531352800000, - -195426000000, - -184197600000, - -155163600000, - -150069600000, - -128898000000, - -121125600000, - -99954000000, - -89589600000, - -68418000000, - -57967200000, - 499748400000, - 511236000000, - 530593200000, - 540266400000, - 562129200000, - 571197600000, - 592974000000, - 602042400000, - 624423600000, - 634701600000, - 656478000000, - 666756000000, - 687927600000, - 697600800000, - 719982000000, - 728445600000, - 750826800000, - 761709600000, - 782276400000, - 793159200000, - 813726000000, - 824004000000, - 844570800000, - 856058400000, - 876106800000, - 888717600000, - 908074800000, - 919562400000, - 938919600000, - 951616800000, - 970974000000, - 982461600000, - 1003028400000, - 1013911200000, - 1036292400000, - 1045360800000, - 1066532400000, - 1076810400000, - 1099364400000, - 1108864800000, - 1129431600000, - 1140314400000, - 1162695600000, - 1172368800000, - 1192330800000, - 1203213600000, - 1224385200000, - 1234663200000, - 1255834800000, - 1266717600000, - 1287284400000, - 1298167200000, - 1318734000000, - 1330221600000, - 1350788400000, - 1361066400000, - 1382238000000, - 1392516000000, - 1413687600000, - 1424570400000, - 1445137200000, - 1456020000000, - 1476586800000, - 1487469600000, - 1508036400000, - 1518919200000, - 1540090800000, - 1550368800000, - 1571540400000, - 1581818400000, - 1602990000000, - 1613872800000, - 1634439600000, - 1645322400000, - 1665889200000, - 1677376800000, - 1697338800000, - 1708221600000, - 1729393200000, - 1739671200000, - 1760842800000, - 1771725600000, - 1792292400000, - 1803175200000, - 1823742000000, - 1834624800000, - 1855191600000, - 1866074400000, - 1887246000000, - 1897524000000, - 1918695600000, - 1928973600000, - 1950145200000, - 1960423200000, - 1981594800000, - 1992477600000, - 2013044400000, - 2024532000000, - 2044494000000, - 2055376800000, - 2076548400000, - 2086826400000, - 2107998000000, - 2118880800000, - 2139447600000, - null - ], - "offsets": [ - 186.4667, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120, - 180, - 120 - ], - "population": 20365000 - }; - var expectedDate = { - Date: 18, - Month: 1, - Year: 2017, - Hours: 23, - Minutes: 0, - Seconds:0 - }; - assert.magicDate( date, TzData_Sao_Paulo, expectedDate ); - expectedDate = { - Date: 18, - Month: 1, - Year: 2017, - Hours: 23, - Minutes: 0, - Seconds:0 - }; - assert.magicDate( date1, TzData_Sao_Paulo, expectedDate ); -}); - -}); diff --git a/test/unit/date/parse-properties.js b/test/unit/date/parse-properties.js index 96ba9c976..4f9d39c06 100644 --- a/test/unit/date/parse-properties.js +++ b/test/unit/date/parse-properties.js @@ -4,10 +4,11 @@ define([ "json!cldr-data/main/en/ca-gregorian.json", "json!cldr-data/supplemental/likelySubtags.json", "json!cldr-data/supplemental/timeData.json", + "json!iana-tz-data.json", "cldr/event", "cldr/supplemental" -], function( Cldr, parseProperties, enCaGregorian, likelySubtags, timeData ) { +], function( Cldr, parseProperties, enCaGregorian, likelySubtags, timeData, ianaTimezoneData ) { var cldr; @@ -17,12 +18,26 @@ Cldr.load( timeData ); +// Needed for globalizeDate. +Cldr.load({ + "globalize-iana": ianaTimezoneData +}); + cldr = new Cldr( "en" ); QUnit.module( "Date Parse Properties" ); QUnit.test( "should return parse properties", function( assert ) { + var properties; + assert.ok( "preferredTimeData" in parseProperties( cldr ) ); + + properties = parseProperties( cldr, "America/Los_Angeles" ); + assert.ok( "preferredTimeData" in properties ); + assert.ok( "timeZoneData" in properties ); + assert.ok( "offsets" in properties.timeZoneData ); + assert.ok( "untils" in properties.timeZoneData ); + assert.ok( "isdsts" in properties.timeZoneData ); }); }); diff --git a/test/unit/date/parse.js b/test/unit/date/parse.js index 034b71bb3..e370ed0f8 100644 --- a/test/unit/date/parse.js +++ b/test/unit/date/parse.js @@ -12,25 +12,31 @@ define([ "json!cldr-data/supplemental/likelySubtags.json", "json!cldr-data/supplemental/timeData.json", "json!cldr-data/supplemental/weekData.json", + "json!iana-tz-data.json", "../../util", "cldr/event", "cldr/supplemental" ], function( Cldr, parse, parseProperties, startOf, tokenizer, numberTokenizerProperties, - enCaGregorian, enNumbers, zhCaGregorian, zhNumbers, likelySubtags, timeData, weekData, util ) { + enCaGregorian, enNumbers, zhCaGregorian, zhNumbers, likelySubtags, timeData, weekData, + ianaTimezoneData, util ) { var cldr, date1, date2, midnight, zh; -function assertParse( assert, stringDate, pattern, cldr, date ) { +QUnit.assert.dateParse = function( stringDate, pattern, cldr, expected ) { + this.dateParseWithTimezone( stringDate, pattern, undefined, cldr, expected ); +}; + +QUnit.assert.dateParseWithTimezone = function( stringDate, pattern, timeZone, cldr, expected ) { var tokenizerProperties, tokens; tokenizerProperties = numberTokenizerProperties( pattern, cldr ); tokens = tokenizer( stringDate, simpleNumberParser, tokenizerProperties ); - assert.deepEqual( parse( stringDate, tokens, parseProperties( cldr ) ), date ); -} + this.deepEqual( parse( stringDate, tokens, parseProperties( cldr, timeZone ) ), expected ); +}; -function assertParseTimezone( assert, stringDate, pattern, cldr, timezoneOffset ) { +QUnit.assert.timezoneParse = function( stringDate, pattern, cldr, timezoneOffset ) { var parsedTimezoneOffset, parsedDate, tokenizerProperties, tokens, testPattern = "HH:mm " + pattern, testStringDate = "00:00 " + stringDate; @@ -40,9 +46,9 @@ function assertParseTimezone( assert, stringDate, pattern, cldr, timezoneOffset parsedDate = parse( testStringDate, tokens, parseProperties( cldr ) ); parsedTimezoneOffset = ( parsedDate - midnight ) / 1000 / 60 + midnight.getTimezoneOffset(); - assert.equal( parsedTimezoneOffset, timezoneOffset, "stringDate `" + stringDate + + this.equal( parsedTimezoneOffset, timezoneOffset, "stringDate `" + stringDate + "` pattern `" + pattern + "`" ); -} +}; // Simple number parser for this test purposes. function simpleNumberParser( value ) { @@ -59,6 +65,11 @@ Cldr.load( weekData ); +// Needed for globalizeDate. +Cldr.load({ + "globalize-iana": ianaTimezoneData +}); + cldr = new Cldr( "en" ); zh = new Cldr( "zh" ); @@ -76,12 +87,12 @@ QUnit.test( "should parse era (G|GG|GGG)", function( assert ) { date2 = new Date( 0, 0 ); date1.setFullYear( 4 ); date2.setFullYear( -4 ); - assertParse( assert, "AD 4", "G y", cldr, date1 ); - assertParse( assert, "BC 5", "G y", cldr, date2 ); - assertParse( assert, "AD 4", "GG y", cldr, date1 ); - assertParse( assert, "BC 5", "GG y", cldr, date2 ); - assertParse( assert, "AD 4", "GGG y", cldr, date1 ); - assertParse( assert, "BC 5", "GGG y", cldr, date2 ); + assert.dateParse( "AD 4", "G y", cldr, date1 ); + assert.dateParse( "BC 5", "G y", cldr, date2 ); + assert.dateParse( "AD 4", "GG y", cldr, date1 ); + assert.dateParse( "BC 5", "GG y", cldr, date2 ); + assert.dateParse( "AD 4", "GGG y", cldr, date1 ); + assert.dateParse( "BC 5", "GGG y", cldr, date2 ); }); QUnit.test( "should parse era (GGGG)", function( assert ) { @@ -89,8 +100,8 @@ QUnit.test( "should parse era (GGGG)", function( assert ) { date2 = new Date( 0, 0 ); date1.setFullYear( 4 ); date2.setFullYear( -4 ); - assertParse( assert, "Anno Domini 4", "GGGG y", cldr, date1 ); - assertParse( assert, "Before Christ 5", "GGGG y", cldr, date2 ); + assert.dateParse( "Anno Domini 4", "GGGG y", cldr, date1 ); + assert.dateParse( "Before Christ 5", "GGGG y", cldr, date2 ); }); QUnit.test( "should parse era (GGGGG)", function( assert ) { @@ -98,8 +109,8 @@ QUnit.test( "should parse era (GGGGG)", function( assert ) { date2 = new Date( 0, 0 ); date1.setFullYear( 4 ); date2.setFullYear( -4 ); - assertParse( assert, "A 4", "GGGGG y", cldr, date1 ); - assertParse( assert, "B 5", "GGGGG y", cldr, date2 ); + assert.dateParse( "A 4", "GGGGG y", cldr, date1 ); + assert.dateParse( "B 5", "GGGGG y", cldr, date2 ); }); /** @@ -107,27 +118,27 @@ QUnit.test( "should parse era (GGGGG)", function( assert ) { */ QUnit.test( "should parse year (y) with no padding", function( assert ) { - assertParse( assert, "1982", "y", cldr, new Date( 1982, 0 ) ); + assert.dateParse( "1982", "y", cldr, new Date( 1982, 0 ) ); date1 = new Date(0, 0); date1.setFullYear(2); - assertParse( assert, "2", "y", cldr, date1 ); - assertParse( assert, "02", "y", cldr, date1 ); + assert.dateParse( "2", "y", cldr, date1 ); + assert.dateParse( "02", "y", cldr, date1 ); }); QUnit.test( "should parse year (yy) with padding, and limit 2 digits", function( assert ) { // This may change in the future, eg. 82 could eventually be 2082, same for the below years. - assertParse( assert, "82", "yy", cldr, new Date( 1982, 0 ) ); - assertParse( assert, "9", "yy", cldr, new Date( 2009, 0 ) ); - assertParse( assert, "09", "yy", cldr, new Date( 2009, 0 ) ); + assert.dateParse( "82", "yy", cldr, new Date( 1982, 0 ) ); + assert.dateParse( "9", "yy", cldr, new Date( 2009, 0 ) ); + assert.dateParse( "09", "yy", cldr, new Date( 2009, 0 ) ); }); QUnit.test( "should parse year (yyy+) with padding", function( assert ) { - assertParse( assert, "1982", "yyy", cldr, new Date( 1982, 0 ) ); - assertParse( assert, "82", "yyy", cldr, null ); + assert.dateParse( "1982", "yyy", cldr, new Date( 1982, 0 ) ); + assert.dateParse( "82", "yyy", cldr, null ); - assertParse( assert, "01982", "yyyyy", cldr, new Date( 1982, 0 ) ); - assertParse( assert, "1982", "yyyyy", cldr, null ); + assert.dateParse( "01982", "yyyyy", cldr, new Date( 1982, 0 ) ); + assert.dateParse( "1982", "yyyyy", cldr, null ); }); /** @@ -138,42 +149,42 @@ QUnit.test( "should parse month (M|L) with no padding", function( assert ) { date1 = new Date(); date1.setMonth( 0 ); date1 = startOf( date1, "month" ); - assertParse( assert, "1", "M", cldr, date1 ); - assertParse( assert, "1", "L", cldr, date1 ); + assert.dateParse( "1", "M", cldr, date1 ); + assert.dateParse( "1", "L", cldr, date1 ); }); QUnit.test( "should parse month (MM|LL) with padding", function( assert ) { date1 = new Date(); date1.setMonth( 0 ); date1 = startOf( date1, "month" ); - assertParse( assert, "1", "MM", cldr, date1 ); - assertParse( assert, "01", "MM", cldr, date1 ); - assertParse( assert, "1", "LL", cldr, date1 ); - assertParse( assert, "01", "LL", cldr, date1 ); + assert.dateParse( "1", "MM", cldr, date1 ); + assert.dateParse( "01", "MM", cldr, date1 ); + assert.dateParse( "1", "LL", cldr, date1 ); + assert.dateParse( "01", "LL", cldr, date1 ); }); QUnit.test( "should parse month (MMM|LLL)", function( assert ) { date1 = new Date(); date1.setMonth( 0 ); date1 = startOf( date1, "month" ); - assertParse( assert, "Jan", "MMM", cldr, date1 ); - assertParse( assert, "Jan", "LLL", cldr, date1 ); + assert.dateParse( "Jan", "MMM", cldr, date1 ); + assert.dateParse( "Jan", "LLL", cldr, date1 ); }); QUnit.test( "should parse month (MMMM|LLLL)", function( assert ) { date1 = new Date(); date1.setMonth( 0 ); date1 = startOf( date1, "month" ); - assertParse( assert, "January", "MMMM", cldr, date1 ); - assertParse( assert, "January", "LLLL", cldr, date1 ); + assert.dateParse( "January", "MMMM", cldr, date1 ); + assert.dateParse( "January", "LLLL", cldr, date1 ); }); QUnit.test( "should parse month (MMMMM|LLLLL)", function( assert ) { date1 = new Date(); date1.setMonth( 0 ); date1 = startOf( date1, "month" ); - assertParse( assert, "J", "MMMMM", cldr, date1 ); - assertParse( assert, "J", "LLLLL", cldr, date1 ); + assert.dateParse( "J", "MMMMM", cldr, date1 ); + assert.dateParse( "J", "LLLLL", cldr, date1 ); }); QUnit.test( "should parse February correctly in leap year", function( assert ) { @@ -189,8 +200,8 @@ QUnit.test( "should parse February correctly in leap year", function( assert ) { date1.setMonth( 1 ); date1 = startOf( date1, "month" ); date1.setYear( 2015 ); - assertParse( assert, "2/2015", "M/y", cldr, date1 ); - assertParse( assert, "2/2015", "L/y", cldr, date1 ); + assert.dateParse( "2/2015", "M/y", cldr, date1 ); + assert.dateParse( "2/2015", "L/y", cldr, date1 ); Date = OrigDate; }); @@ -205,7 +216,7 @@ QUnit.test( "should parse day (d) with no padding", function( assert ) { date1 = new Date(); date1.setDate( 2 ); date1 = startOf( date1, "day" ); - assertParse( assert, "2", "d", cldr, date1 ); + assert.dateParse( "2", "d", cldr, date1 ); /* globals Date:true */ // Test #323 - Day parsing must use the correct day range given its corresponding month/year. @@ -215,19 +226,19 @@ QUnit.test( "should parse day (d) with no padding", function( assert ) { date1 = new Date( 2014, 1, 28 ); date1 = startOf( date1, "day" ); util.FakeDate.today = new Date( 2014, 1 ); - assertParse( assert, "29", "d", cldr, null ); - assertParse( assert, "28", "d", cldr, date1 ); + assert.dateParse( "29", "d", cldr, null ); + assert.dateParse( "28", "d", cldr, date1 ); date2 = new Date( 2016, 1, 29 ); date2 = startOf( date2, "day" ); util.FakeDate.today = new Date( 2016, 1 ); - assertParse( assert, "30", "d", cldr, null ); - assertParse( assert, "29", "d", cldr, date2 ); + assert.dateParse( "30", "d", cldr, null ); + assert.dateParse( "29", "d", cldr, date2 ); // Test #612 - Incorrect parsing when days in today's month is bigger than // the parsing month. util.FakeDate.today = new Date( 2016, 11, 31 ); - assertParse( assert, "2/2/2015", "M/d/y", cldr, new Date( 2015, 1, 2 ) ); + assert.dateParse( "2/2/2015", "M/d/y", cldr, new Date( 2015, 1, 2 ) ); Date = OrigDate; }); @@ -236,8 +247,8 @@ QUnit.test( "should parse day (dd) with padding", function( assert ) { date1 = new Date(); date1.setDate( 2 ); date1 = startOf( date1, "day" ); - assertParse( assert, "2", "dd", cldr, date1 ); - assertParse( assert, "02", "dd", cldr, date1 ); + assert.dateParse( "2", "dd", cldr, date1 ); + assert.dateParse( "02", "dd", cldr, date1 ); }); QUnit.test( "should parse day of year (D) with no padding", function( assert ) { @@ -247,7 +258,7 @@ QUnit.test( "should parse day of year (D) with no padding", function( assert ) { date1.setMonth( 0 ); date1.setDate( 2 ); date1 = startOf( date1, "day" ); - assertParse( assert, "2", "D", cldr, date1 ); + assert.dateParse( "2", "D", cldr, date1 ); /* globals Date:true */ // Test #323 - Day of year parsing must use the correct day range given leap year into account. @@ -255,12 +266,12 @@ QUnit.test( "should parse day of year (D) with no padding", function( assert ) { Date = util.FakeDate; util.FakeDate.today = new Date( 2014, 1 ); - assertParse( assert, "366", "D", cldr, null ); + assert.dateParse( "366", "D", cldr, null ); // date1 = last day of 2016 date1 = new Date( 2017, 0, 0 ); util.FakeDate.today = new Date( 2016, 1 ); - assertParse( assert, "366", "D", cldr, date1 ); + assert.dateParse( "366", "D", cldr, date1 ); Date = OrigDate; }); @@ -270,8 +281,8 @@ QUnit.test( "should parse day of year (DD|DDD) with padding", function( assert ) date1.setMonth( 0 ); date1.setDate( 2 ); date1 = startOf( date1, "day" ); - assertParse( assert, "02", "DD", cldr, date1 ); - assertParse( assert, "002", "DDD", cldr, date1 ); + assert.dateParse( "02", "DD", cldr, date1 ); + assert.dateParse( "002", "DDD", cldr, date1 ); }); /** @@ -285,10 +296,10 @@ QUnit.test( "should parse period (a)", function( assert ) { date2.setHours( 17 ); date1 = startOf( date1, "hour" ); date2 = startOf( date2, "hour" ); - assertParse( assert, "5 AM", "h a", cldr, date1 ); - assertParse( assert, "5 PM", "h a", cldr, date2 ); - assertParse( assert, "上午5", "ah", zh, date1 ); - assertParse( assert, "下午5", "ah", zh, date2 ); + assert.dateParse( "5 AM", "h a", cldr, date1 ); + assert.dateParse( "5 PM", "h a", cldr, date2 ); + assert.dateParse( "上午5", "ah", zh, date1 ); + assert.dateParse( "下午5", "ah", zh, date2 ); }); /** @@ -296,135 +307,135 @@ QUnit.test( "should parse period (a)", function( assert ) { */ QUnit.test( "should parse hour (h) using 12-hour-cycle [1-12] with no padding", function( assert ) { - assertParse( assert, "1", "h", cldr, null, "12-hour time without period should return null" ); - assertParse( assert, "0 AM", "h a", cldr, null, "Out of range should return null" ); - assertParse( assert, "13 AM", "h a", cldr, null, "Out of range should return null" ); + assert.dateParse( "1", "h", cldr, null, "12-hour time without period should return null" ); + assert.dateParse( "0 AM", "h a", cldr, null, "Out of range should return null" ); + assert.dateParse( "13 AM", "h a", cldr, null, "Out of range should return null" ); date1 = new Date(); date1.setHours( 9 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "9 AM", "h a", cldr, date1 ); + assert.dateParse( "9 AM", "h a", cldr, date1 ); date1.setHours( 0 ); - assertParse( assert, "12 AM", "h a", cldr, date1 ); + assert.dateParse( "12 AM", "h a", cldr, date1 ); date1.setHours( 1 ); - assertParse( assert, "1 AM", "h a", cldr, date1 ); + assert.dateParse( "1 AM", "h a", cldr, date1 ); date1.setHours( 12 ); - assertParse( assert, "12 PM", "h a", cldr, date1 ); + assert.dateParse( "12 PM", "h a", cldr, date1 ); date1.setHours( 13 ); - assertParse( assert, "1 PM", "h a", cldr, date1 ); + assert.dateParse( "1 PM", "h a", cldr, date1 ); }); QUnit.test( "should parse hour (hh) using 12-hour-cycle [1-12] with padding", function( assert ) { date1 = new Date(); date1.setHours( 9 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "9 AM", "hh a", cldr, date1 ); - assertParse( assert, "09 AM", "hh a", cldr, date1 ); + assert.dateParse( "9 AM", "hh a", cldr, date1 ); + assert.dateParse( "09 AM", "hh a", cldr, date1 ); }); QUnit.test( "should parse hour (H) using 24-hour-cycle [0-23] with no padding", function( assert ) { - assertParse( assert, "24", "H", cldr, null, "Out of range should return null" ); + assert.dateParse( "24", "H", cldr, null, "Out of range should return null" ); date1 = new Date(); date1.setHours( 0 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "0", "H", cldr, date1 ); + assert.dateParse( "0", "H", cldr, date1 ); date1.setHours( 1 ); - assertParse( assert, "1", "H", cldr, date1 ); + assert.dateParse( "1", "H", cldr, date1 ); date1.setHours( 12 ); - assertParse( assert, "12", "H", cldr, date1 ); + assert.dateParse( "12", "H", cldr, date1 ); date1.setHours( 16 ); - assertParse( assert, "16", "H", cldr, date1 ); + assert.dateParse( "16", "H", cldr, date1 ); }); QUnit.test( "should parse hour (HH) using 24-hour-cycle [0-23] with padding", function( assert ) { date1 = new Date(); date1.setHours( 9 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "9", "HH", cldr, date1 ); - assertParse( assert, "09", "HH", cldr, date1 ); + assert.dateParse( "9", "HH", cldr, date1 ); + assert.dateParse( "09", "HH", cldr, date1 ); date1.setHours( 16 ); - assertParse( assert, "16", "HH", cldr, date1 ); + assert.dateParse( "16", "HH", cldr, date1 ); }); QUnit.test( "should parse hour (K) using 12-hour-cycle [0-11] with no padding", function( assert ) { - assertParse( assert, "1", "K", cldr, null, "12-hour time without period should return null" ); - assertParse( assert, "12 AM", "K a", cldr, null, "Out of range should return null" ); - assertParse( assert, "13 AM", "K a", cldr, null, "Out of range should return null" ); + assert.dateParse( "1", "K", cldr, null, "12-hour time without period should return null" ); + assert.dateParse( "12 AM", "K a", cldr, null, "Out of range should return null" ); + assert.dateParse( "13 AM", "K a", cldr, null, "Out of range should return null" ); date1 = new Date(); date1.setHours( 0 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "0 AM", "K a", cldr, date1 ); + assert.dateParse( "0 AM", "K a", cldr, date1 ); date1.setHours( 8 ); - assertParse( assert, "8 AM", "K a", cldr, date1 ); + assert.dateParse( "8 AM", "K a", cldr, date1 ); date1.setHours( 12 ); - assertParse( assert, "0 PM", "K a", cldr, date1 ); + assert.dateParse( "0 PM", "K a", cldr, date1 ); date1.setHours( 20 ); - assertParse( assert, "8 PM", "K a", cldr, date1 ); + assert.dateParse( "8 PM", "K a", cldr, date1 ); }); QUnit.test( "should parse hour (KK) using 12-hour-cycle [0-11] with padding", function( assert ) { date1 = new Date(); date1.setHours( 8 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "8 AM", "KK a", cldr, date1 ); - assertParse( assert, "08 AM", "KK a", cldr, date1 ); + assert.dateParse( "8 AM", "KK a", cldr, date1 ); + assert.dateParse( "08 AM", "KK a", cldr, date1 ); }); QUnit.test( "should parse hour (k) using 24-hour-cycle [1-24] with no padding", function( assert ) { - assertParse( assert, "0", "k", cldr, null, "Out of range should return null" ); + assert.dateParse( "0", "k", cldr, null, "Out of range should return null" ); date1 = new Date(); date1.setHours( 0 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "24", "k", cldr, date1 ); + assert.dateParse( "24", "k", cldr, date1 ); date1.setHours( 8 ); - assertParse( assert, "8", "k", cldr, date1 ); + assert.dateParse( "8", "k", cldr, date1 ); date1.setHours( 12 ); - assertParse( assert, "12", "k", cldr, date1 ); + assert.dateParse( "12", "k", cldr, date1 ); date1.setHours( 20 ); - assertParse( assert, "20", "k", cldr, date1 ); + assert.dateParse( "20", "k", cldr, date1 ); }); QUnit.test( "should parse hour (kk) using 24-hour-cycle [1-24] with padding", function( assert ) { date1 = new Date(); date1.setHours( 5 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "5", "kk", cldr, date1 ); - assertParse( assert, "05", "kk", cldr, date1 ); + assert.dateParse( "5", "kk", cldr, date1 ); + assert.dateParse( "05", "kk", cldr, date1 ); date1.setHours( 17 ); - assertParse( assert, "17", "kk", cldr, date1 ); + assert.dateParse( "17", "kk", cldr, date1 ); }); QUnit.test( "should parse hour (j) using preferred hour format for the locale (h, H, K, or k) with no padding", function( assert ) { date1 = new Date(); date1.setHours( 9 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "9 AM", "j a", cldr, date1 ); + assert.dateParse( "9 AM", "j a", cldr, date1 ); }); QUnit.test( "should parse hour (jj) using preferred hour format for the locale (h, H, K, or k) with padding", function( assert ) { date1 = new Date(); date1.setHours( 9 ); date1 = startOf( date1, "hour" ); - assertParse( assert, "9 AM", "jj a", cldr, date1 ); - assertParse( assert, "09 AM", "jj a", cldr, date1 ); + assert.dateParse( "9 AM", "jj a", cldr, date1 ); + assert.dateParse( "09 AM", "jj a", cldr, date1 ); }); /** @@ -435,15 +446,15 @@ QUnit.test( "should parse minute (m) with no padding", function( assert ) { date1 = new Date(); date1.setMinutes( 5 ); date1 = startOf( date1, "minute" ); - assertParse( assert, "5", "m", cldr, date1 ); + assert.dateParse( "5", "m", cldr, date1 ); }); QUnit.test( "should parse minute (mm) with padding", function( assert ) { date1 = new Date(); date1.setMinutes( 5 ); date1 = startOf( date1, "minute" ); - assertParse( assert, "5", "mm", cldr, date1 ); - assertParse( assert, "05", "mm", cldr, date1 ); + assert.dateParse( "5", "mm", cldr, date1 ); + assert.dateParse( "05", "mm", cldr, date1 ); }); /** @@ -454,46 +465,46 @@ QUnit.test( "should parse second (s) with no padding", function( assert ) { date1 = new Date(); date1.setSeconds( 59 ); date1 = startOf( date1, "second" ); - assertParse( assert, "59", "s", cldr, date1 ); + assert.dateParse( "59", "s", cldr, date1 ); }); QUnit.test( "should parse second (ss) with padding", function( assert ) { date1 = new Date(); date1.setSeconds( 59 ); date1 = startOf( date1, "second" ); - assertParse( assert, "59", "ss", cldr, date1 ); + assert.dateParse( "59", "ss", cldr, date1 ); date1.setSeconds( 9 ); - assertParse( assert, "9", "ss", cldr, date1 ); - assertParse( assert, "09", "ss", cldr, date1 ); + assert.dateParse( "9", "ss", cldr, date1 ); + assert.dateParse( "09", "ss", cldr, date1 ); }); QUnit.test( "should parse milliseconds (S+)", function( assert ) { date1 = new Date(); date1.setSeconds( 0 ); date1.setMilliseconds( 400 ); - assertParse( assert, "0 4", "s S", cldr, date1 ); + assert.dateParse( "0 4", "s S", cldr, date1 ); date1.setMilliseconds( 370 ); - assertParse( assert, "0 37", "s SS", cldr, date1 ); + assert.dateParse( "0 37", "s SS", cldr, date1 ); date1.setMilliseconds( 369 ); - assertParse( assert, "0 369", "s SSS", cldr, date1 ); - assertParse( assert, "0 3690", "s SSSS", cldr, date1 ); - assertParse( assert, "0 36900", "s SSSSS", cldr, date1 ); + assert.dateParse( "0 369", "s SSS", cldr, date1 ); + assert.dateParse( "0 3690", "s SSSS", cldr, date1 ); + assert.dateParse( "0 36900", "s SSSSS", cldr, date1 ); }); QUnit.test( "should parse milliseconds in a day (A+)", function( assert ) { date1 = new Date(); date1 = startOf( date1, "day" ); date1.setMilliseconds( 63307400 ); - assertParse( assert, "633074", "A", cldr, date1 ); + assert.dateParse( "633074", "A", cldr, date1 ); date1 = startOf( date1, "day" ); date1.setMilliseconds( 63307370 ); - assertParse( assert, "6330737", "AA", cldr, date1 ); + assert.dateParse( "6330737", "AA", cldr, date1 ); date1 = startOf( date1, "day" ); date1.setMilliseconds( 63307369 ); - assertParse( assert, "63307369", "AAA", cldr, date1 ); - assertParse( assert, "633073690", "AAAA", cldr, date1 ); - assertParse( assert, "6330736900", "AAAAA", cldr, date1 ); + assert.dateParse( "63307369", "AAA", cldr, date1 ); + assert.dateParse( "633073690", "AAAA", cldr, date1 ); + assert.dateParse( "6330736900", "AAAAA", cldr, date1 ); }); /** @@ -502,113 +513,221 @@ QUnit.test( "should parse milliseconds in a day (A+)", function( assert ) { QUnit.test( "should parse timezone (z)", function( assert ) { [ "z", "zz", "zzz", "zzzz" ].forEach(function( z ) { - assertParseTimezone( assert, "GMT", z, cldr, 0 ); + assert.timezoneParse( "GMT", z, cldr, 0 ); }); - assertParseTimezone( assert, "GMT-3", "z", cldr, 180 ); - assertParseTimezone( assert, "GMT-3", "zz", cldr, 180 ); - assertParseTimezone( assert, "GMT-3", "zzz", cldr, 180 ); - assertParseTimezone( assert, "GMT-03:00", "zzzz", cldr, 180 ); + assert.timezoneParse( "GMT-3", "z", cldr, 180 ); + assert.timezoneParse( "GMT-3", "zz", cldr, 180 ); + assert.timezoneParse( "GMT-3", "zzz", cldr, 180 ); + assert.timezoneParse( "GMT-03:00", "zzzz", cldr, 180 ); - assertParseTimezone( assert, "GMT+11", "z", cldr, -660 ); - assertParseTimezone( assert, "GMT+11", "zz", cldr, -660 ); - assertParseTimezone( assert, "GMT+11", "zzz", cldr, -660 ); - assertParseTimezone( assert, "GMT+11:00", "zzzz", cldr, -660 ); + assert.timezoneParse( "GMT+11", "z", cldr, -660 ); + assert.timezoneParse( "GMT+11", "zz", cldr, -660 ); + assert.timezoneParse( "GMT+11", "zzz", cldr, -660 ); + assert.timezoneParse( "GMT+11:00", "zzzz", cldr, -660 ); }); QUnit.test( "should parse timezone (Z)", function( assert ) { - assertParseTimezone( assert, "+0000", "Z", cldr, 0 ); - assertParseTimezone( assert, "+0000", "ZZ", cldr, 0 ); - assertParseTimezone( assert, "+0000", "ZZZ", cldr, 0 ); - assertParseTimezone( assert, "GMT", "ZZZZ", cldr, 0 ); - assertParseTimezone( assert, "Z", "ZZZZZ", cldr, 0 ); + assert.timezoneParse( "+0000", "Z", cldr, 0 ); + assert.timezoneParse( "+0000", "ZZ", cldr, 0 ); + assert.timezoneParse( "+0000", "ZZZ", cldr, 0 ); + assert.timezoneParse( "GMT", "ZZZZ", cldr, 0 ); + assert.timezoneParse( "Z", "ZZZZZ", cldr, 0 ); - assertParseTimezone( assert, "-0300", "Z", cldr, 180 ); - assertParseTimezone( assert, "-0300", "ZZ", cldr, 180 ); - assertParseTimezone( assert, "-0300", "ZZZ", cldr, 180 ); - assertParseTimezone( assert, "GMT-03:00", "ZZZZ" , cldr, 180 ); - assertParseTimezone( assert, "-03:00", "ZZZZZ", cldr, 180 ); + assert.timezoneParse( "-0300", "Z", cldr, 180 ); + assert.timezoneParse( "-0300", "ZZ", cldr, 180 ); + assert.timezoneParse( "-0300", "ZZZ", cldr, 180 ); + assert.timezoneParse( "GMT-03:00", "ZZZZ" , cldr, 180 ); + assert.timezoneParse( "-03:00", "ZZZZZ", cldr, 180 ); - assertParseTimezone( assert, "+1100", "Z", cldr, -660 ); - assertParseTimezone( assert, "+1100", "ZZ", cldr, -660 ); - assertParseTimezone( assert, "+1100", "ZZZ", cldr, -660 ); - assertParseTimezone( assert, "GMT+11:00", "ZZZZ" , cldr, -660 ); - assertParseTimezone( assert, "+11:00", "ZZZZZ", cldr, -660 ); + assert.timezoneParse( "+1100", "Z", cldr, -660 ); + assert.timezoneParse( "+1100", "ZZ", cldr, -660 ); + assert.timezoneParse( "+1100", "ZZZ", cldr, -660 ); + assert.timezoneParse( "GMT+11:00", "ZZZZ" , cldr, -660 ); + assert.timezoneParse( "+11:00", "ZZZZZ", cldr, -660 ); }); QUnit.test( "should parse timezone (O)", function( assert ) { - assertParseTimezone( assert, "GMT", "O", cldr, 0 ); - assertParseTimezone( assert, "GMT", "OOOO", cldr, 0 ); + assert.timezoneParse( "GMT", "O", cldr, 0 ); + assert.timezoneParse( "GMT", "OOOO", cldr, 0 ); - assertParseTimezone( assert, "GMT-3", "O", cldr, 180 ); - assertParseTimezone( assert, "GMT-03:00", "OOOO" , cldr, 180 ); + assert.timezoneParse( "GMT-3", "O", cldr, 180 ); + assert.timezoneParse( "GMT-03:00", "OOOO" , cldr, 180 ); - assertParseTimezone( assert, "GMT+11", "O", cldr, -660 ); - assertParseTimezone( assert, "GMT+11:00", "OOOO" , cldr, -660 ); + assert.timezoneParse( "GMT+11", "O", cldr, -660 ); + assert.timezoneParse( "GMT+11:00", "OOOO" , cldr, -660 ); }); QUnit.test( "should parse timezone (X)", function( assert ) { - assertParseTimezone( assert, "Z", "X", cldr, 0 ); - assertParseTimezone( assert, "Z", "XX", cldr, 0 ); - assertParseTimezone( assert, "Z", "XXX", cldr, 0 ); - assertParseTimezone( assert, "Z", "XXXX", cldr, 0 ); - assertParseTimezone( assert, "Z", "XXXXX", cldr, 0 ); - - assertParseTimezone( assert, "-03", "X", cldr, 180 ); - assertParseTimezone( assert, "-0300", "XX", cldr, 180 ); - assertParseTimezone( assert, "-03:00", "XXX", cldr, 180 ); - assertParseTimezone( assert, "-0300", "XXXX", cldr, 180 ); - assertParseTimezone( assert, "-03:00", "XXXXX", cldr, 180 ); - - assertParseTimezone( assert, "+0530", "XX", cldr, -330 ); - assertParseTimezone( assert, "+05:30", "XXX", cldr, -330 ); - assertParseTimezone( assert, "+0530", "XXXX", cldr, -330 ); - assertParseTimezone( assert, "+05:30", "XXXXX", cldr, -330 ); - - assertParseTimezone( assert, "+11", "X", cldr, -660 ); - assertParseTimezone( assert, "+1100", "XX", cldr, -660 ); - assertParseTimezone( assert, "+11:00", "XXX", cldr, -660 ); - assertParseTimezone( assert, "+1100", "XXXX", cldr, -660 ); - assertParseTimezone( assert, "+11:00", "XXXXX", cldr, -660 ); + assert.timezoneParse( "Z", "X", cldr, 0 ); + assert.timezoneParse( "Z", "XX", cldr, 0 ); + assert.timezoneParse( "Z", "XXX", cldr, 0 ); + assert.timezoneParse( "Z", "XXXX", cldr, 0 ); + assert.timezoneParse( "Z", "XXXXX", cldr, 0 ); + + assert.timezoneParse( "-03", "X", cldr, 180 ); + assert.timezoneParse( "-0300", "XX", cldr, 180 ); + assert.timezoneParse( "-03:00", "XXX", cldr, 180 ); + assert.timezoneParse( "-0300", "XXXX", cldr, 180 ); + assert.timezoneParse( "-03:00", "XXXXX", cldr, 180 ); + + assert.timezoneParse( "+0530", "XX", cldr, -330 ); + assert.timezoneParse( "+05:30", "XXX", cldr, -330 ); + assert.timezoneParse( "+0530", "XXXX", cldr, -330 ); + assert.timezoneParse( "+05:30", "XXXXX", cldr, -330 ); + + assert.timezoneParse( "+11", "X", cldr, -660 ); + assert.timezoneParse( "+1100", "XX", cldr, -660 ); + assert.timezoneParse( "+11:00", "XXX", cldr, -660 ); + assert.timezoneParse( "+1100", "XXXX", cldr, -660 ); + assert.timezoneParse( "+11:00", "XXXXX", cldr, -660 ); }); QUnit.test( "should parse timezone (x)", function( assert ) { - assertParseTimezone( assert, "+00", "x", cldr, 0 ); - assertParseTimezone( assert, "+0000", "xx", cldr, 0 ); - assertParseTimezone( assert, "+00:00", "xxx", cldr, 0 ); - assertParseTimezone( assert, "+0000", "xxxx", cldr, 0 ); - assertParseTimezone( assert, "+00:00", "xxxxx", cldr, 0 ); - - assertParseTimezone( assert, "-03", "x", cldr, 180 ); - assertParseTimezone( assert, "-0300", "xx", cldr, 180 ); - assertParseTimezone( assert, "-03:00", "xxx", cldr, 180 ); - assertParseTimezone( assert, "-0300", "xxxx", cldr, 180 ); - assertParseTimezone( assert, "-03:00", "xxxxx", cldr, 180 ); - - assertParseTimezone( assert, "+0530", "xx", cldr, -330 ); - assertParseTimezone( assert, "+05:30", "xxx", cldr, -330 ); - assertParseTimezone( assert, "+0530", "xxxx", cldr, -330 ); - assertParseTimezone( assert, "+05:30", "xxxxx", cldr, -330 ); - - assertParseTimezone( assert, "+11", "x", cldr, -660 ); - assertParseTimezone( assert, "+1100", "xx", cldr, -660 ); - assertParseTimezone( assert, "+11:00", "xxx", cldr, -660 ); - assertParseTimezone( assert, "+1100", "xxxx", cldr, -660 ); - assertParseTimezone( assert, "+11:00", "xxxxx", cldr, -660 ); + assert.timezoneParse( "+00", "x", cldr, 0 ); + assert.timezoneParse( "+0000", "xx", cldr, 0 ); + assert.timezoneParse( "+00:00", "xxx", cldr, 0 ); + assert.timezoneParse( "+0000", "xxxx", cldr, 0 ); + assert.timezoneParse( "+00:00", "xxxxx", cldr, 0 ); + + assert.timezoneParse( "-03", "x", cldr, 180 ); + assert.timezoneParse( "-0300", "xx", cldr, 180 ); + assert.timezoneParse( "-03:00", "xxx", cldr, 180 ); + assert.timezoneParse( "-0300", "xxxx", cldr, 180 ); + assert.timezoneParse( "-03:00", "xxxxx", cldr, 180 ); + + assert.timezoneParse( "+0530", "xx", cldr, -330 ); + assert.timezoneParse( "+05:30", "xxx", cldr, -330 ); + assert.timezoneParse( "+0530", "xxxx", cldr, -330 ); + assert.timezoneParse( "+05:30", "xxxxx", cldr, -330 ); + + assert.timezoneParse( "+11", "x", cldr, -660 ); + assert.timezoneParse( "+1100", "xx", cldr, -660 ); + assert.timezoneParse( "+11:00", "xxx", cldr, -660 ); + assert.timezoneParse( "+1100", "xxxx", cldr, -660 ); + assert.timezoneParse( "+11:00", "xxxxx", cldr, -660 ); +}); + +QUnit.test( "should parse date according to passed timeZone in various datetime patterns", + function( assert ) { + assert.dateParseWithTimezone( + "1/1/2017 12:00 AM", + "M/d/y h:mm a", + "Etc/UTC", + cldr, + new Date( "2017-01-01T00:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017 12:00 AM", + "M/d/y h:mm a", + "Europe/Berlin", + cldr, + new Date( "2016-12-31T23:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017 12:00 AM", + "M/d/y h:mm a", + "America/Sao_Paulo", + cldr, + new Date( "2017-01-01T02:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017 12:00 AM", + "M/d/y h:mm a", + "America/New_York", + cldr, + new Date( "2017-01-01T05:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017 12:00 AM", + "M/d/y h:mm a", + "America/Los_Angeles", + cldr, + new Date( "2017-01-01T08:00:00.000Z" ) + ); + + // Testing DST edge cases... + + // Note we can't reliably parse overlapping times (daylight to standard cases). For example, we + // can't reliably parse "2/18/2017 11:00 PM" for America/Sao_Paulo into + // "2017-02-19T01:00:00.000Z" or "2017-02-19T02:00:00.000Z" without providing the zone string, + // e.g., 11:00 PM BRT or 11:00 PM BRST (both times are valid). Therefore, formatting either one + // should return back the parsed string. This is tested on functional tests. + + // PST + assert.dateParseWithTimezone( + "3/12/2017 1:00 AM", + "M/d/y h:mm a", + "America/Los_Angeles", + cldr, + new Date( "2017-03-12T09:00:00.000Z" ) + ); + + // PDT + assert.dateParseWithTimezone( + "3/12/2017 3:00 AM", + "M/d/y h:mm a", + "America/Los_Angeles", + cldr, + new Date( "2017-03-12T10:00:00.000Z" ) + ); +}); + +QUnit.test( "should parse date according to passed timeZone in various date patterns", + function( assert ) { + assert.dateParseWithTimezone( + "1/1/2017", + "M/d/y", + "Etc/UTC", + cldr, + new Date( "2017-01-01T00:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017", + "M/d/y", + "Europe/Berlin", + cldr, + new Date( "2016-12-31T23:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017", + "M/d/y", + "America/Sao_Paulo", + cldr, + new Date( "2017-01-01T02:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017", + "M/d/y", + "America/New_York", + cldr, + new Date( "2017-01-01T05:00:00.000Z" ) + ); + assert.dateParseWithTimezone( + "1/1/2017", + "M/d/y", + "America/Los_Angeles", + cldr, + new Date( "2017-01-01T08:00:00.000Z" ) + ); }); +/** + * Literal + */ + QUnit.test( "should parse literal (')", function( assert ) { var date = new Date(); date.setHours( 9 ); date = startOf( date, "hour" ); - assertParse( assert, "09 o'clock AM", "hh 'o''clock' a", cldr, date ); + assert.dateParse( "09 o'clock AM", "hh 'o''clock' a", cldr, date ); }); QUnit.test( "should parse invalid literal as null", function( assert ) { - assertParse( assert, "2-20-2017", "M/d/y", cldr, null ); - assertParse( assert, "2a20a2017", "M/d/y", cldr, null ); - assertParse( assert, "2/20/2017", "M-d-y", cldr, null ); - assertParse( assert, "2/20/2017x5xAM", "M/d/y h a", cldr, null ); + assert.dateParse( "2-20-2017", "M/d/y", cldr, null ); + assert.dateParse( "2a20a2017", "M/d/y", cldr, null ); + assert.dateParse( "2/20/2017", "M-d-y", cldr, null ); + assert.dateParse( "2/20/2017x5xAM", "M/d/y h a", cldr, null ); }); }); diff --git a/test/unit/util/globalize-date.js b/test/unit/util/globalize-date.js new file mode 100644 index 000000000..bd05dc913 --- /dev/null +++ b/test/unit/util/globalize-date.js @@ -0,0 +1,121 @@ +define([ + "src/util/globalize-date", + "json!iana-tz-data.json" +], function( GlobalizeDate, ianaTzData ) { + +var zoneData = ianaTzData.zoneData; + +QUnit.module( "Util GlobalizeDate" ); + +QUnit.assert.globalizeDate = function( actual, expected, expectedExtra ) { + this.equal( actual.getFullYear(), expected[ 0 ] ); + this.equal( actual.getMonth(), expected[ 1 ] - 1 ); + this.equal( actual.getDate(), expected[ 2 ] ); + this.equal( actual.getHours(), expected[ 3 ] ); + this.equal( actual.getMinutes(), expected[ 4 ] ); + this.equal( actual.getSeconds(), expected[ 5 ] ); + this.equal( actual.isDST(), expectedExtra.isDST ); +}; + +QUnit.test( "should correctly calculate trasitions and DST", function( assert ) { + assert.globalizeDate( + new GlobalizeDate( new Date( "2015-06-13T01:02:03Z" ), zoneData.America.Sao_Paulo ), + [ 2015, 6, 12, 22, 2, 3 ], + { isDST: false } + ); + + // Testing standard time only case. Dubai has local time and then standard time, no daylight + // savings as most of Asia countries/cities. + assert.globalizeDate( + new GlobalizeDate( new Date( "2015-06-13T01:02:03Z" ), zoneData.Asia.Dubai ), + [ 2015, 6, 13, 5, 2, 3 ], + { isDST: false } + ); + + // Testing DST edge cases... + // BRST (daylight savings) + assert.globalizeDate( + new GlobalizeDate( new Date( "2017-02-19T01:00:00Z" ), zoneData.America.Sao_Paulo ), + [ 2017, 2, 18, 23, 0, 0 ], + { isDST: true } + ); + + // BRT + assert.globalizeDate( + new GlobalizeDate( new Date( "2017-02-19T02:00:00Z" ), zoneData.America.Sao_Paulo ), + [ 2017, 2, 18, 23, 0, 0 ], + { isDST: false } + ); + + // PST + assert.globalizeDate( + new GlobalizeDate( new Date( "2017-03-12T09:00:00Z" ), zoneData.America.Los_Angeles ), + [ 2017, 3, 12, 1, 0, 0 ], + { isDST: false } + ); + + // PDT (daylight savings) + assert.globalizeDate( + new GlobalizeDate( new Date( "2017-03-12T10:00:00Z" ), zoneData.America.Los_Angeles ), + [ 2017, 3, 12, 3, 0, 0 ], + { isDST: true } + ); + + // ARST (it's an adhoc daylight savings using -03 offset) + assert.globalizeDate( + new GlobalizeDate( new Date( 938919600000 ), zoneData.America.Argentina.Buenos_Aires ), + [ 1999, 10, 3, 0, 0, 0 ], + { isDST: true } + ); + + // ART + assert.globalizeDate( + new GlobalizeDate( new Date( 952052400000 ), zoneData.America.Argentina.Buenos_Aires ), + [ 2000, 3, 3, 0, 0, 0 ], + { isDST: false } + ); + +}); + +QUnit.test( "should set datetime according to timezone", function( assert ) { + var date; + + // From a BRT time. + date = new GlobalizeDate(new Date( "2017-03-15T17:00:00Z" ), zoneData.America.Sao_Paulo ); + + // Set it to 1/1/2017 12:00 PM BRST + date.setMonth( 0 ); + date.setDate( 1 ); + date.setFullYear( 2017 ); + date.setHours( 12 ); + date.setMinutes( 0 ); + date.setSeconds( 0 ); + date.setMilliseconds( 0 ); + + assert.globalizeDate( + date, + [ 2017, 1, 1, 12, 0, 0 ], + { isDST: true } + ); + + // From a BRST time. + date = new GlobalizeDate(new Date( "2017-03-15T17:00:00Z" ), zoneData.America.Sao_Paulo ); + + // Set it to 3/1/2017 12:00 PM BRT + date.setMonth( 2 ); + date.setDate( 1 ); + date.setFullYear( 2017 ); + date.setHours( 12 ); + date.setMinutes( 0 ); + date.setSeconds( 0 ); + date.setMilliseconds( 0 ); + + assert.globalizeDate( + date, + [ 2017, 3, 1, 12, 0, 0 ], + { isDST: false } + ); + +}); + +});