n8nからAirtableに入れた日時が9時間ずれる原因と書き方

n8nからAirtableに日時を書き込んだら、画面では9時間あとの時刻になっていた。そんな場面のための記事です。予定の候補日や予約の時刻のように、人が画面で見て動く値だと、ずれたまま気づかずに使われます。
この記事の結論
- Airtableの日時の列(dateTime)は、ISO 8601の文字列を「UTCの時刻」として受け取ります。末尾の
ZはUTCという意味です。日本時間の正午のつもりで2026-05-15T12:00:00.000Zと書くと、日本時間の画面では21時になります。 - Codeノードでよく使う
new Date(...).toISOString()は、いつもZ付きのUTCで文字列を作ります。日本時間の数字にZを付けてしまう形が、ずれの典型です。 - 直し方は2つです。日本時間の時刻に
+09:00を付けるか、UTCに直してからZを付けます。 - n8nのAirtableノードで列を1つずつ指定している場合、
+09:00もZも無い文字列は、ワークフローのタイムゾーンの時刻として読まれます。その設定が無いと既定はAmerica/New_Yorkです。時差を書かない文字列は使わないのが安全です。
実際に起きたこと
弊社では、予定を管理する表をAirtableに置いています。日時の列のタイムゾーンは Asia/Tokyo に設定してありました。
2026年4月30日、この表に予定の候補日を3件登録しました。書き込んだ文字列は T10:00:00.000Z の形です。
ところが、画面には「5/14(木) 19:00」と出ました。3件とも同じように9時間あとの時刻になっていました。画面の写しを見た代表の指摘で気づきました。
このときの書き込みは、n8nではなく、AIエージェントからMCP(AIが外部の道具を呼ぶための仕組み)経由でAirtableへ直接行ったものです。ただ、同じ書き方はn8nのCodeノードでもそのまま起きます。そこで弊社では、n8nのワークフローを作る前・直す前に通す点検の手順(preflight)に、この形を見つける検査を足しました。
(r"new\s+Date\([^)]+\)\.toISOString\(\)",
"ℹ️ Airtable dateTime に渡す場合は +09:00 形式推奨(toISOString は UTC 'Z' 終端)",
点検の手順の「知っている落とし穴」の一覧にも、同じことを1行で書いています。
- Airtable dateTime は `+09:00` 終端推奨(`Z` だと UTC 解釈で 9 時間ズレる)
原因
Airtableは日時をUTCとして受け取る
Airtableの公式ドキュメント(Field model)では、日時の列の値は「ISO 8601形式の文字列」で、中身は「UTCの日時」と説明されています。例として 2022-09-05T07:00:00.000Z が載っています。
弊社の件では、列のタイムゾーンを Asia/Tokyo にしていても、Z 付きの値はUTCの時刻として保存されました。そして画面には日本時間に直して表示されました。日本時間はUTCより9時間進んでいます。だから 10:00:00.000Z は日本時間の19時と表示されたわけです。
つまり、Airtableは間違っていません。「日本時間の数字」に「UTCの印」を付けて渡したことが原因です。
CodeノードのtoISOStringはいつもUTCになる
JavaScriptの Date の toISOString() は、UTCに直した時刻に Z を付けて返します。手元で試すと次のとおりでした。
new Date('2026-05-15T12:00:00+09:00').toISOString()
// => "2026-05-15T03:00:00.000Z"
この結果そのものは正しい値です。日本時間の12時はUTCの3時だからです。危ないのは、日本時間の「年月日と時分」を文字列で組み立てて、最後に .000Z を足すような書き方です。数字は日本時間なのに、印はUTCになります。
時差を書かない文字列は、n8nのタイムゾーンで読まれる
n8nのAirtableノード(v2)では、Airtableの dateTime と date の列を、n8nの中の日時の型として扱います。n8n本体のソースでは次のように対応づけています。
dateTime: ['dateTime', 'date'],
列を1つずつ指定するモード(columns.mappingMode が defineBelow)では、入力した値が送信の前に日時として読み直されます。作成(create)と更新(update)のどちらの処理でも同じです。読み直す関数 tryToParseDateTime の中心はこうです。
const isoDate = luxon_1.DateTime.fromISO(dateString, { zone: defaultZone, setZone: true });
ここで使う Luxon(n8nに入っている日時のライブラリ)は、文字列に時差が書いてあればその時差をそのまま使います。書いていなければ、Luxonの既定のタイムゾーンで読みます。その既定は、n8nがワークフローごとに次のように決めています。
this.timezone = workflow.settings?.timezone ?? (0, global_state_1.getGlobalState)().defaultTimezone;
luxon_1.Settings.defaultZone = this.timezone;
ワークフローの設定にタイムゾーンが無ければ、n8n全体のタイムゾーンを使います。n8n全体のタイムゾーンは環境変数 GENERIC_TIMEZONE で決まり、ソースの既定値は次のとおりです。
this.timezone = 'America/New_York';
つまり 2026-05-15T12:00:00 のような時差の無い文字列は、どのn8nで動かすかで意味が変わります。日本時間に設定したn8nなら日本時間の12時です。設定していないn8nなら、ニューヨークの12時として送られます。
なお、入力をそのまま流すモード(autoMapInputData)と、オプションの typecast をオンにしたときは、この読み直しを通りません。文字列はそのままAirtableへ届き、Airtable側の解釈になります。
直し方
手順1:日本時間の時刻には +09:00 を付ける
いちばん確実なのは、送る文字列に時差を書くことです。日本時間の正午なら次のどちらかで送ります。
"2026-05-15T12:00:00+09:00" // 日本時間の12時(おすすめ)
"2026-05-15T03:00:00.000Z" // UTCの3時=日本時間の12時(同じ時刻)
次の2つは使いません。
"2026-05-15T12:00:00.000Z" // 日本時間の画面では 21:00 になる
"2026-05-15T12:00:00" // 時差が無い=どのタイムゾーンで読まれるか環境しだい
手順2:Codeノードでは Luxon で日本時間を作る
Codeノードの中では、Luxonの DateTime がそのまま使えます。setZone で日本時間にしてから toISO() を呼ぶと、末尾に +09:00 が付きます。
// 今の時刻を日本時間で
const nowJst = DateTime.now().setZone('Asia/Tokyo').toISO();
// => "2026-05-15T12:00:00.000+09:00" のような形
// 「2026-05-15 の 12:00(日本時間)」を組み立てる
const slot = DateTime.fromISO('2026-05-15T12:00:00', { zone: 'Asia/Tokyo' }).toISO();
// => "2026-05-15T12:00:00.000+09:00"
return [{ json: { scheduled_at: slot } }];
new DateTime() と書くとエラーになる点は、別の記事「n8n「DateTime is not a constructor」の原因と直し方(Luxon)」にまとめています。
Luxonを使わずに書くなら、点検の手順に載せている書き方もあります。日本時間の日時の文字列を作り、最後に +09:00 を足します。
new Date().toLocaleString('sv',{timeZone:'Asia/Tokyo'}).replace(' ','T')+'+09:00'
手順3:今ある toISOString の使い方を探す
すでに動いているワークフローの中から、日時の列へ toISOString() の結果を渡している箇所を探します。弊社の点検で使っている正規表現はこれです。
new\s+Date\([^)]+\)\.toISOString\(\)
見つかった箇所が「UTCの正しい時刻」を作っているなら、直す必要はありません。日本時間の数字に Z を付けているなら、手順1か手順2の形に直します。
手順4:n8nのタイムゾーンを日本時間にしておく
時差の無い文字列が混ざったときの保険として、n8n全体のタイムゾーンを日本時間にしておきます。セルフホストなら環境変数で設定します。
export GENERIC_TIMEZONE="Asia/Tokyo"
この設定はスケジュールの時刻にも効きます。未設定でスケジュールがずれる件は「n8nのスケジュールがずれる原因と直し方(タイムゾーン設定)」に書きました。ワークフローの設定でタイムゾーンを指定している場合は、そちらが優先されます。
直ったかを確かめる
確かめ方は3つです。
- Airtableの画面で見る。書き込んだ行を開き、日本時間で意図した時刻になっているかを見ます。
- APIで読み戻す。読み戻した値はUTCの
Z付きで返ってきます。日本時間の12時を入れたなら03:00:00.000Zになっていれば正しい値です。12:00:00.000Zなら、まだ9時間ずれています。 - 送る前の文字列を手元で確かめる。n8nに入っているLuxonで、時差の有無による違いを試せます。弊社の手元のn8nは
2.19.2、中のLuxonは3.7.2で、結果は次のとおりでした。
const { DateTime, Settings } = require('luxon');
Settings.defaultZone = 'Asia/Tokyo';
DateTime.fromISO('2026-05-15T12:00:00', { setZone: true }).toJSON();
// => "2026-05-15T12:00:00.000+09:00"
DateTime.fromISO('2026-05-15T12:00:00.000Z', { setZone: true }).toJSON();
// => "2026-05-15T12:00:00.000Z" ← Z はそのまま UTC
Settings.defaultZone = 'America/New_York';
DateTime.fromISO('2026-05-15T12:00:00', { setZone: true }).toJSON();
// => "2026-05-15T12:00:00.000-04:00" ← 時差なしはニューヨーク時間になる
3つ目の結果が、時差を書かない文字列を避ける理由です。同じ文字列でも、n8nのタイムゾーンしだいで送られる時刻が変わります。
それでも直らないとき
- 読み出した値を、日本時間と思って使っていないか。Airtableから読み戻した値は
Z付きのUTCです。その文字列から時刻の部分だけを切り出して画面やメッセージに出すと、今度は読む側で9時間ずれます。表示する前に日本時間に直します。 - ワークフローの設定のタイムゾーンを見る。n8n全体を日本時間にしても、ワークフローの設定で別のタイムゾーンを選んでいれば、そちらが使われます。
- まだ時差の無い文字列を送っていないか。実行結果のAirtableノードの入力を開き、日時の値の末尾に
+09:00かZが付いているかを見ます。どちらも無ければ手順1に戻ります。
まとめ
- Airtableの日時の列はUTCで受け取ります。末尾の
ZはUTCの印です。 - 日本時間の数字に
Zを付けると、日本時間の画面では9時間あとになります。 - 日本時間なら
+09:00を付けて送ります。CodeノードではDateTime.now().setZone('Asia/Tokyo').toISO()が手軽です。 - 時差を書かない文字列は、n8nのタイムゾーン(既定は
America/New_York)で読まれます。使わないのが安全です。 - 直ったかは、画面の表示とAPIの読み戻しの両方で確かめます。
参考にした公式ドキュメント
- Airtable Web API: Field model
- n8n Docs: Timezone and localization environment variables
- n8n Docs: Date and time with Luxon
n8nの点検と構築のご相談
「Airtableに入れた日時が画面でずれている」といったn8nとAirtableの連携の不具合の点検や、業務の自動化の仕組みづくりをお引き受けしています。






