├── 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 | --------------------------------------------------------------------------------