You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

730 lines
17 KiB

  1. //! moment-timezone.js
  2. //! version : 0.5.46
  3. //! Copyright (c) JS Foundation and other contributors
  4. //! license : MIT
  5. //! github.com/moment/moment-timezone
  6. (function (root, factory) {
  7. "use strict";
  8. /*global define*/
  9. if (typeof module === 'object' && module.exports) {
  10. module.exports = factory(require('moment')); // Node
  11. } else if (typeof define === 'function' && define.amd) {
  12. define(['moment'], factory); // AMD
  13. } else {
  14. factory(root.moment); // Browser
  15. }
  16. }(this, function (moment) {
  17. "use strict";
  18. // Resolves es6 module loading issue
  19. if (moment.version === undefined && moment.default) {
  20. moment = moment.default;
  21. }
  22. // Do not load moment-timezone a second time.
  23. // if (moment.tz !== undefined) {
  24. // logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion);
  25. // return moment;
  26. // }
  27. var VERSION = "0.5.46",
  28. zones = {},
  29. links = {},
  30. countries = {},
  31. names = {},
  32. guesses = {},
  33. cachedGuess;
  34. if (!moment || typeof moment.version !== 'string') {
  35. logError('Moment Timezone requires Moment.js. See https://momentjs.com/timezone/docs/#/use-it/browser/');
  36. }
  37. var momentVersion = moment.version.split('.'),
  38. major = +momentVersion[0],
  39. minor = +momentVersion[1];
  40. // Moment.js version check
  41. if (major < 2 || (major === 2 && minor < 6)) {
  42. logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com');
  43. }
  44. /************************************
  45. Unpacking
  46. ************************************/
  47. function charCodeToInt(charCode) {
  48. if (charCode > 96) {
  49. return charCode - 87;
  50. } else if (charCode > 64) {
  51. return charCode - 29;
  52. }
  53. return charCode - 48;
  54. }
  55. function unpackBase60(string) {
  56. var i = 0,
  57. parts = string.split('.'),
  58. whole = parts[0],
  59. fractional = parts[1] || '',
  60. multiplier = 1,
  61. num,
  62. out = 0,
  63. sign = 1;
  64. // handle negative numbers
  65. if (string.charCodeAt(0) === 45) {
  66. i = 1;
  67. sign = -1;
  68. }
  69. // handle digits before the decimal
  70. for (i; i < whole.length; i++) {
  71. num = charCodeToInt(whole.charCodeAt(i));
  72. out = 60 * out + num;
  73. }
  74. // handle digits after the decimal
  75. for (i = 0; i < fractional.length; i++) {
  76. multiplier = multiplier / 60;
  77. num = charCodeToInt(fractional.charCodeAt(i));
  78. out += num * multiplier;
  79. }
  80. return out * sign;
  81. }
  82. function arrayToInt (array) {
  83. for (var i = 0; i < array.length; i++) {
  84. array[i] = unpackBase60(array[i]);
  85. }
  86. }
  87. function intToUntil (array, length) {
  88. for (var i = 0; i < length; i++) {
  89. array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds
  90. }
  91. array[length - 1] = Infinity;
  92. }
  93. function mapIndices (source, indices) {
  94. var out = [], i;
  95. for (i = 0; i < indices.length; i++) {
  96. out[i] = source[indices[i]];
  97. }
  98. return out;
  99. }
  100. function unpack (string) {
  101. var data = string.split('|'),
  102. offsets = data[2].split(' '),
  103. indices = data[3].split(''),
  104. untils = data[4].split(' ');
  105. arrayToInt(offsets);
  106. arrayToInt(indices);
  107. arrayToInt(untils);
  108. intToUntil(untils, indices.length);
  109. return {
  110. name : data[0],
  111. abbrs : mapIndices(data[1].split(' '), indices),
  112. offsets : mapIndices(offsets, indices),
  113. untils : untils,
  114. population : data[5] | 0
  115. };
  116. }
  117. /************************************
  118. Zone object
  119. ************************************/
  120. function Zone (packedString) {
  121. if (packedString) {
  122. this._set(unpack(packedString));
  123. }
  124. }
  125. function closest (num, arr) {
  126. var len = arr.length;
  127. if (num < arr[0]) {
  128. return 0;
  129. } else if (len > 1 && arr[len - 1] === Infinity && num >= arr[len - 2]) {
  130. return len - 1;
  131. } else if (num >= arr[len - 1]) {
  132. return -1;
  133. }
  134. var mid;
  135. var lo = 0;
  136. var hi = len - 1;
  137. while (hi - lo > 1) {
  138. mid = Math.floor((lo + hi) / 2);
  139. if (arr[mid] <= num) {
  140. lo = mid;
  141. } else {
  142. hi = mid;
  143. }
  144. }
  145. return hi;
  146. }
  147. Zone.prototype = {
  148. _set : function (unpacked) {
  149. this.name = unpacked.name;
  150. this.abbrs = unpacked.abbrs;
  151. this.untils = unpacked.untils;
  152. this.offsets = unpacked.offsets;
  153. this.population = unpacked.population;
  154. },
  155. _index : function (timestamp) {
  156. var target = +timestamp,
  157. untils = this.untils,
  158. i;
  159. i = closest(target, untils);
  160. if (i >= 0) {
  161. return i;
  162. }
  163. },
  164. countries : function () {
  165. var zone_name = this.name;
  166. return Object.keys(countries).filter(function (country_code) {
  167. return countries[country_code].zones.indexOf(zone_name) !== -1;
  168. });
  169. },
  170. parse : function (timestamp) {
  171. var target = +timestamp,
  172. offsets = this.offsets,
  173. untils = this.untils,
  174. max = untils.length - 1,
  175. offset, offsetNext, offsetPrev, i;
  176. for (i = 0; i < max; i++) {
  177. offset = offsets[i];
  178. offsetNext = offsets[i + 1];
  179. offsetPrev = offsets[i ? i - 1 : i];
  180. if (offset < offsetNext && tz.moveAmbiguousForward) {
  181. offset = offsetNext;
  182. } else if (offset > offsetPrev && tz.moveInvalidForward) {
  183. offset = offsetPrev;
  184. }
  185. if (target < untils[i] - (offset * 60000)) {
  186. return offsets[i];
  187. }
  188. }
  189. return offsets[max];
  190. },
  191. abbr : function (mom) {
  192. return this.abbrs[this._index(mom)];
  193. },
  194. offset : function (mom) {
  195. logError("zone.offset has been deprecated in favor of zone.utcOffset");
  196. return this.offsets[this._index(mom)];
  197. },
  198. utcOffset : function (mom) {
  199. return this.offsets[this._index(mom)];
  200. }
  201. };
  202. /************************************
  203. Country object
  204. ************************************/
  205. function Country (country_name, zone_names) {
  206. this.name = country_name;
  207. this.zones = zone_names;
  208. }
  209. /************************************
  210. Current Timezone
  211. ************************************/
  212. function OffsetAt(at) {
  213. var timeString = at.toTimeString();
  214. var abbr = timeString.match(/\([a-z ]+\)/i);
  215. if (abbr && abbr[0]) {
  216. // 17:56:31 GMT-0600 (CST)
  217. // 17:56:31 GMT-0600 (Central Standard Time)
  218. abbr = abbr[0].match(/[A-Z]/g);
  219. abbr = abbr ? abbr.join('') : undefined;
  220. } else {
  221. // 17:56:31 CST
  222. // 17:56:31 GMT+0800 (台北標準時間)
  223. abbr = timeString.match(/[A-Z]{3,5}/g);
  224. abbr = abbr ? abbr[0] : undefined;
  225. }
  226. if (abbr === 'GMT') {
  227. abbr = undefined;
  228. }
  229. this.at = +at;
  230. this.abbr = abbr;
  231. this.offset = at.getTimezoneOffset();
  232. }
  233. function ZoneScore(zone) {
  234. this.zone = zone;
  235. this.offsetScore = 0;
  236. this.abbrScore = 0;
  237. }
  238. ZoneScore.prototype.scoreOffsetAt = function (offsetAt) {
  239. this.offsetScore += Math.abs(this.zone.utcOffset(offsetAt.at) - offsetAt.offset);
  240. if (this.zone.abbr(offsetAt.at).replace(/[^A-Z]/g, '') !== offsetAt.abbr) {
  241. this.abbrScore++;
  242. }
  243. };
  244. function findChange(low, high) {
  245. var mid, diff;
  246. while ((diff = ((high.at - low.at) / 12e4 | 0) * 6e4)) {
  247. mid = new OffsetAt(new Date(low.at + diff));
  248. if (mid.offset === low.offset) {
  249. low = mid;
  250. } else {
  251. high = mid;
  252. }
  253. }
  254. return low;
  255. }
  256. function userOffsets() {
  257. var startYear = new Date().getFullYear() - 2,
  258. last = new OffsetAt(new Date(startYear, 0, 1)),
  259. lastOffset = last.offset,
  260. offsets = [last],
  261. change, next, nextOffset, i;
  262. for (i = 1; i < 48; i++) {
  263. nextOffset = new Date(startYear, i, 1).getTimezoneOffset();
  264. if (nextOffset !== lastOffset) {
  265. // Create OffsetAt here to avoid unnecessary abbr parsing before checking offsets
  266. next = new OffsetAt(new Date(startYear, i, 1));
  267. change = findChange(last, next);
  268. offsets.push(change);
  269. offsets.push(new OffsetAt(new Date(change.at + 6e4)));
  270. last = next;
  271. lastOffset = nextOffset;
  272. }
  273. }
  274. for (i = 0; i < 4; i++) {
  275. offsets.push(new OffsetAt(new Date(startYear + i, 0, 1)));
  276. offsets.push(new OffsetAt(new Date(startYear + i, 6, 1)));
  277. }
  278. return offsets;
  279. }
  280. function sortZoneScores (a, b) {
  281. if (a.offsetScore !== b.offsetScore) {
  282. return a.offsetScore - b.offsetScore;
  283. }
  284. if (a.abbrScore !== b.abbrScore) {
  285. return a.abbrScore - b.abbrScore;
  286. }
  287. if (a.zone.population !== b.zone.population) {
  288. return b.zone.population - a.zone.population;
  289. }
  290. return b.zone.name.localeCompare(a.zone.name);
  291. }
  292. function addToGuesses (name, offsets) {
  293. var i, offset;
  294. arrayToInt(offsets);
  295. for (i = 0; i < offsets.length; i++) {
  296. offset = offsets[i];
  297. guesses[offset] = guesses[offset] || {};
  298. guesses[offset][name] = true;
  299. }
  300. }
  301. function guessesForUserOffsets (offsets) {
  302. var offsetsLength = offsets.length,
  303. filteredGuesses = {},
  304. out = [],
  305. checkedOffsets = {},
  306. i, j, offset, guessesOffset;
  307. for (i = 0; i < offsetsLength; i++) {
  308. offset = offsets[i].offset;
  309. if (checkedOffsets.hasOwnProperty(offset)) {
  310. continue;
  311. }
  312. guessesOffset = guesses[offset] || {};
  313. for (j in guessesOffset) {
  314. if (guessesOffset.hasOwnProperty(j)) {
  315. filteredGuesses[j] = true;
  316. }
  317. }
  318. checkedOffsets[offset] = true;
  319. }
  320. for (i in filteredGuesses) {
  321. if (filteredGuesses.hasOwnProperty(i)) {
  322. out.push(names[i]);
  323. }
  324. }
  325. return out;
  326. }
  327. function rebuildGuess () {
  328. // use Intl API when available and returning valid time zone
  329. try {
  330. var intlName = Intl.DateTimeFormat().resolvedOptions().timeZone;
  331. if (intlName && intlName.length > 3) {
  332. var name = names[normalizeName(intlName)];
  333. if (name) {
  334. return name;
  335. }
  336. logError("Moment Timezone found " + intlName + " from the Intl api, but did not have that data loaded.");
  337. }
  338. } catch (e) {
  339. // Intl unavailable, fall back to manual guessing.
  340. }
  341. var offsets = userOffsets(),
  342. offsetsLength = offsets.length,
  343. guesses = guessesForUserOffsets(offsets),
  344. zoneScores = [],
  345. zoneScore, i, j;
  346. for (i = 0; i < guesses.length; i++) {
  347. zoneScore = new ZoneScore(getZone(guesses[i]), offsetsLength);
  348. for (j = 0; j < offsetsLength; j++) {
  349. zoneScore.scoreOffsetAt(offsets[j]);
  350. }
  351. zoneScores.push(zoneScore);
  352. }
  353. zoneScores.sort(sortZoneScores);
  354. return zoneScores.length > 0 ? zoneScores[0].zone.name : undefined;
  355. }
  356. function guess (ignoreCache) {
  357. if (!cachedGuess || ignoreCache) {
  358. cachedGuess = rebuildGuess();
  359. }
  360. return cachedGuess;
  361. }
  362. /************************************
  363. Global Methods
  364. ************************************/
  365. function normalizeName (name) {
  366. return (name || '').toLowerCase().replace(/\//g, '_');
  367. }
  368. function addZone (packed) {
  369. var i, name, split, normalized;
  370. if (typeof packed === "string") {
  371. packed = [packed];
  372. }
  373. for (i = 0; i < packed.length; i++) {
  374. split = packed[i].split('|');
  375. name = split[0];
  376. normalized = normalizeName(name);
  377. zones[normalized] = packed[i];
  378. names[normalized] = name;
  379. addToGuesses(normalized, split[2].split(' '));
  380. }
  381. }
  382. function getZone (name, caller) {
  383. name = normalizeName(name);
  384. var zone = zones[name];
  385. var link;
  386. if (zone instanceof Zone) {
  387. return zone;
  388. }
  389. if (typeof zone === 'string') {
  390. zone = new Zone(zone);
  391. zones[name] = zone;
  392. return zone;
  393. }
  394. // Pass getZone to prevent recursion more than 1 level deep
  395. if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) {
  396. zone = zones[name] = new Zone();
  397. zone._set(link);
  398. zone.name = names[name];
  399. return zone;
  400. }
  401. return null;
  402. }
  403. function getNames () {
  404. var i, out = [];
  405. for (i in names) {
  406. if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) {
  407. out.push(names[i]);
  408. }
  409. }
  410. return out.sort();
  411. }
  412. function getCountryNames () {
  413. return Object.keys(countries);
  414. }
  415. function addLink (aliases) {
  416. var i, alias, normal0, normal1;
  417. if (typeof aliases === "string") {
  418. aliases = [aliases];
  419. }
  420. for (i = 0; i < aliases.length; i++) {
  421. alias = aliases[i].split('|');
  422. normal0 = normalizeName(alias[0]);
  423. normal1 = normalizeName(alias[1]);
  424. links[normal0] = normal1;
  425. names[normal0] = alias[0];
  426. links[normal1] = normal0;
  427. names[normal1] = alias[1];
  428. }
  429. }
  430. function addCountries (data) {
  431. var i, country_code, country_zones, split;
  432. if (!data || !data.length) return;
  433. for (i = 0; i < data.length; i++) {
  434. split = data[i].split('|');
  435. country_code = split[0].toUpperCase();
  436. country_zones = split[1].split(' ');
  437. countries[country_code] = new Country(
  438. country_code,
  439. country_zones
  440. );
  441. }
  442. }
  443. function getCountry (name) {
  444. name = name.toUpperCase();
  445. return countries[name] || null;
  446. }
  447. function zonesForCountry(country, with_offset) {
  448. country = getCountry(country);
  449. if (!country) return null;
  450. var zones = country.zones.sort();
  451. if (with_offset) {
  452. return zones.map(function (zone_name) {
  453. var zone = getZone(zone_name);
  454. return {
  455. name: zone_name,
  456. offset: zone.utcOffset(new Date())
  457. };
  458. });
  459. }
  460. return zones;
  461. }
  462. function loadData (data) {
  463. addZone(data.zones);
  464. addLink(data.links);
  465. addCountries(data.countries);
  466. tz.dataVersion = data.version;
  467. }
  468. function zoneExists (name) {
  469. if (!zoneExists.didShowError) {
  470. zoneExists.didShowError = true;
  471. logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')");
  472. }
  473. return !!getZone(name);
  474. }
  475. function needsOffset (m) {
  476. var isUnixTimestamp = (m._f === 'X' || m._f === 'x');
  477. return !!(m._a && (m._tzm === undefined) && !isUnixTimestamp);
  478. }
  479. function logError (message) {
  480. if (typeof console !== 'undefined' && typeof console.error === 'function') {
  481. console.error(message);
  482. }
  483. }
  484. /************************************
  485. moment.tz namespace
  486. ************************************/
  487. function tz (input) {
  488. var args = Array.prototype.slice.call(arguments, 0, -1),
  489. name = arguments[arguments.length - 1],
  490. out = moment.utc.apply(null, args),
  491. zone;
  492. if (!moment.isMoment(input) && needsOffset(out) && (zone = getZone(name))) {
  493. out.add(zone.parse(out), 'minutes');
  494. }
  495. out.tz(name);
  496. return out;
  497. }
  498. tz.version = VERSION;
  499. tz.dataVersion = '';
  500. tz._zones = zones;
  501. tz._links = links;
  502. tz._names = names;
  503. tz._countries = countries;
  504. tz.add = addZone;
  505. tz.link = addLink;
  506. tz.load = loadData;
  507. tz.zone = getZone;
  508. tz.zoneExists = zoneExists; // deprecated in 0.1.0
  509. tz.guess = guess;
  510. tz.names = getNames;
  511. tz.Zone = Zone;
  512. tz.unpack = unpack;
  513. tz.unpackBase60 = unpackBase60;
  514. tz.needsOffset = needsOffset;
  515. tz.moveInvalidForward = true;
  516. tz.moveAmbiguousForward = false;
  517. tz.countries = getCountryNames;
  518. tz.zonesForCountry = zonesForCountry;
  519. /************************************
  520. Interface with Moment.js
  521. ************************************/
  522. var fn = moment.fn;
  523. moment.tz = tz;
  524. moment.defaultZone = null;
  525. moment.updateOffset = function (mom, keepTime) {
  526. var zone = moment.defaultZone,
  527. offset;
  528. if (mom._z === undefined) {
  529. if (zone && needsOffset(mom) && !mom._isUTC && mom.isValid()) {
  530. mom._d = moment.utc(mom._a)._d;
  531. mom.utc().add(zone.parse(mom), 'minutes');
  532. }
  533. mom._z = zone;
  534. }
  535. if (mom._z) {
  536. offset = mom._z.utcOffset(mom);
  537. if (Math.abs(offset) < 16) {
  538. offset = offset / 60;
  539. }
  540. if (mom.utcOffset !== undefined) {
  541. var z = mom._z;
  542. mom.utcOffset(-offset, keepTime);
  543. mom._z = z;
  544. } else {
  545. mom.zone(offset, keepTime);
  546. }
  547. }
  548. };
  549. fn.tz = function (name, keepTime) {
  550. if (name) {
  551. if (typeof name !== 'string') {
  552. throw new Error('Time zone name must be a string, got ' + name + ' [' + typeof name + ']');
  553. }
  554. this._z = getZone(name);
  555. if (this._z) {
  556. moment.updateOffset(this, keepTime);
  557. } else {
  558. logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/.");
  559. }
  560. return this;
  561. }
  562. if (this._z) { return this._z.name; }
  563. };
  564. function abbrWrap (old) {
  565. return function () {
  566. if (this._z) { return this._z.abbr(this); }
  567. return old.call(this);
  568. };
  569. }
  570. function resetZoneWrap (old) {
  571. return function () {
  572. this._z = null;
  573. return old.apply(this, arguments);
  574. };
  575. }
  576. function resetZoneWrap2 (old) {
  577. return function () {
  578. if (arguments.length > 0) this._z = null;
  579. return old.apply(this, arguments);
  580. };
  581. }
  582. fn.zoneName = abbrWrap(fn.zoneName);
  583. fn.zoneAbbr = abbrWrap(fn.zoneAbbr);
  584. fn.utc = resetZoneWrap(fn.utc);
  585. fn.local = resetZoneWrap(fn.local);
  586. fn.utcOffset = resetZoneWrap2(fn.utcOffset);
  587. moment.tz.setDefault = function(name) {
  588. if (major < 2 || (major === 2 && minor < 9)) {
  589. logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.');
  590. }
  591. moment.defaultZone = name ? getZone(name) : null;
  592. return moment;
  593. };
  594. // Cloning a moment should include the _z property.
  595. var momentProperties = moment.momentProperties;
  596. if (Object.prototype.toString.call(momentProperties) === '[object Array]') {
  597. // moment 2.8.1+
  598. momentProperties.push('_z');
  599. momentProperties.push('_a');
  600. } else if (momentProperties) {
  601. // moment 2.7.0
  602. momentProperties._z = null;
  603. }
  604. // INJECT DATA
  605. return moment;
  606. }));