Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
691 lines (571 sloc)
15.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! moment-timezone.js | |
//! version : 0.5.28 | |
//! Copyright (c) JS Foundation and other contributors | |
//! license : MIT | |
//! github.com/moment/moment-timezone | |
(function (root, factory) { | |
"use strict"; | |
/*global define*/ | |
if (typeof module === 'object' && module.exports) { | |
module.exports = factory(require('moment')); // Node | |
} else if (typeof define === 'function' && define.amd) { | |
define(['moment'], factory); // AMD | |
} else { | |
factory(root.moment); // Browser | |
} | |
}(this, function (moment) { | |
"use strict"; | |
// Do not load moment-timezone a second time. | |
// if (moment.tz !== undefined) { | |
// logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion); | |
// return moment; | |
// } | |
var VERSION = "0.5.28", | |
zones = {}, | |
links = {}, | |
countries = {}, | |
names = {}, | |
guesses = {}, | |
cachedGuess; | |
if (!moment || typeof moment.version !== 'string') { | |
logError('Moment Timezone requires Moment.js. See https://momentjs.com/timezone/docs/#/use-it/browser/'); | |
} | |
var momentVersion = moment.version.split('.'), | |
major = +momentVersion[0], | |
minor = +momentVersion[1]; | |
// Moment.js version check | |
if (major < 2 || (major === 2 && minor < 6)) { | |
logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com'); | |
} | |
/************************************ | |
Unpacking | |
************************************/ | |
function charCodeToInt(charCode) { | |
if (charCode > 96) { | |
return charCode - 87; | |
} else if (charCode > 64) { | |
return charCode - 29; | |
} | |
return charCode - 48; | |
} | |
function unpackBase60(string) { | |
var i = 0, | |
parts = string.split('.'), | |
whole = parts[0], | |
fractional = parts[1] || '', | |
multiplier = 1, | |
num, | |
out = 0, | |
sign = 1; | |
// handle negative numbers | |
if (string.charCodeAt(0) === 45) { | |
i = 1; | |
sign = -1; | |
} | |
// handle digits before the decimal | |
for (i; i < whole.length; i++) { | |
num = charCodeToInt(whole.charCodeAt(i)); | |
out = 60 * out + num; | |
} | |
// handle digits after the decimal | |
for (i = 0; i < fractional.length; i++) { | |
multiplier = multiplier / 60; | |
num = charCodeToInt(fractional.charCodeAt(i)); | |
out += num * multiplier; | |
} | |
return out * sign; | |
} | |
function arrayToInt (array) { | |
for (var i = 0; i < array.length; i++) { | |
array[i] = unpackBase60(array[i]); | |
} | |
} | |
function intToUntil (array, length) { | |
for (var i = 0; i < length; i++) { | |
array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds | |
} | |
array[length - 1] = Infinity; | |
} | |
function mapIndices (source, indices) { | |
var out = [], i; | |
for (i = 0; i < indices.length; i++) { | |
out[i] = source[indices[i]]; | |
} | |
return out; | |
} | |
function unpack (string) { | |
var data = string.split('|'), | |
offsets = data[2].split(' '), | |
indices = data[3].split(''), | |
untils = data[4].split(' '); | |
arrayToInt(offsets); | |
arrayToInt(indices); | |
arrayToInt(untils); | |
intToUntil(untils, indices.length); | |
return { | |
name : data[0], | |
abbrs : mapIndices(data[1].split(' '), indices), | |
offsets : mapIndices(offsets, indices), | |
untils : untils, | |
population : data[5] | 0 | |
}; | |
} | |
/************************************ | |
Zone object | |
************************************/ | |
function Zone (packedString) { | |
if (packedString) { | |
this._set(unpack(packedString)); | |
} | |
} | |
Zone.prototype = { | |
_set : function (unpacked) { | |
this.name = unpacked.name; | |
this.abbrs = unpacked.abbrs; | |
this.untils = unpacked.untils; | |
this.offsets = unpacked.offsets; | |
this.population = unpacked.population; | |
}, | |
_index : function (timestamp) { | |
var target = +timestamp, | |
untils = this.untils, | |
i; | |
for (i = 0; i < untils.length; i++) { | |
if (target < untils[i]) { | |
return i; | |
} | |
} | |
}, | |
countries : function () { | |
var zone_name = this.name; | |
return Object.keys(countries).filter(function (country_code) { | |
return countries[country_code].zones.indexOf(zone_name) !== -1; | |
}); | |
}, | |
parse : function (timestamp) { | |
var target = +timestamp, | |
offsets = this.offsets, | |
untils = this.untils, | |
max = untils.length - 1, | |
offset, offsetNext, offsetPrev, i; | |
for (i = 0; i < max; i++) { | |
offset = offsets[i]; | |
offsetNext = offsets[i + 1]; | |
offsetPrev = offsets[i ? i - 1 : i]; | |
if (offset < offsetNext && tz.moveAmbiguousForward) { | |
offset = offsetNext; | |
} else if (offset > offsetPrev && tz.moveInvalidForward) { | |
offset = offsetPrev; | |
} | |
if (target < untils[i] - (offset * 60000)) { | |
return offsets[i]; | |
} | |
} | |
return offsets[max]; | |
}, | |
abbr : function (mom) { | |
return this.abbrs[this._index(mom)]; | |
}, | |
offset : function (mom) { | |
logError("zone.offset has been deprecated in favor of zone.utcOffset"); | |
return this.offsets[this._index(mom)]; | |
}, | |
utcOffset : function (mom) { | |
return this.offsets[this._index(mom)]; | |
} | |
}; | |
/************************************ | |
Country object | |
************************************/ | |
function Country (country_name, zone_names) { | |
this.name = country_name; | |
this.zones = zone_names; | |
} | |
/************************************ | |
Current Timezone | |
************************************/ | |
function OffsetAt(at) { | |
var timeString = at.toTimeString(); | |
var abbr = timeString.match(/\([a-z ]+\)/i); | |
if (abbr && abbr[0]) { | |
// 17:56:31 GMT-0600 (CST) | |
// 17:56:31 GMT-0600 (Central Standard Time) | |
abbr = abbr[0].match(/[A-Z]/g); | |
abbr = abbr ? abbr.join('') : undefined; | |
} else { | |
// 17:56:31 CST | |
// 17:56:31 GMT+0800 (台北標準時間) | |
abbr = timeString.match(/[A-Z]{3,5}/g); | |
abbr = abbr ? abbr[0] : undefined; | |
} | |
if (abbr === 'GMT') { | |
abbr = undefined; | |
} | |
this.at = +at; | |
this.abbr = abbr; | |
this.offset = at.getTimezoneOffset(); | |
} | |
function ZoneScore(zone) { | |
this.zone = zone; | |
this.offsetScore = 0; | |
this.abbrScore = 0; | |
} | |
ZoneScore.prototype.scoreOffsetAt = function (offsetAt) { | |
this.offsetScore += Math.abs(this.zone.utcOffset(offsetAt.at) - offsetAt.offset); | |
if (this.zone.abbr(offsetAt.at).replace(/[^A-Z]/g, '') !== offsetAt.abbr) { | |
this.abbrScore++; | |
} | |
}; | |
function findChange(low, high) { | |
var mid, diff; | |
while ((diff = ((high.at - low.at) / 12e4 | 0) * 6e4)) { | |
mid = new OffsetAt(new Date(low.at + diff)); | |
if (mid.offset === low.offset) { | |
low = mid; | |
} else { | |
high = mid; | |
} | |
} | |
return low; | |
} | |
function userOffsets() { | |
var startYear = new Date().getFullYear() - 2, | |
last = new OffsetAt(new Date(startYear, 0, 1)), | |
offsets = [last], | |
change, next, i; | |
for (i = 1; i < 48; i++) { | |
next = new OffsetAt(new Date(startYear, i, 1)); | |
if (next.offset !== last.offset) { | |
change = findChange(last, next); | |
offsets.push(change); | |
offsets.push(new OffsetAt(new Date(change.at + 6e4))); | |
} | |
last = next; | |
} | |
for (i = 0; i < 4; i++) { | |
offsets.push(new OffsetAt(new Date(startYear + i, 0, 1))); | |
offsets.push(new OffsetAt(new Date(startYear + i, 6, 1))); | |
} | |
return offsets; | |
} | |
function sortZoneScores (a, b) { | |
if (a.offsetScore !== b.offsetScore) { | |
return a.offsetScore - b.offsetScore; | |
} | |
if (a.abbrScore !== b.abbrScore) { | |
return a.abbrScore - b.abbrScore; | |
} | |
if (a.zone.population !== b.zone.population) { | |
return b.zone.population - a.zone.population; | |
} | |
return b.zone.name.localeCompare(a.zone.name); | |
} | |
function addToGuesses (name, offsets) { | |
var i, offset; | |
arrayToInt(offsets); | |
for (i = 0; i < offsets.length; i++) { | |
offset = offsets[i]; | |
guesses[offset] = guesses[offset] || {}; | |
guesses[offset][name] = true; | |
} | |
} | |
function guessesForUserOffsets (offsets) { | |
var offsetsLength = offsets.length, | |
filteredGuesses = {}, | |
out = [], | |
i, j, guessesOffset; | |
for (i = 0; i < offsetsLength; i++) { | |
guessesOffset = guesses[offsets[i].offset] || {}; | |
for (j in guessesOffset) { | |
if (guessesOffset.hasOwnProperty(j)) { | |
filteredGuesses[j] = true; | |
} | |
} | |
} | |
for (i in filteredGuesses) { | |
if (filteredGuesses.hasOwnProperty(i)) { | |
out.push(names[i]); | |
} | |
} | |
return out; | |
} | |
function rebuildGuess () { | |
// use Intl API when available and returning valid time zone | |
try { | |
var intlName = Intl.DateTimeFormat().resolvedOptions().timeZone; | |
if (intlName && intlName.length > 3) { | |
var name = names[normalizeName(intlName)]; | |
if (name) { | |
return name; | |
} | |
logError("Moment Timezone found " + intlName + " from the Intl api, but did not have that data loaded."); | |
} | |
} catch (e) { | |
// Intl unavailable, fall back to manual guessing. | |
} | |
var offsets = userOffsets(), | |
offsetsLength = offsets.length, | |
guesses = guessesForUserOffsets(offsets), | |
zoneScores = [], | |
zoneScore, i, j; | |
for (i = 0; i < guesses.length; i++) { | |
zoneScore = new ZoneScore(getZone(guesses[i]), offsetsLength); | |
for (j = 0; j < offsetsLength; j++) { | |
zoneScore.scoreOffsetAt(offsets[j]); | |
} | |
zoneScores.push(zoneScore); | |
} | |
zoneScores.sort(sortZoneScores); | |
return zoneScores.length > 0 ? zoneScores[0].zone.name : undefined; | |
} | |
function guess (ignoreCache) { | |
if (!cachedGuess || ignoreCache) { | |
cachedGuess = rebuildGuess(); | |
} | |
return cachedGuess; | |
} | |
/************************************ | |
Global Methods | |
************************************/ | |
function normalizeName (name) { | |
return (name || '').toLowerCase().replace(/\//g, '_'); | |
} | |
function addZone (packed) { | |
var i, name, split, normalized; | |
if (typeof packed === "string") { | |
packed = [packed]; | |
} | |
for (i = 0; i < packed.length; i++) { | |
split = packed[i].split('|'); | |
name = split[0]; | |
normalized = normalizeName(name); | |
zones[normalized] = packed[i]; | |
names[normalized] = name; | |
addToGuesses(normalized, split[2].split(' ')); | |
} | |
} | |
function getZone (name, caller) { | |
name = normalizeName(name); | |
var zone = zones[name]; | |
var link; | |
if (zone instanceof Zone) { | |
return zone; | |
} | |
if (typeof zone === 'string') { | |
zone = new Zone(zone); | |
zones[name] = zone; | |
return zone; | |
} | |
// Pass getZone to prevent recursion more than 1 level deep | |
if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) { | |
zone = zones[name] = new Zone(); | |
zone._set(link); | |
zone.name = names[name]; | |
return zone; | |
} | |
return null; | |
} | |
function getNames () { | |
var i, out = []; | |
for (i in names) { | |
if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) { | |
out.push(names[i]); | |
} | |
} | |
return out.sort(); | |
} | |
function getCountryNames () { | |
return Object.keys(countries); | |
} | |
function addLink (aliases) { | |
var i, alias, normal0, normal1; | |
if (typeof aliases === "string") { | |
aliases = [aliases]; | |
} | |
for (i = 0; i < aliases.length; i++) { | |
alias = aliases[i].split('|'); | |
normal0 = normalizeName(alias[0]); | |
normal1 = normalizeName(alias[1]); | |
links[normal0] = normal1; | |
names[normal0] = alias[0]; | |
links[normal1] = normal0; | |
names[normal1] = alias[1]; | |
} | |
} | |
function addCountries (data) { | |
var i, country_code, country_zones, split; | |
if (!data || !data.length) return; | |
for (i = 0; i < data.length; i++) { | |
split = data[i].split('|'); | |
country_code = split[0].toUpperCase(); | |
country_zones = split[1].split(' '); | |
countries[country_code] = new Country( | |
country_code, | |
country_zones | |
); | |
} | |
} | |
function getCountry (name) { | |
name = name.toUpperCase(); | |
return countries[name] || null; | |
} | |
function zonesForCountry(country, with_offset) { | |
country = getCountry(country); | |
if (!country) return null; | |
var zones = country.zones.sort(); | |
if (with_offset) { | |
return zones.map(function (zone_name) { | |
var zone = getZone(zone_name); | |
return { | |
name: zone_name, | |
offset: zone.utcOffset(new Date()) | |
}; | |
}); | |
} | |
return zones; | |
} | |
function loadData (data) { | |
addZone(data.zones); | |
addLink(data.links); | |
addCountries(data.countries); | |
tz.dataVersion = data.version; | |
} | |
function zoneExists (name) { | |
if (!zoneExists.didShowError) { | |
zoneExists.didShowError = true; | |
logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')"); | |
} | |
return !!getZone(name); | |
} | |
function needsOffset (m) { | |
var isUnixTimestamp = (m._f === 'X' || m._f === 'x'); | |
return !!(m._a && (m._tzm === undefined) && !isUnixTimestamp); | |
} | |
function logError (message) { | |
if (typeof console !== 'undefined' && typeof console.error === 'function') { | |
console.error(message); | |
} | |
} | |
/************************************ | |
moment.tz namespace | |
************************************/ | |
function tz (input) { | |
var args = Array.prototype.slice.call(arguments, 0, -1), | |
name = arguments[arguments.length - 1], | |
zone = getZone(name), | |
out = moment.utc.apply(null, args); | |
if (zone && !moment.isMoment(input) && needsOffset(out)) { | |
out.add(zone.parse(out), 'minutes'); | |
} | |
out.tz(name); | |
return out; | |
} | |
tz.version = VERSION; | |
tz.dataVersion = ''; | |
tz._zones = zones; | |
tz._links = links; | |
tz._names = names; | |
tz._countries = countries; | |
tz.add = addZone; | |
tz.link = addLink; | |
tz.load = loadData; | |
tz.zone = getZone; | |
tz.zoneExists = zoneExists; // deprecated in 0.1.0 | |
tz.guess = guess; | |
tz.names = getNames; | |
tz.Zone = Zone; | |
tz.unpack = unpack; | |
tz.unpackBase60 = unpackBase60; | |
tz.needsOffset = needsOffset; | |
tz.moveInvalidForward = true; | |
tz.moveAmbiguousForward = false; | |
tz.countries = getCountryNames; | |
tz.zonesForCountry = zonesForCountry; | |
/************************************ | |
Interface with Moment.js | |
************************************/ | |
var fn = moment.fn; | |
moment.tz = tz; | |
moment.defaultZone = null; | |
moment.updateOffset = function (mom, keepTime) { | |
var zone = moment.defaultZone, | |
offset; | |
if (mom._z === undefined) { | |
if (zone && needsOffset(mom) && !mom._isUTC) { | |
mom._d = moment.utc(mom._a)._d; | |
mom.utc().add(zone.parse(mom), 'minutes'); | |
} | |
mom._z = zone; | |
} | |
if (mom._z) { | |
offset = mom._z.utcOffset(mom); | |
if (Math.abs(offset) < 16) { | |
offset = offset / 60; | |
} | |
if (mom.utcOffset !== undefined) { | |
var z = mom._z; | |
mom.utcOffset(-offset, keepTime); | |
mom._z = z; | |
} else { | |
mom.zone(offset, keepTime); | |
} | |
} | |
}; | |
fn.tz = function (name, keepTime) { | |
if (name) { | |
if (typeof name !== 'string') { | |
throw new Error('Time zone name must be a string, got ' + name + ' [' + typeof name + ']'); | |
} | |
this._z = getZone(name); | |
if (this._z) { | |
moment.updateOffset(this, keepTime); | |
} else { | |
logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/."); | |
} | |
return this; | |
} | |
if (this._z) { return this._z.name; } | |
}; | |
function abbrWrap (old) { | |
return function () { | |
if (this._z) { return this._z.abbr(this); } | |
return old.call(this); | |
}; | |
} | |
function resetZoneWrap (old) { | |
return function () { | |
this._z = null; | |
return old.apply(this, arguments); | |
}; | |
} | |
function resetZoneWrap2 (old) { | |
return function () { | |
if (arguments.length > 0) this._z = null; | |
return old.apply(this, arguments); | |
}; | |
} | |
fn.zoneName = abbrWrap(fn.zoneName); | |
fn.zoneAbbr = abbrWrap(fn.zoneAbbr); | |
fn.utc = resetZoneWrap(fn.utc); | |
fn.local = resetZoneWrap(fn.local); | |
fn.utcOffset = resetZoneWrap2(fn.utcOffset); | |
moment.tz.setDefault = function(name) { | |
if (major < 2 || (major === 2 && minor < 9)) { | |
logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.'); | |
} | |
moment.defaultZone = name ? getZone(name) : null; | |
return moment; | |
}; | |
// Cloning a moment should include the _z property. | |
var momentProperties = moment.momentProperties; | |
if (Object.prototype.toString.call(momentProperties) === '[object Array]') { | |
// moment 2.8.1+ | |
momentProperties.push('_z'); | |
momentProperties.push('_a'); | |
} else if (momentProperties) { | |
// moment 2.7.0 | |
momentProperties._z = null; | |
} | |
// INJECT DATA | |
return moment; | |
})); |