timezone-soft 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 spencer kelly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,160 +1,219 @@
1
1
  <div align="center">
2
-
3
- <div>parse abbreviated, sloppy, and informal timezone names</div>
4
- <div><img src="https://cloud.githubusercontent.com/assets/399657/23590290/ede73772-01aa-11e7-8915-181ef21027bc.png" /></div>
5
-
6
- <div align="center">
7
- <a href="https://npmjs.org/package/timezone-soft">
8
- <img src="https://img.shields.io/npm/v/timezone-soft.svg?style=flat-square" />
9
- </a>
10
- <!-- <a href="https://codecov.io/gh/spencermountain/timezone-soft">
11
- <img src="https://codecov.io/gh/spencermountain/timezone-soft/branch/master/graph/badge.svg" />
12
- </a> -->
13
- <a href="https://unpkg.com/timezone-soft/builds/timezone-soft.min.js">
14
- <img src="https://badge-size.herokuapp.com/spencermountain/timezone-soft/master/builds/timezone-soft.min.js" />
15
- </a>
16
- </div>
17
- <div align="center">
18
- <code>npm install timezone-soft</code>
19
- </div>
20
- <sub>
21
- by
22
- <a href="https://spencermountain.github.io/">Spencer Kelly</a>
23
- </sub>
24
- <div align="center">
25
- <sup><i>(formerly called 'spacetime-informal')</i></sup>
26
- </div>
2
+ <img src="https://cloud.githubusercontent.com/assets/399657/23590290/ede73772-01aa-11e7-8915-181ef21027bc.png" />
3
+ <div>informal timezone lookup</div>
4
+ <a href="https://npmjs.org/package/timezone-soft">
5
+ <img src="https://img.shields.io/npm/v/timezone-soft.svg?style=flat-square" />
6
+ </a>
7
+ <a href="https://bundlephobia.com/result?p=timezone-soft@latest">
8
+ <img src="https://badgen.net/bundlejs/min/timezone-soft" />
9
+ </a>
10
+ <div><code>npm install timezone-soft</code></div>
27
11
  </div>
28
- <p></p>
29
12
 
30
13
  <!-- spacer -->
31
- <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
14
+ <img height="50px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
32
15
 
33
16
  ```js
34
- import soft from 'timezone-soft'
35
-
36
- // get an IANA tz from user input
37
- let timezones = soft('milwaukee')[0]
38
- /*[{
39
- iana: 'America/Chicago',
40
- standard: { name: 'Central Standard Time', abbrev: 'CST' },
41
- daylight: { name: 'Central Daylight Time', abbrev: 'CDT' }
42
- }
43
- ]*/
17
+ import tzSoft from 'timezone-soft'
18
+
19
+ const matches = tzSoft('milwaukee')
20
+ matches[0].iana // 'America/Chicago'
44
21
  ```
45
22
 
46
- <!-- spacer -->
47
- <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
23
+ People are not often aware of timezone [IANA IDs](https://www.iana.org/time-zones), and tend to use informal schemes to refer to timezones - things like `'PST'`, `'eastern time'`, `'vancouver bc'`, and `'china'`.
48
24
 
49
- **[IANA timezone codes](https://www.iana.org/time-zones)** are the official reference for timezone information, and is what you should use, whenever possible.
25
+ These names have cultural overlap, and their meaning can depend on the date.
50
26
 
51
- Humans though, _are goofballs_, and use a whole different informal scheme:
27
+ This library applies opinionated heuristics to help turn this user-input into ranked matching IANA candidates.
52
28
 
53
- ---
29
+ Originally built for [spacetime](https://github.com/spencermountain/spacetime),
30
+ and formerly called `timezone-soft-informal`. This is a compressed dictionary of lookup terms for timezone ids, and some basic ranking heuristics when >1 results.
54
31
 
55
- - In (North) America: **PST, MST, EST**...
56
- - in Europe (lately): **WEST, CEST, EEST**...
57
- - in Africa: **EAT, CAT, WAST**...
58
- - in Australia: **AWST, AEDT, ACST**...
32
+ <!-- spacer -->
33
+ <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
59
34
 
60
- ---
35
+ <div align="center">
36
+ <img src="https://cloud.githubusercontent.com/assets/399657/23590290/ede73772-01aa-11e7-8915-181ef21027bc.png" />
37
+ </div>
61
38
 
62
- #### these line-up with the IANA codes sometimes.
39
+ ### Usage
40
+ ```js
41
+ const tzSoft = require('timezone-soft') //commonjs supported
63
42
 
64
- #### ...other times they don't.
43
+ tzSoft('EST')[0].iana // 'America/New_York'
44
+ tzSoft('central')[0].iana // 'America/Chicago'
45
+ tzSoft('venezuela')[0].iana // 'America/Caracas'
46
+ tzSoft('south east asia')[0].iana // 'Asia/Bangkok'
47
+ ```
65
48
 
66
- <!-- spacer -->
67
- <img height="15px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
49
+ `tzSoft(input: string)`
68
50
 
69
- These names also collide -
51
+ This returns an array of matching timezone objects, ordered by preference. An empty or
52
+ unrecognized string returns `[]`
70
53
 
71
- '**_IST_**' is used to mean:
54
+ A match looks like this:
55
+ ```js
56
+ {
57
+ name: 'Central Time',
58
+ iana: 'America/Chicago',
59
+ standard: {
60
+ name: 'Central Standard Time',
61
+ abbr: 'CST',
62
+ offset: -6
63
+ },
64
+ daylight: {
65
+ name: 'Central Daylight Time',
66
+ abbr: 'CDT',
67
+ offset: -5,
68
+ start: '2nd-sun-mar-2h',
69
+ end: '1st-sun-nov-2h'
70
+ },
71
+ long: '(UTC-06:00) Central Time (US & Canada)'
72
+ }
73
+ ```
72
74
 
73
- - '_Indian Stardard Time_'
74
- - '_Irish Stardard Time_'
75
- - '_Israeli Stardard Time_'
75
+ Offsets are hours east of UTC; negative values are west of UTC. `daylight` can be
76
+ `null`.
76
77
 
77
- These names also produce all-sorts of ambiguities, regarding DST-changes-
78
+ `start` and `end` values are descriptive rule strings.
78
79
 
79
- Both Winnipeg and Mexico City are **CST**, but have a much different DST schedule:
80
- ![image](https://user-images.githubusercontent.com/399657/52489224-b34d0e00-2b8f-11e9-9de8-0688bec52464.png)
81
80
 
82
- _(thanks [timeanddate.com](https://www.timeanddate.com)!)_
81
+ ## Ambiguous inputs
83
82
 
84
- -of course, there's a bunch of political/historical/disputed stuff going on, too. Apologies if this library steps into that unknowingly.
83
+ Abbreviations can describe several places. For example:
85
84
 
86
- <img height="15px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
85
+ ```js
86
+ soft('IST').map(zone => zone.iana)
87
+ // ['Asia/Kolkata', 'Asia/Jerusalem', 'Europe/Dublin', 'Asia/Colombo']
88
+ ```
87
89
 
88
- ...so that's what we're trying to fix - to _'soften'_ this exchange, between human and IANA timezone nomenclature, using some _opinionated-but-common-sense_ rules and decision-making.
90
+ Explicit IANA IDs containing `/` are resolved case-insensitively through the pinned
91
+ IANA **2026d** Zone/Link table before informal matching. Unknown IDs are not guessed
92
+ from their city component. A recognized ID without bundled display metadata returns
93
+ `[]`. Curated non-IANA phrases containing `/` can still match registered aliases.
89
94
 
90
- It was originally built for use in the _[spacetime timezone library](https://github.com/spencermountain/spacetime)_.
95
+ All returned IDs use that table's canonical targets. For example, `Europe/Kiev`
96
+ returns `Europe/Kyiv`, `Asia/Kashgar` returns `Asia/Urumqi` (UTC+6, distinct from
97
+ Shanghai's UTC+8), and `America/Yellowknife` returns `America/Edmonton`.
98
+ This policy uses the main IANA files plus `backward`, not the optional `backzone`
99
+ historical split. Ordinary abbreviations such as `EST` remain informal queries.
91
100
 
92
- <!-- spacer -->
93
- <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
101
+ Alias matches are sorted by
102
+ the number of packed aliases associated with each zone, descending. Ties preserve
103
+ insertion order in the source data. Canonicalization then merges duplicate targets
104
+ while preserving their first occurrence. This is a heuristic, not a population ranking
105
+ or a confidence score; adding aliases can change the preferred result.
94
106
 
95
- ### Usage
107
+ Show all candidates when ambiguity matters, or ask for a city or IANA ID. The
108
+ library does not use the user's location to choose a result. Regression fixtures
109
+ cover the ordering of `CST`, `IST`, and `BST`.
110
+
111
+ ## Combined lookup strings
112
+
113
+ When the whole string does not match, commas and parentheses split it into
114
+ additional lookups using the existing aliases:
96
115
 
97
116
  ```js
98
- const soft = require('timezone-soft')
117
+ soft('Springfield, Missouri')[0].iana // 'America/Chicago' (matches Missouri)
118
+ soft('Springfield (Missouri)')[0].iana // 'America/Chicago'
119
+ soft('Toronto, Ontario, Canada')[0].iana // 'America/Toronto'
120
+ soft('CST China')[0].iana // 'Asia/Shanghai'
121
+ ```
99
122
 
100
- soft('EST')
101
- // 'America/New_York'
123
+ Recognized parts are intersected, preserving the first part's result order.
124
+ Unknown comma-separated or parenthesized parts are ignored; conflicting known
125
+ parts return `[]`. Without punctuation, both sides of a word-boundary split must
126
+ match, so `Springfield Missouri` still returns `[]` while `CST China` resolves.
102
127
 
103
- soft('central')
104
- // 'America/Chicago'
128
+ These are alias fallbacks, not geographic validation. No additional city/country
129
+ dataset is stored: the Springfield examples resolve through `Missouri`, not through
130
+ a Springfield city record. Existing whole-string matches take precedence.
105
131
 
106
- soft('venezuela')
107
- // 'America/Caracas'
132
+ ## UTC and GMT offsets
108
133
 
109
- soft('south east asia')
110
- // 'Asia/Bangkok'
111
- ```
134
+ `UTC` (including lowercase or surrounding whitespace) resolves only to `Etc/UTC`,
135
+ with abbreviation `UTC` and name `Coordinated Universal Time`. The aliases `UCT`,
136
+ `universal`, `zulu`, and `coordinated universal time` resolve to the same record.
137
+ `GMT` resolves to `Etc/GMT`. Geographic aliases cannot outrank these inputs.
112
138
 
113
- Typescript/Deno/Webpack:
139
+ Whole-hour offsets from UTC-12 through UTC+14 are supported:
114
140
 
115
141
  ```js
116
- import soft from 'timezone-soft'
142
+ soft('UTC+0')[0].iana // 'Etc/GMT'
143
+ soft('UTC+14')[0].iana // 'Etc/GMT-14'
144
+ soft('-5h')[0].iana // 'Etc/GMT+5'
117
145
  ```
118
146
 
119
- it was built to be as forgiving as possible, and return the most common-sense IANA timezone id from user-input.
120
-
121
- <div align="center">
122
- <img height="50px" src="https://user-images.githubusercontent.com/399657/68221814-05ed1680-ffb8-11e9-8b6b-c7528d163871.png"/>
123
- </div>
147
+ Surrounding whitespace is accepted for offset inputs. `UTC-5` means five hours
148
+ behind UTC. For compatibility, `GMT+5` follows the reversed IANA `Etc/GMT+5`
149
+ convention; its numeric offset and `long` description use the normal UTC sign.
150
+ `Etc/GMT+13` and `Etc/GMT+14` return `[]` because they are not IANA IDs;
151
+ `Etc/GMT-13` and `Etc/GMT-14` remain valid.
124
152
 
125
- ---
153
+ Fractional offset strings such as `UTC+5:30` return `[]`: the IANA fixed-offset
154
+ `Etc/GMT` IDs have whole-hour precision. Use a named zone such as `Asia/Kolkata` or
155
+ `india` instead. See the [IANA definitions](https://data.iana.org/time-zones/tzdb/etcetera).
126
156
 
127
- <!-- spacer -->
128
- <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
157
+ ## Dates and daylight saving time
129
158
 
130
- ### DST
159
+ This package finds timezone names and supplies curated display metadata. Its
160
+ bundled DST rules are approximate, are not versioned by year, and are not suitable
161
+ for calculating historical or future transitions. See the
162
+ [data notes](data/README.md) for the rule syntax and provenance limitations.
131
163
 
132
- Often, the proper timezone name will depend on which date you are referencing.
133
- You can reckon this pretty-easily with [spacetime](https://github.com/spencermountain/spacetime), like this:
164
+ Use a date-aware timezone library to determine the applicable abbreviation at a
165
+ specific instant. For example, with [spacetime](https://github.com/spencermountain/timezone-soft):
134
166
 
135
167
  ```js
136
- const spacetime = require('spacetime')
137
- const soft = require('timezone-soft')
138
-
139
- let display = soft('montreal')[0]
140
- let show = display.standard.abbrev
141
-
142
- // are we in standard time, or daylight time?
143
- let s = spacetime.now(display.iana)
144
- if (display.daylight && s.isDST()) {
145
- show = display.daylight.abbrev
168
+ import spacetime from 'spacetime'
169
+ import tzSoft from 'timezone-soft'
170
+
171
+ const display = tzSoft('montreal')[0]
172
+ if (display) {
173
+ const now = spacetime.now(display.iana)
174
+ const info = now.isDST() && display.daylight ? display.daylight : display.standard
175
+ console.log(now.time() + ' ' + info.abbr)
146
176
  }
147
- console.log(s.time() + ' ' + show)
148
- // '4:20pm EDT'
149
177
  ```
150
178
 
151
- <!-- spacer -->
152
- <img height="25px" src="https://user-images.githubusercontent.com/399657/68221862-17ceb980-ffb8-11e9-87d4-7b30b6488f16.png"/>
179
+ The `standard` and `daylight` fields are conventional display categories, not
180
+ IANA's `tm_isdst` flags. In Dublin, `standard` means winter GMT (UTC+0, Greenwich
181
+ Mean Time), and `daylight` means summer IST (UTC+1, Irish Standard Time). IANA's
182
+ native model treats Irish summer as standard and winter as negative DST; this API
183
+ retains its existing winter/summer arrangement for compatibility. Do not select a
184
+ field using a raw IANA DST flag without reconciling those conventions.
185
+
186
+ The identifier table is versioned independently of display metadata. The returned
187
+ metadata is only as current as this package's curated data. Coverage includes
188
+ `America/Ciudad_Juarez` (Mountain time with US DST rules) and
189
+ `America/Coyhaique` (permanent UTC−3). Runtime coverage checks flag new missing
190
+ records.
191
+
153
192
 
154
- work-in-progress!
193
+ ## TypeScript
194
+
195
+ ```ts
196
+ import tzSoft, { type DisplayFormat } from 'timezone-soft'
197
+
198
+ const matches: DisplayFormat[] = tzSoft('montreal')
199
+ const zone = matches[0]
200
+
201
+ if (zone) {
202
+ console.log(zone.iana) // 'America/Toronto'
203
+ console.log(zone.standard.abbr) // 'EST'
204
+ console.log(zone.daylight?.abbr) // 'EDT'; undefined for zones without DST
205
+ } else {
206
+ console.log('No matching timezone')
207
+ }
208
+ ```
155
209
 
156
210
  ### See also
157
211
 
158
- - [TimeZoneNames](https://github.com/mattjohnsonpint/TimeZoneNames) .NET Standard Library by Matt Johnson-Pint
212
+ - [city-timezones](https://github.com/kevinroberts/city-timezones) — find IANA timezones by city, state, or country.
213
+ - [@vvo/tzdb](https://github.com/vvo/tzdb) — timezone data with friendly names and major cities for timezone selectors.
214
+ - [@coroboros/location-timezone](https://github.com/elysiumphase/node-location-timezone) — timezone lookups by city, country, or capital.
215
+ - [chrono-node](https://github.com/wanasit/chrono) — natural-language date parsing with timezone abbreviation support.
216
+ - [tz-lookup](https://github.com/darkskyapp/tz-lookup-oss) — approximate timezone lookup from latitude and longitude.
217
+ - [TimeZoneNames](https://github.com/mattjohnsonpint/TimeZoneNames) for .NET.
159
218
 
160
- MIT
219
+ MIT, PRs welcome