├── README
└── detect_timezone.js
/README:
--------------------------------------------------------------------------------
1 | Makes a robust determination of a user's timezone through Javascript.
2 |
3 | /*
4 | * Original script by Josh Fraser (http://www.onlineaspect.com)
5 | * Continued by Jon Nylander, (jon at pageloom dot com)
6 | * Refactored to be more "library"-like by Rowan Crawford (wombleton at gmail punctuation com)
7 | * According to both of us, you are absolutely free to do whatever
8 | * you want with this code.
9 | *
10 | * This code is maintained at bitbucket.org as jsTimezoneDetect.
11 | */
12 |
--------------------------------------------------------------------------------
/detect_timezone.js:
--------------------------------------------------------------------------------
1 | /*jslint indent: 2, nomen: false, white: false, plusplus: false, regexp: false */
2 | /*global jQuery: false */
3 |
4 | /*
5 | * Original script by Josh Fraser (http://www.onlineaspect.com)
6 | * Continued by Jon Nylander, (jon at pageloom dot com)
7 | * According to both of us, you are absolutely free to do whatever
8 | * you want with this code.
9 | *
10 | * This code is maintained at bitbucket.org as jsTimezoneDetect.
11 | */
12 | (function($) {var HEMISPHERE_SOUTH = 'SOUTH',
13 | HEMISPHERE_NORTH = 'NORTH',
14 | HEMISPHERE_UNKNOWN = 'N/A',
15 | olson = {};
16 |
17 | /**
18 | * A simple object containing information of utc_offset, which olson timezone key to use,
19 | * and if the timezone cares about daylight savings or not.
20 | *
21 | * @constructor
22 | * @param {string} offset - for example '-11:00'
23 | * @param {string} olson_tz - the olson Identifier, such as "America/Denver"
24 | * @param {boolean} uses_dst - flag for whether the time zone somehow cares about daylight savings.
25 | */
26 | function TimeZone(offset, olson_tz, uses_dst) {
27 | this.utc_offset = offset;
28 | this.olson_tz = olson_tz;
29 | this.uses_dst = uses_dst;
30 | }
31 |
32 | /**
33 | * Prints out the result.
34 | * But before it does that, it calls this.ambiguity_check.
35 | */
36 | TimeZone.prototype.display = function() {
37 | this.ambiguity_check();
38 | var response_text = 'UTC-offset: ' + this.utc_offset + '
';
39 | response_text += 'Olson database name: ' + this.olson_tz + '
';
40 | response_text += 'Daylight Savings: ' + (this.uses_dst ? 'yes' : 'no') + '
';
41 |
42 | return response_text;
43 | };
44 |
45 | /**
46 | * Gets the offset in minutes from UTC for a certain date.
47 | *
48 | * @param date
49 | * @returns {number}
50 | */
51 | function get_date_offset(date) {
52 | return -date.getTimezoneOffset();
53 | }
54 |
55 | function get_january_offset() {
56 | return get_date_offset(new Date(2011, 0, 1, 0, 0, 0, 0));
57 | }
58 |
59 | function get_june_offset() {
60 | return get_date_offset(new Date(2011, 5, 1, 0, 0, 0, 0));
61 | }
62 |
63 | /**
64 | * Checks whether a given date is in daylight savings time.
65 | *
66 | * If the date supplied is after june, we assume that we're checking
67 | * for southern hemisphere DST.
68 | *
69 | * @param {Date} date
70 | * @returns {boolean}
71 | */
72 | function date_is_dst(date) {
73 | var base_offset = ( (date.getMonth() > 5 ? get_june_offset() : get_january_offset()) ),
74 | date_offset = get_date_offset(date);
75 |
76 | return (base_offset - date_offset) !== 0;
77 | }
78 |
79 | /**
80 | * Checks if a timezone has possible ambiguities. I.e timezones that are similar.
81 | *
82 | * If the preliminary scan determines that we're in America/Denver. We double check
83 | * here that we're really there and not in America/Mazatlan.
84 | *
85 | * This is done by checking known dates for when daylight savings start for different
86 | * timezones.
87 | */
88 | TimeZone.prototype.ambiguity_check = function() {
89 | var i,
90 | local_ambiguity_list = olson.ambiguity_list[this.olson_tz],
91 | length,
92 | tz;
93 |
94 | if (typeof(local_ambiguity_list) === 'undefined') {
95 | return;
96 | }
97 |
98 | length = local_ambiguity_list.length;
99 |
100 | for (i = 0; i < length; i++) {
101 | tz = local_ambiguity_list[i];
102 |
103 | if (date_is_dst(olson.dst_start_dates[tz])) {
104 | this.olson_tz = tz;
105 | return;
106 | }
107 | }
108 | };
109 |
110 | /**
111 | * The keys in this dictionary are comma separated as such:
112 | *
113 | * First the offset compared to UTC time in minutes.
114 | *
115 | * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it does.
116 | *
117 | * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere, only interesting for timezones with DST.
118 | *
119 | * The values of the dictionary are TimeZone objects.
120 | */
121 | olson.timezones = {
122 | '-720,0' : new TimeZone('-12:00', 'Etc/GMT+12', false),
123 | '-660,0' : new TimeZone('-11:00', 'Pacific/Pago_Pago', false),
124 | '-600,1' : new TimeZone('-11:00', 'America/Adak', true),
125 | '-660,1,s' : new TimeZone('-11:00', 'Pacific/Apia', true),
126 | '-600,0' : new TimeZone('-10:00', 'Pacific/Honolulu', false),
127 | '-570,0' : new TimeZone('-10:30', 'Pacific/Marquesas', false),
128 | '-540,0' : new TimeZone('-09:00', 'Pacific/Gambier', false),
129 | '-540,1' : new TimeZone('-09:00', 'America/Anchorage', true),
130 | '-480,1' : new TimeZone('-08:00', 'America/Los_Angeles', true),
131 | '-480,0' : new TimeZone('-08:00', 'Pacific/Pitcairn', false),
132 | '-420,0' : new TimeZone('-07:00', 'America/Phoenix', false),
133 | '-420,1' : new TimeZone('-07:00', 'America/Denver', true),
134 | '-360,0' : new TimeZone('-06:00', 'America/Guatemala', false),
135 | '-360,1' : new TimeZone('-06:00', 'America/Chicago', true),
136 | '-360,1,s' : new TimeZone('-06:00', 'Pacific/Easter', true),
137 | '-300,0' : new TimeZone('-05:00', 'America/Bogota', false),
138 | '-300,1' : new TimeZone('-05:00', 'America/New_York', true),
139 | '-270,0' : new TimeZone('-04:30', 'America/Caracas', false),
140 | '-240,1' : new TimeZone('-04:00', 'America/Halifax', true),
141 | '-240,0' : new TimeZone('-04:00', 'America/Santo_Domingo', false),
142 | '-240,1,s' : new TimeZone('-04:00', 'America/Asuncion', true),
143 | '-210,1' : new TimeZone('-03:30', 'America/St_Johns', true),
144 | '-180,1' : new TimeZone('-03:00', 'America/Godthab', true),
145 | '-180,0' : new TimeZone('-03:00', 'America/Argentina/Buenos_Aires,', false),
146 | '-180,1,s' : new TimeZone('-03:00', 'America/Montevideo', true),
147 | '-120,0' : new TimeZone('-02:00', 'America/Noronha', false),
148 | '-120,1' : new TimeZone('-02:00', 'Etc/GMT+2', true),
149 | '-60,1' : new TimeZone('-01:00', 'Atlantic/Azores', true),
150 | '-60,0' : new TimeZone('-01:00', 'Atlantic/Cape_Verde', false),
151 | '0,0' : new TimeZone('00:00', 'Africa/Casablanca', false),
152 | '0,1' : new TimeZone('00:00', 'Europe/London', true),
153 | '60,1' : new TimeZone('+01:00', 'Europe/Berlin', true),
154 | '60,0' : new TimeZone('+01:00', 'Africa/Lagos', false),
155 | '60,1,s' : new TimeZone('+01:00', 'Africa/Windhoek', true),
156 | '120,1' : new TimeZone('+02:00', 'Asia/Beirut', true),
157 | '120,0' : new TimeZone('+02:00', 'Africa/Johannesburg', false),
158 | '180,1' : new TimeZone('+03:00', 'Europe/Moscow', true),
159 | '180,0' : new TimeZone('+03:00', 'Asia/Baghdad', false),
160 | '210,1' : new TimeZone('+03:30', 'Asia/Tehran', true),
161 | '240,0' : new TimeZone('+04:00', 'Asia/Dubai', false),
162 | '240,1' : new TimeZone('+04:00', 'Asia/Yerevan', true),
163 | '270,0' : new TimeZone('+04:30', 'Asia/Kabul', false),
164 | '300,1' : new TimeZone('+05:00', 'Asia/Yekaterinburg', true),
165 | '300,0' : new TimeZone('+05:00', 'Asia/Karachi', false),
166 | '330,0' : new TimeZone('+05:30', 'Asia/Kolkata', false),
167 | '345,0' : new TimeZone('+05:45', 'Asia/Kathmandu', false),
168 | '360,0' : new TimeZone('+06:00', 'Asia/Dhaka', false),
169 | '360,1' : new TimeZone('+06:00', 'Asia/Omsk', true),
170 | '390,0' : new TimeZone('+06:30', 'Asia/Rangoon', false),
171 | '420,1' : new TimeZone('+07:00', 'Asia/Krasnoyarsk', true),
172 | '420,0' : new TimeZone('+07:00', 'Asia/Jakarta', false),
173 | '480,0' : new TimeZone('+08:00', 'Asia/Shanghai', false),
174 | '480,1' : new TimeZone('+08:00', 'Asia/Irkutsk', true),
175 | '525,0' : new TimeZone('+08:45', 'Australia/Eucla', true),
176 | '525,1,s' : new TimeZone('+08:45', 'Australia/Eucla', true),
177 | '540,1' : new TimeZone('+09:00', 'Asia/Yakutsk', true),
178 | '540,0' : new TimeZone('+09:00', 'Asia/Tokyo', false),
179 | '570,0' : new TimeZone('+09:30', 'Australia/Darwin', false),
180 | '570,1,s' : new TimeZone('+09:30', 'Australia/Adelaide', true),
181 | '600,0' : new TimeZone('+10:00', 'Australia/Brisbane', false),
182 | '600,1' : new TimeZone('+10:00', 'Asia/Vladivostok', true),
183 | '600,1,s' : new TimeZone('+10:00', 'Australia/Sydney', true),
184 | '630,1,s' : new TimeZone('+10:30', 'Australia/Lord_Howe', true),
185 | '660,1' : new TimeZone('+11:00', 'Asia/Kamchatka', true),
186 | '660,0' : new TimeZone('+11:00', 'Pacific/Noumea', false),
187 | '690,0' : new TimeZone('+11:30', 'Pacific/Norfolk', false),
188 | '720,1,s' : new TimeZone('+12:00', 'Pacific/Auckland', true),
189 | '720,0' : new TimeZone('+12:00', 'Pacific/Tarawa', false),
190 | '765,1,s' : new TimeZone('+12:45', 'Pacific/Chatham', true),
191 | '780,0' : new TimeZone('+13:00', 'Pacific/Tongatapu', false),
192 | '840,0' : new TimeZone('+14:00', 'Pacific/Kiritimati', false)
193 | };
194 |
195 | /**
196 | * This object contains information on when daylight savings starts for
197 | * different timezones.
198 | *
199 | * The list is short for a reason. Often we do not have to be very specific
200 | * to single out the correct timezone. But when we do, this list comes in
201 | * handy.
202 | *
203 | * Each value is a date denoting when daylight savings starts for that timezone.
204 | */
205 | olson.dst_start_dates = {
206 | 'America/Denver' : new Date(2011, 2, 13, 3, 0, 0, 0),
207 | 'America/Mazatlan' : new Date(2011, 3, 3, 3, 0, 0, 0),
208 | 'America/Chicago' : new Date(2011, 2, 13, 3, 0, 0, 0),
209 | 'America/Mexico_City' : new Date(2011, 3, 3, 3, 0, 0, 0),
210 | 'Atlantic/Stanley' : new Date(2011, 8, 4, 7, 0, 0, 0),
211 | 'America/Asuncion' : new Date(2011, 9, 2, 3, 0, 0, 0),
212 | 'America/Santiago' : new Date(2011, 9, 9, 3, 0, 0, 0),
213 | 'America/Campo_Grande' : new Date(2011, 9, 16, 5, 0, 0, 0),
214 | 'America/Montevideo' : new Date(2011, 9, 2, 3, 0, 0, 0),
215 | 'America/Sao_Paolo' : new Date(2011, 9, 16, 5, 0, 0, 0),
216 | 'America/Los_Angeles' : new Date(2011, 2, 13, 8, 0, 0, 0),
217 | 'America/Santa_Isabel' : new Date(2011, 3, 5, 8, 0, 0, 0),
218 | 'America/Havana' : new Date(2011, 2, 13, 2, 0, 0, 0),
219 | 'America/New_York' : new Date(2011, 2, 13, 7, 0, 0, 0),
220 | 'Asia/Gaza' : new Date(2011, 2, 26, 23, 0, 0, 0),
221 | 'Asia/Beirut' : new Date(2011, 2, 27, 1, 0, 0, 0),
222 | 'Europe/Minsk' : new Date(2011, 2, 27, 3, 0, 0, 0),
223 | 'Europe/Istanbul' : new Date(2011, 2, 27, 7, 0, 0, 0),
224 | 'Asia/Damascus' : new Date(2011, 3, 1, 2, 0, 0, 0),
225 | 'Asia/Jerusalem' : new Date(2011, 3, 1, 6, 0, 0, 0),
226 | 'Africa/Cairo' : new Date(2011, 3, 29, 4, 0, 0, 0),
227 | 'Asia/Yerevan' : new Date(2011, 2, 27, 4, 0, 0, 0),
228 | 'Asia/Baku' : new Date(2011, 2, 27, 8, 0, 0, 0),
229 | 'Pacific/Auckland' : new Date(2011, 8, 26, 7, 0, 0, 0),
230 | 'Pacific/Fiji' : new Date(2010, 11, 29, 23, 0, 0, 0),
231 | 'America/Halifax' : new Date(2011, 2, 13, 6, 0, 0, 0),
232 | 'America/Goose_Bay' : new Date(2011, 2, 13, 2, 1, 0, 0),
233 | 'America/Miquelon' : new Date(2011, 2, 13, 5, 0, 0, 0),
234 | 'America/Godthab' : new Date(2011, 2, 27, 1, 0, 0, 0)
235 | };
236 |
237 | /**
238 | * The keys in this object are timezones that we know may be ambiguous after
239 | * a preliminary scan through the olson_tz object.
240 | *
241 | * The array of timezones to compare must be in the order that daylight savings
242 | * starts for the regions.
243 | */
244 | olson.ambiguity_list = {
245 | 'America/Denver' : ['America/Denver','America/Mazatlan'],
246 | 'America/Chicago' : ['America/Chicago','America/Mexico_City'],
247 | 'America/Asuncion' : ['Atlantic/Stanley', 'America/Asuncion', 'America/Santiago','America/Campo_Grande'],
248 | 'America/Montevideo' : ['America/Montevideo', 'America/Sao_Paolo'],
249 | 'Asia/Beirut' : ['Asia/Gaza','Asia/Beirut', 'Europe/Minsk', 'Europe/Istanbul', 'Asia/Damascus', 'Asia/Jerusalem','Africa/Cairo'],
250 | 'Asia/Yerevan' : ['Asia/Yerevan', 'Asia/Baku'],
251 | 'Pacific/Auckland' : ['Pacific/Auckland', 'Pacific/Fiji'],
252 | 'America/Los_Angeles' : ['America/Los_Angeles', 'America/Santa_Isabel'],
253 | 'America/New_York' : ['America/Havana','America/New_York'],
254 | 'America/Halifax' : ['America/Goose_Bay','America/Halifax'],
255 | 'America/Godthab' : ['America/Miquelon', 'America/Godthab']
256 | };
257 |
258 |
259 | /**
260 | * This function does some basic calculations to create information about
261 | * the user's timezone.
262 | *
263 | * Returns a primitive object on the format
264 | * {'utc_offset' : -9, 'dst': 1, hemisphere' : 'north'}
265 | * where dst is 1 if the region uses daylight savings.
266 | *
267 | * @returns {Object}
268 | */
269 | function get_timezone_info() {
270 | var january_offset = get_january_offset(),
271 | june_offset = get_june_offset(),
272 | diff = january_offset - june_offset;
273 |
274 | if (diff < 0) {
275 | return {'utc_offset' : january_offset,
276 | 'dst': 1,
277 | 'hemisphere' : HEMISPHERE_NORTH};
278 | }
279 | else if (diff > 0) {
280 | return {'utc_offset' : june_offset,
281 | 'dst' : 1,
282 | 'hemisphere' : HEMISPHERE_SOUTH};
283 | }
284 |
285 | return {'utc_offset' : january_offset,
286 | 'dst': 0,
287 | 'hemisphere' : HEMISPHERE_UNKNOWN};
288 | }
289 |
290 | /**
291 | * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary.
292 | *
293 | * Returns a primitive object on the format:
294 | * {'timezone': TimeZone, 'key' : 'the key used to find the TimeZone object'}
295 | *
296 | * @returns Object
297 | */
298 | function determine_timezone() {
299 | var timezone_key_info = get_timezone_info(),
300 | hemisphere_suffix = '',
301 | tz_key;
302 |
303 | if (timezone_key_info.hemisphere === HEMISPHERE_SOUTH) {
304 | hemisphere_suffix = ',s';
305 | }
306 |
307 | tz_key = timezone_key_info.utc_offset + ',' + timezone_key_info.dst + hemisphere_suffix;
308 |
309 | return {
310 | 'timezone' : olson.timezones[tz_key],
311 | 'key' : tz_key,
312 | name: olson.timezones[tz_key].olson_tz
313 | };
314 | }
315 |
316 | if ($ && $.extend) {
317 | $.extend({
318 | timezone: determine_timezone
319 | });
320 | } else {
321 | jsDetectTimezone = {
322 | timezone: determine_timezone
323 | };
324 | }
325 | }(jQuery));
326 |
--------------------------------------------------------------------------------