lltz 0.0.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/LICENSE +21 -0
- package/README.md +109 -0
- package/data/timezones.lltz +0 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +186 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Utkarsh Kukreti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# lltz (Latitude & Longitude → Time Zone)
|
|
2
|
+
|
|
3
|
+
A high-performance, memory-efficient offline timezone lookup library for TypeScript using a custom binary format and quadtree spatial indexing.
|
|
4
|
+
|
|
5
|
+
This library uses GeoJSON data from [timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Fast**: Performs **25-35 million lookups per second** (~30-40ns per op) on an Apple M4 Pro CPU.
|
|
10
|
+
- **Tiny Memory Footprint**: Operates directly on raw binary data (ArrayBuffer/Uint8Array). Memory usage is limited to the LLTZ binary file size (approximately **26-44MiB**), with no additional object overhead.
|
|
11
|
+
- **Zero Dependencies**: A lightweight, standalone TypeScript library with no external runtime dependencies.
|
|
12
|
+
- **Universal**: Runs in both server (Node.js, Bun) and client (browser) environments.
|
|
13
|
+
- **High Accuracy**: Validated to match [`geo-tz`](https://www.npmjs.com/package/geo-tz) results in **>99.99%** of cases.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add lltz # or npm, yarn, bun
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Data
|
|
24
|
+
|
|
25
|
+
Three data variants are available, as described in the [timezone-boundary-builder README](https://github.com/evansiroky/timezone-boundary-builder#readme). We recommend `timezones.lltz` for the most comprehensive coverage, which is included in the NPM package.
|
|
26
|
+
|
|
27
|
+
The other variants, `timezones-1970.lltz` and `timezones-now.lltz`, can be downloaded from the [releases page](https://github.com/utkarshkukreti/lltz/releases) and loaded similarly.
|
|
28
|
+
|
|
29
|
+
### Node.js / Bun
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import fs from 'node:fs'
|
|
33
|
+
|
|
34
|
+
import * as Lltz from 'lltz'
|
|
35
|
+
|
|
36
|
+
const buffer = fs.readFileSync(new URL(import.meta.resolve('lltz/data/timezones.lltz')))
|
|
37
|
+
// ↳ or fs.readFileSync('node_modules/lltz/data/timezones.lltz')
|
|
38
|
+
const lookup = Lltz.make(buffer)
|
|
39
|
+
|
|
40
|
+
const timezones = lookup(40.7128, -74.006) // New York
|
|
41
|
+
console.log(timezones) // ['America/New_York']
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Browser
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import * as Lltz from 'lltz'
|
|
48
|
+
|
|
49
|
+
const response = await fetch('/path/to/timezones.lltz')
|
|
50
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
51
|
+
const lookup = Lltz.make(arrayBuffer)
|
|
52
|
+
|
|
53
|
+
console.log(lookup(40.7128, -74.006)) // ['America/New_York']
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Architecture
|
|
57
|
+
|
|
58
|
+
### Binary Format (.lltz)
|
|
59
|
+
|
|
60
|
+
The LLTZ binary file format is designed for efficient querying on raw bytes. It consists of the following sections:
|
|
61
|
+
|
|
62
|
+
- **Header**: The first eight bytes are `LLTZ1\0\0\0`.
|
|
63
|
+
- **Timezone Strings**: A null-terminated list of timezone IDs.
|
|
64
|
+
- **Grid Index**: A 180x360 coarse grid (1-degree resolution) for O(1) access.
|
|
65
|
+
- **QuadTree Nodes**: Hierarchical spatial subdivision for complex regions.
|
|
66
|
+
- **Polygon Data**: Compressed relative integer coordinates for final containment checks.
|
|
67
|
+
|
|
68
|
+
### Builder & Runtime
|
|
69
|
+
|
|
70
|
+
- **Builder (Python)**: Normalizes `timezone-boundary-builder` GeoJSON files to a 1,000,000 scale grid and constructs the spatial index.
|
|
71
|
+
- **Runtime (TypeScript)**: Performs an initial O(1) lookup using a coarse grid. For points near boundaries, it falls back to precise point-in-polygon ray-casting. Oceans default to `Etc/GMT` offsets based on 15-degree longitude bands.
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
### Data Preparation
|
|
76
|
+
|
|
77
|
+
To download the latest GeoJSON data and convert it into the optimized LLTZ binary format, run:
|
|
78
|
+
|
|
79
|
+
> Requires [uv](https://docs.astral.sh/uv/) to be installed.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
make -j
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Running Tests
|
|
86
|
+
|
|
87
|
+
Test lookup results against the `geo-tz` package:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Benchmarks
|
|
94
|
+
|
|
95
|
+
Benchmark performance against the `geo-tz` package:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pnpm bench
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Benchmark memory usage against the `geo-tz` package:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pnpm bench:memory
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT for the code, [ODbL for the data](https://github.com/evansiroky/timezone-boundary-builder).
|
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type Lookup =
|
|
3
|
+
/**
|
|
4
|
+
* Returns an array of timezone IDs for the given latitude and longitude.
|
|
5
|
+
* @param latitude - The latitude of the point in degrees (-90 to 90).
|
|
6
|
+
* @param longitude - The longitude of the point in degrees (-180 to 180).
|
|
7
|
+
* @throws An error if the latitude or longitude is out of range.
|
|
8
|
+
* @returns An array of timezone IDs. For points in unmapped areas (e.g., oceans), returns
|
|
9
|
+
* 'Etc/GMT'-based timezones.
|
|
10
|
+
*/
|
|
11
|
+
(latitude: number, longitude: number) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* Creates a timezone lookup function from the provided binary data.
|
|
14
|
+
*
|
|
15
|
+
* @param arrayBufferOrUint8Array - The binary data containing the timezone database (LLTZ format).
|
|
16
|
+
* @returns {Lookup} A timezone lookup function.
|
|
17
|
+
* @throws An error if the binary data is invalid.
|
|
18
|
+
*/
|
|
19
|
+
declare const make: (arrayBufferOrUint8Array: ArrayBuffer | Uint8Array) => Lookup;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Lookup, make };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* The expected header for the LLTZ binary file format.
|
|
4
|
+
*/
|
|
5
|
+
const HEADER = "LLTZ1\0\0\0";
|
|
6
|
+
/**
|
|
7
|
+
* The scale factor used to convert floating-point coordinates to integers in the binary format.
|
|
8
|
+
* 1 degree = 1,000,000 units.
|
|
9
|
+
*/
|
|
10
|
+
const SCALE = 1e6;
|
|
11
|
+
/**
|
|
12
|
+
* Default timezones returned when querying latitude 90 (North Pole).
|
|
13
|
+
* Since lines of longitude converge at the poles, all GMT offsets are technically valid.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_LATITUDE_90 = [
|
|
16
|
+
"Etc/GMT",
|
|
17
|
+
...Array.from({ length: 12 }, (_, i) => `Etc/GMT+${i + 1}`),
|
|
18
|
+
...Array.from({ length: 12 }, (_, i) => `Etc/GMT-${i + 1}`)
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Default timezones returned when querying longitude -180 or 180 (International Date Line).
|
|
22
|
+
* Can be either GMT+12 or GMT-12.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_LONGITUDE_ABS_180 = ["Etc/GMT+12", "Etc/GMT-12"];
|
|
25
|
+
/**
|
|
26
|
+
* Checks if the point (x, y) is inside or on the boundary of a polygon ring using the ray casting
|
|
27
|
+
* algorithm.
|
|
28
|
+
* @param dataView - The DataView of the binary data.
|
|
29
|
+
* @param x - The integer-scaled x coordinate of the point relative to the polygon's base x.
|
|
30
|
+
* @param y - The integer-scaled y coordinate of the point relative to the polygon's base y.
|
|
31
|
+
* @param offset - The offset to the start of the polygon ring.
|
|
32
|
+
* @returns A tuple containing a status ('in' | 'on' | false) indicating if the point is inside, on
|
|
33
|
+
* the boundary, or outside the ring, and the offset to the next polygon ring.
|
|
34
|
+
*/
|
|
35
|
+
const isPointInOrOnRing = (dataView, x, y, offset) => {
|
|
36
|
+
const pointsCount = dataView.getUint16(offset, true);
|
|
37
|
+
offset += 2;
|
|
38
|
+
let xPrevious = dataView.getUint16(offset, true);
|
|
39
|
+
offset += 2;
|
|
40
|
+
let yPrevious = dataView.getUint16(offset, true);
|
|
41
|
+
offset += 2;
|
|
42
|
+
const xFirst = xPrevious;
|
|
43
|
+
const yFirst = yPrevious;
|
|
44
|
+
let inside = false;
|
|
45
|
+
for (let i = 1; i <= pointsCount; i++) {
|
|
46
|
+
let xCurrent = xFirst;
|
|
47
|
+
let yCurrent = yFirst;
|
|
48
|
+
if (i < pointsCount) {
|
|
49
|
+
xCurrent = dataView.getUint16(offset, true);
|
|
50
|
+
offset += 2;
|
|
51
|
+
yCurrent = dataView.getUint16(offset, true);
|
|
52
|
+
offset += 2;
|
|
53
|
+
}
|
|
54
|
+
const dx = xCurrent - xPrevious;
|
|
55
|
+
const dy = yCurrent - yPrevious;
|
|
56
|
+
const dpx = x - xPrevious;
|
|
57
|
+
const crossProduct = dx * (y - yPrevious) - dy * dpx;
|
|
58
|
+
if (crossProduct === 0 && (x >= xPrevious && x <= xCurrent || x >= xCurrent && x <= xPrevious) && (y >= yPrevious && y <= yCurrent || y >= yCurrent && y <= yPrevious)) return ["on", offset];
|
|
59
|
+
if (yCurrent > y !== yPrevious > y) {
|
|
60
|
+
if (yCurrent > yPrevious === crossProduct > 0) inside = !inside;
|
|
61
|
+
}
|
|
62
|
+
xPrevious = xCurrent;
|
|
63
|
+
yPrevious = yCurrent;
|
|
64
|
+
}
|
|
65
|
+
return [inside ? "in" : false, offset];
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Checks if the point (x, y) is inside a polygon, accounting for holes (inner rings).
|
|
69
|
+
* @param dataView - The DataView of the binary data.
|
|
70
|
+
* @param x - The integer-scaled x coordinate of the point.
|
|
71
|
+
* @param y - The integer-scaled y coordinate of the point.
|
|
72
|
+
* @param offset - The offset to the start of the polygon.
|
|
73
|
+
* @param xMinBase - The integer-scaled base x coordinate of the polygon.
|
|
74
|
+
* @param yMinBase - The integer-scaled base y coordinate of the polygon.
|
|
75
|
+
* @returns A tuple containing a boolean indicating if the point is inside or on the boundary of the
|
|
76
|
+
* polygon and the offset to the next polygon.
|
|
77
|
+
*/
|
|
78
|
+
const isPointInPolygon = (dataView, x, y, offset, xMinBase, yMinBase) => {
|
|
79
|
+
const size = dataView.getUint16(offset, true);
|
|
80
|
+
offset += 2;
|
|
81
|
+
const nextPolygonOffset = offset + size;
|
|
82
|
+
const ringsCount = dataView.getUint8(offset);
|
|
83
|
+
offset += 1;
|
|
84
|
+
const xMin = dataView.getUint16(offset, true) + xMinBase;
|
|
85
|
+
offset += 2;
|
|
86
|
+
const xMax = dataView.getUint16(offset, true) + xMinBase;
|
|
87
|
+
offset += 2;
|
|
88
|
+
const yMin = dataView.getUint16(offset, true) + yMinBase;
|
|
89
|
+
offset += 2;
|
|
90
|
+
const yMax = dataView.getUint16(offset, true) + yMinBase;
|
|
91
|
+
offset += 2;
|
|
92
|
+
if (x < xMin || x > xMax || y < yMin || y > yMax) return [false, nextPolygonOffset];
|
|
93
|
+
const [where, nextRingOffset] = isPointInOrOnRing(dataView, x - xMinBase, y - yMinBase, offset);
|
|
94
|
+
if (where !== "in") return [where === "on", nextPolygonOffset];
|
|
95
|
+
offset = nextRingOffset;
|
|
96
|
+
for (let i = 1; i < ringsCount; i++) {
|
|
97
|
+
const [where$1, nextRingOffset$1] = isPointInOrOnRing(dataView, x - xMinBase, y - yMinBase, offset);
|
|
98
|
+
if (where$1 !== false) return [where$1 === "on", nextPolygonOffset];
|
|
99
|
+
offset = nextRingOffset$1;
|
|
100
|
+
}
|
|
101
|
+
return [true, nextPolygonOffset];
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Creates a timezone lookup function from the provided binary data.
|
|
105
|
+
*
|
|
106
|
+
* @param arrayBufferOrUint8Array - The binary data containing the timezone database (LLTZ format).
|
|
107
|
+
* @returns {Lookup} A timezone lookup function.
|
|
108
|
+
* @throws An error if the binary data is invalid.
|
|
109
|
+
*/
|
|
110
|
+
const make = (arrayBufferOrUint8Array) => {
|
|
111
|
+
const bytes = ArrayBuffer.isView(arrayBufferOrUint8Array) ? arrayBufferOrUint8Array : new Uint8Array(arrayBufferOrUint8Array);
|
|
112
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
113
|
+
let offset = 0;
|
|
114
|
+
for (let i = 0; i < 8; i++) if (dataView.getUint8(offset + i) !== HEADER.charCodeAt(i)) throw new Error(`invalid file format: missing header ${JSON.stringify(HEADER)}`);
|
|
115
|
+
offset += 8;
|
|
116
|
+
const timezonesLength = dataView.getUint16(offset, true);
|
|
117
|
+
offset += 2;
|
|
118
|
+
const timezones = new TextDecoder().decode(bytes.subarray(offset, offset + timezonesLength)).split("\0");
|
|
119
|
+
offset += timezonesLength;
|
|
120
|
+
const offsetsOffset = offset;
|
|
121
|
+
offset += 180 * 360 * 4;
|
|
122
|
+
const baseOffset = offset;
|
|
123
|
+
return (latitude, longitude) => {
|
|
124
|
+
if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) throw new Error(`invalid latitude or longitude: ${latitude}, ${longitude}`);
|
|
125
|
+
const latitudeIndex = Math.min(latitude + 90 | 0, 179);
|
|
126
|
+
const longitudeIndex = Math.min(longitude + 180 | 0, 359);
|
|
127
|
+
let value = dataView.getUint32(offsetsOffset + (latitudeIndex * 360 + longitudeIndex) * 4, true);
|
|
128
|
+
let tag = value >>> 30;
|
|
129
|
+
if (tag === 1) return [timezones[value & (1 << 30) - 1]];
|
|
130
|
+
if (tag !== 0) {
|
|
131
|
+
const latitudeInteger = (latitude + 90) * SCALE + .5 | 0;
|
|
132
|
+
const longitudeInteger = (longitude + 180) * SCALE + .5 | 0;
|
|
133
|
+
let offset$1 = baseOffset;
|
|
134
|
+
let xMin = longitudeIndex * SCALE;
|
|
135
|
+
let yMin = latitudeIndex * SCALE;
|
|
136
|
+
let xMax = xMin + SCALE;
|
|
137
|
+
let yMax = yMin + SCALE;
|
|
138
|
+
while (tag === 3) {
|
|
139
|
+
offset$1 += value & (1 << 30) - 1;
|
|
140
|
+
const xMid = xMin + xMax >> 1;
|
|
141
|
+
const yMid = yMin + yMax >> 1;
|
|
142
|
+
const quadtreeIndex = (latitudeInteger >= yMid ? 1 : 0) << 1 | (longitudeInteger >= xMid ? 1 : 0);
|
|
143
|
+
value = dataView.getUint32(offset$1 + quadtreeIndex * 4, true);
|
|
144
|
+
tag = value >>> 30;
|
|
145
|
+
offset$1 += 16;
|
|
146
|
+
latitudeInteger >= yMid ? yMin = yMid : yMax = yMid;
|
|
147
|
+
longitudeInteger >= xMid ? xMin = xMid : xMax = xMid;
|
|
148
|
+
}
|
|
149
|
+
if (tag === 1) return [timezones[value & (1 << 30) - 1]];
|
|
150
|
+
else if (tag === 2) {
|
|
151
|
+
const output = [];
|
|
152
|
+
offset$1 += value & (1 << 30) - 1;
|
|
153
|
+
const count = dataView.getUint8(offset$1);
|
|
154
|
+
offset$1 += 1;
|
|
155
|
+
for (let i = 0; i < count; i++) {
|
|
156
|
+
const index = dataView.getUint16(offset$1, true);
|
|
157
|
+
offset$1 += 2;
|
|
158
|
+
const polygonsCount = dataView.getUint8(offset$1);
|
|
159
|
+
offset$1 += 1;
|
|
160
|
+
for (let j = 0; j < polygonsCount; j++) {
|
|
161
|
+
const [isIn, offset_] = isPointInPolygon(dataView, longitudeInteger, latitudeInteger, offset$1, xMin, yMin);
|
|
162
|
+
offset$1 = offset_;
|
|
163
|
+
if (isIn) {
|
|
164
|
+
output.push(timezones[index]);
|
|
165
|
+
for (let k = j + 1; k < polygonsCount; k++) offset$1 += 2 + dataView.getUint16(offset$1, true);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (output.length > 0) return output;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (latitude === 90) return DEFAULT_LATITUDE_90;
|
|
174
|
+
else if (longitude === -180 || longitude === 180) return DEFAULT_LONGITUDE_ABS_180;
|
|
175
|
+
else {
|
|
176
|
+
const output = [];
|
|
177
|
+
const min = Math.ceil(longitude / 15 - .5);
|
|
178
|
+
const max = Math.floor(longitude / 15 + .5);
|
|
179
|
+
for (let n = min; n <= max; n++) output.push(n === 0 ? "Etc/GMT" : n > 0 ? `Etc/GMT-${n}` : `Etc/GMT+${-n}`);
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { make };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lltz",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Utkarsh Kukreti <utkarshkukreti@gmail.com>",
|
|
5
|
+
"description": "A high-performance, memory-efficient offline timezone lookup library for TypeScript using a custom binary format and quadtree spatial indexing.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"geo",
|
|
8
|
+
"latitude",
|
|
9
|
+
"longitude",
|
|
10
|
+
"lookup",
|
|
11
|
+
"timezone"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/utkarshkukreti/lltz",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/utkarshkukreti/lltz.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/utkarshkukreti/lltz/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./dist/index.js",
|
|
28
|
+
"./data/*": "./data/*"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"data/timezones.lltz",
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
|
|
36
|
+
"@types/node": "24.10.8",
|
|
37
|
+
"geo-tz": "8.1.5",
|
|
38
|
+
"prettier": "3.7.4",
|
|
39
|
+
"tinybench": "6.0.0",
|
|
40
|
+
"tsdown": "0.19.0",
|
|
41
|
+
"typescript": "5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc && tsdown",
|
|
45
|
+
"lint": "prettier --check .",
|
|
46
|
+
"format": "prettier --write .",
|
|
47
|
+
"test": "node --max-old-space-size=8192 tests/index.ts",
|
|
48
|
+
"bench": "node benches/index.ts",
|
|
49
|
+
"bench:memory": "node benches/memory.ts lltz:timezones && node benches/memory.ts geo-tz:timezones"
|
|
50
|
+
}
|
|
51
|
+
}
|