├── .gitignore ├── test.html ├── package.json ├── getTimezoneOffset.js ├── README.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tzdata 3 | test.browser.js 4 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | getTimezoneOffset test 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-timezone-offset", 3 | "version": "1.0.5", 4 | "description": "Tiny library to get timezone offsets for a place and time", 5 | "main": "getTimezoneOffset.js", 6 | "scripts": { 7 | "test:browser": "browserify test.js > test.browser.js", 8 | "test": "node test.js" 9 | }, 10 | "engines": { 11 | "node" : ">=4.0.0" 12 | }, 13 | "author": "mobz", 14 | "license": "CC0-1.0", 15 | "devDependencies": { 16 | "browserify": "^14.3.0", 17 | "tape": "^4.6.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /getTimezoneOffset.js: -------------------------------------------------------------------------------- 1 | var locale = 'en-US'; 2 | var us_re = /(\d+).(\d+).(\d+),?\s+(\d+).(\d+)(.(\d+))?/; 3 | 4 | var format = { 5 | timeZone: "UTC", 6 | hour12: false, 7 | year: 'numeric', 8 | month: 'numeric', 9 | day: 'numeric', 10 | hour: 'numeric', 11 | minute: 'numeric' 12 | }; 13 | 14 | var utc_f = new Intl.DateTimeFormat(locale, format ); 15 | 16 | function parseDate( date_str ) { 17 | date_str = date_str.replace(/[\u200E\u200F]/g, ''); 18 | return [].slice.call(us_re.exec( date_str ), 1) 19 | .map( Math.floor ); 20 | } 21 | 22 | function diffMinutes( d1, d2 ) { 23 | var d1h = d1[3] === 24 ? 0: d1[3]; 24 | var d2h = d2[3] === 24 ? 0: d2[3]; 25 | var day = d1[1] - d2[1]; 26 | var hour = d1h - d2h; 27 | var min = d1[4] - d2[4]; 28 | 29 | if( day > 15 ) day = -1; 30 | if( day < -15 ) day = 1; 31 | 32 | return 60 * ( 24 * day + hour ) + min; 33 | } 34 | 35 | module.exports = function getTimezoneOffset( tz_str, date ) { 36 | 37 | format.timeZone = tz_str; 38 | 39 | var loc_f = new Intl.DateTimeFormat(locale, format ); 40 | 41 | return diffMinutes( 42 | parseDate( utc_f.format( date )), 43 | parseDate( loc_f.format( date )) 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # getTimezoneOffset 2 | 3 | getTimezoneOffset is a tiny library ( less than 1k ) for getting timezone offsets. 4 | 5 | It returns the timezone offset in minutes for any IANA timezone name for any date in the past, present and future. 6 | 7 | It runs in any browsers or on the server, it's very small, very fast and has no dependencies[^1] 8 | 9 | ``` 10 | npm i get-timezone-offset 11 | ``` 12 | 13 | ## Example 14 | 15 | var now = new Date(); 16 | getTimezoneOffset( 'America/New_York', now ); // returns 300 17 | getTimezoneOffset( 'Antarctica/Davis', now ); // returns -420 18 | 19 | ## How it works 20 | 21 | getTimezoneOffset is tiny and fast because it ships _without a database of timezones_, using the tz database of the underlying operating system via the global `Intl` object. 22 | 23 | Internationalization was introduced in 2012; Engine support is tracked [ here ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat#browser_compatibility), but boils down to; 24 | 25 | * Node: v4+ 26 | * Bun, Deno and other javascript runtimes 27 | * All browsers 28 | 29 | ## Tests 30 | 31 | The tests will validate the library functionality _and_ the javascript engine's implementation of `Intl.DateTimeFormat` ( _and_ the operating systems tzinfo database ). 32 | 33 | To test in node 34 | 35 | npm test 36 | 37 | To test in a browser 38 | 39 | npm run test:browser 40 | 41 | Then open `test.html` in a browser and view the console output. 42 | 43 | ## Troubleshooting 44 | 45 | Results for locations that experience daylight saving change throughout the year 46 | 47 | Results for locations that [have moved timezone](https://data.iana.org/time-zones/tzdb/NEWS) may vary as the tz database is modified. This sometimes includes retrospective changes. 48 | 49 | [^1]: If you are running getTimezoneOffset on the server and using alpine or other ultra minimal docker image without a timezone database, you need to install the `tzdata` package. Most docker images, including the ["slim" images publishd by node](https://hub.docker.com/_/node/tags?name=slim) already include `tzdata`. 50 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var getTimezoneOffset = require('./getTimezoneOffset.js'); 3 | 4 | var nv = 1000; 5 | if(process.versions.node) { 6 | const version = process.versions.node.split('.').map(Number); 7 | nv = version[0]; 8 | } 9 | 10 | // some tests excluded based on old versions of node giving inconsistant results 11 | 12 | test('getTimezoneOffset', function(t) { 13 | 14 | var tzinfo = [ 15 | [ "UTC", 1483272000000, 0 ], 16 | 17 | [ 'Pacific/Midway', 1483272000000, 660 ], 18 | [ 'Pacific/Honolulu', 1483272000000, 600 ], 19 | [ 'America/Anchorage', 1483272000000, 540 ], 20 | [ 'America/Los_Angeles', 1483272000000, 480 ], 21 | [ 'America/Denver', 1483272000000, 420 ], 22 | [ 'America/Mexico_City', 1483272000000, 360 ], 23 | [ "America/New_York", 1483272000000, 300 ], 24 | [ 'America/Curacao', 1483272000000, 240 ], 25 | [ 'America/Argentina/Buenos_Aires', 1483272000000, 180 ], 26 | [ 'America/Noronha', 1483272000000, 120 ], 27 | [ 'Atlantic/Azores', 1483272000000, 60 ], 28 | [ 'Africa/Casablanca', 1483272000000, 0 ], 29 | [ 'Africa/Lagos', 1483272000000, -60 ], 30 | [ 'Africa/Johannesburg', 1483272000000, -120 ], 31 | [ 'Europe/Moscow', 1483272000000, -180 ], 32 | [ 'Asia/Dubai', 1483272000000, -240], 33 | [ 'Asia/Ashgabat', 1483272000000, -300], 34 | [ 'Asia/Dhaka', 1483272000000, -360], 35 | [ 'Asia/Bangkok', 1483272000000, -420], 36 | [ 'Asia/Harbin', 1483272000000, -480], 37 | [ 'Asia/Seoul', 1483272000000, -540], 38 | [ 'Australia/Brisbane', 1483272000000, -600], 39 | nv > 4 ? [ 'Pacific/Norfolk', 1483272000000, -660] : null, 40 | [ 'Pacific/Funafuti', 1483272000000, -720], 41 | 42 | [ 'Australia/Melbourne', 1452859200000, -660 ], 43 | [ 'Australia/Melbourne', 1455537600000, -660 ], 44 | [ 'Australia/Melbourne', 1458043200000, -660 ], 45 | [ 'Australia/Melbourne', 1460725200000, -600 ], 46 | [ 'Australia/Melbourne', 1463317200000, -600 ], 47 | [ 'Australia/Melbourne', 1465995600000, -600 ], 48 | [ 'Australia/Melbourne', 1468587600000, -600 ], 49 | [ 'Australia/Melbourne', 1471266000000, -600 ], 50 | [ 'Australia/Melbourne', 1473944400000, -600 ], 51 | [ 'Australia/Melbourne', 1476532800000, -660 ], 52 | [ 'Australia/Melbourne', 1479211200000, -660 ], 53 | [ 'Australia/Melbourne', 1481803200000, -660 ], 54 | 55 | [ "Australia/Eucla", 1483272000000, -525 ], 56 | [ "Antarctica/Davis", -1472640600000, 0 ], 57 | [ "Antarctica/Davis", 1483272000000, -420 ], 58 | [ "Asia/Tehran", -1472640600000, -205 ], 59 | [ "Asia/Tehran", 0, -210 ], 60 | [ "Asia/Tehran", 946684800000, -210 ], 61 | nv > 12 ? [ "Asia/Tehran", 1780725966000, -210 ] : null, 62 | [ "Pacific/Apia", -1472640600000, 690 ], 63 | [ "Pacific/Apia", 946684800000, 660 ], 64 | [ "Pacific/Apia", 1456833600000, -840 ], 65 | [ "Pacific/Apia", 1780725966000, -780 ], 66 | [ "Pacific/Chatham", -1472640600000, -735 ], 67 | [ "Pacific/Chatham", 0, -765 ], 68 | [ "Pacific/Chatham", 1456833600000, -825 ], 69 | [ "Pacific/Chatham", 1780725966000, -765 ], 70 | [ "Africa/Monrovia", -2208988800000, 44 ], 71 | [ "Africa/Monrovia", -63158400000, 45 ], 72 | [ "Africa/Monrovia", 1483272000000, 0 ], 73 | 74 | ]; 75 | 76 | tzinfo.forEach( function( scenario ) { 77 | if(scenario === null) { 78 | return; 79 | } 80 | var tz_name = scenario[0]; 81 | var instant = new Date( scenario[1] ); 82 | var expect_minutes = scenario[2]; 83 | t.equal( getTimezoneOffset( tz_name, instant ), expect_minutes, 84 | tz_name + " at " + instant.toISOString() ); 85 | }); 86 | 87 | t.end(); 88 | }); 89 | --------------------------------------------------------------------------------