timezone.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var dataUtils = require('utils/data_manipulation');
  19. /**
  20. * Transitional list of timezones (used to create list of shownTimezone @see shownTimezone)
  21. *
  22. * <code>utcOffset</code> - offset-value (0, 180, 240 etc)
  23. * <code>formattedOffset</code> - formatted offset-value ('+00:00', '-02:00' etc)
  24. * <code>value</code> - timezone's name (like 'Europe/Athens')
  25. * <code>region</code> - timezone's region (for 'Europe/Athens' it will be 'Europe')
  26. * <code>city</code> - timezone's city (for 'Europe/Athens' it will be 'Athens')
  27. *
  28. * @typedef {{utcOffset: number, formattedOffset: string, value: string, region: string, city: string}} formattedTimezone
  29. */
  30. /**
  31. * List of timezones used in the user's settings popup
  32. *
  33. * <code>utcOffset</code> - offset-value (0, 180, 240 etc)
  34. * <code>value</code> - string like '(UTC+02:00) Europe / Athens, Kiev, Minsk'
  35. * <code>zones</code> - list of zone-objects from <code>moment.tz</code> included to the <code>value</code>
  36. *
  37. * @typedef {{utcOffset: number, label: string, value: string, zones: object[]}} shownTimezone
  38. */
  39. module.exports = Em.Object.create({
  40. /**
  41. * @type {shownTimezone[]}
  42. * @readOnly
  43. */
  44. timezones: [],
  45. /**
  46. * Map of <code>timezones</code>
  47. * Key - timezone value (like '(UTC+01:00) Region / City1, City2')
  48. * Value - zone-object
  49. *
  50. * @type {object}
  51. * @readOnly
  52. */
  53. timezonesMappedByLabel: function () {
  54. var ret = {};
  55. this.get('timezones').forEach(function (tz) {
  56. ret[tz.value] = tz;
  57. });
  58. return ret;
  59. }.property('timezones.[]'),
  60. init: function () {
  61. this.set('timezones', this._parseTimezones());
  62. return this._super();
  63. },
  64. /**
  65. * Load list of timezones from moment.tz
  66. * Zones "Etc/*" and abbreviations are excluded
  67. *
  68. * @returns {string[]}
  69. */
  70. getAllTimezoneNames: function () {
  71. return moment.tz.names().filter(function (timeZoneName) {
  72. return timeZoneName.indexOf('Etc/') !== 0 && timeZoneName !== timeZoneName.toUpperCase();
  73. });
  74. },
  75. /**
  76. * Try detect user's timezone using timezoneOffset and moment.tz
  77. * Checking current year January and July offsets
  78. * If <code>region</code> is provided, timezone for it is returned and not first valid
  79. *
  80. * @param {string} [region] preferred region (may be 'Europe', 'America', 'Africa', 'Asia' etc)
  81. * @returns {string}
  82. */
  83. detectUserTimezone: function (region) {
  84. region = (region || '').toLowerCase();
  85. var currentYear = new Date().getFullYear();
  86. var jan = new Date(currentYear, 0, 1);
  87. var jul = new Date(currentYear, 6, 1);
  88. var janOffset = jan.getTimezoneOffset();
  89. var julOffset = jul.getTimezoneOffset();
  90. var timezones = this.get('timezones');
  91. var validZones = [];
  92. for (var i = 0; i < timezones.length; i++) {
  93. var zones = timezones[i].zones;
  94. for (var j = 0; j < zones.length; j++) {
  95. var zone = moment.tz.zone(zones[j].value);
  96. if ((zone.offset(jan) === janOffset) && (zone.offset(jul) === julOffset)) {
  97. validZones.pushObject(timezones[i].value);
  98. }
  99. }
  100. }
  101. if (validZones.length) {
  102. if (region) {
  103. for (i = 0; i < validZones.length; i++) {
  104. if (validZones[i].toLowerCase().indexOf(region) !== -1) {
  105. return validZones[i];
  106. }
  107. }
  108. // Timezone for `region` wasn't found
  109. return validZones[0];
  110. }
  111. // `region` isn't provided, so return first valid timezone
  112. return validZones[0];
  113. }
  114. // are you from Venus?
  115. return '';
  116. },
  117. /**
  118. * Reformat timezones list and sort it by utcOffset and timeZoneName
  119. *
  120. * @private
  121. * @method _parseTimezones
  122. * @returns {shownTimezone[]}
  123. */
  124. _parseTimezones: function () {
  125. var currentYear = new Date().getFullYear();
  126. var jan = new Date(currentYear, 0, 1);
  127. var jul = new Date(currentYear, 6, 1);
  128. var zones = this.getAllTimezoneNames().map(function (timeZoneName) {
  129. var zone = moment(new Date()).tz(timeZoneName);
  130. var z = moment.tz.zone(timeZoneName);
  131. var offset = zone.format('Z');
  132. var regionCity = timeZoneName.split('/');
  133. var region = regionCity[0];
  134. var city = regionCity.length === 2 ? regionCity[1] : '';
  135. return {
  136. groupByKey: z.offset(jan) + '' + z.offset(jul),
  137. utcOffset: zone.utcOffset(),
  138. formattedOffset: offset,
  139. value: timeZoneName,
  140. region: region,
  141. city: city.replace(/_/g, ' ')
  142. };
  143. }).sort(function (zoneA, zoneB) {
  144. if (zoneA.utcOffset === zoneB.utcOffset) {
  145. if (zoneA.value === zoneB.value) {
  146. return 0;
  147. }
  148. return zoneA.value < zoneB.value ? -1 : 1;
  149. } else {
  150. if(zoneA.utcOffset === zoneB.utcOffset) {
  151. return 0;
  152. }
  153. return zoneA.utcOffset < zoneB.utcOffset ? -1 : 1;
  154. }
  155. });
  156. return this._groupTimezones(zones);
  157. },
  158. /**
  159. * Group timezones by <code>groupByKey</code>
  160. * Group timezones in the each group by <code>region</code>
  161. * <code>city</code> for each regions are joined into string 'city1, city2, city3' (empty cities and abbreviations are ignored)
  162. * Example:
  163. * <pre>
  164. * var zones = [
  165. * {groupByKey: 1, formattedOffset: '+01:00', value: 'a/Aa', region: 'a', city: 'Aa'},
  166. * {groupByKey: 1, formattedOffset: '+01:00', value: 'a/Bb', region: 'a', city: 'Bb'},
  167. * {groupByKey: 2, formattedOffset: '+02:00', value: 'a/Cc', region: 'a', city: 'Cc'},
  168. * {groupByKey: 2, formattedOffset: '+02:00', value: 'a/Dd', region: 'a', city: 'Dd'},
  169. * {groupByKey: 1, formattedOffset: '+01:00', value: 'b/Ee', region: 'b', city: 'Ee'},
  170. * {groupByKey: 1, formattedOffset: '+01:00', value: 'b/Ff', region: 'b', city: 'Ff'},
  171. * {groupByKey: 2, formattedOffset: '+02:00', value: 'b/Gg', region: 'b', city: 'Gg'},
  172. * {groupByKey: 2, formattedOffset: '+02:00', value: 'b/Hh', region: 'b', city: 'Hh'},
  173. * {groupByKey: 2, formattedOffset: '+02:00', value: 'b/II', region: 'b', city: 'II'}, // will be ignored, because city is abbreviation
  174. * {groupByKey: 2, formattedOffset: '+02:00', value: 'b', region: 'b', city: '' } // will be ignored, because city is empty
  175. * ];
  176. * var groupedZones = _groupTimezones(zones);
  177. * // groupedZones is:
  178. * [
  179. * {utcOffset: 1, value: '(UTC+01:00) a / Aa, Bb'},
  180. * {utcOffset: 1, value: '(UTC+01:00) b / Ee, Ff'},
  181. * {utcOffset: 2, value: '(UTC+02:00) a / Cc, Dd'},
  182. * {utcOffset: 2, value: '(UTC+02:00) b / Gg, Hh'}
  183. * ]
  184. * </pre>
  185. *
  186. * @param {formattedTimezone[]} zones
  187. * @returns {shownTimezone[]}
  188. * @method _groupTimezones
  189. * @private
  190. */
  191. _groupTimezones: function (zones) {
  192. var z = dataUtils.groupPropertyValues(zones, 'groupByKey');
  193. var newZones = [];
  194. Object.keys(z).forEach(function (offset) {
  195. var groupedByRegionZones = dataUtils.groupPropertyValues(z[offset], 'region');
  196. Object.keys(groupedByRegionZones).forEach(function (region) {
  197. var cities = groupedByRegionZones[region].mapProperty('city').filter(function (city) {
  198. return city !== '' && city !== city.toUpperCase();
  199. }).uniq().join(', ');
  200. var formattedOffset = Em.get(groupedByRegionZones[region], 'firstObject.formattedOffset');
  201. var utcOffset = Em.get(groupedByRegionZones[region], 'firstObject.utcOffset');
  202. newZones.pushObject({
  203. utcOffset: utcOffset,
  204. value: '(UTC' + formattedOffset + ') ' + region + (cities ? ' / ' + cities : ''),
  205. zones: groupedByRegionZones[region]
  206. });
  207. });
  208. });
  209. return newZones.sortProperty('utcOffset');
  210. }
  211. });