airport-utils 1.2.0 → 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.
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ indent_style = space
7
+ indent_size = 2
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -5,9 +5,11 @@ jobs:
5
5
  runs-on: ubuntu-latest
6
6
  steps:
7
7
  - uses: actions/checkout@v3
8
- - uses: actions/setup-node@v3
8
+ - uses: actions/setup-node@v4
9
9
  with:
10
- node-version: '20'
10
+ node-version-file: '.nvmrc'
11
11
  - run: npm ci
12
+ - run: npm run lint
13
+ - run: npm run format:check
12
14
  - run: npm run build
13
- - run: npm test
15
+ - run: npm test
@@ -35,6 +35,12 @@ jobs:
35
35
  - name: Install dependencies
36
36
  run: npm ci
37
37
 
38
+ - name: Lint
39
+ run: npm run lint
40
+
41
+ - name: Format check
42
+ run: npm run format:check
43
+
38
44
  - name: Build
39
45
  run: npm run build
40
46
 
@@ -45,4 +51,4 @@ jobs:
45
51
  env:
46
52
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
47
53
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48
- run: npx semantic-release
54
+ run: npx semantic-release
@@ -30,7 +30,7 @@ jobs:
30
30
  - name: Setup Node.js
31
31
  uses: actions/setup-node@v4
32
32
  with:
33
- node-version: '20'
33
+ node-version-file: '.nvmrc'
34
34
 
35
35
  - name: Install dependencies
36
36
  run: npm ci
@@ -0,0 +1,6 @@
1
+ {
2
+ "singleQuote": true,
3
+ "semi": true,
4
+ "trailingComma": "none",
5
+ "printWidth": 100
6
+ }
package/README.md CHANGED
@@ -6,7 +6,7 @@ Convert local ISO 8601 timestamps to UTC using airport IATA codes, with airport
6
6
 
7
7
  - **Local → UTC** conversion only (ISO 8601 in, ISO 8601 UTC out)
8
8
  - Built-in IATA→IANA timezone mapping (OPTD)
9
- - Built-in airport geo-data: latitude, longitude, name, city, country
9
+ - Built-in airport geo-data: latitude, longitude, name, city, country, country name
10
10
  - TypeScript support, Node 20+
11
11
  - Synchronous API with custom error classes
12
12
  - Day.js (UTC & Timezone plugins) under the hood
@@ -59,6 +59,7 @@ try {
59
59
  // name: 'John F. Kennedy International Airport',
60
60
  // city: 'New York',
61
61
  // country: 'US',
62
+ // countryName: 'United States',
62
63
  // continent: 'North America'
63
64
  // }
64
65
  } catch (err) {
@@ -91,6 +92,7 @@ export function getAirportInfo(iata: string): {
91
92
  name: string;
92
93
  city: string;
93
94
  country: string;
95
+ countryName: string;
94
96
  continent: string;
95
97
  };
96
98
 
@@ -102,6 +104,7 @@ export function getAllAirports(): {
102
104
  name: string;
103
105
  city: string;
104
106
  country: string;
107
+ countryName: string;
105
108
  continent: string;
106
109
  }[];
107
110
 
@@ -1,78 +1,91 @@
1
1
  'use strict';
2
2
 
3
- var dateFns = require('date-fns');
4
3
  var tz = require('@date-fns/tz');
5
4
  var timezones = require('./mapping/timezones.js');
6
5
  var errors = require('./errors.js');
7
6
 
8
7
  const ISO_LOCAL_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
9
- function parseLocalIso(localIso) {
8
+ const VALID_TIMEZONE_CACHE = new Map();
9
+ function isLeapYear(year) {
10
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
11
+ }
12
+ function daysInMonth(year, month) {
13
+ if (month === 1)
14
+ return isLeapYear(year) ? 29 : 28;
15
+ if (month === 3 || month === 5 || month === 8 || month === 10)
16
+ return 30;
17
+ return 31;
18
+ }
19
+ function parseLocalIsoStrict(localIso) {
10
20
  const m = ISO_LOCAL_RE.exec(localIso);
11
21
  if (!m)
12
22
  throw new errors.InvalidTimestampError(localIso);
13
23
  const [, Y, Mo, D, h, mi, s] = m;
14
- return [
15
- Number(Y),
16
- Number(Mo) - 1,
17
- Number(D),
18
- Number(h),
19
- Number(mi),
20
- s ? Number(s) : 0
21
- ];
22
- }
23
- /**
24
- * Convert a local ISO‐8601 string at an airport (IATA) into a UTC ISO string.
25
- * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
26
- */
27
- function convertToUTC(localIso, iata) {
28
- const tz$1 = timezones.timezones[iata];
29
- if (!tz$1)
30
- throw new errors.UnknownAirportError(iata);
31
- // Quick semantic check
32
- const base = dateFns.parseISO(localIso);
33
- if (isNaN(base.getTime()))
24
+ const year = Number(Y);
25
+ const month = Number(Mo) - 1;
26
+ const day = Number(D);
27
+ const hour = Number(h);
28
+ const minute = Number(mi);
29
+ const second = s ? Number(s) : 0;
30
+ if (month < 0 || month > 11)
34
31
  throw new errors.InvalidTimestampError(localIso);
35
- const [year, month, day, hour, minute, second] = parseLocalIso(localIso);
36
- let zoned;
37
- try {
38
- zoned = tz.TZDate.tz(tz$1, year, month, day, hour, minute, second);
39
- }
40
- catch {
32
+ if (day < 1 || day > daysInMonth(year, month))
41
33
  throw new errors.InvalidTimestampError(localIso);
42
- }
43
- if (isNaN(zoned.getTime()))
34
+ if (hour < 0 || hour > 23)
44
35
  throw new errors.InvalidTimestampError(localIso);
45
- // Strip ".000" from the ISO string
46
- return new Date(zoned.getTime()).toISOString().replace('.000Z', 'Z');
36
+ if (minute < 0 || minute > 59)
37
+ throw new errors.InvalidTimestampError(localIso);
38
+ if (second < 0 || second > 59)
39
+ throw new errors.InvalidTimestampError(localIso);
40
+ return [year, month, day, hour, minute, second];
47
41
  }
48
- /**
49
- * Convert a local ISO‐8601 string in any IANA timezone into a UTC ISO string.
50
- * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
51
- */
52
- function convertLocalToUTCByZone(localIso, timeZone) {
53
- // Validate timezone
42
+ function assertValidTimezone(timeZone) {
43
+ const cached = VALID_TIMEZONE_CACHE.get(timeZone);
44
+ if (cached === true)
45
+ return;
46
+ if (cached === false)
47
+ throw new errors.UnknownTimezoneError(timeZone);
54
48
  try {
55
49
  new Intl.DateTimeFormat('en-US', { timeZone }).format();
50
+ VALID_TIMEZONE_CACHE.set(timeZone, true);
56
51
  }
57
52
  catch {
53
+ VALID_TIMEZONE_CACHE.set(timeZone, false);
58
54
  throw new errors.UnknownTimezoneError(timeZone);
59
55
  }
60
- // Quick semantic check
61
- const base = dateFns.parseISO(localIso);
62
- if (isNaN(base.getTime()))
63
- throw new errors.InvalidTimestampError(localIso);
64
- const [year, month, day, hour, minute, second] = parseLocalIso(localIso);
56
+ }
57
+ function toUtcIso(localIso, timeZone, onTimeZoneError) {
58
+ const [year, month, day, hour, minute, second] = parseLocalIsoStrict(localIso);
65
59
  let zoned;
66
60
  try {
67
61
  zoned = tz.TZDate.tz(timeZone, year, month, day, hour, minute, second);
68
62
  }
69
- catch {
70
- throw new errors.UnknownTimezoneError(timeZone);
63
+ catch (err) {
64
+ throw onTimeZoneError(err);
71
65
  }
72
66
  if (isNaN(zoned.getTime()))
73
67
  throw new errors.InvalidTimestampError(localIso);
68
+ // Strip ".000" from the ISO string
74
69
  return new Date(zoned.getTime()).toISOString().replace('.000Z', 'Z');
75
70
  }
71
+ /**
72
+ * Convert a local ISO‐8601 string at an airport (IATA) into a UTC ISO string.
73
+ * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
74
+ */
75
+ function convertToUTC(localIso, iata) {
76
+ const tz = timezones.timezones[iata];
77
+ if (!tz)
78
+ throw new errors.UnknownAirportError(iata);
79
+ return toUtcIso(localIso, tz, () => new errors.InvalidTimestampError(localIso));
80
+ }
81
+ /**
82
+ * Convert a local ISO‐8601 string in any IANA timezone into a UTC ISO string.
83
+ * Always emits "YYYY-MM-DDTHH:mm:ssZ" (no milliseconds).
84
+ */
85
+ function convertLocalToUTCByZone(localIso, timeZone) {
86
+ assertValidTimezone(timeZone);
87
+ return toUtcIso(localIso, timeZone, () => new errors.UnknownTimezoneError(timeZone));
88
+ }
76
89
 
77
90
  exports.convertLocalToUTCByZone = convertLocalToUTCByZone;
78
91
  exports.convertToUTC = convertToUTC;
package/dist/cjs/info.js CHANGED
@@ -13,11 +13,15 @@ function getAirportInfo(iata) {
13
13
  return { timezone: tz, ...g };
14
14
  }
15
15
  function getAllAirports() {
16
- return Object.keys(geo.geo).map(iata => {
16
+ const all = [];
17
+ for (const iata of Object.keys(geo.geo)) {
17
18
  const tz = timezones.timezones[iata];
18
19
  const g = geo.geo[iata];
19
- return { iata, timezone: tz, ...g };
20
- });
20
+ if (!tz || !g)
21
+ continue;
22
+ all.push({ iata, timezone: tz, ...g });
23
+ }
24
+ return all;
21
25
  }
22
26
 
23
27
  exports.getAirportInfo = getAirportInfo;