tzf v2
2026 年春のアップデートで、tzf のデータ側の作業は完了しました。トポロジー認識簡略化、共有エッジ重複排除、polyline 圧縮により、完全精度データセットは約 90 MB から約 17 MB になりました。このとき変わらなかったのは、そのデータがメモリに届くまでの経路です。どの実装も起動時に protobuf メッセージをオブジェクトグラフへ解析し、そこからクエリ構造を構築してグラフを破棄していました。
v2 ではランタイムとパイプラインの両方から protobuf を削除しました。境界データは、Finder が直接読み込むコンテナで配布されます。
protobuf を削除した理由
解析ステップに起因するコストが 3 つあります。
オープン時間。 Go で完全精度データセットをロードすると 288 ms かかっていました。同じデータセットを新しい形式で開くと 78.5 ms です。Rust では lite データセットが 71 ms からコールド 18 ms になりました。
ロード中のピークメモリ。 デコード後のメッセージと、そこから構築されるクエリ構造は同時に生存するため、高水位は定常状態を大きく上回ります。2026-09-11 のベンチマークスナップショットでは、Go の完全精度 Finder はピーク 315.0 MiB に対して常駐 147.0 MiB です。コンテナのメモリ上限が収容しなければならないのはこのピークです。
インプレースの選択肢がないこと。 protobuf メッセージはどのフィールドを読むにも全体を解析する必要があり、ファイルの数キロバイトを読んでクエリに応答する方式を取れません。数十 MB のメモリに制限された配置に対応する仕組みがありませんでした。
データを生成するパイプラインにも protobuf が含まれ、生成コードとスキーマコンパイラがビルドに入っていました。中間表現は現在、ネイティブな Go の構造体と encoding/gob を使用しており、ビルド内部のもので配布されません。完全精度データセットを生の GeoJSON から再ビルドするとバイト単位で同一の full.tzb が生成され、この方法で変更を検証しました。
埋め込みバイナリ形式
.tzb ファイルはセクション構成のリトルエンディアンコンテナです。64 バイトのヘッダ、セクションテーブル、データセクション、CRC32 フッタで構成されます。ディレクトリは固定幅のレコードで、すべてのリング、グループ、チャンクがバウンディングボックスを持ち、座標は 100000 倍された int32 の度です。読み手は 1° × 1° のグリッドセクションで候補のタイムゾーンを特定し、バウンディングボックスをたどって、それらを通過したジオメトリだけをデコードします。
ヘッダのバイトで選択される 2 つのプロファイルが、このコンテナを共有します。
| プロファイル | 拡張子 | ジオメトリの格納方法 |
|---|---|---|
| E(embedded) | .tzb | チャンク化された zigzag-LEB128 varint ストリーム |
| M(メモリイメージ) | .tzm | (int32, int32) ペアの 1 つのフラット配列 |
M プロファイルはクエリコードが使うレイアウトで点を格納するため、リングのストレージがデコードもコピーもなくマップされたファイルを直接指します。サイズは大きくなり、同じ lite データセットで 3.97 MB に対して 10.18 MB です。full.tzm は約 67 MB になるため配布しておらず、cmd/tzb2tzm により使用するホスト上で生成します。
セクション種別 10 はタイルプレインデックスを保持します。これは以前のバージョンでは独立したファイルとして配布していたものです。2026c データセットでは 87,572 個のタイルを保持し、そのうち 156 個が 2 つのタイムゾーンを指し、サイズは約 880 KB です。公開している 3 つの成果物すべてがこれを含みます。
レイアウト全体は埋め込みバイナリ形式に記載しています。
Finder
Go
コンストラクタは 5 つあり、いずれも tzf.F インターフェイスを返します。このパッケージは Finder 型もオプションもエクスポートしません。
| コンストラクタ | データ | 常駐 | クエリ |
|---|---|---|---|
NewDefaultFinder() | lite .tzm をインプレースで参照 | ヒープ約 12 MB + 読み取り専用 10 MB | 298 ns |
NewEmbeddedFinder() | lite .tzb をインプレースで参照 | 約 3 MB | 約 6 µs |
NewFullFinder() | full .tzb を展開 | 約 145 MB | 約 300 ns |
NewFinderFromTZB(data) | 呼び出し側が用意した .tzb | ファイルに依存 | 約 290 ns |
NewFinderFromTZM(data) | 呼び出し側が用意した .tzm | ファイルに依存 | 約 300 ns |
NewEmbeddedFinder は新規追加です。ファイルバイト列に加えて 1 KB 未満のヒープを保持し、アロケーションなしでクエリを処理します。呼び出し側が既に保持しているバイト列や mmap した領域に対しては、x.NewFinderFromTZBReaderAt が io.ReaderAt を受け取ります。x パッケージはモジュールのセマンティックバージョニングの約束の対象外です。
GeoJSONer は F の一部ではなくアサートして使用する形式であり、境界ポリゴンに加えてプレインデックスタイルもエクスポートするようになりました。
Rust
tzf-rs 2.0 は DefaultFinder と EmbeddedFinder を提供します。FuzzyFinder、Finder、FinderOptions は削除されました。プレインデックスは両方の Finder 内部の高速パスであり、YStripes インデックスは常に構築されます。この crate が読み込むのは .tzb のみで、.tzm のバイト列に対しては Error::Profile を返します。crates.io のパッケージには lite.tzb(約 4 MB)が含まれ、full.tzb(約 14 MB)は full feature を通じて git から取得します。
バイト列を受け取るコンストラクタは Result を返すようになりました。ファイルはオープン時に CRC と構造の検証を受けるため、不正なデータは空の Finder ではなくエラーとして現れます。
Python
tzfpy 2.0 は tzf-rs 2.0 をバインドします。4 つのクエリ関数は変更されておらず、GeoJSON のエクスポート関数が 2 つ追加されました。protobuf 経路の削除により wheel は 4.31 MB から 2.76 MB に、Finder の初期化は 68 ms から 15 ms になり、クエリレイテンシは変わりませんでした。_TZFPY_DISABLE_Y_STRIPES の環境変数は、それが制御していたオプションとともに削除されました。
測定値
Apple M3 Max、2026c データセットによる測定です。オープン時間と常駐メモリは v2 の設計記録から、クエリレイテンシ、精度、メモリの各列は、公開された 2.0.0 リリースを対象に同じマシンでローカルに取得した tz-benchmark の 2026-09-11 スナップショットからの値です。
| 経路 | オープン | 常駐 | クエリ(世界都市) |
|---|---|---|---|
| Go、full protobuf (v1) | 288 ms | 約 153 MB | 約 290 ns |
Go、full .tzb を展開 | 78.5 ms | 約 145 MB | 約 300 ns |
Go、lite .tzm | 7.7 ms | ヒープ約 12 MB + 読み取り専用 10 MB | 298 ns |
Go、lite .tzb をインプレースで参照 | 1.7 ms | 約 3 MB | 約 6 µs |
| Rust、lite protobuf (v1) | 71 ms | ピーク RSS 77.8 MiB | 316 ns |
Rust、lite .tzb | コールド 18 ms | ピーク RSS 44.1 MiB | 260 ns |
Go と Rust は、成果物ごとに約 195,000 件の境界を多く含むサンプルと世界都市のデータセットに対して、GetTimezoneName と GetTimezoneNames で同一の結果を返します。完全精度の正解データに対して、lite の Finder は 154,694 件の世界都市のうち 1 件(0.0006%)で異なる結果を返し、その結果も UTC オフセットは同じです。
Rust 向けにも .tzm のローダーを実装しました。オープン時間を約 3 ms 短縮する一方でピーク RSS が 44.1 MiB から 68.7 MiB に増加したため削除し、tzf-rs が読み込むのは E プロファイルのみとなっています。
移行
3 つの言語すべてでクエリのインターフェイスは変わっていません。大部分の呼び出し箇所は依存関係の変更のみで済みます。
- Go:モジュールパスが
github.com/ringsaturn/tzf/v2になります。対応表は Go ガイドを参照してください。 - Rust:
DefaultFinder::new()とnew_full()のシグネチャは変わりません。削除された型は Rust ガイドに一覧があります。 - Python:Python ガイドを参照してください。
- どのコンストラクタがどの配置に適合するかは Finder の選択に記載しています。
v1 系列の状況
tzf v1.2.x、tzf-rs 1.3.x、tzfpy 1.3.x は引き続き利用でき、動作します。tzf-dist は protobuf の成果物の配布を終了するため、これらのバージョンは最後のデータリリースで凍結されます。更新された境界データを使用するには v2 への移行が必要です。tzf-wasm 2.0.0 は tzf-rs 2.0.0 の上に構築されており、tzf-web はそれを使用しています。tzf-swift は v1 系列の上に構築されています。tzf-rb は HarlemSquirrel が保守するサードパーティの Ruby バインディングであり、現在は v1 系列の上に構築されています。
謝辞
YStripes インデックスは Josh Baker の tidwall/tg によるものです。tzf はこれを再実装せず移植しています。境界データは IANA タイムゾーンデータベースのリリースを追跡する evansiroky/timezone-boundary-builder によるものです。v2 の実装と Go・Rust 間の一致検証は、Claude と Codex を用いた複数ラウンドの実装、検証、リファクタリングによって行いました。