lltz 1.0.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 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
@@ -28,6 +70,21 @@ The other variants, `timezones-1970.lltz` and `timezones-now.lltz`, can be downl
28
70
 
29
71
  ### Node.js / Bun
30
72
 
73
+ #### Simple Usage
74
+
75
+ ```typescript
76
+ import * as Lltz from 'lltz/server'
77
+
78
+ const lookup = Lltz.make() // Automatically loads the built-in timezones.lltz file
79
+
80
+ const timezones = lookup(40.7128, -74.006) // New York
81
+ console.log(timezones) // ['America/New_York']
82
+ ```
83
+
84
+ #### Manual Data Loading
85
+
86
+ If you need to use a different data file or have custom requirements:
87
+
31
88
  ```typescript
32
89
  import fs from 'node:fs'
33
90
 
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$1, nextRingOffset$1] = isPointInOrOnRing(dataView, x - xMinBase, y - yMinBase, offset);
98
- if (where$1 !== false) return [where$1 === "on", nextPolygonOffset];
99
- offset = nextRingOffset$1;
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$1 = baseOffset;
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$1 += value & (1 << 30) - 1;
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$1 + quadtreeIndex * 4, true);
143
+ value = dataView.getUint32(offset + quadtreeIndex * 4, true);
144
144
  tag = value >>> 30;
145
- offset$1 += 16;
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$1 += value & (1 << 30) - 1;
153
- const count = dataView.getUint8(offset$1);
154
- offset$1 += 1;
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$1, true);
157
- offset$1 += 2;
158
- const polygonsCount = dataView.getUint8(offset$1);
159
- offset$1 += 1;
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$1, xMin, yMin);
162
- offset$1 = 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$1 += 2 + dataView.getUint16(offset$1, true);
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 };
@@ -0,0 +1,14 @@
1
+ import { Lookup } from "./index.js";
2
+
3
+ //#region src/server.d.ts
4
+ /**
5
+ * Creates a timezone lookup function from the provided binary data.
6
+ *
7
+ * @param arrayBufferOrUint8Array - Optional binary data containing the timezone database (LLTZ
8
+ * format). If not provided, the built-in `timezones.lltz` file will be automatically loaded.
9
+ * @returns {Lookup} A timezone lookup function.
10
+ * @throws An error if the binary data is invalid or if the built-in data file cannot be loaded.
11
+ */
12
+ declare const make: (arrayBufferOrUint8Array?: ArrayBuffer | Uint8Array) => Lookup;
13
+ //#endregion
14
+ export { type Lookup, make };
package/dist/server.js ADDED
@@ -0,0 +1,26 @@
1
+ import { make as make$1 } from "./index.js";
2
+ import fs from "node:fs";
3
+ //#region src/server.ts
4
+ const load = () => {
5
+ try {
6
+ return fs.readFileSync(new URL(import.meta.resolve("lltz/data/timezones.lltz")));
7
+ } catch {}
8
+ try {
9
+ return fs.readFileSync(new URL("../data/timezones.lltz", import.meta.url));
10
+ } catch {}
11
+ try {
12
+ return fs.readFileSync("node_modules/lltz/data/timezones.lltz");
13
+ } catch {}
14
+ throw new Error("failed to load built-in timezones.lltz file");
15
+ };
16
+ /**
17
+ * Creates a timezone lookup function from the provided binary data.
18
+ *
19
+ * @param arrayBufferOrUint8Array - Optional binary data containing the timezone database (LLTZ
20
+ * format). If not provided, the built-in `timezones.lltz` file will be automatically loaded.
21
+ * @returns {Lookup} A timezone lookup function.
22
+ * @throws An error if the binary data is invalid or if the built-in data file cannot be loaded.
23
+ */
24
+ const make = (arrayBufferOrUint8Array) => make$1(arrayBufferOrUint8Array ?? load());
25
+ //#endregion
26
+ export { make };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lltz",
3
- "version": "1.0.0",
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,12 +20,13 @@
20
20
  },
21
21
  "license": "MIT",
22
22
  "type": "module",
23
- "packageManager": "pnpm@10.28.0",
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",
27
27
  "exports": {
28
28
  ".": "./dist/index.js",
29
+ "./server": "./dist/server.js",
29
30
  "./data/*": "./data/*"
30
31
  },
31
32
  "files": [
@@ -42,12 +43,12 @@
42
43
  "bench:memory": "node benches/memory.ts lltz:timezones && node benches/memory.ts geo-tz:timezones"
43
44
  },
44
45
  "devDependencies": {
45
- "@ianvs/prettier-plugin-sort-imports": "4.7.0",
46
- "@types/node": "24.10.8",
47
- "geo-tz": "8.1.5",
48
- "prettier": "3.7.4",
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",
49
50
  "tinybench": "6.0.0",
50
- "tsdown": "0.19.0",
51
+ "tsdown": "0.21.1",
51
52
  "typescript": "5.9.3"
52
53
  }
53
54
  }