Compare commits

...
10 Commits
Author SHA1 Message Date
dualfroz 87b3e97a0e Describe this repository 2026-10-22 21:07:17 +02:00
dualfroz e678c52b19 test: move the tzdb version test into tests/ so npm test runs it 2026-10-15 18:18:17 +02:00
upstream caad5a27d6 Upstream spencermountain/spacetime state of the touched files before 49cfa3d312 2026-10-15 18:18:17 +02:00
dualfroz 297f022747 test: move the tzdb version test into tests/ so npm test runs it 2026-10-16 09:04:30 +02:00
dualfroz 27503a50da fix(timezone): use iana spelling for legacy zone names
titleCase() wrote 45 bundled names differently from tzdb, mostly legacy
links like US/Eastern (Us/Eastern), Australia/NSW, Etc/UCT, EST5EDT and
America/Knox_IN. Look up the segments it can't derive in a small table;
the new test checks every bundled zone that tzdb 2026c knows.
2026-10-15 18:18:13 +02:00
dualfroz 1668c0dd6b feat: expose the tzdb version of the bundled zonefile 2026-10-03 15:30:58 +02:00
upstream d47824c8f3 Upstream spencermountain/spacetime state of the touched files before fe90cae667 2026-10-02 16:02:11 +02:00
dualfroz 081c965030 fix(whereIts): return titlecased timezone names 2026-10-15 18:14:21 +02:00
upstream fb5ecec15c Upstream spencermountain/spacetime state of the touched files before b6fbec9c97 2026-10-02 15:56:00 +02:00
dualfroz d48b259893 fix(set): clamp hour() to 23 instead of the invalid 24
hour(n) with n >= 24 clamped n to 24, which walkTo() rejects as an invalid
hour (valid range 0-23), nulling the epoch and invalidating the object.
Clamp to 23 to match the other overflow-clamping setters (date, month).
2026-09-04 09:43:57 +02:00
16 changed files with 2833 additions and 1 deletions

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
# Fix hour() setter clamping to an invalid hour (24)
## Problem
Calling `.hour(n)` with `n >= 24` produces an invalid spacetime whose epoch is
`null`, instead of clamping to a valid hour:
```js
const s = spacetime('2021-03-14 10:00:00', 'UTC')
s.hour(24).isValid() // false
s.hour(24).epoch // null
s.hour(25).format('iso') // '' (empty - object is invalid)
```
This is inconsistent with every other overflow-clamping setter:
- `.date(40)` clamps to the last day of the month,
- `.month(13)` clamps to December (11),
- `.hour(-1)` clamps to 0.
Only the upper bound of `.hour()` is broken.
## Root cause
`src/methods/set/set.js`, in `hours()`:
```js
if (n >= 24) {
n = 24
} else if (n < 0) {
n = 0
}
```
Valid hours are `0-23`. Clamping to `24` yields a value that
`src/methods/set/walk.js` rejects (`hour.valid: (n) => n >= 0 && n < 24`), and
on an invalid unit `walkTo()` sets `s.epoch = null`, invalidating the whole
object. The negative branch is already correct (clamps to the valid `0`); the
`>= 24` branch is off by one and should clamp to `23`.
## Fix
```js
if (n >= 24) {
n = 23
} else if (n < 0) {
n = 0
}
```
`hour(24)`, `hour(25)`, `hour(100)` now all clamp to `23:00` on the same day,
matching the behavior of the other setters.
## Test
Added a test to `test/set.test.js` (next to the existing month/date clamp test)
asserting that out-of-range hours clamp to `23`, keep the object valid, keep the
same day, and that negatives still clamp to `0`.
Counterfactual (revert `n = 23` back to `n = 24`):
```
not ok - hour(24) clamps to 23
operator: equal
expected: 23
actual: 0
```
(the invalid object reports hour `0`).
## Gate
- `npm test` (tape) run against `src` (the default dev target of
`test/lib/index.js`): the new test passes; the full suite shows only 18
pre-existing DST/timezone failures (`europe/london`, `atlantic/madeira`,
`atlantic/azores`, some date-dependent "dst-now") that are present verbatim
without this change and are unrelated to it.
- `npm run lint` (eslint ./src): exit 0.
+12
View File
@@ -0,0 +1,12 @@
# Contributions to spencermountain/spacetime
A lightweight javascript timezone library
This repository holds only my commits to the upstream project, with their original author, date and diff.
Each of my commits is preceded by a snapshot of the upstream files it touched, so every diff shows exactly what I changed.
## Merged pull requests
- [Return titlecased timezone names from whereIts](https://github.com/spencermountain/spacetime/pull/474) (2026-10-15)
- [Expose the tzdb version as spacetime.tzdbVersion](https://github.com/spencermountain/spacetime/pull/473) (2026-10-15)
- [Fix hour() setter clamping to an invalid hour (24)](https://github.com/spencermountain/spacetime/pull/467) (2026-09-08)
+212
View File
@@ -0,0 +1,212 @@
// CLI orchestration: read zoneinfo, normalize records, report differences, then write.
import { execFileSync } from 'node:child_process'
import {
existsSync,
readFileSync,
readdirSync,
renameSync,
statSync,
unlinkSync,
writeFileSync
} from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import data from '../../zonefile/iana.js'
import aliases from '../../zonefile/aliases.js'
import { parseIntervals, normalizeZone } from './lib.js'
import { printHeader, printReport, printResult, printError, printHelp } from './report.js'
const root = fileURLToPath(new URL('../../', import.meta.url))
// Directory-only legacy keys may be retained with --allow-unsupported.
// Missing or malformed files remain fatal because their timezone state is unknown.
export class UnsupportedZoneError extends Error {}
export const parseArgs = (args) => {
const options = {
year: new Date().getUTCFullYear(),
zoneinfoDir: '/usr/share/zoneinfo',
check: false,
verbose: false,
allowUnsupported: false
}
for (let i = 0; i < args.length; i += 1) {
const arg = args[i]
if (arg === '--check') {
options.check = true
} else if (arg === '--verbose') {
options.verbose = true
} else if (arg === '--allow-unsupported') {
options.allowUnsupported = true
} else if (arg === '--help') {
options.help = true
} else if (['--year', '--zoneinfo-dir', '--output'].includes(arg)) {
const value = args[++i]
if (!value || value.startsWith('--')) {
throw new Error('Missing value for ' + arg)
}
if (arg === '--year') {
if (!/^\d{4}$/.test(value) || Number(value) < 1900 || Number(value) > 9998) {
throw new Error('Year must be 1900–9998')
}
options.year = Number(value)
} else {
options[arg === '--output' ? 'output' : 'zoneinfoDir'] = resolve(value)
}
} else {
throw new Error('Unknown option: ' + arg)
}
}
options.output ||= join(root, `zonefile.${options.year}.js`)
return options
}
// Library keys are lowercase; zoneinfo paths are case-sensitive on many systems.
// Apply known aliases, then resolve every component using its actual spelling.
export const resolveZone = (directory, name) => {
name = aliases[name] || name
let path = directory
for (const component of name.split('/')) {
if (!component || component === '.' || component === '..') {
throw new Error('Invalid zone name')
}
const matches = readdirSync(path).filter(
(entry) => entry.toLowerCase() === component.toLowerCase()
)
if (matches.length !== 1) {
throw new Error('Missing or ambiguous zone: ' + name)
}
path = join(path, matches[0])
}
const stats = statSync(path)
// zdump can silently report UTC for a directory. Reject it before invocation.
if (stats.isDirectory()) {
throw Object.assign(new UnsupportedZoneError('Zone name resolves to a directory, not a timezone: ' + name), {
details: [
`Resolved path: ${path}`,
`Available entries: ${readdirSync(path).sort().join(', ')}`,
'Choose a specific timezone or define an explicit library alias; a directory has no offset or transitions.'
]
})
}
if (!stats.isFile() || readFileSync(path).subarray(0, 4).toString() !== 'TZif') {
throw new Error('Not a compiled TZif file: ' + name)
}
return path
}
// macOS provides +VERSION; common Linux distributions embed it in tzdata.zi.
// File modification times are not reliable tzdata release identifiers.
const sourceVersion = (directory) => {
for (const name of ['+VERSION', 'tzdata.zi']) {
const path = join(directory, name)
if (existsSync(path)) {
const text = readFileSync(path, 'utf8')
const version = name === '+VERSION' ? text.trim() : /^# version (\S+)/m.exec(text)?.[1]
if (version) {
return version
}
}
}
return 'unknown'
}
// Inject the reader so callers can supply fixtures without invoking zdump.
// Build a fresh result and retain diagnostic context without mutating source data.
export const updateZones = (zones, year, readZone) => {
const result = {}
const changes = []
const unsupported = []
for (const [name, previous] of Object.entries(zones)) {
// Acquisition and parsing errors must fail, even with --allow-unsupported.
let intervals
try {
intervals = parseIntervals(readZone(name))
} catch (error) {
if (error instanceof UnsupportedZoneError) {
unsupported.push({ name, reason: error.message, previous, details: error.details })
result[name] = { ...previous }
continue
}
throw new Error(`${name}: ${error.message}`)
}
try {
result[name] = normalizeZone(intervals, previous, name, year)
} catch (error) {
unsupported.push({ name, reason: error.message, previous, intervals, details: error.details })
result[name] = { ...previous }
continue
}
if (JSON.stringify(previous) !== JSON.stringify(result[name])) {
changes.push({ name, before: previous, after: result[name] })
}
}
return { result, changes, unsupported }
}
export const main = (args = process.argv.slice(2)) => {
const options = parseArgs(args)
if (options.help) {
printHelp()
return
}
const { year, zoneinfoDir } = options
const version = sourceVersion(zoneinfoDir)
const total = Object.keys(data).length
printHeader({ ...options, version, total })
const { result, changes, unsupported } = updateZones(data, year, (name) => {
const path = resolveZone(zoneinfoDir, name)
// Bound extraction to one UTC year; argument arrays avoid shell interpretation.
// Interval output includes an initial offset even for zones with no transitions.
return execFileSync('zdump', ['-i', '-c', `${year},${year + 1}`, path], {
encoding: 'utf8',
timeout: 10000,
maxBuffer: 1024 * 1024,
env: { ...process.env, LC_ALL: 'C' },
stdio: ['ignore', 'pipe', 'pipe']
})
})
printReport({ changes, unsupported, total, verbose: options.verbose })
// Check mode and the unsupported-zone gate must run before any file write.
if (options.check) {
if (changes.length > 0 || unsupported.length > 0) {
process.exitCode = 1
}
printResult(
changes.length > 0 || unsupported.length > 0 ? 'Check needs attention. No files written.' : 'Check passed. No files written.',
Boolean(changes.length > 0 || unsupported.length > 0)
)
return
}
if (unsupported.length > 0 && !options.allowUnsupported) {
throw new Error(
'No output written. Resolve unsupported zones or explicitly preserve them with --allow-unsupported.'
)
}
const source = JSON.stringify({
year,
tzdata: version,
unsupported: unsupported.map((o) => o.name)
})
const text = `// Generated by scripts/tz/update.js: ${source}\nexport const tzdbVersion = ${JSON.stringify(version)}\nexport default ${JSON.stringify(result, null, 2)}\n`
// Rename within the destination directory so readers never see a partial file.
// Only remove a temporary file created by this run (exclusive creation may fail).
const temporary = join(dirname(options.output), `.zonefile-${process.pid}.tmp`)
let created = false
try {
writeFileSync(temporary, text, { encoding: 'utf8', flag: 'wx' })
created = true
renameSync(temporary, options.output)
} finally {
if (created && existsSync(temporary)) {
unlinkSync(temporary)
}
}
printResult(`Wrote ${options.output}`)
}
try {
main()
} catch (error) {
printError(error.message)
process.exitCode = 1
}
+70
View File
@@ -0,0 +1,70 @@
import Spacetime from './spacetime.js'
import whereIts from './whereIts.js'
import version from './_version.js'
import { tzdbVersion } from '../zonefile/_build.js'
const main = (input, tz, options) => new Spacetime(input, tz, options)
// set all properties of a given 'today' object
const setToday = function (s) {
const today = s._today || {}
Object.keys(today).forEach((k) => {
s = s[k](today[k])
})
return s
}
//some helper functions on the main method
main.now = (tz, options) => {
let s = new Spacetime(new Date().getTime(), tz, options)
s = setToday(s)
return s
}
main.today = (tz, options) => {
let s = new Spacetime(new Date().getTime(), tz, options)
s = setToday(s)
return s.startOf('day')
}
main.tomorrow = (tz, options) => {
let s = new Spacetime(new Date().getTime(), tz, options)
s = setToday(s)
return s.add(1, 'day').startOf('day')
}
main.yesterday = (tz, options) => {
let s = new Spacetime(new Date().getTime(), tz, options)
s = setToday(s)
return s.subtract(1, 'day').startOf('day')
}
main.fromUnixSeconds = (secs, tz, options) => {
return new Spacetime(secs * 1000, tz, options)
}
main.extend = function (obj = {}) {
Object.keys(obj).forEach((k) => {
Spacetime.prototype[k] = obj[k]
})
return this
}
main.timezones = function () {
const s = new Spacetime()
return s.timezones
}
main.max = function (tz, options) {
const s = new Spacetime(null, tz, options)
s.epoch = 8640000000000000
return s
}
main.min = function (tz, options) {
const s = new Spacetime(null, tz, options)
s.epoch = -8640000000000000
return s
}
//find tz by time
main.whereIts = whereIts
main.version = version
main.tzdbVersion = tzdbVersion
//aliases:
main.plugin = main.extend
export default main
+3 -1
View File
@@ -67,8 +67,10 @@ const minutes = function (s, n, goFwd) {
const hours = function (s, n, goFwd) { const hours = function (s, n, goFwd) {
n = validate(n) n = validate(n)
// clamp to a valid hour (0-23). Clamping to 24 produces an hour that
// walkTo() rejects, which nulls the epoch and invalidates the object.
if (n >= 24) { if (n >= 24) {
n = 24 n = 23
} else if (n < 0) { } else if (n < 0) {
n = 0 n = 0
} }
+79
View File
@@ -0,0 +1,79 @@
import findTz from './find.js'
import inSummerTime from './summerTime.js'
import getDstShift from './dstShift.js'
import titleCase from './titleCase.js'
const parseDst = dst => {
if (!dst) {
return []
}
return dst.split('->')
}
//get metadata about this timezone
const timezone = s => {
const zones = s.timezones
let tz = s.tz
if (zones.hasOwnProperty(tz) === false) {
tz = findTz(s.tz, zones)
}
if (tz === null) {
if (s.silent === false) {
console.warn("Warn: could not find given or local timezone - '" + s.tz + "'") // eslint-disable-line no-console
}
return {
current: {
epochShift: 0
}
}
}
const found = zones[tz]
const result = {
name: titleCase(tz),
hasDst: Boolean(found.dst),
default_offset: found.offset,
//do north-hemisphere version as default (sorry!)
hemisphere: found.hem === 's' ? 'South' : 'North',
current: {}
}
if (result.hasDst) {
const arr = parseDst(found.dst)
result.change = {
start: arr[0],
back: arr[1]
}
}
//find the offsets for summer/winter times
//(these variable names are north-centric)
const summer = found.offset // (july)
let winter = summer // (january) assume it's the same for now
//most zones shift by 1hr on dst, but Lord Howe is a ½-hour shift
//and Troll (Antarctica) makes a 2-hour jump between +00 and +02
const dstShift = getDstShift(tz)
if (result.hasDst === true) {
if (result.hemisphere === 'North') {
winter = summer - dstShift
} else {
//southern hemisphere
winter = found.offset + dstShift
}
}
//find out which offset to use right now
//use 'summer' time july-time
if (result.hasDst === false) {
result.current.offset = summer
result.current.isDST = false
} else if (inSummerTime(s.epoch, result.change.start, result.change.back, summer, winter) === true) {
result.current.offset = summer
result.current.isDST = result.hemisphere === 'North' //dst 'on' in winter in north
} else {
//use 'winter' january-time
result.current.offset = winter
result.current.isDST = result.hemisphere === 'South' //dst 'on' in summer in south
}
return result
}
export default timezone
+24
View File
@@ -0,0 +1,24 @@
//iana spellings that title-casing alone gets wrong, mostly legacy names
const spellings = [
'ACT', 'CET', 'EET', 'EST', 'GB', 'HST', 'LHI', 'MET', 'MST', 'NSW', 'NZ', 'PRC', 'ROC', 'ROK',
'UCT', 'US', 'WET', 'CST6CDT', 'EST5EDT', 'MST7MDT', 'PST8PDT', 'GMT0', 'GMT+0', 'GMT-0',
'GB-Eire', 'NZ-CHAT', 'W-SU', 'BajaNorte', 'BajaSur', 'ComodRivadavia', 'DeNoronha',
'DumontDUrville', 'EasterIsland', 'Knox_IN', 'McMurdo', 'Port-au-Prince'
]
const special = new Map(spellings.map(w => [w.toLowerCase(), w]))
//iana codes are case-sensitive, technically
const titleCase = str => {
str = str[0].toUpperCase() + str.substr(1)
str = str.replace(/[/_-]([a-z])/gi, s => {
return s.toUpperCase()
})
str = str.replace(/_(of|es)_/i, (s) => s.toLowerCase())
str = str.replace(/\/gmt/i, '/GMT')
str = str.replace(/\/utc/i, '/UTC')
if (str === 'Utc') {
str = 'UTC'
}
return str.split('/').map(part => special.get(part.toLowerCase()) || part).join('/')
}
export default titleCase
+39
View File
@@ -0,0 +1,39 @@
import Spacetime from './spacetime.js'
import titleCase from './timezone/titleCase.js'
// const timezones = require('../data');
const whereIts = (a, b) => {
let start = new Spacetime(null)
let end = new Spacetime(null)
start = start.time(a)
//if b is undefined, use as 'within one hour'
if (b) {
end = end.time(b)
} else {
end = start.add(59, 'minutes')
}
const startHour = start.hour()
const endHour = end.hour()
const tzs = Object.keys(start.timezones).filter((tz) => {
if (tz.indexOf('/') === -1) {
return false
}
const m = new Spacetime(null, tz)
const hour = m.hour()
//do 'calendar-compare' not real-time-compare
if (hour >= startHour && hour <= endHour) {
//test minutes too, if applicable
if (hour === startHour && m.minute() < start.minute()) {
return false
}
if (hour === endHour && m.minute() > end.minute()) {
return false
}
return true
}
return false
})
return tzs.map(titleCase)
}
export default whereIts
+14
View File
@@ -238,3 +238,17 @@ test('month()/date() setters clamp the day to the month length', (t) => {
t.equal(iso(spacetime('2021-02-10').date(31)), '2021-02-28', 'date(31) in a common february clamps to the 28th') t.equal(iso(spacetime('2021-02-10').date(31)), '2021-02-28', 'date(31) in a common february clamps to the 28th')
t.end() t.end()
}) })
test('hour() setter clamps out-of-range hours to a valid hour', (t) => {
const s = spacetime('2021-03-14 10:00:00', 'UTC')
// an out-of-range hour must clamp to the last valid hour (23), not produce
// an invalid spacetime with a null epoch
t.equal(s.hour(24).hour(), 23, 'hour(24) clamps to 23')
t.equal(s.hour(25).hour(), 23, 'hour(25) clamps to 23')
t.equal(s.hour(100).hour(), 23, 'hour(100) clamps to 23')
t.equal(s.hour(24).isValid(), true, 'hour(24) stays valid')
t.equal(s.hour(24).format('iso-short'), '2021-03-14', 'hour(24) keeps the same day')
// negative hours still clamp to 0
t.equal(s.hour(-1).hour(), 0, 'hour(-1) clamps to 0')
t.end()
})
+124
View File
@@ -0,0 +1,124 @@
import test from 'tape'
import spacetime from '../_lib/index.js'
// spelling of every bundled zone that tzdb 2026c knows, 'backward' links included
const iana = `
Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers Africa/Asmara Africa/Asmera
Africa/Bamako Africa/Bangui Africa/Banjul Africa/Bissau Africa/Blantyre Africa/Brazzaville
Africa/Bujumbura Africa/Cairo Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar
Africa/Dar_es_Salaam Africa/Djibouti Africa/Douala Africa/El_Aaiun Africa/Freetown Africa/Gaborone
Africa/Harare Africa/Johannesburg Africa/Juba Africa/Kampala Africa/Khartoum Africa/Kigali
Africa/Kinshasa Africa/Lagos Africa/Libreville Africa/Lome Africa/Luanda Africa/Lubumbashi
Africa/Lusaka Africa/Malabo Africa/Maputo Africa/Maseru Africa/Mbabane Africa/Mogadishu
Africa/Monrovia Africa/Nairobi Africa/Ndjamena Africa/Niamey Africa/Nouakchott Africa/Ouagadougou
Africa/Porto-Novo Africa/Sao_Tome Africa/Timbuktu Africa/Tripoli Africa/Tunis Africa/Windhoek
America/Adak America/Anchorage America/Anguilla America/Antigua America/Araguaina
America/Argentina/Buenos_Aires America/Argentina/Catamarca America/Argentina/ComodRivadavia
America/Argentina/Cordoba America/Argentina/Jujuy America/Argentina/La_Rioja
America/Argentina/Mendoza America/Argentina/Rio_Gallegos America/Argentina/Salta
America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman
America/Argentina/Ushuaia America/Aruba America/Asuncion America/Atikokan America/Atka America/Bahia
America/Bahia_Banderas America/Barbados America/Belem America/Belize America/Blanc-Sablon
America/Boa_Vista America/Bogota America/Boise America/Buenos_Aires America/Cambridge_Bay
America/Campo_Grande America/Cancun America/Caracas America/Catamarca America/Cayenne America/Cayman
America/Chicago America/Chihuahua America/Ciudad_Juarez America/Coral_Harbour America/Cordoba
America/Costa_Rica America/Coyhaique America/Creston America/Cuiaba America/Curacao
America/Danmarkshavn America/Dawson America/Dawson_Creek America/Denver America/Detroit
America/Dominica America/Edmonton America/Eirunepe America/El_Salvador America/Ensenada
America/Fort_Nelson America/Fort_Wayne America/Fortaleza America/Glace_Bay America/Godthab
America/Goose_Bay America/Grand_Turk America/Grenada America/Guadeloupe America/Guatemala
America/Guayaquil America/Guyana America/Halifax America/Havana America/Hermosillo
America/Indiana/Indianapolis America/Indiana/Knox America/Indiana/Marengo America/Indiana/Petersburg
America/Indiana/Tell_City America/Indiana/Vevay America/Indiana/Vincennes America/Indiana/Winamac
America/Indianapolis America/Inuvik America/Iqaluit America/Jamaica America/Jujuy America/Juneau
America/Kentucky/Louisville America/Kentucky/Monticello America/Knox_IN America/Kralendijk
America/La_Paz America/Lima America/Los_Angeles America/Louisville America/Lower_Princes
America/Maceio America/Managua America/Manaus America/Marigot America/Martinique America/Matamoros
America/Mazatlan America/Mendoza America/Menominee America/Merida America/Metlakatla
America/Mexico_City America/Miquelon America/Moncton America/Monterrey America/Montevideo
America/Montreal America/Montserrat America/Nassau America/New_York America/Nipigon America/Nome
America/Noronha America/North_Dakota/Beulah America/North_Dakota/Center
America/North_Dakota/New_Salem America/Nuuk America/Ojinaga America/Panama America/Pangnirtung
America/Paramaribo America/Phoenix America/Port_of_Spain America/Port-au-Prince America/Porto_Acre
America/Porto_Velho America/Puerto_Rico America/Punta_Arenas America/Rainy_River
America/Rankin_Inlet America/Recife America/Regina America/Resolute America/Rio_Branco
America/Rosario America/Santa_Isabel America/Santarem America/Santiago America/Santo_Domingo
America/Sao_Paulo America/Scoresbysund America/Shiprock America/Sitka America/St_Barthelemy
America/St_Johns America/St_Kitts America/St_Lucia America/St_Thomas America/St_Vincent
America/Swift_Current America/Tegucigalpa America/Thule America/Thunder_Bay America/Tijuana
America/Toronto America/Tortola America/Vancouver America/Virgin America/Whitehorse America/Winnipeg
America/Yakutat America/Yellowknife
Antarctica/Casey Antarctica/Davis Antarctica/DumontDUrville Antarctica/Macquarie Antarctica/Mawson
Antarctica/McMurdo Antarctica/Palmer Antarctica/Rothera Antarctica/South_Pole Antarctica/Syowa
Antarctica/Troll Antarctica/Vostok
Arctic/Longyearbyen
Asia/Aden Asia/Almaty Asia/Amman Asia/Anadyr Asia/Aqtau Asia/Aqtobe Asia/Ashgabat Asia/Ashkhabad
Asia/Atyrau Asia/Baghdad Asia/Bahrain Asia/Baku Asia/Bangkok Asia/Barnaul Asia/Beirut Asia/Bishkek
Asia/Brunei Asia/Calcutta Asia/Chita Asia/Choibalsan Asia/Chongqing Asia/Chungking Asia/Colombo
Asia/Dacca Asia/Damascus Asia/Dhaka Asia/Dili Asia/Dubai Asia/Dushanbe Asia/Famagusta Asia/Gaza
Asia/Harbin Asia/Hebron Asia/Ho_Chi_Minh Asia/Hong_Kong Asia/Hovd Asia/Irkutsk Asia/Istanbul
Asia/Jakarta Asia/Jayapura Asia/Jerusalem Asia/Kabul Asia/Kamchatka Asia/Karachi Asia/Kashgar
Asia/Kathmandu Asia/Katmandu Asia/Khandyga Asia/Kolkata Asia/Krasnoyarsk Asia/Kuala_Lumpur
Asia/Kuching Asia/Kuwait Asia/Macao Asia/Macau Asia/Magadan Asia/Makassar Asia/Manila Asia/Muscat
Asia/Nicosia Asia/Novokuznetsk Asia/Novosibirsk Asia/Omsk Asia/Oral Asia/Phnom_Penh Asia/Pontianak
Asia/Pyongyang Asia/Qatar Asia/Qostanay Asia/Qyzylorda Asia/Rangoon Asia/Riyadh Asia/Saigon
Asia/Sakhalin Asia/Samarkand Asia/Seoul Asia/Shanghai Asia/Singapore Asia/Srednekolymsk Asia/Taipei
Asia/Tashkent Asia/Tbilisi Asia/Tehran Asia/Tel_Aviv Asia/Thimbu Asia/Thimphu Asia/Tokyo Asia/Tomsk
Asia/Ujung_Pandang Asia/Ulaanbaatar Asia/Ulan_Bator Asia/Urumqi Asia/Ust-Nera Asia/Vientiane
Asia/Vladivostok Asia/Yakutsk Asia/Yangon Asia/Yekaterinburg Asia/Yerevan
Atlantic/Azores Atlantic/Bermuda Atlantic/Canary Atlantic/Cape_Verde Atlantic/Faeroe Atlantic/Faroe
Atlantic/Jan_Mayen Atlantic/Madeira Atlantic/Reykjavik Atlantic/South_Georgia Atlantic/St_Helena
Atlantic/Stanley
Australia/ACT Australia/Adelaide Australia/Brisbane Australia/Broken_Hill Australia/Canberra
Australia/Currie Australia/Darwin Australia/Eucla Australia/Hobart Australia/LHI Australia/Lindeman
Australia/Lord_Howe Australia/Melbourne Australia/North Australia/NSW Australia/Perth
Australia/Queensland Australia/South Australia/Sydney Australia/Tasmania Australia/Victoria
Australia/West Australia/Yancowinna
Brazil/Acre Brazil/DeNoronha Brazil/East Brazil/West
Canada/Atlantic Canada/Central Canada/Eastern Canada/Mountain Canada/Newfoundland Canada/Pacific
Canada/Saskatchewan Canada/Yukon
CET CST6CDT Cuba EET Egypt Eire EST EST5EDT Factory GB GB-Eire GMT-0 GMT+0 GMT0 Greenwich Hongkong
HST Iceland Iran Israel Jamaica Japan Kwajalein Libya MET MST MST7MDT Navajo NZ NZ-CHAT Poland
Portugal PRC PST8PDT ROC ROK Singapore Turkey UCT Universal UTC W-SU WET Zulu
Chile/Continental Chile/EasterIsland
Etc/GMT Etc/GMT-1 Etc/GMT-10 Etc/GMT-11 Etc/GMT-12 Etc/GMT-13 Etc/GMT-14 Etc/GMT-2 Etc/GMT-3
Etc/GMT-4 Etc/GMT-5 Etc/GMT-6 Etc/GMT-7 Etc/GMT-8 Etc/GMT-9 Etc/GMT+1 Etc/GMT+10 Etc/GMT+11
Etc/GMT+12 Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 Etc/GMT+5 Etc/GMT+6 Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 Etc/GMT0
Etc/Greenwich Etc/UCT Etc/Universal Etc/UTC Etc/Zulu
Europe/Amsterdam Europe/Andorra Europe/Astrakhan Europe/Athens Europe/Belfast Europe/Belgrade
Europe/Berlin Europe/Bratislava Europe/Brussels Europe/Bucharest Europe/Budapest Europe/Busingen
Europe/Chisinau Europe/Copenhagen Europe/Dublin Europe/Gibraltar Europe/Guernsey Europe/Helsinki
Europe/Isle_of_Man Europe/Istanbul Europe/Jersey Europe/Kaliningrad Europe/Kiev Europe/Kirov
Europe/Kyiv Europe/Lisbon Europe/Ljubljana Europe/London Europe/Luxembourg Europe/Madrid
Europe/Malta Europe/Mariehamn Europe/Minsk Europe/Monaco Europe/Moscow Europe/Nicosia Europe/Oslo
Europe/Paris Europe/Podgorica Europe/Prague Europe/Riga Europe/Rome Europe/Samara Europe/San_Marino
Europe/Sarajevo Europe/Saratov Europe/Simferopol Europe/Skopje Europe/Sofia Europe/Stockholm
Europe/Tallinn Europe/Tirane Europe/Tiraspol Europe/Ulyanovsk Europe/Uzhgorod Europe/Vaduz
Europe/Vatican Europe/Vienna Europe/Vilnius Europe/Volgograd Europe/Warsaw Europe/Zagreb
Europe/Zaporozhye Europe/Zurich
Indian/Antananarivo Indian/Chagos Indian/Christmas Indian/Cocos Indian/Comoro Indian/Kerguelen
Indian/Mahe Indian/Maldives Indian/Mauritius Indian/Mayotte Indian/Reunion
Mexico/BajaNorte Mexico/BajaSur Mexico/General
Pacific/Apia Pacific/Auckland Pacific/Bougainville Pacific/Chatham Pacific/Chuuk Pacific/Easter
Pacific/Efate Pacific/Enderbury Pacific/Fakaofo Pacific/Fiji Pacific/Funafuti Pacific/Galapagos
Pacific/Gambier Pacific/Guadalcanal Pacific/Guam Pacific/Honolulu Pacific/Johnston Pacific/Kanton
Pacific/Kiritimati Pacific/Kosrae Pacific/Kwajalein Pacific/Majuro Pacific/Marquesas Pacific/Midway
Pacific/Nauru Pacific/Niue Pacific/Norfolk Pacific/Noumea Pacific/Pago_Pago Pacific/Palau
Pacific/Pitcairn Pacific/Pohnpei Pacific/Ponape Pacific/Port_Moresby Pacific/Rarotonga
Pacific/Saipan Pacific/Samoa Pacific/Tahiti Pacific/Tarawa Pacific/Tongatapu Pacific/Truk
Pacific/Wake Pacific/Wallis Pacific/Yap
US/Alaska US/Aleutian US/Arizona US/Central US/East-Indiana US/Eastern US/Hawaii US/Indiana-Starke
US/Michigan US/Mountain US/Pacific US/Samoa
`.trim().split(/\s+/)
test('timezone name matches iana spelling', (t) => {
const spelling = new Map(iana.map((tz) => [tz.toLowerCase(), tz]))
const zones = Object.keys(spacetime().timezones)
for (let i = 0; i < zones.length; i += 1) {
const expected = spelling.get(zones[i])
if (expected) {
t.equal(spacetime.now(zones[i]).timezone().name, expected, expected)
}
}
t.end()
})
+34
View File
@@ -0,0 +1,34 @@
import test from 'tape'
import spacetime from '../_lib/index.js'
test('titlecase', (t) => {
const arr = [
'Africa/Dar_es_Salaam',
'Africa/Porto-Novo',
'America/Blanc-Sablon',
'America/Port-au-Prince',
'America/Port_of_Spain',
'Europe/Isle_of_Man',
'Antarctica/DumontDUrville',
'Antarctica/McMurdo',
'Asia/Ust-Nera',
'Europe/Zagreb',
'America/Bahia_Banderas',
'Asia/Kuching',
'Etc/GMT+7',
]
arr.forEach(tz => {
const s = spacetime.now(tz)
t.equal(s.timezone().name, tz, tz)
})
t.end()
})
test('whereIts returns titlecased names', (t) => {
const tzs = spacetime.whereIts('12:00am', '11:59pm')
t.ok(tzs.includes('Antarctica/Mawson'), 'Antarctica/Mawson')
t.ok(tzs.includes('America/Port-au-Prince'), 'America/Port-au-Prince')
const wrong = tzs.filter(tz => tz !== spacetime.now(tz).timezone().name)
t.deepEqual(wrong, [], 'same name as timezone()')
t.end()
})
+9
View File
@@ -0,0 +1,9 @@
import test from 'tape'
import spacetime from './lib/index.js'
import { tzdbVersion } from '../zonefile/iana.js'
test('tzdb version', (t) => {
t.ok(/^\d{4}[a-z]+$/.test(spacetime.tzdbVersion), `looks like '2026c'`)
t.equal(spacetime.tzdbVersion, tzdbVersion, 'same as zonefile/iana.js')
t.end()
})
+97
View File
@@ -0,0 +1,97 @@
import type { Spacetime, ParsableDate, TimezoneSet } from './types.js'
export interface SpacetimeConstructorOptions {
/** javascript dates use millisecond-epochs, instead of second-epochs, like some other languages. This is a common bug, and by default spacetime warns if you set an epoch within January 1970. to disable set to true */
silent?: boolean
/** the day number, between 0-6, that the week starts on. (Sunday is 0) */
weekStart?: number
/** pass true to change parsing behaviour to dd/mm/yyyy. By default American interpretation will be used. */
dmy?: boolean
}
export interface SpacetimeConstructor {
/**
* @param epoch Timestamp in **milliseconds**. If you are getting a date in 1970, you are likely using seconds.
* @param timezone Optional timezone. If omitted uses the browser timezone.
* @param options Options for silencing warnings.
*/
(epoch: number, timezone?: string, options?: SpacetimeConstructorOptions): Spacetime
/**
* @param arr Date values in an array such as [yyyy, m, d].
*
* ```typescript
* let d = spacetime([2011, 1, 15]);
* d.format('nice-year'); // "Feb 15th, 2011"
* ```
* @param timezone Optional timezone. If omitted uses the browser timezone.
* @param options Options for silencing warnings.
*/
(arr: Array<number>, timezone?: string, options?: SpacetimeConstructorOptions): Spacetime
/**
* @param obj Date as a key-value object. ex {month:'june', year:2019}
* @param timezone Optional timezone. If omitted uses the browser timezone.
* @param options Options for silencing warnings.
*/
(
obj: { [unit: string]: string | number },
timezone?: string,
options?: SpacetimeConstructorOptions
): Spacetime
/**
* @param iso Date as an iso string. ex '2017-04-03T08:00:00'
* @param timezone Optional timezone. If omitted uses the browser timezone.
* @param options Options for silencing warnings.
*/
(iso: string, timezone?: string, options?: SpacetimeConstructorOptions): Spacetime
/**
* @param parsableDate a parsable date like object
* @param timezone Optional timezone. If omitted uses the browser timezone.
* @param options Options for silencing warnings.
*/
(
parsableDate?: ParsableDate | null,
timezone?: string,
options?: SpacetimeConstructorOptions
): Spacetime
}
export interface SpacetimeStatic extends SpacetimeConstructor {
/** set as the current time */
now: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** set as this morning */
today: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** set as tomorrow morning */
tomorrow: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** set as yesterday morning */
yesterday: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** set epoch using seconds instead of milliseconds */
fromUnixSeconds: (seconds: number, timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** Extend Spacetime with a custom function/object. */
extend: (extension: { [key: string]: any } | {}) => SpacetimeStatic
/** get a list of current timezones and their offsets */
timezones: () => TimezoneSet
/** list timezones by their time */
whereIts: (a: string, b?: string) => string[]
/** IANA tzdb release of the bundled timezone data, like '2026c' */
tzdbVersion: string
/** set as earliest-possible date */
min: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
/** set as furthest-possible future date */
max: (timezone?: string, options?: SpacetimeConstructorOptions) => Spacetime
}
+89
View File
@@ -0,0 +1,89 @@
export const tzdbVersion = "2026c"
export default {
"9|s": "2/dili,2/jayapura",
"9|n": "2/chita,2/khandyga,2/pyongyang,2/seoul,2/tokyo,2/yakutsk,11/palau,japan,rok",
"9.5|s|04/05:03->10/04:02": "4/adelaide,4/broken_hill,4/south,4/yancowinna",
"9.5|s": "4/darwin,4/north",
"8|s": "12/casey,2/makassar,4/perth,2/ujung_pandang,4/west",
"8|n": "2/brunei,2/hong_kong,2/irkutsk,2/kuala_lumpur,2/kuching,2/macau,2/manila,2/shanghai,2/singapore,2/taipei,2/ulaanbaatar,2/chongqing,2/chungking,2/harbin,2/macao,2/ulan_bator,2/choibalsan,hongkong,prc,roc,singapore",
"8.75|s": "4/eucla",
"7|s": "12/davis,2/jakarta,2/pontianak,9/christmas",
"7|n": "2/bangkok,2/barnaul,2/hovd,2/krasnoyarsk,2/novokuznetsk,2/novosibirsk,2/phnom_penh,2/ho_chi_minh,2/tomsk,2/vientiane,2/saigon",
"6|s": "9/chagos",
"6|n": "2/bishkek,2/dhaka,2/omsk,2/thimphu,2/urumqi,2/dacca,2/kashgar,2/thimbu",
"6.5|s": "9/cocos",
"6.5|n": "2/yangon,2/rangoon",
"5|s": "12/mawson,12/vostok,9/kerguelen",
"5|n": "2/almaty,2/aqtau,2/aqtobe,2/ashgabat,2/atyrau,2/dushanbe,2/karachi,2/oral,2/qyzylorda,2/qostanay,2/samarkand,2/tashkent,2/yekaterinburg,9/maldives,2/ashkhabad",
"5.75|n": "2/kathmandu,2/katmandu",
"5.5|n": "2/kolkata,2/colombo,2/calcutta",
"4|s": "9/mahe,9/mauritius,9/reunion",
"4|n": "2/baku,2/dubai,2/muscat,2/tbilisi,2/yerevan,8/astrakhan,8/samara,8/saratov,8/ulyanovsk",
"4.5|n": "2/kabul",
"3|s": "0/dar_es_salaam,0/nairobi,12/syowa,9/antananarivo,9/comoro,9/mayotte",
"3|n|04/24:00->10/29:24": "0/cairo,egypt",
"3|n|03/29:03->10/25:04": "2/famagusta,2/nicosia,8/athens,8/bucharest,8/chisinau,8/helsinki,8/kyiv,8/mariehamn,8/riga,8/sofia,8/tallinn,8/uzhgorod,8/vilnius,8/zaporozhye,8/nicosia,8/tiraspol,8/kiev,eet",
"3|n|03/29:00->10/24:24": "2/beirut",
"3|n|03/28:02->10/24:02": "2/gaza,2/hebron",
"3|n|03/27:02->10/25:02": "2/jerusalem,2/tel_aviv,israel",
"3|n": "0/addis_ababa,0/asmara,0/asmera,0/djibouti,0/kampala,0/mogadishu,2/aden,2/amman,2/baghdad,2/bahrain,2/damascus,2/kuwait,2/qatar,2/riyadh,8/istanbul,8/kirov,8/minsk,8/moscow,8/simferopol,8/volgograd,2/volgograd,2/istanbul,turkey,w-su",
"3.5|n": "2/tehran,iran",
"2|s": "0/blantyre,0/bujumbura,0/gaborone,0/harare,0/johannesburg,0/kigali,0/lubumbashi,0/lusaka,0/maputo,0/maseru,0/mbabane,0/windhoek",
"2|n|03/29:02->10/25:03": "0/ceuta,arctic/longyearbyen,8/amsterdam,8/andorra,8/belgrade,8/berlin,8/bratislava,8/brussels,8/budapest,8/busingen,8/copenhagen,8/gibraltar,8/ljubljana,8/luxembourg,8/madrid,8/malta,8/monaco,8/oslo,8/paris,8/podgorica,8/prague,8/rome,8/san_marino,8/sarajevo,8/skopje,8/stockholm,8/tirane,8/vaduz,8/vatican,8/vienna,8/warsaw,8/zagreb,8/zurich,3/jan_mayen,poland,cet,met",
"2|n|03/29:01->10/25:03": "12/troll",
"2|n": "0/juba,0/khartoum,0/tripoli,8/kaliningrad,libya",
"1|s": "0/brazzaville,0/kinshasa,0/luanda",
"1|n|03/29:01->10/25:02": "3/canary,3/faroe,3/madeira,8/dublin,8/guernsey,8/isle_of_man,8/jersey,8/lisbon,8/london,3/faeroe,eire,8/belfast,gb-eire,gb,portugal,wet",
"1|n": "0/algiers,0/bangui,0/douala,0/lagos,0/libreville,0/malabo,0/ndjamena,0/niamey,0/porto-novo,0/tunis",
"14|n": "11/kiritimati",
"13|s": "11/apia,11/enderbury,11/kanton,11/fakaofo,11/tongatapu",
"12|s|04/05:03->09/27:02": "12/mcmurdo,11/auckland,12/south_pole,nz",
"12|s": "11/fiji,11/funafuti,11/nauru,11/wallis",
"12|n": "2/anadyr,2/kamchatka,11/kwajalein,11/majuro,11/tarawa,11/wake,kwajalein",
"12.75|s|04/05:03:45->09/27:02:45": "11/chatham,nz-chat",
"11|s|04/05:03->10/04:02": "11/norfolk",
"11|s": "11/bougainville,11/efate,11/guadalcanal,11/noumea",
"11|n": "2/magadan,2/sakhalin,2/srednekolymsk,11/kosrae,11/pohnpei,11/ponape",
"10|s|04/05:03->10/04:02": "12/macquarie,4/currie,4/hobart,4/melbourne,4/sydney,4/act,4/canberra,4/nsw,4/tasmania,4/victoria",
"10|s": "12/dumontdurville,4/brisbane,4/lindeman,11/port_moresby,4/queensland",
"10|n": "2/ust-nera,2/vladivostok,11/guam,11/saipan,11/chuuk,11/truk,11/yap",
"10.5|s|04/05:02->10/04:02": "4/lord_howe,4/lhi",
"0|s|02/15:03->09/20:02": "0/casablanca",
"0|s|02/15:03->03/22:02": "0/el_aaiun",
"0|s": "3/st_helena",
"0|n|03/29:00->10/25:01": "3/azores",
"0|n": "0/abidjan,0/accra,0/bamako,0/banjul,0/bissau,0/conakry,0/dakar,0/freetown,0/lome,0/monrovia,0/nouakchott,0/ouagadougou,0/sao_tome,1/danmarkshavn,3/reykjavik,13/gmt,13/utc,0/timbuktu,13/greenwich,13/uct,13/universal,13/zulu,gmt-0,gmt+0,gmt0,greenwich,iceland,uct,universal,utc,zulu,13/unknown,factory",
"-9|s": "11/gambier",
"-9|n|03/08:02->11/01:02": "1/adak,1/atka,us/aleutian",
"-9.5|s": "11/marquesas",
"-8|s": "11/pitcairn",
"-8|n|03/08:02->11/01:02": "1/anchorage,1/juneau,1/metlakatla,1/nome,1/sitka,1/yakutat,us/alaska",
"-7|n|03/08:02->12/31:23": "1/vancouver,6/pacific",
"-7|n|03/08:02->11/01:02": "1/los_angeles,1/santa_isabel,1/tijuana,1/ensenada,10/bajanorte,us/pacific-new,us/pacific,pst8pdt",
"-7|n": "1/creston,1/dawson,1/dawson_creek,1/fort_nelson,1/hermosillo,1/mazatlan,1/phoenix,1/whitehorse,6/yukon,10/bajasur,us/arizona,mst",
"-6|s|04/04:22->09/05:22": "11/easter,7/easterisland",
"-6|s": "11/galapagos",
"-6|n|03/08:02->12/31:23": "1/edmonton,1/yellowknife,6/mountain",
"-6|n|03/08:02->11/01:02": "1/boise,1/cambridge_bay,1/denver,1/inuvik,1/ciudad_juarez,1/shiprock,navajo,us/mountain,mst7mdt",
"-6|n": "1/bahia_banderas,1/belize,1/chihuahua,1/costa_rica,1/el_salvador,1/guatemala,1/managua,1/merida,1/mexico_city,1/monterrey,1/regina,1/swift_current,1/tegucigalpa,6/east-saskatchewan,6/saskatchewan,10/general",
"-5|s": "1/eirunepe,1/guayaquil,1/lima,1/rio_branco,1/porto_acre,5/acre",
"-5|n|03/08:02->11/01:02": "1/chicago,1/matamoros,1/menominee,1/north_dakota,1/ojinaga,1/rainy_river,1/rankin_inlet,1/resolute,1/winnipeg,1/indiana/knox,1/indiana/tell_city,1/north_dakota/beulah,1/north_dakota/center,1/north_dakota/new_salem,1/knox_in,6/central,us/central,us/indiana-starke,cst6cdt",
"-5|n": "1/bogota,1/cancun,1/cayman,1/coral_harbour,1/jamaica,1/panama,1/atikokan,jamaica,est",
"-4|s|04/04:24->09/06:00": "1/santiago,7/continental",
"-4|s": "1/campo_grande,1/cuiaba,1/la_paz,1/manaus,1/porto_velho,5/west",
"-4|n|03/08:02->11/01:02": "1/detroit,1/grand_turk,1/indiana,1/indianapolis,1/iqaluit,1/kentucky,1/louisville,1/montreal,1/nassau,1/new_york,1/nipigon,1/pangnirtung,1/port-au-prince,1/thunder_bay,1/toronto,1/indiana/marengo,1/indiana/petersburg,1/indiana/vevay,1/indiana/vincennes,1/indiana/winamac,1/kentucky/monticello,1/fort_wayne,1/indiana/indianapolis,1/kentucky/louisville,6/eastern,us/east-indiana,us/eastern,us/michigan,est5edt",
"-4|n|03/08:00->11/01:01": "1/havana,cuba",
"-4|n": "1/anguilla,1/antigua,1/aruba,1/barbados,1/blanc-sablon,1/boa_vista,1/caracas,1/curacao,1/dominica,1/grenada,1/guadeloupe,1/guyana,1/kralendijk,1/lower_princes,1/marigot,1/martinique,1/montserrat,1/port_of_spain,1/puerto_rico,1/santo_domingo,1/st_barthelemy,1/st_kitts,1/st_lucia,1/st_thomas,1/st_vincent,1/tortola,1/virgin",
"-3|s": "1/araguaina,1/argentina,1/asuncion,1/bahia,1/belem,1/buenos_aires,1/catamarca,1/cordoba,1/coyhaique,1/fortaleza,1/jujuy,1/maceio,1/mendoza,1/montevideo,1/punta_arenas,1/recife,1/santarem,1/sao_paulo,12/palmer,12/rothera,3/stanley,1/argentina/la_rioja,1/argentina/rio_gallegos,1/argentina/salta,1/argentina/san_juan,1/argentina/san_luis,1/argentina/tucuman,1/argentina/ushuaia,1/argentina/comodrivadavia,1/argentina/buenos_aires,1/argentina/catamarca,1/argentina/cordoba,1/argentina/jujuy,1/argentina/mendoza,1/argentina/rosario,1/rosario,5/east",
"-3|n|03/08:02->11/01:02": "1/glace_bay,1/goose_bay,1/halifax,1/moncton,1/thule,3/bermuda,6/atlantic",
"-3|n": "1/cayenne,1/paramaribo",
"-2|s": "1/noronha,3/south_georgia,5/denoronha",
"-2|n|03/08:02->11/01:02": "1/miquelon",
"-2.5|n|03/08:02->11/01:02": "1/st_johns,6/newfoundland",
"-1|n|03/28:23->10/24:24": "1/nuuk,1/scoresbysund,1/godthab",
"-1|n": "3/cape_verde",
"-11|s": "11/niue,11/pago_pago,11/samoa,us/samoa",
"-11|n": "11/midway",
"-10|s": "11/rarotonga,11/tahiti",
"-10|n": "11/honolulu,11/johnston,us/hawaii,hst"
}
+1898
View File
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
//turn our timezone data into a small-as-possible string
import { writeFileSync } from 'node:fs'
import iana, { tzdbVersion } from './iana.js'
import aliases from './aliases.js'
import prefixes from './_prefixes.js'
const all = {}
// add aliases in
Object.keys(aliases).forEach((k) => {
const found = iana[aliases[k]]
if (!found) {
console.log('missing', aliases[k]) //eslint-disable-line
}
iana[k] = Object.assign({}, found)
})
//pack iana data into a [o|h] object
Object.keys(iana).forEach((k) => {
const o = iana[k]
let key = o.offset + '|' + o.hem
if (o.dst) {
key += '|' + o.dst
}
all[key] = all[key] || []
const name = k.replace(/(.*?)\//, (a, prefix) => {
const index = prefixes.indexOf(prefix)
if (index !== -1) {
return index + '/'
}
return a
})
all[key].push(name)
})
//add-in informal abbreviations
// all = addHemisphere(all, informal.south, 's')
// all = addHemisphere(all, informal.north, 'n')
let keys = Object.keys(all)
keys = keys.sort((a, b) => (a < b ? 1 : -1))
const result = {}
keys.forEach((k) => {
result[k] = all[k].join(',')
})
// console.log(result)
writeFileSync(
'./zonefile/_build.js',
`export const tzdbVersion = ${JSON.stringify(tzdbVersion)}\nexport default ${JSON.stringify(result, null, 2)}`
)