airport-utils 1.0.28 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,75 +1,88 @@
1
- import { parseISO } from 'date-fns';
2
1
  import { TZDate } from '@date-fns/tz';
3
2
  import { timezones } from './mapping/timezones.js';
4
3
  import { UnknownAirportError, InvalidTimestampError, UnknownTimezoneError } from './errors.js';
5
4
 
6
5
  const ISO_LOCAL_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
7
- function parseLocalIso(localIso) {
6
+ const VALID_TIMEZONE_CACHE = new Map();
7
+ function isLeapYear(year) {
8
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
9
+ }
10
+ function daysInMonth(year, month) {
11
+ if (month === 1)
12
+ return isLeapYear(year) ? 29 : 28;
13
+ if (month === 3 || month === 5 || month === 8 || month === 10)
14
+ return 30;
15
+ return 31;
16
+ }
17
+ function parseLocalIsoStrict(localIso) {
8
18
  const m = ISO_LOCAL_RE.exec(localIso);
9
19
  if (!m)
10
20
  throw new InvalidTimestampError(localIso);
11
21
  const [, Y, Mo, D, h, mi, s] = m;
12
- return [
13
- Number(Y),
14
- Number(Mo) - 1,
15
- Number(D),
16
- Number(h),
17
- Number(mi),
18
- s ? Number(s) : 0
19
- ];
20
- }
21
- /**
22
- * Convert a local ISO‐8601 string at an airport (IATA) into a UTC ISO string.
23
- * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
24
- */
25
- function convertToUTC(localIso, iata) {
26
- const tz = timezones[iata];
27
- if (!tz)
28
- throw new UnknownAirportError(iata);
29
- // Quick semantic check
30
- const base = parseISO(localIso);
31
- if (isNaN(base.getTime()))
22
+ const year = Number(Y);
23
+ const month = Number(Mo) - 1;
24
+ const day = Number(D);
25
+ const hour = Number(h);
26
+ const minute = Number(mi);
27
+ const second = s ? Number(s) : 0;
28
+ if (month < 0 || month > 11)
32
29
  throw new InvalidTimestampError(localIso);
33
- const [year, month, day, hour, minute, second] = parseLocalIso(localIso);
34
- let zoned;
35
- try {
36
- zoned = TZDate.tz(tz, year, month, day, hour, minute, second);
37
- }
38
- catch {
30
+ if (day < 1 || day > daysInMonth(year, month))
39
31
  throw new InvalidTimestampError(localIso);
40
- }
41
- if (isNaN(zoned.getTime()))
32
+ if (hour < 0 || hour > 23)
42
33
  throw new InvalidTimestampError(localIso);
43
- // Strip ".000" from the ISO string
44
- return new Date(zoned.getTime()).toISOString().replace('.000Z', 'Z');
34
+ if (minute < 0 || minute > 59)
35
+ throw new InvalidTimestampError(localIso);
36
+ if (second < 0 || second > 59)
37
+ throw new InvalidTimestampError(localIso);
38
+ return [year, month, day, hour, minute, second];
45
39
  }
46
- /**
47
- * Convert a local ISO‐8601 string in any IANA timezone into a UTC ISO string.
48
- * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
49
- */
50
- function convertLocalToUTCByZone(localIso, timeZone) {
51
- // Validate timezone
40
+ function assertValidTimezone(timeZone) {
41
+ const cached = VALID_TIMEZONE_CACHE.get(timeZone);
42
+ if (cached === true)
43
+ return;
44
+ if (cached === false)
45
+ throw new UnknownTimezoneError(timeZone);
52
46
  try {
53
47
  new Intl.DateTimeFormat('en-US', { timeZone }).format();
48
+ VALID_TIMEZONE_CACHE.set(timeZone, true);
54
49
  }
55
50
  catch {
51
+ VALID_TIMEZONE_CACHE.set(timeZone, false);
56
52
  throw new UnknownTimezoneError(timeZone);
57
53
  }
58
- // Quick semantic check
59
- const base = parseISO(localIso);
60
- if (isNaN(base.getTime()))
61
- throw new InvalidTimestampError(localIso);
62
- const [year, month, day, hour, minute, second] = parseLocalIso(localIso);
54
+ }
55
+ function toUtcIso(localIso, timeZone, onTimeZoneError) {
56
+ const [year, month, day, hour, minute, second] = parseLocalIsoStrict(localIso);
63
57
  let zoned;
64
58
  try {
65
59
  zoned = TZDate.tz(timeZone, year, month, day, hour, minute, second);
66
60
  }
67
- catch {
68
- throw new UnknownTimezoneError(timeZone);
61
+ catch (err) {
62
+ throw onTimeZoneError(err);
69
63
  }
70
64
  if (isNaN(zoned.getTime()))
71
65
  throw new InvalidTimestampError(localIso);
66
+ // Strip ".000" from the ISO string
72
67
  return new Date(zoned.getTime()).toISOString().replace('.000Z', 'Z');
73
68
  }
69
+ /**
70
+ * Convert a local ISO‐8601 string at an airport (IATA) into a UTC ISO string.
71
+ * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
72
+ */
73
+ function convertToUTC(localIso, iata) {
74
+ const tz = timezones[iata];
75
+ if (!tz)
76
+ throw new UnknownAirportError(iata);
77
+ return toUtcIso(localIso, tz, () => new InvalidTimestampError(localIso));
78
+ }
79
+ /**
80
+ * Convert a local ISO‐8601 string in any IANA timezone into a UTC ISO string.
81
+ * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
82
+ */
83
+ function convertLocalToUTCByZone(localIso, timeZone) {
84
+ assertValidTimezone(timeZone);
85
+ return toUtcIso(localIso, timeZone, () => new UnknownTimezoneError(timeZone));
86
+ }
74
87
 
75
88
  export { convertLocalToUTCByZone, convertToUTC };
package/dist/esm/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { convertLocalToUTCByZone, convertToUTC } from './converter.js';
2
- export { getAirportInfo } from './info.js';
2
+ export { getAirportInfo, getAllAirports } from './info.js';
3
3
  export { InvalidTimestampError, UnknownAirportError, UnknownTimezoneError } from './errors.js';
package/dist/esm/info.js CHANGED
@@ -10,5 +10,16 @@ function getAirportInfo(iata) {
10
10
  throw new UnknownAirportError(iata);
11
11
  return { timezone: tz, ...g };
12
12
  }
13
+ function getAllAirports() {
14
+ const all = [];
15
+ for (const iata of Object.keys(geo)) {
16
+ const tz = timezones[iata];
17
+ const g = geo[iata];
18
+ if (!tz || !g)
19
+ continue;
20
+ all.push({ iata, timezone: tz, ...g });
21
+ }
22
+ return all;
23
+ }
13
24
 
14
- export { getAirportInfo };
25
+ export { getAirportInfo, getAllAirports };