airport-utils 1.0.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.
@@ -0,0 +1,71 @@
1
+ import dayjs from 'dayjs';
2
+ import { convertToUTC, convertLocalToUTCByZone } from '../src/converter';
3
+ import {
4
+ UnknownAirportError,
5
+ InvalidTimestampError,
6
+ UnknownTimezoneError
7
+ } from '../src/errors';
8
+ import { timezones } from '../src/mapping/timezones';
9
+
10
+ // Dynamically find the first 3-letter code not in our mapping
11
+ function getInvalidIata(): string {
12
+ const existing = new Set(Object.keys(timezones));
13
+ for (let a = 65; a <= 90; a++) {
14
+ for (let b = 65; b <= 90; b++) {
15
+ for (let c = 65; c <= 90; c++) {
16
+ const code = String.fromCharCode(a, b, c);
17
+ if (!existing.has(code)) return code;
18
+ }
19
+ }
20
+ }
21
+ throw new Error('All 3-letter codes are taken?!');
22
+ }
23
+
24
+ const invalidIata = getInvalidIata();
25
+
26
+ describe('convertToUTC (Day.js)', () => {
27
+ it('converts JFK local time (UTC–4 in May) correctly', () => {
28
+ expect(convertToUTC('2025-05-02T14:30', 'JFK'))
29
+ .toBe('2025-05-02T18:30:00Z');
30
+ });
31
+
32
+ it('throws UnknownAirportError for bad IATA', () => {
33
+ expect(() => convertToUTC('2025-05-02T14:30', invalidIata))
34
+ .toThrow(UnknownAirportError);
35
+ });
36
+
37
+ it('throws InvalidTimestampError for malformed timestamp', () => {
38
+ expect(() => convertToUTC('invalid-format', 'JFK'))
39
+ .toThrow(InvalidTimestampError);
40
+ });
41
+
42
+ it('throws InvalidTimestampError if dayjs.tz unexpectedly throws', () => {
43
+ const orig = (dayjs as any).tz;
44
+ (dayjs as any).tz = () => { throw new Error(); };
45
+ try {
46
+ expect(() => convertToUTC('2025-05-02T14:30', 'JFK'))
47
+ .toThrow(InvalidTimestampError);
48
+ } finally {
49
+ (dayjs as any).tz = orig;
50
+ }
51
+ });
52
+ });
53
+
54
+ describe('convertLocalToUTCByZone', () => {
55
+ it('converts London local time to UTC', () => {
56
+ expect(convertLocalToUTCByZone('2025-05-02T14:30:00', 'Europe/London'))
57
+ .toBe('2025-05-02T13:30:00Z');
58
+ });
59
+
60
+ it('throws UnknownTimezoneError for invalid tz', () => {
61
+ expect(() =>
62
+ convertLocalToUTCByZone('2025-05-02T14:30:00', 'Invalid/Zone')
63
+ ).toThrow(UnknownTimezoneError);
64
+ });
65
+
66
+ it('throws InvalidTimestampError for malformed timestamp', () => {
67
+ expect(() =>
68
+ convertLocalToUTCByZone('bad-format', 'Europe/London')
69
+ ).toThrow(InvalidTimestampError);
70
+ });
71
+ });
@@ -0,0 +1,38 @@
1
+ import { getAirportInfo } from '../src/info';
2
+ import { UnknownAirportError } from '../src/errors';
3
+ import { timezones } from '../src/mapping/timezones';
4
+ import { geo } from '../src/mapping/geo';
5
+
6
+ // Dynamically find the first 3-letter code not in our mapping
7
+ function getInvalidIata(): string {
8
+ const existing = new Set(Object.keys(timezones));
9
+ for (let a = 65; a <= 90; a++) {
10
+ for (let b = 65; b <= 90; b++) {
11
+ for (let c = 65; c <= 90; c++) {
12
+ const code = String.fromCharCode(a, b, c);
13
+ if (!existing.has(code)) return code;
14
+ }
15
+ }
16
+ }
17
+ throw new Error('All 3-letter codes are taken?!');
18
+ }
19
+
20
+ const invalidIata = getInvalidIata();
21
+
22
+ describe('getAirportInfo', () => {
23
+ const validCodes = Object.keys(timezones).filter(i => geo[i]);
24
+ const sample = validCodes.length > 0 ? validCodes[0] : 'JFK';
25
+
26
+ it('returns full info for a valid IATA', () => {
27
+ const info = getAirportInfo(sample);
28
+ expect(info).toEqual({
29
+ timezone: timezones[sample],
30
+ ...geo[sample]
31
+ });
32
+ });
33
+
34
+ it('throws UnknownAirportError for missing IATA', () => {
35
+ expect(() => getAirportInfo(invalidIata))
36
+ .toThrow(UnknownAirportError);
37
+ });
38
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*", "scripts/**/*", "tests/**/*"]
14
+ }