1 | /****************************************************************************** |
---|
2 | * Dojo port of fleegix date plugin from |
---|
3 | * |
---|
4 | * http://js.fleegix.org/plugins/date/date |
---|
5 | * |
---|
6 | * contributed to Dojo under CLA, with thanks to Matthew Eernisse (mde@fleegix.org) |
---|
7 | * and Open Source Applications Foundation |
---|
8 | * |
---|
9 | * Credits: Ideas included from incomplete JS implementation of Olson |
---|
10 | * parser, "XMLDate" by Philippe Goetz (philippe.goetz@wanadoo.fr) |
---|
11 | *****************************************************************************/ |
---|
12 | |
---|
13 | define(["dojo", "dojo/date", "dojo/date/locale", "dojo/_base/array", "dojo/_base/xhr"], |
---|
14 | function(dojo, _dd, _ddl){ |
---|
15 | |
---|
16 | dojo.experimental("dojox.date.timezone"); |
---|
17 | dojo.getObject("date.timezone", true, dojox); |
---|
18 | |
---|
19 | var cfg = dojo.config; |
---|
20 | var _zoneFiles = [ "africa", "antarctica", "asia", "australasia", "backward", |
---|
21 | "etcetera", "europe", "northamerica", "pacificnew", |
---|
22 | "southamerica" ]; |
---|
23 | |
---|
24 | // Our mins an maxes for years that we care about |
---|
25 | var _minYear = 1835, |
---|
26 | _maxYear = 2038; |
---|
27 | |
---|
28 | var _loadedZones = {}, |
---|
29 | _zones = {}, |
---|
30 | _loadedRanges = {}, |
---|
31 | _rules = {}; |
---|
32 | |
---|
33 | // loadingScheme: String |
---|
34 | // One of "preloadAll", "lazyLoad" (Defaults "lazyLoad") |
---|
35 | var loadingScheme = cfg.timezoneLoadingScheme || "preloadAll"; |
---|
36 | |
---|
37 | // defaultZoneFile: String or String[] |
---|
38 | // The default file (or files) to load on startup - other files will |
---|
39 | // be lazily-loaded on-demand |
---|
40 | var defaultZoneFile = cfg.defaultZoneFile || |
---|
41 | ((loadingScheme == "preloadAll") ? _zoneFiles : "northamerica"); |
---|
42 | |
---|
43 | // Set our olson-zoneinfo content handler |
---|
44 | dojo._contentHandlers["olson-zoneinfo"] = function(xhr){ |
---|
45 | var str = dojo._contentHandlers["text"](xhr), |
---|
46 | s = "", |
---|
47 | lines = str.split("\n"), |
---|
48 | arr = [], |
---|
49 | chunk = "", |
---|
50 | zone = null, |
---|
51 | rule = null, |
---|
52 | ret = {zones: {}, rules: {}}; |
---|
53 | |
---|
54 | for(var i = 0; i < lines.length; i++){ |
---|
55 | var l = lines[i]; |
---|
56 | if(l.match(/^\s/)){ |
---|
57 | l = "Zone " + zone + l; |
---|
58 | } |
---|
59 | l = l.split("#")[0]; |
---|
60 | if(l.length > 3){ |
---|
61 | arr = l.split(/\s+/); |
---|
62 | chunk = arr.shift(); |
---|
63 | switch(chunk){ |
---|
64 | case 'Zone': |
---|
65 | zone = arr.shift(); |
---|
66 | if(arr[0]){ |
---|
67 | // Handle extra commas in the middle of a zone |
---|
68 | if(!ret.zones[zone]){ ret.zones[zone] = []; } |
---|
69 | ret.zones[zone].push(arr); |
---|
70 | } |
---|
71 | break; |
---|
72 | case 'Rule': |
---|
73 | rule = arr.shift(); |
---|
74 | if(!ret.rules[rule]){ ret.rules[rule] = []; } |
---|
75 | ret.rules[rule].push(arr); |
---|
76 | break; |
---|
77 | case 'Link': |
---|
78 | // No zones for these should already exist |
---|
79 | if(ret.zones[arr[1]]){ |
---|
80 | throw new Error('Error with Link ' + arr[1]); |
---|
81 | } |
---|
82 | // Create the link |
---|
83 | ret.zones[arr[1]] = arr[0]; |
---|
84 | break; |
---|
85 | case 'Leap': |
---|
86 | break; |
---|
87 | default: |
---|
88 | // Fail silently |
---|
89 | break; |
---|
90 | } |
---|
91 | } |
---|
92 | } |
---|
93 | return ret; // Object |
---|
94 | }; |
---|
95 | |
---|
96 | function loadZoneData(/* Object */ data){ |
---|
97 | // summary: |
---|
98 | // Loads the given data object into the zone database |
---|
99 | // data: Object |
---|
100 | // The data to load - contains "zones" and "rules" parameters |
---|
101 | data = data || {}; |
---|
102 | _zones = dojo.mixin(_zones, data.zones||{}); |
---|
103 | _rules = dojo.mixin(_rules, data.rules||{}); |
---|
104 | } |
---|
105 | |
---|
106 | function loadZoneFile(/* String */ fileName){ |
---|
107 | // summary: |
---|
108 | // Loads the given URL of the Olson zone information into the |
---|
109 | // zone database |
---|
110 | // |
---|
111 | // fileName: String |
---|
112 | // The zoneinfo file name to load |
---|
113 | |
---|
114 | // TODO: Maybe behave similar to requireLocalization - rather than |
---|
115 | // Using dojo.xhrGet? |
---|
116 | _loadedZones[fileName] = true; |
---|
117 | dojo.xhrGet({ |
---|
118 | url: require.toUrl((cfg.timezoneFileBasePath || "dojox/date/zoneinfo") + "/" + fileName), |
---|
119 | sync: true, // Needs to be synchronous so we can return values |
---|
120 | handleAs: "olson-zoneinfo", |
---|
121 | load: loadZoneData, |
---|
122 | error: function(e){ |
---|
123 | console.error("Error loading zone file:", e); |
---|
124 | throw e; |
---|
125 | } |
---|
126 | }); |
---|
127 | } |
---|
128 | |
---|
129 | var monthMap = { 'jan': 0, 'feb': 1, 'mar': 2, 'apr': 3,'may': 4, 'jun': 5, |
---|
130 | 'jul': 6, 'aug': 7, 'sep': 8, 'oct': 9, 'nov': 10, 'dec': 11 }, |
---|
131 | dayMap = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, |
---|
132 | 'fri': 5, 'sat': 6 }, |
---|
133 | regionMap = {'EST': "northamerica", 'MST': "northamerica", |
---|
134 | 'HST': "northamerica", 'EST5EDT': "northamerica", |
---|
135 | 'CST6CDT': "northamerica", 'MST7MDT': "northamerica", |
---|
136 | 'PST8PDT': "northamerica", 'America': "northamerica", |
---|
137 | 'Pacific': "australasia", 'Atlantic': "europe", |
---|
138 | 'Africa': "africa", 'Indian': "africa", |
---|
139 | 'Antarctica': "antarctica", 'Asia': "asia", |
---|
140 | 'Australia': "australasia", 'Europe': "europe", |
---|
141 | 'WET': "europe", 'CET': "europe", 'MET': "europe", |
---|
142 | 'EET': "europe"}, |
---|
143 | regionExceptions = {'Pacific/Honolulu':"northamerica", |
---|
144 | 'Atlantic/Bermuda':"northamerica", |
---|
145 | 'Atlantic/Cape_Verde':"africa", |
---|
146 | 'Atlantic/St_Helena':"africa", |
---|
147 | 'Indian/Kerguelen':"antarctica", |
---|
148 | 'Indian/Chagos':"asia", |
---|
149 | 'Indian/Maldives':"asia", |
---|
150 | 'Indian/Christmas':"australasia", |
---|
151 | 'Indian/Cocos':"australasia", |
---|
152 | 'America/Danmarkshavn':"europe", |
---|
153 | 'America/Scoresbysund':"europe", |
---|
154 | 'America/Godthab':"europe", |
---|
155 | 'America/Thule':"europe", |
---|
156 | 'Asia/Yekaterinburg':"europe", |
---|
157 | 'Asia/Omsk':"europe", |
---|
158 | 'Asia/Novosibirsk':"europe", |
---|
159 | 'Asia/Krasnoyarsk':"europe", |
---|
160 | 'Asia/Irkutsk':"europe", |
---|
161 | 'Asia/Yakutsk':"europe", |
---|
162 | 'Asia/Vladivostok':"europe", |
---|
163 | 'Asia/Sakhalin':"europe", |
---|
164 | 'Asia/Magadan':"europe", |
---|
165 | 'Asia/Kamchatka':"europe", |
---|
166 | 'Asia/Anadyr':"europe", |
---|
167 | 'Africa/Ceuta':"europe", |
---|
168 | 'America/Argentina/Buenos_Aires':"southamerica", |
---|
169 | 'America/Argentina/Cordoba':"southamerica", |
---|
170 | 'America/Argentina/Tucuman':"southamerica", |
---|
171 | 'America/Argentina/La_Rioja':"southamerica", |
---|
172 | 'America/Argentina/San_Juan':"southamerica", |
---|
173 | 'America/Argentina/Jujuy':"southamerica", |
---|
174 | 'America/Argentina/Catamarca':"southamerica", |
---|
175 | 'America/Argentina/Mendoza':"southamerica", |
---|
176 | 'America/Argentina/Rio_Gallegos':"southamerica", |
---|
177 | 'America/Argentina/Ushuaia':"southamerica", |
---|
178 | 'America/Aruba':"southamerica", |
---|
179 | 'America/La_Paz':"southamerica", |
---|
180 | 'America/Noronha':"southamerica", |
---|
181 | 'America/Belem':"southamerica", |
---|
182 | 'America/Fortaleza':"southamerica", |
---|
183 | 'America/Recife':"southamerica", |
---|
184 | 'America/Araguaina':"southamerica", |
---|
185 | 'America/Maceio':"southamerica", |
---|
186 | 'America/Bahia':"southamerica", |
---|
187 | 'America/Sao_Paulo':"southamerica", |
---|
188 | 'America/Campo_Grande':"southamerica", |
---|
189 | 'America/Cuiaba':"southamerica", |
---|
190 | 'America/Porto_Velho':"southamerica", |
---|
191 | 'America/Boa_Vista':"southamerica", |
---|
192 | 'America/Manaus':"southamerica", |
---|
193 | 'America/Eirunepe':"southamerica", |
---|
194 | 'America/Rio_Branco':"southamerica", |
---|
195 | 'America/Santiago':"southamerica", |
---|
196 | 'Pacific/Easter':"southamerica", |
---|
197 | 'America/Bogota':"southamerica", |
---|
198 | 'America/Curacao':"southamerica", |
---|
199 | 'America/Guayaquil':"southamerica", |
---|
200 | 'Pacific/Galapagos':"southamerica", |
---|
201 | 'Atlantic/Stanley':"southamerica", |
---|
202 | 'America/Cayenne':"southamerica", |
---|
203 | 'America/Guyana':"southamerica", |
---|
204 | 'America/Asuncion':"southamerica", |
---|
205 | 'America/Lima':"southamerica", |
---|
206 | 'Atlantic/South_Georgia':"southamerica", |
---|
207 | 'America/Paramaribo':"southamerica", |
---|
208 | 'America/Port_of_Spain':"southamerica", |
---|
209 | 'America/Montevideo':"southamerica", |
---|
210 | 'America/Caracas':"southamerica"}, |
---|
211 | abbrExceptions = { 'US': "S", 'Chatham': "S", 'NZ': "S", 'NT_YK': "S", |
---|
212 | 'Edm': "S", 'Salv': "S", 'Canada': "S", 'StJohns': "S", |
---|
213 | 'TC': "S", 'Guat': "S", 'Mexico': "S", 'Haiti': "S", |
---|
214 | 'Barb': "S", 'Belize': "S", 'CR': "S", 'Moncton': "S", |
---|
215 | 'Swift': "S", 'Hond': "S", 'Thule': "S", 'NZAQ': "S", |
---|
216 | 'Zion': "S", 'ROK': "S", 'PRC': "S", 'Taiwan': "S", |
---|
217 | 'Ghana': "GMT", 'SL': "WAT", 'Chicago': "S", |
---|
218 | 'Detroit': "S", 'Vanc': "S", 'Denver': "S", |
---|
219 | 'Halifax': "S", 'Cuba': "S", 'Indianapolis': "S", |
---|
220 | 'Starke': "S", 'Marengo': "S", 'Pike': "S", |
---|
221 | 'Perry': "S", 'Vincennes': "S", 'Pulaski': "S", |
---|
222 | 'Louisville': "S", 'CA': "S", 'Nic': "S", |
---|
223 | 'Menominee': "S", 'Mont': "S", 'Bahamas': "S", |
---|
224 | 'NYC': "S", 'Regina': "S", 'Resolute': "ES", |
---|
225 | 'DR': "S", 'Toronto': "S", 'Winn': "S" }; |
---|
226 | |
---|
227 | function invalidTZError(t) { |
---|
228 | throw new Error('Timezone "' + t + |
---|
229 | '" is either incorrect, or not loaded in the timezone registry.'); |
---|
230 | } |
---|
231 | |
---|
232 | function getRegionForTimezone(/* String */ tz) { |
---|
233 | // summary: |
---|
234 | // Returns the Olson region for the given timezone |
---|
235 | var ret = regionExceptions[tz]; |
---|
236 | if(!ret){ |
---|
237 | var reg = tz.split('/')[0]; |
---|
238 | ret = regionMap[reg]; |
---|
239 | // If there's nothing listed in the main regions for |
---|
240 | // this TZ, check the 'backward' links |
---|
241 | if(!ret){ |
---|
242 | var link = _zones[tz]; |
---|
243 | if(typeof link == 'string'){ |
---|
244 | return getRegionForTimezone(link); // String |
---|
245 | }else{ |
---|
246 | // Backward-compat file hasn't loaded yet, try looking in there |
---|
247 | if (!_loadedZones.backward) { |
---|
248 | // This is for obvious legacy zones (e.g., Iceland) that |
---|
249 | // don't even have a prefix like "America/" that look like |
---|
250 | // normal zones |
---|
251 | loadZoneFile("backward"); |
---|
252 | return getRegionForTimezone(tz); // String |
---|
253 | }else{ |
---|
254 | invalidTZError(tz); |
---|
255 | } |
---|
256 | } |
---|
257 | } |
---|
258 | } |
---|
259 | return ret; // String |
---|
260 | } |
---|
261 | |
---|
262 | function parseTimeString(/* String */ str) { |
---|
263 | // summary: |
---|
264 | // Parses the given time string and returns it as an integer array |
---|
265 | var pat = /(\d+)(?::0*(\d*))?(?::0*(\d*))?([su])?$/; |
---|
266 | var hms = str.match(pat); |
---|
267 | if(!hms){ |
---|
268 | return null; |
---|
269 | } |
---|
270 | hms[1] = parseInt(hms[1], 10); |
---|
271 | hms[2] = hms[2] ? parseInt(hms[2], 10) : 0; |
---|
272 | hms[3] = hms[3] ? parseInt(hms[3], 10) : 0; |
---|
273 | return hms; // int[] |
---|
274 | } |
---|
275 | |
---|
276 | function getUTCStamp(/* int */ y, /* int */ m, /* int */ d, /* int */ h, |
---|
277 | /* int */ mn, /* int */ s, /* int? */ off){ |
---|
278 | // summary: |
---|
279 | // Returns the UTC timestamp, adjusted by the given (optional) offset |
---|
280 | return Date.UTC(y, m, d, h, mn, s) + ((off||0) * 60 * 1000); |
---|
281 | } |
---|
282 | |
---|
283 | function getMonthNumber(/* String */ m){ |
---|
284 | // summary: |
---|
285 | // Returns the javascript month number for the given string |
---|
286 | return monthMap[m.substr(0, 3).toLowerCase()]; |
---|
287 | } |
---|
288 | |
---|
289 | function getOffsetInMins(/* String */ str){ |
---|
290 | // summary: |
---|
291 | // Returns the offset value represented by the string, in minutes |
---|
292 | var off = parseTimeString(str); |
---|
293 | if(off === null){ return 0; } |
---|
294 | var adj = str.indexOf('-') === 0 ? -1 : 1; |
---|
295 | off = adj * (((off[1] * 60 + off[2]) *60 + off[3]) * 1000); |
---|
296 | return -off/60/1000; |
---|
297 | } |
---|
298 | |
---|
299 | function _getRuleStart(/* Rule */ rule, /* int */ year, /* int */ off){ |
---|
300 | // summary: |
---|
301 | // Returns a date that the rule begins matching in the given year. |
---|
302 | var month = getMonthNumber(rule[3]), |
---|
303 | day = rule[4], |
---|
304 | time = parseTimeString(rule[5]); |
---|
305 | if(time[4] == "u"){ |
---|
306 | // We are UTC - so there is no offset to use |
---|
307 | off = 0; |
---|
308 | } |
---|
309 | |
---|
310 | var d, dtDay, incr; |
---|
311 | if(isNaN(day)){ |
---|
312 | if(day.substr(0, 4) == "last"){ |
---|
313 | // Last day of the month at the desired time of day |
---|
314 | day = dayMap[day.substr(4,3).toLowerCase()]; |
---|
315 | d = new Date(getUTCStamp(year, month + 1, 1, |
---|
316 | time[1] - 24, time[2], time[3], |
---|
317 | off)); |
---|
318 | dtDay = _dd.add(d, "minute", -off).getUTCDay(); |
---|
319 | // Set it to the final day of the correct weekday that month |
---|
320 | incr = (day > dtDay) ? (day - dtDay - 7) : (day - dtDay); |
---|
321 | if(incr !== 0){ |
---|
322 | d = _dd.add(d, "hour", incr * 24); |
---|
323 | } |
---|
324 | return d; |
---|
325 | }else{ |
---|
326 | day = dayMap[day.substr(0, 3).toLowerCase()]; |
---|
327 | if(day != "undefined"){ |
---|
328 | if(rule[4].substr(3, 2) == '>='){ |
---|
329 | // The stated date of the month |
---|
330 | d = new Date(getUTCStamp(year, month, parseInt(rule[4].substr(5), 10), |
---|
331 | time[1], time[2], time[3], off)); |
---|
332 | dtDay = _dd.add(d, "minute", -off).getUTCDay(); |
---|
333 | // Set to the first correct weekday after the stated date |
---|
334 | incr = (day < dtDay) ? (day - dtDay + 7) : (day - dtDay); |
---|
335 | if(incr !== 0){ |
---|
336 | d = _dd.add(d, "hour", incr * 24); |
---|
337 | } |
---|
338 | return d; |
---|
339 | }else if(day.substr(3, 2) == '<='){ |
---|
340 | // The stated date of the month |
---|
341 | d = new Date(getUTCStamp(year, month, parseInt(rule[4].substr(5), 10), |
---|
342 | time[1], time[2], time[3], off)); |
---|
343 | dtDay = _dd.add(d, "minute", -off).getUTCDay(); |
---|
344 | // Set to first correct weekday before the stated date |
---|
345 | incr = (day > dtDay) ? (day - dtDay - 7) : (day - dtDay); |
---|
346 | if(incr !== 0){ |
---|
347 | d = _dd.add(d, "hour", incr * 24); |
---|
348 | } |
---|
349 | return d; |
---|
350 | } |
---|
351 | } |
---|
352 | } |
---|
353 | }else{ |
---|
354 | // Numeric date |
---|
355 | d = new Date(getUTCStamp(year, month, parseInt(day, 10), |
---|
356 | time[1], time[2], time[3], off)); |
---|
357 | return d; |
---|
358 | } |
---|
359 | return null; |
---|
360 | } |
---|
361 | |
---|
362 | function _getRulesForYear(/* Zone */ zone, /* int */ year){ |
---|
363 | var rules = []; |
---|
364 | dojo.forEach(_rules[zone[1]]||[], function(r){ |
---|
365 | // Clean up rules as needed |
---|
366 | for(var i = 0; i < 2; i++){ |
---|
367 | switch(r[i]){ |
---|
368 | case "min": |
---|
369 | r[i] = _minYear; |
---|
370 | break; |
---|
371 | case "max": |
---|
372 | r[i] = _maxYear; |
---|
373 | break; |
---|
374 | case "only": |
---|
375 | break; |
---|
376 | default: |
---|
377 | r[i] = parseInt(r[i], 10); |
---|
378 | if(isNaN(r[i])){ |
---|
379 | throw new Error('Invalid year found on rule'); |
---|
380 | } |
---|
381 | break; |
---|
382 | } |
---|
383 | } |
---|
384 | if(typeof r[6] == "string"){ |
---|
385 | // Change our offset to be an integer |
---|
386 | r[6] = getOffsetInMins(r[6]); |
---|
387 | } |
---|
388 | |
---|
389 | // Quick-filter to grab all rules that match my year |
---|
390 | if((r[0] <= year && r[1] >= year) || // Matches my y |
---|
391 | (r[0] == year && r[1] == "only")){ // Matches my only |
---|
392 | rules.push({r: r, d: _getRuleStart(r, year, zone[0])}); |
---|
393 | } |
---|
394 | }); |
---|
395 | return rules; |
---|
396 | } |
---|
397 | |
---|
398 | |
---|
399 | function _loadZoneRanges(/* String */ tz, /* Object[] */ zoneList) { |
---|
400 | // summary: |
---|
401 | // Loads the zone ranges for the given timezone |
---|
402 | |
---|
403 | var zr = _loadedRanges[tz] = []; |
---|
404 | for(var i = 0; i < zoneList.length; i++){ |
---|
405 | var z = zoneList[i]; |
---|
406 | var r = zr[i] = []; |
---|
407 | var prevZone = null; |
---|
408 | var prevRange = null; |
---|
409 | var prevRules = []; |
---|
410 | |
---|
411 | // Set up our zone offset to not be a string anymore |
---|
412 | if(typeof z[0] == "string"){ |
---|
413 | z[0] = getOffsetInMins(z[0]); |
---|
414 | } |
---|
415 | |
---|
416 | if(i === 0){ |
---|
417 | // The beginning of zoneinfo time - let's not worry about |
---|
418 | // to-the-hour accuracy before Jan 1, 1835 |
---|
419 | r[0] = Date.UTC(_minYear,0,1,0,0,0,0); |
---|
420 | }else{ |
---|
421 | r[0] = zr[i - 1][1]; |
---|
422 | prevZone = zoneList[i - 1]; |
---|
423 | prevRange = zr[i - 1]; |
---|
424 | prevRules = prevRange[2]; |
---|
425 | } |
---|
426 | |
---|
427 | // Load the rules that will be going in to our zone |
---|
428 | var startYear = new Date(r[0]).getUTCFullYear(); |
---|
429 | var endYear = z[3] ? parseInt(z[3], 10) : _maxYear; |
---|
430 | var rlz = []; |
---|
431 | var j; |
---|
432 | for(j = startYear; j <= endYear; j++){ |
---|
433 | rlz = rlz.concat(_getRulesForYear(z, j)); |
---|
434 | } |
---|
435 | rlz.sort(function(a, b){ |
---|
436 | return _dd.compare(a.d, b.d); |
---|
437 | }); |
---|
438 | var rl; |
---|
439 | for(j = 0, rl; (rl = rlz[j]); j++){ |
---|
440 | var prevRule = j > 0 ? rlz[j - 1] : null; |
---|
441 | if(rl.r[5].indexOf("u") < 0 && rl.r[5].indexOf("s") < 0){ |
---|
442 | if(j === 0 && i > 0){ |
---|
443 | if(prevRules.length){ |
---|
444 | // We have a previous rule - so use it |
---|
445 | rl.d = _dd.add(rl.d, "minute", prevRules[prevRules.length - 1].r[6]); |
---|
446 | }else if(_dd.compare(new Date(prevRange[1]), rl.d, "date") === 0){ |
---|
447 | // No previous rules - but our date is the same as the |
---|
448 | // previous zone ended on - so use that. |
---|
449 | rl.d = new Date(prevRange[1]); |
---|
450 | }else{ |
---|
451 | rl.d = _dd.add(rl.d, "minute", getOffsetInMins(prevZone[1])); |
---|
452 | } |
---|
453 | }else if(j > 0){ |
---|
454 | rl.d = _dd.add(rl.d, "minute", prevRule.r[6]); |
---|
455 | } |
---|
456 | } |
---|
457 | } |
---|
458 | r[2] = rlz; |
---|
459 | |
---|
460 | if(!z[3]){ |
---|
461 | // The end of zoneinfo time - we'll cross this bridge when we |
---|
462 | // get close to Dec 31, 2038 |
---|
463 | r[1] = Date.UTC(_maxYear,11,31,23,59,59,999); |
---|
464 | }else{ |
---|
465 | var year = parseInt(z[3], 10), |
---|
466 | month = getMonthNumber(z[4]||"Jan"), |
---|
467 | day = parseInt(z[5]||"1", 10), |
---|
468 | time = parseTimeString(z[6]||"0"); |
---|
469 | var utcStmp = r[1] = getUTCStamp(year, month, day, |
---|
470 | time[1], time[2], time[3], |
---|
471 | ((time[4] == "u") ? 0 : z[0])); |
---|
472 | if(isNaN(utcStmp)){ |
---|
473 | utcStmp = r[1] = _getRuleStart([0,0,0,z[4],z[5],z[6]||"0"], |
---|
474 | year, ((time[4] == "u") ? 0 : z[0])).getTime(); |
---|
475 | } |
---|
476 | var matches = dojo.filter(rlz, function(rl, idx){ |
---|
477 | var o = idx > 0 ? rlz[idx - 1].r[6] * 60 * 1000 : 0; |
---|
478 | return (rl.d.getTime() < utcStmp + o); |
---|
479 | }); |
---|
480 | if(time[4] != "u" && time[4] != "s"){ |
---|
481 | if(matches.length){ |
---|
482 | r[1] += matches[matches.length - 1].r[6] * 60 * 1000; |
---|
483 | }else{ |
---|
484 | r[1] += getOffsetInMins(z[1]) * 60 * 1000; |
---|
485 | } |
---|
486 | } |
---|
487 | } |
---|
488 | } |
---|
489 | } |
---|
490 | |
---|
491 | function getZoneInfo(/* String */ dt, /* String */ tz) { |
---|
492 | // summary: |
---|
493 | // Returns the zone entry from the zoneinfo database for the given date |
---|
494 | // and timezone |
---|
495 | var t = tz; |
---|
496 | var zoneList = _zones[t]; |
---|
497 | |
---|
498 | // Follow links to get to an actual zone |
---|
499 | while(typeof zoneList == "string"){ |
---|
500 | t = zoneList; |
---|
501 | zoneList = _zones[t]; |
---|
502 | } |
---|
503 | if(!zoneList){ |
---|
504 | // Backward-compat file hasn't loaded yet, try looking in there |
---|
505 | if(!_loadedZones.backward){ |
---|
506 | // This is for backward entries like "America/Fort_Wayne" that |
---|
507 | // getRegionForTimezone *thinks* it has a region file and zone |
---|
508 | // for (e.g., America => 'northamerica'), but in reality it's a |
---|
509 | // legacy zone we need the backward file for |
---|
510 | var parsed = loadZoneFile("backward", true); |
---|
511 | return getZoneInfo(dt, tz); //Object |
---|
512 | } |
---|
513 | invalidTZError(t); |
---|
514 | } |
---|
515 | |
---|
516 | if(!_loadedRanges[tz]){ |
---|
517 | _loadZoneRanges(tz, zoneList); |
---|
518 | } |
---|
519 | var ranges = _loadedRanges[tz]; |
---|
520 | var tm = dt.getTime(); |
---|
521 | for(var i = 0, r; (r = ranges[i]); i++){ |
---|
522 | if(tm >= r[0] && tm < r[1]){ |
---|
523 | return {zone: zoneList[i], range: ranges[i], idx: i}; |
---|
524 | } |
---|
525 | } |
---|
526 | throw new Error('No Zone found for "' + tz + '" on ' + dt); |
---|
527 | } |
---|
528 | |
---|
529 | function getRule(/* Date */ dt, /* ZoneInfo */ zoneInfo) { |
---|
530 | // summary: |
---|
531 | // Returns the latest-matching rule entry from the zoneinfo |
---|
532 | // database for the given date and zone |
---|
533 | |
---|
534 | var lastMatch = -1; |
---|
535 | var rules = zoneInfo.range[2]||[]; |
---|
536 | var tsp = dt.getTime(); |
---|
537 | var zr = zoneInfo.range; |
---|
538 | for(var i = 0, r; (r = rules[i]); i++){ |
---|
539 | if(tsp >= r.d.getTime()){ |
---|
540 | lastMatch = i; |
---|
541 | } |
---|
542 | } |
---|
543 | if(lastMatch >= 0){ |
---|
544 | return rules[lastMatch].r; |
---|
545 | } |
---|
546 | return null; |
---|
547 | } |
---|
548 | |
---|
549 | function getAbbreviation(/* String */ tz, /* Object */ zoneInfo, /* Object */ rule) { |
---|
550 | // summary: |
---|
551 | // Returns the abbreviation for the given zone and rule |
---|
552 | var res; |
---|
553 | var zone = zoneInfo.zone; |
---|
554 | var base = zone[2]; |
---|
555 | if(base.indexOf('%s') > -1){ |
---|
556 | var repl; |
---|
557 | if(rule){ |
---|
558 | repl = rule[7]; |
---|
559 | if(repl == "-"){ repl = ""; } |
---|
560 | }else if(zone[1] in abbrExceptions){ |
---|
561 | repl = abbrExceptions[zone[1]]; |
---|
562 | }else{ |
---|
563 | if(zoneInfo.idx > 0){ |
---|
564 | // Check if our previous zone's base is the same as our |
---|
565 | // current in "S" (standard) mode. If so, then use "S" |
---|
566 | // for our replacement |
---|
567 | var pz = _zones[tz][zoneInfo.idx - 1]; |
---|
568 | var pb = pz[2]; |
---|
569 | if(pb.indexOf('%s') < 0){ |
---|
570 | if(base.replace('%s', "S") == pb){ |
---|
571 | repl = "S"; |
---|
572 | }else{ |
---|
573 | repl = ""; |
---|
574 | } |
---|
575 | }else{ |
---|
576 | repl = ""; |
---|
577 | } |
---|
578 | }else{ |
---|
579 | repl = ""; |
---|
580 | } |
---|
581 | } |
---|
582 | res = base.replace('%s', repl); |
---|
583 | }else if(base.indexOf("/") > -1){ |
---|
584 | var bs = base.split("/"); |
---|
585 | if(rule){ |
---|
586 | res = bs[rule[6] === 0 ? 0 : 1]; |
---|
587 | }else{ |
---|
588 | res = bs[0]; |
---|
589 | } |
---|
590 | }else{ |
---|
591 | res = base; |
---|
592 | } |
---|
593 | return res; // String |
---|
594 | } |
---|
595 | |
---|
596 | /*===== |
---|
597 | |
---|
598 | // TODO: none of this is AMD friendly. It's setting global variables in dojox,and not returning anything from the module. |
---|
599 | // Plus, the override of dojo/date/locale's format() and _getZone() below. This needs to be refactored. |
---|
600 | |
---|
601 | dojox.date.timezone = function(){ |
---|
602 | // summary: |
---|
603 | // mix-in to dojo.date to provide timezones based on |
---|
604 | // the Olson timezone data |
---|
605 | // description: |
---|
606 | // mix-in to dojo.date to provide timezones based on |
---|
607 | // the Olson timezone data. |
---|
608 | // If you pass "timezone" as a parameter to your format options, |
---|
609 | // then you get the date formatted (and offset) for that timezone |
---|
610 | |
---|
611 | //TODOC |
---|
612 | }; |
---|
613 | |
---|
614 | dojox.date.timezone.getTzInfo = function(dt, tz){ |
---|
615 | // summary: |
---|
616 | // Returns the timezone information for the given date and |
---|
617 | // timezone string |
---|
618 | // dt: Date |
---|
619 | // The Date - a "proxyDate" |
---|
620 | // tz: String |
---|
621 | // String representation of the timezone you want to get info |
---|
622 | // for date |
---|
623 | }; |
---|
624 | |
---|
625 | dojox.date.timezone.loadZoneData = function(data){ |
---|
626 | // summary: |
---|
627 | // Loads the given data object into the zone database |
---|
628 | // data: Object |
---|
629 | // The data to load - contains "zones" and "rules" parameters |
---|
630 | }; |
---|
631 | |
---|
632 | dojox.date.timezone.getAllZones = function(){ |
---|
633 | // summary: |
---|
634 | // Returns an array of zones that have been loaded |
---|
635 | }; |
---|
636 | =====*/ |
---|
637 | dojo.setObject("dojox.date.timezone", { |
---|
638 | getTzInfo: function(/* Date */ dt, /* String */ tz){ |
---|
639 | // Lazy-load any zones not yet loaded |
---|
640 | if(loadingScheme == "lazyLoad"){ |
---|
641 | // Get the correct region for the zone |
---|
642 | var zoneFile = getRegionForTimezone(tz); |
---|
643 | if(!zoneFile){ |
---|
644 | throw new Error("Not a valid timezone ID."); |
---|
645 | }else{ |
---|
646 | if(!_loadedZones[zoneFile]){ |
---|
647 | // Get the file and parse it -- use synchronous XHR |
---|
648 | loadZoneFile(zoneFile); |
---|
649 | } |
---|
650 | } |
---|
651 | } |
---|
652 | var zoneInfo = getZoneInfo(dt, tz); |
---|
653 | var off = zoneInfo.zone[0]; |
---|
654 | // See if the offset needs adjustment |
---|
655 | var rule = getRule(dt, zoneInfo); |
---|
656 | if(rule){ |
---|
657 | off += rule[6]; |
---|
658 | }else{ |
---|
659 | if(_rules[zoneInfo.zone[1]] && zoneInfo.idx > 0){ |
---|
660 | off += getOffsetInMins(_zones[tz][zoneInfo.idx - 1][1]); |
---|
661 | }else{ |
---|
662 | off += getOffsetInMins(zoneInfo.zone[1]); |
---|
663 | } |
---|
664 | } |
---|
665 | |
---|
666 | var abbr = getAbbreviation(tz, zoneInfo, rule); |
---|
667 | return { tzOffset: off, tzAbbr: abbr }; // Object |
---|
668 | }, |
---|
669 | loadZoneData: function(data){ |
---|
670 | loadZoneData(data); |
---|
671 | }, |
---|
672 | getAllZones: function(){ |
---|
673 | var arr = []; |
---|
674 | for(var z in _zones){ arr.push(z); } |
---|
675 | arr.sort(); |
---|
676 | return arr; // String[] |
---|
677 | } |
---|
678 | }); |
---|
679 | |
---|
680 | // Now - initialize the stuff that we should have pre-loaded |
---|
681 | if(typeof defaultZoneFile == "string" && defaultZoneFile){ |
---|
682 | defaultZoneFile = [defaultZoneFile]; |
---|
683 | } |
---|
684 | if(dojo.isArray(defaultZoneFile)){ |
---|
685 | dojo.forEach(defaultZoneFile, loadZoneFile); |
---|
686 | } |
---|
687 | |
---|
688 | // And enhance the default formatting functions |
---|
689 | // If you pass "timezone" as a parameter to your format options, |
---|
690 | // then you get the date formatted (and offset) for that timezone |
---|
691 | var oLocaleFmt = _ddl.format, |
---|
692 | oGetZone = _ddl._getZone; |
---|
693 | _ddl.format = function(dateObject, options){ |
---|
694 | options = options||{}; |
---|
695 | if(options.timezone && !options._tzInfo){ |
---|
696 | // Store it in our options so we can use it later |
---|
697 | options._tzInfo = dojox.date.timezone.getTzInfo(dateObject, options.timezone); |
---|
698 | } |
---|
699 | if(options._tzInfo){ |
---|
700 | // Roll our date to display the correct time according to the |
---|
701 | // desired offset |
---|
702 | var offset = dateObject.getTimezoneOffset() - options._tzInfo.tzOffset; |
---|
703 | dateObject = new Date(dateObject.getTime() + (offset * 60 * 1000)); |
---|
704 | } |
---|
705 | return oLocaleFmt.call(this, dateObject, options); |
---|
706 | }; |
---|
707 | _ddl._getZone = function(dateObject, getName, options){ |
---|
708 | if(options._tzInfo){ |
---|
709 | return getName ? options._tzInfo.tzAbbr : options._tzInfo.tzOffset; |
---|
710 | } |
---|
711 | return oGetZone.call(this, dateObject, getName, options); |
---|
712 | }; |
---|
713 | |
---|
714 | /*===== |
---|
715 | // Hide these enhancements from the doc parser because they obscure the original definition of _getZone() and |
---|
716 | // format. TODO: change above overrides to around() advice so that original definitions aren't changed. |
---|
717 | _ddl.format = oLocaleFmt; |
---|
718 | _ddl._getZone = oGetZone; |
---|
719 | =====*/ |
---|
720 | }); |
---|