airport-utils 1.3.29 → 1.4.1
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/README.md +6 -4
- package/dist/cjs/converter.cjs +63 -6
- package/dist/cjs/info.cjs +2 -1
- package/dist/cjs/mapping/geo.cjs +378 -369
- package/dist/cjs/mapping/timezones.cjs +1 -0
- package/dist/esm/converter.js +64 -7
- package/dist/esm/info.js +2 -1
- package/dist/esm/mapping/geo.js +378 -369
- package/dist/esm/mapping/timezones.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@ Convert local ISO 8601 timestamps to UTC using airport IATA codes, with airport
|
|
|
7
7
|
- **Local → UTC** conversion only (ISO 8601 in, ISO 8601 UTC out)
|
|
8
8
|
- Built-in IATA→IANA timezone mapping (OPTD)
|
|
9
9
|
- Built-in airport geo-data: latitude, longitude, name, city, country, country name
|
|
10
|
-
- TypeScript support, Node
|
|
10
|
+
- TypeScript support, Node 22+
|
|
11
11
|
- Synchronous API with custom error classes
|
|
12
|
-
-
|
|
12
|
+
- `@date-fns/tz` under the hood
|
|
13
13
|
- Daily auto-updated mapping via GitHub Actions
|
|
14
14
|
- Jest tests with 100% coverage
|
|
15
15
|
- Automated releases via semantic-release
|
|
@@ -38,6 +38,7 @@ try {
|
|
|
38
38
|
console.log(utc); // "2025-05-02T18:30:00Z"
|
|
39
39
|
} catch (err) {
|
|
40
40
|
// handle UnknownAirportError or InvalidTimestampError
|
|
41
|
+
// InvalidTimestampError is also thrown for nonexistent or ambiguous DST wall-clock times
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// Convert local time by zone
|
|
@@ -46,6 +47,7 @@ try {
|
|
|
46
47
|
console.log(utc2); // "2025-05-02T13:30:00Z"
|
|
47
48
|
} catch (err) {
|
|
48
49
|
// handle UnknownTimezoneError or InvalidTimestampError
|
|
50
|
+
// InvalidTimestampError is also thrown for nonexistent or ambiguous DST wall-clock times
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// Get full airport info
|
|
@@ -69,7 +71,7 @@ try {
|
|
|
69
71
|
// Get all airports
|
|
70
72
|
import { getAllAirports } from 'airport-utils';
|
|
71
73
|
const airports = getAllAirports();
|
|
72
|
-
console.log(airports.length); // >
|
|
74
|
+
console.log(airports.length); // > 8000
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
### API
|
|
@@ -141,4 +143,4 @@ npm run update:mapping
|
|
|
141
143
|
|
|
142
144
|
## License
|
|
143
145
|
|
|
144
|
-
MIT
|
|
146
|
+
MIT
|
package/dist/cjs/converter.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var errors = require('./errors.cjs');
|
|
|
6
6
|
|
|
7
7
|
const ISO_LOCAL_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
8
8
|
const VALID_TIMEZONE_CACHE = new Map();
|
|
9
|
+
const INVALID_LOCAL_INTERVAL_CACHE = new Map();
|
|
9
10
|
function isLeapYear(year) {
|
|
10
11
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
11
12
|
}
|
|
@@ -54,19 +55,75 @@ function assertValidTimezone(timeZone) {
|
|
|
54
55
|
throw new errors.UnknownTimezoneError(timeZone);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
function localPartsToNaiveUtcMs(parts) {
|
|
59
|
+
const [year, month, day, hour, minute, second] = parts;
|
|
60
|
+
return Date.UTC(year, month, day, hour, minute, second);
|
|
61
|
+
}
|
|
62
|
+
function resolveUtcMs(localNaiveMs, timeZone) {
|
|
63
|
+
let utcMs = localNaiveMs;
|
|
64
|
+
for (let i = 0; i < 4; i++) {
|
|
65
|
+
const offsetMinutes = tz.tzOffset(timeZone, new Date(utcMs));
|
|
66
|
+
if (!Number.isFinite(offsetMinutes)) {
|
|
67
|
+
throw new RangeError(`Invalid timezone offset for: ${timeZone}`);
|
|
68
|
+
}
|
|
69
|
+
const nextUtcMs = localNaiveMs - offsetMinutes * 60_000;
|
|
70
|
+
if (nextUtcMs === utcMs)
|
|
71
|
+
return utcMs;
|
|
72
|
+
utcMs = nextUtcMs;
|
|
73
|
+
}
|
|
74
|
+
return utcMs;
|
|
75
|
+
}
|
|
76
|
+
function getInvalidLocalIntervals(timeZone, year) {
|
|
77
|
+
const cacheKey = `${timeZone}:${year}`;
|
|
78
|
+
const cached = INVALID_LOCAL_INTERVAL_CACHE.get(cacheKey);
|
|
79
|
+
if (cached)
|
|
80
|
+
return cached;
|
|
81
|
+
const yearStart = Date.UTC(year, 0, 1, 0, 0, 0);
|
|
82
|
+
const yearEnd = Date.UTC(year + 1, 0, 1, 0, 0, 0);
|
|
83
|
+
const transitions = tz.tzScan(timeZone, {
|
|
84
|
+
start: new Date(Date.UTC(year - 1, 11, 31, 0, 0, 0)),
|
|
85
|
+
end: new Date(Date.UTC(year + 1, 0, 2, 0, 0, 0))
|
|
86
|
+
});
|
|
87
|
+
const intervals = transitions
|
|
88
|
+
.map((transition) => {
|
|
89
|
+
const previousOffset = transition.offset - transition.change;
|
|
90
|
+
if (transition.change < 0) {
|
|
91
|
+
return {
|
|
92
|
+
start: transition.date.getTime() + transition.offset * 60_000,
|
|
93
|
+
end: transition.date.getTime() + previousOffset * 60_000
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
start: transition.date.getTime() + previousOffset * 60_000,
|
|
98
|
+
end: transition.date.getTime() + transition.offset * 60_000
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
.filter((interval) => interval.end > yearStart && interval.start < yearEnd);
|
|
102
|
+
INVALID_LOCAL_INTERVAL_CACHE.set(cacheKey, intervals);
|
|
103
|
+
return intervals;
|
|
104
|
+
}
|
|
105
|
+
function assertResolvableLocalTime(localIso, timeZone, year, localNaiveMs) {
|
|
106
|
+
const intervals = getInvalidLocalIntervals(timeZone, year);
|
|
107
|
+
for (const interval of intervals) {
|
|
108
|
+
if (localNaiveMs >= interval.start && localNaiveMs < interval.end) {
|
|
109
|
+
throw new errors.InvalidTimestampError(localIso);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
57
113
|
function toUtcIso(localIso, timeZone, onTimeZoneError) {
|
|
58
|
-
const
|
|
59
|
-
|
|
114
|
+
const parts = parseLocalIsoStrict(localIso);
|
|
115
|
+
const [year] = parts;
|
|
116
|
+
const localNaiveMs = localPartsToNaiveUtcMs(parts);
|
|
117
|
+
assertResolvableLocalTime(localIso, timeZone, year, localNaiveMs);
|
|
118
|
+
let utcMs;
|
|
60
119
|
try {
|
|
61
|
-
|
|
120
|
+
utcMs = resolveUtcMs(localNaiveMs, timeZone);
|
|
62
121
|
}
|
|
63
122
|
catch (err) {
|
|
64
123
|
throw onTimeZoneError(err);
|
|
65
124
|
}
|
|
66
|
-
if (isNaN(zoned.getTime()))
|
|
67
|
-
throw new errors.InvalidTimestampError(localIso);
|
|
68
125
|
// Strip ".000" from the ISO string
|
|
69
|
-
return new Date(
|
|
126
|
+
return new Date(utcMs).toISOString().replace('.000Z', 'Z');
|
|
70
127
|
}
|
|
71
128
|
/**
|
|
72
129
|
* Convert a local ISO‐8601 string at an airport (IATA) into a UTC ISO string.
|
package/dist/cjs/info.cjs
CHANGED
|
@@ -4,6 +4,7 @@ var timezones = require('./mapping/timezones.cjs');
|
|
|
4
4
|
var geo = require('./mapping/geo.cjs');
|
|
5
5
|
var errors = require('./errors.cjs');
|
|
6
6
|
|
|
7
|
+
const GEO_IATAS = Object.keys(geo.geo);
|
|
7
8
|
/** @throws UnknownAirportError */
|
|
8
9
|
function getAirportInfo(iata) {
|
|
9
10
|
const tz = timezones.timezones[iata];
|
|
@@ -14,7 +15,7 @@ function getAirportInfo(iata) {
|
|
|
14
15
|
}
|
|
15
16
|
function getAllAirports() {
|
|
16
17
|
const all = [];
|
|
17
|
-
for (const iata of
|
|
18
|
+
for (const iata of GEO_IATAS) {
|
|
18
19
|
const tz = timezones.timezones[iata];
|
|
19
20
|
const g = geo.geo[iata];
|
|
20
21
|
if (!tz || !g)
|