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