lltz 1.1.0 → 1.2.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/README.md +42 -0
- package/data/timezones.lltz +0 -0
- package/dist/index.js +18 -19
- package/dist/server.d.ts +0 -1
- package/dist/server.js +1 -3
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -12,6 +12,48 @@ This library uses GeoJSON data from [timezone-boundary-builder](https://github.c
|
|
|
12
12
|
- **Universal**: Runs in both server (Node.js, Bun) and client (browser) environments.
|
|
13
13
|
- **High Accuracy**: Validated to match [`geo-tz`](https://www.npmjs.com/package/geo-tz) results in **>99.99%** of cases.
|
|
14
14
|
|
|
15
|
+
## Performance
|
|
16
|
+
|
|
17
|
+
The `benches` directory contains performance benchmarks comparing `lltz` with `geo-tz`. These are the performance numbers obtained on Node 24.13.0 on an Apple M4 Pro CPU.
|
|
18
|
+
|
|
19
|
+
### CPU
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ node benches/index.ts lltz:timezones geo-tz:timezones
|
|
23
|
+
┌─────────┬───────────────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
|
|
24
|
+
│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
|
|
25
|
+
├─────────┼───────────────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
|
|
26
|
+
│ 0 │ 'lltz: timezones: 1000x <random>' │ '41876 ± 0.20%' │ '41250 ± 1458.0' │ '24112 ± 0.10%' │ '24242 ± 851' │ 23880 │
|
|
27
|
+
│ 1 │ 'lltz: timezones: 1000x <random> in central US' │ '31205 ± 0.23%' │ '30791 ± 583.00' │ '32386 ± 0.07%' │ '32477 ± 626' │ 32047 │
|
|
28
|
+
│ 2 │ 'geo-tz: timezones: 1000x <random>' │ '1809670 ± 8.08%' │ '1270167 ± 425375' │ '819 ± 3.92%' │ '787 ± 279' │ 553 │
|
|
29
|
+
│ 3 │ 'geo-tz: timezones: 1000x <random> in central US' │ '237485 ± 1.28%' │ '231583 ± 4875.0' │ '4291 ± 0.23%' │ '4318 ± 91' │ 4211 │
|
|
30
|
+
└─────────┴───────────────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
lltz is roughly 30x faster than geo-tz for random lookups in the entire world and 7x faster for random lookups in the central US.
|
|
34
|
+
|
|
35
|
+
### Memory
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ node benches/memory.ts lltz:timezones && node benches/memory.ts geo-tz:timezones
|
|
39
|
+
lltz: timezones
|
|
40
|
+
Δ time: 56.807 ms
|
|
41
|
+
Δ rss: 57.891 MiB
|
|
42
|
+
Δ heapTotal: 4.250 MiB
|
|
43
|
+
Δ heapUsed: 0.824 MiB
|
|
44
|
+
Δ external: 42.179 MiB
|
|
45
|
+
Δ arrayBuffers: 42.015 MiB
|
|
46
|
+
geo-tz: timezones
|
|
47
|
+
Δ time: 1560.153 ms
|
|
48
|
+
Δ rss: 1581.656 MiB
|
|
49
|
+
Δ heapTotal: 1528.344 MiB
|
|
50
|
+
Δ heapUsed: 1431.117 MiB
|
|
51
|
+
Δ external: -1.598 MiB
|
|
52
|
+
Δ arrayBuffers: -1.688 MiB
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Loading the data from disk and then querying 1 million random points is 27x faster with lltz than with geo-tz and uses 27x less memory.
|
|
56
|
+
|
|
15
57
|
## Usage
|
|
16
58
|
|
|
17
59
|
### Installation
|
package/data/timezones.lltz
CHANGED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -94,9 +94,9 @@ const isPointInPolygon = (dataView, x, y, offset, xMinBase, yMinBase) => {
|
|
|
94
94
|
if (where !== "in") return [where === "on", nextPolygonOffset];
|
|
95
95
|
offset = nextRingOffset;
|
|
96
96
|
for (let i = 1; i < ringsCount; i++) {
|
|
97
|
-
const [where
|
|
98
|
-
if (where
|
|
99
|
-
offset = nextRingOffset
|
|
97
|
+
const [where, nextRingOffset] = isPointInOrOnRing(dataView, x - xMinBase, y - yMinBase, offset);
|
|
98
|
+
if (where !== false) return [where === "on", nextPolygonOffset];
|
|
99
|
+
offset = nextRingOffset;
|
|
100
100
|
}
|
|
101
101
|
return [true, nextPolygonOffset];
|
|
102
102
|
};
|
|
@@ -130,39 +130,39 @@ const make = (arrayBufferOrUint8Array) => {
|
|
|
130
130
|
if (tag !== 0) {
|
|
131
131
|
const latitudeInteger = (latitude + 90) * SCALE + .5 | 0;
|
|
132
132
|
const longitudeInteger = (longitude + 180) * SCALE + .5 | 0;
|
|
133
|
-
let offset
|
|
133
|
+
let offset = baseOffset;
|
|
134
134
|
let xMin = longitudeIndex * SCALE;
|
|
135
135
|
let yMin = latitudeIndex * SCALE;
|
|
136
136
|
let xMax = xMin + SCALE;
|
|
137
137
|
let yMax = yMin + SCALE;
|
|
138
138
|
while (tag === 3) {
|
|
139
|
-
offset
|
|
139
|
+
offset += value & (1 << 30) - 1;
|
|
140
140
|
const xMid = xMin + xMax >> 1;
|
|
141
141
|
const yMid = yMin + yMax >> 1;
|
|
142
142
|
const quadtreeIndex = (latitudeInteger >= yMid ? 1 : 0) << 1 | (longitudeInteger >= xMid ? 1 : 0);
|
|
143
|
-
value = dataView.getUint32(offset
|
|
143
|
+
value = dataView.getUint32(offset + quadtreeIndex * 4, true);
|
|
144
144
|
tag = value >>> 30;
|
|
145
|
-
offset
|
|
145
|
+
offset += 16;
|
|
146
146
|
latitudeInteger >= yMid ? yMin = yMid : yMax = yMid;
|
|
147
147
|
longitudeInteger >= xMid ? xMin = xMid : xMax = xMid;
|
|
148
148
|
}
|
|
149
149
|
if (tag === 1) return [timezones[value & (1 << 30) - 1]];
|
|
150
150
|
else if (tag === 2) {
|
|
151
151
|
const output = [];
|
|
152
|
-
offset
|
|
153
|
-
const count = dataView.getUint8(offset
|
|
154
|
-
offset
|
|
152
|
+
offset += value & (1 << 30) - 1;
|
|
153
|
+
const count = dataView.getUint8(offset);
|
|
154
|
+
offset += 1;
|
|
155
155
|
for (let i = 0; i < count; i++) {
|
|
156
|
-
const index = dataView.getUint16(offset
|
|
157
|
-
offset
|
|
158
|
-
const polygonsCount = dataView.getUint8(offset
|
|
159
|
-
offset
|
|
156
|
+
const index = dataView.getUint16(offset, true);
|
|
157
|
+
offset += 2;
|
|
158
|
+
const polygonsCount = dataView.getUint8(offset);
|
|
159
|
+
offset += 1;
|
|
160
160
|
for (let j = 0; j < polygonsCount; j++) {
|
|
161
|
-
const [isIn, offset_] = isPointInPolygon(dataView, longitudeInteger, latitudeInteger, offset
|
|
162
|
-
offset
|
|
161
|
+
const [isIn, offset_] = isPointInPolygon(dataView, longitudeInteger, latitudeInteger, offset, xMin, yMin);
|
|
162
|
+
offset = offset_;
|
|
163
163
|
if (isIn) {
|
|
164
164
|
output.push(timezones[index]);
|
|
165
|
-
for (let k = j + 1; k < polygonsCount; k++) offset
|
|
165
|
+
for (let k = j + 1; k < polygonsCount; k++) offset += 2 + dataView.getUint16(offset, true);
|
|
166
166
|
break;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -181,6 +181,5 @@ const make = (arrayBufferOrUint8Array) => {
|
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
183
|
};
|
|
184
|
-
|
|
185
184
|
//#endregion
|
|
186
|
-
export { make };
|
|
185
|
+
export { make };
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { make as make$1 } from "./index.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
|
|
4
3
|
//#region src/server.ts
|
|
5
4
|
const load = () => {
|
|
6
5
|
try {
|
|
@@ -23,6 +22,5 @@ const load = () => {
|
|
|
23
22
|
* @throws An error if the binary data is invalid or if the built-in data file cannot be loaded.
|
|
24
23
|
*/
|
|
25
24
|
const make = (arrayBufferOrUint8Array) => make$1(arrayBufferOrUint8Array ?? load());
|
|
26
|
-
|
|
27
25
|
//#endregion
|
|
28
|
-
export { make };
|
|
26
|
+
export { make };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lltz",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"author": "Utkarsh Kukreti <utkarshkukreti@gmail.com>",
|
|
5
5
|
"description": "A high-performance, memory-efficient offline timezone lookup library for TypeScript using a custom binary format and quadtree spatial indexing.",
|
|
6
6
|
"keywords": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"type": "module",
|
|
23
|
-
"packageManager": "pnpm@10.
|
|
23
|
+
"packageManager": "pnpm@10.32.1",
|
|
24
24
|
"main": "./dist/index.js",
|
|
25
25
|
"module": "./dist/index.js",
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"bench:memory": "node benches/memory.ts lltz:timezones && node benches/memory.ts geo-tz:timezones"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@ianvs/prettier-plugin-sort-imports": "4.7.
|
|
47
|
-
"@types/node": "24.
|
|
48
|
-
"geo-tz": "8.1.
|
|
49
|
-
"prettier": "3.
|
|
46
|
+
"@ianvs/prettier-plugin-sort-imports": "4.7.1",
|
|
47
|
+
"@types/node": "24.12.0",
|
|
48
|
+
"geo-tz": "8.1.6",
|
|
49
|
+
"prettier": "3.8.1",
|
|
50
50
|
"tinybench": "6.0.0",
|
|
51
|
-
"tsdown": "0.
|
|
51
|
+
"tsdown": "0.21.1",
|
|
52
52
|
"typescript": "5.9.3"
|
|
53
53
|
}
|
|
54
54
|
}
|