stale_orbit

NocoBaseコミュニティで繰り返し報告されるバグに、「すべての時刻が8時間ずれている」「日付が前日になる」というものがあります。特に中国語フォーラムでよく見られます。中国はUTC+8なので、ずれも8時間です。私はUTC+9でインスタンスを運用していますが、予想通り9時間のずれが発生しました。オフセットが何であれ、その分だけずれます。

このパターンは、偶然の破損ではなく、明確な仕組みであることを強く示唆しています。そこで、NocoBase 2.xをPostgreSQLとMySQLの両方で実際に構築し、実際に保存される値と再解釈の方法を計測し、謎に具体的な答えを導き出しました。

テスト環境:NocoBase 2.0.51および2.1.23(公式Dockerイメージ)× PostgreSQL 16およびMySQL 8.4。すべてのデータはREST APIを通じて書き込み・読み込みを行い、サーバーのタイムゾーンはコンテナのTZ環境変数で制御しました。ブラウザ側のレンダリングは意図的に無視し、ここではサーバーが何を保存し、どのように解釈するかに焦点を当てています。

背景:2.xには4種類の日時フィールド型がある

NocoBase 2.xのコレクションには、4種類の日時関連フィールド型があります(公式リスト — ただし、タイプごとの詳細ページの多くに「To be added」と記載されているため、実際に計測しました):

用途
Datetime(タイムゾーンあり) 絶対的な瞬間 — イベント開始時刻、ログなど
Datetime(タイムゾーンなし) そのまま保存したい時刻(壁時計時間)
Date only 誕生日、締切日、記念日など
Unix timestamp システム連携

計測1:各型が実際に保存するもの

「2026-07-12 09:00」をxlsxでインポートし、各データベースの生の値を確認しました(2.0.51と2.1.23で同一):

フィールド型 PostgreSQL MySQL
Datetime(タイムゾーンあり) timestamptz2026-07-12 09:00:00+09オフセットを含む絶対的な瞬間 DATETIME2026-07-12 09:00:00壁時計時間のみ — オフセット情報なし
Datetime(タイムゾーンなし) timestamp09:00:00 DATETIME09:00:00
Date only date2026-07-12 date2026-07-12

最初の行がすべてを物語っています。同じフィールド型「Datetime(タイムゾーンあり)」でも、PostgreSQLでは絶対的な瞬間として保存され、MySQLでは裸の壁時計時間として保存されます。MySQLのDATETIMEにはオフセットを格納する場所がありません。次の実験のためにこの点を覚えておいてください。

計測2:サーバーのTZを変更すると保存されたデータの意味が変わる(MySQL)

MySQL環境で、サーバーをTZ=UTCで動作させながら「2026-07-12 09:00」を書き込みました。その後、アプリコンテナをTZ=Asia/Tokyoに変更して再起動しました。他は何も変更していません。

# MySQLの生の値(1バイトも変わっていない)
dt_tz: 2026-07-12 09:00:00

# APIが返す値
TZ=UTCの場合        → "2026-07-12T09:00:00.000Z"   (= UTC+9では18:00)
TZ=Asia/Tokyoの場合 → "2026-07-12T00:00:00.000Z"   (= UTC+9では09:00)

Enter fullscreen mode Exit fullscreen mode

ディスク上のデータはバイト単位で同一ですが、絶対的な瞬間としての意味が9時間ずれる。 MySQLのDATETIMEは「この09:00がどのタイムゾーンのものか」を記録しないため、NocoBaseは読み書きのたびにサーバーのTZで解釈する必要があります。後からTZを変更すると、データベースに保存されたすべての日時が一斉に意味を変えてしまうのです。

この仕組みが、「8時間ずれ」という繰り返しの報告のほとんどを説明します。インスタンスがTZ未設定(= UTC)で開始され、後でローカル時間に設定された場合や、ステージングと本番でTZが異なる場合などです。データは損なわれておらず、解釈が変わっただけです。

ではPostgreSQLではどうでしょうか。同じ実験を行いましたが、値は動きませんでした。 timestamptzにオフセットが保存されているため、再解釈の余地がありません。

ここから2つのルールが導き出されます:

  1. 初日にサーバーのTZを選び、決して変更しない — 特にMySQLの場合。composeファイルで明示的に固定し、開発・ステージング・本番などすべての環境で同一に保つ。
  2. データベースを選べるなら、PostgreSQLはこの種の事故に対して構造的に免疫がある。

別件:インポート日付バグ(実在し、修正済み)

日付のずれがすべてこの仕組みによるものとは限りません。2.x時代に実際に存在したバグもありました。v2.0.44〜2.0.51では、データベース・アプリ・サーバーのタイムゾーンがすべて一致していても、CSV/Excelインポートで日付が1日前に保存されることがありました。これは欠陥として確認され、2026年5月中旬に修正されました(フォーラムスレッド t/12426t/12494)。

参考までに、2.0.51でAPI経由のxlsx/CSVインポートは再現できませんでした。両方のデータベースで正しく保存されました。報告はUI経由のパスが関与していた可能性があり、他のルートや環境要因が影響したと考えられます。いずれにせよ、現在のバージョンでは修正されています。

実践的な教訓:日付がずれた場合、「これはこの仕組みによるものか?」と「これは自分のバージョンで既知のバグか?」を区別することです。計測1と2を理解すれば前者の問いに答えられ、フォーラム検索(中国語カテゴリを含む — 最も活発です)で後者の問いに答えられます。

チェックリスト

  1. フィールド型を意味に合わせる。 誕生日や締切日は「Date only」に。絶対的な瞬間は「Datetime(タイムゾーンあり)」に。「Datetime(タイムゾーンあり)」を日付のみのデータに使うと、日付が1日ずれて表示される典型的な原因になります。
  2. 初日にcomposeファイルでサーバーのTZを固定し、以後触らない(特にMySQL)。UTCでもローカルゾーンでも構いません。どちらかを選んで一貫して使用してください。
  3. インポート後は1件のレコードを開いて日時を確認する。 これらのずれはすべての行に一様に影響します。1件のレコードで全体がわかります。
  4. 何かずれている場合:バージョン確認後、フォーラムを検索する — 特に中国語カテゴリで、実際の報告のほとんどがそこで見つかります。

まとめ

  • NocoBase 2.xの「Datetime(タイムゾーンあり)」は、PostgreSQLでは絶対的な瞬間ですが、MySQLでは壁時計時間 + TZによる解釈です(実測)。
  • そのため、後からサーバーのTZを変更すると、MySQLでは保存されたすべての日時の意味がずれてしまいます。これが「8時間ずれ」(または9時間、またはオフセットに応じたずれ)の繰り返しの原因です。
  • インポート時の日付ずれバグは実在しましたが、修正済みです。仕組みと既知のバグを区別することで、診断が迅速になります。

(2.0.51 / 2.1.23 × PostgreSQL 16 / MySQL 8.4で計測。将来的なバージョンで動作が変わる可能性があります。)

参考文献