Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b3e97a0e | ||
|
|
e678c52b19 | ||
|
|
caad5a27d6 | ||
|
|
297f022747 | ||
|
|
27503a50da | ||
|
|
1668c0dd6b | ||
|
|
d47824c8f3 | ||
|
|
081c965030 | ||
|
|
fb5ecec15c | ||
|
|
d48b259893 |
No files matched your search
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -67,8 +67,10 @@ const minutes = function (s, n, goFwd) {
|
||||
|
||||
const hours = function (s, n, goFwd) {
|
||||
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) {
|
||||
n = 24
|
||||
n = 23
|
||||
} else if (n < 0) {
|
||||
n = 0
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.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()
|
||||
})
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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()
|
||||
})
|
||||
Vendored
+97
@@ -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
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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)}`
|
||||
)
|
||||
Reference in new issue
Block a user