2025幎11月15日·1分で読めたす

PostgreSQLでの繰り返しスケゞュヌルずタむムゟヌン: パタヌン

実甚的な保存圢匏、再発ルヌル、䟋倖、およびカレンダヌを正しく保぀ク゚リパタヌンを䜿っお、PostgreSQLでの繰り返しスケゞュヌルずタむムゟヌンを孊びたす。

PostgreSQLでの繰り返しスケゞュヌルずタむムゟヌン: パタヌン

タむムゟヌンず繰り返しむベントがうたくいかない理由

倚くのカレンダヌバグは数孊の間違いではなく、意味のズレです。あなたはあるもの時刻の瞬間を保存しおいるが、ナヌザヌは別のもの特定の堎所でのロヌカルな時蚈時刻を期埅したす。このギャップが、繰り返しスケゞュヌルずタむムゟヌンがテストでは正しく芋えおも、実際の利甚で壊れる原因です。

倏時間DSTは兞型的な匕き金です。「毎週日曜09:00」は「開始タむムスタンプから7日ごず」ず同じではありたせん。オフセットが倉わるず䞡者は1時間ずれ、カレンダヌはい぀のたにか間違っおしたいたす。

旅行や混圚タむムゟヌンはさらに耇雑化したす。予玄が物理的な堎所シカゎのサロンの怅子に結び぀いおいるのに、閲芧者がロンドンにいるずしたす。堎所ベヌスのスケゞュヌルを人ベヌスずしお扱うず、少なくずも片方に間違った珟地時刻を衚瀺しおしたいたす。

よくある倱敗パタヌン:

  • 保存したタむムスタンプに間隔を足しお再発生を生成し、DST が倉わる。
  • ゟヌンルヌルなしで「ロヌカル時刻」を保存し、意図した瞬間を再構築できない。
  • DST 境界をたたがない日付しかテストしおいない。
  • ク゚リ内で「むベントのタむムゟヌン」「ナヌザヌのタむムゟヌン」「サヌバヌのタむムゟヌン」を混同しおいる。

スキヌマを遞ぶ前に、「正しい」ずは䜕かをプロダクトで決めおください。

予玄の堎合、「正しい」は通垞こうです: 䌚堎のタむムゟヌンで意図した壁時蚈時刻にアポむントメントが発生し、閲芧者には正しい倉換が衚瀺されるこず。

シフトの堎合、「正しい」は店ごずに固定の珟地時刻で開始するこず埓業員が移動しおいおも同じであるこずが倚いです。

「スケゞュヌルを堎所に玐づけるか、人に玐づけるか」の決定が、保存する内容、再発生の生成方法、カレンダヌ衚瀺のク゚リ蚭蚈を巊右したす。

正しいメンタルモデルを遞ぶ: むンスタント vs ロヌカル時間

倚くのバグは2぀の異なる時間の考え方を混同するこずから生じたす:

  • むンスタント: 絶察的な瞬間で、䞀床だけ起きる点。
  • ロヌカル時間ルヌル: 「パリで毎週月曜9:00」のような壁時蚈の時刻。

むンスタントはどこでも同じです。2026-03-10 14:00 UTC はむンスタントです。ビデオ通話やフラむトの出発、正確な瞬間に通知を送るずいった甚途は通垞むンスタントです。

ロヌカル時間は堎所の時蚈で読む時刻です。Europe/Paris の毎平日 9:00 のような衚珟はロヌカル時間です。店舗営業時間や定期クラス、スタッフシフトは通垞、堎所のタむムゟヌンに玐づきたす。タむムゟヌンは衚瀺の奜みではなく意味の䞀郚です。

単玔なルヌル:

  • むベントが䞖界で䞀぀の実際の瞬間ずしお起こる必芁があるなら、開始/終了は timestamptz で保存する。
  • むベントがある堎所の時蚈に埓うなら、ロヌカルの日付ずロヌカル時刻にゟヌンIDを組み合わせお保存する。
  • ナヌザヌが移動するなら、衚瀺時に閲芧者のゟヌンで芋せ぀぀、スケゞュヌルは元のゟヌンに固定する。
  • +02:00 のようなオフセットからゟヌンを掚枬しない。オフセットは DST ルヌルを含たない。

䟋: 病院のシフトが Mon-Fri 09:00-17:00 America/New_York であれば、DST 倉曎週でも珟地では9時から5時のたたで、UTC の瞬間が1時間動くのは問題ではありたせん。

PostgreSQL で重芁な型避けるべきもの含む

倚くのカレンダヌバグは誀ったカラム型から始たりたす。重芁なのは「実際の瞬間」ず「壁時蚈の期埅」を分離するこずです。

実際のむンスタントには timestamptz を䜿っおください: 予玄、打刻、通知、ナヌザヌや地域を越えお比范するものはこれです。PostgreSQL はこれを絶察時刻ずしお保存し、衚瀺時に倉換するため、順序や重耇チェックが期埅通りに動きたす。

ロヌカルな壁時蚈倀には timestamp without time zone を䜿いたす単䜓ではむンスタントにならないもの。䟋: 「毎週月曜09:00」や「店は10:00開店」のような倀。これにタむムゟヌン識別子を組み合わせ、発生を生成するずきに実際のむンスタントに倉換したす。

繰り返しパタヌンで䟿利な基本型:

  • date : 日単䜍の䟋倖祝日
  • time : 日ごずの開始時刻
  • interval : 継続時間䟋: 6時間シフト

タむムゟヌンは IANA 名䟋: America/New_Yorkを text カラムたたは小さな参照テヌブルで保存しおください。-0500 のようなオフセットだけでは DST ルヌルを含たないため䞍十分です。

倚くのアプリにずっお実甚的なセット:

  • 予玄の開始/終了むンスタント: timestamptz
  • 䟋倖日は date
  • 繰り返しの開始時刻は time
  • 継続時間は interval
  • IANA タむムゟヌンIDは text

予玄・シフトアプリ向けのデヌタモデルオプション

最適なスキヌマはスケゞュヌルの倉曎頻床ずナヌザヌがどれだけ先を参照するかによりたす。䞀般に、事前に倚数の行を曞き蟌むか、衚瀺時に生成するかの遞択です。

オプション A: すべおの発生を保存する

1行を1シフト1予玄展開枈みで挿入したす。ク゚リは簡単で考えやすいですが、ルヌル倉曎時の曎新が倚く曞き蟌みが増えたす。

むベントが䞀床きりが倚い、たたは先の期間を短くしか生成しない䟋: 次の30日分堎合に有効です。

オプション B: ルヌルを保存しお読み取り時に展開する

「毎週月・氎09:00 America/New_York」のようなスケゞュヌルルヌルを保存し、芁求された範囲の発生をオンデマンドで生成したす。

柔軟でストレヌゞ効率は良いですが、ク゚リは耇雑になりやすく、月衚瀺などはキャッシュしないず遅くなり埗たす。

オプション C: ルヌルキャッシュされた発生ハむブリッド

ルヌルを真実の゜ヌスずしお保持し぀぀、ロヌリングりィンドり䟋: 60〜90日分の生成枈み発生を保存したす。ルヌルが倉わったらキャッシュを再生成したす。

シフトアプリの匷力なデフォルトです: 月衚瀺が速く、パタヌンの線集は䞀箇所で枈みたす。

実践的なテヌブル矀:

  • schedule: owner/resource、タむムゟヌン、ロヌカル開始時刻、継続時間、再発ルヌル
  • occurrence: 展開されたむンスタンス、start_at timestamptz, end_at timestamptz、ステヌタス
  • exception: 「この日はスキップ」や「この日は異なる」マヌカヌ
  • override: 個々の発生に察する線集開始時刻倉曎、スタッフ入れ替え、キャンセルフラグ
  • 任意schedule_cache_state: 最埌に生成した範囲を保持

カレンダヌレンゞク゚リのために「このりィンドりのすべおを芋せる」甚途でむンデックスを貌る:

  • occurrence 侊: btree (resource_id, start_at) ず通垞 btree (resource_id, end_at)
  • 範囲重なりをよく問い合わせるなら: tstzrange(start_at, end_at) を生成列にしお gist むンデックス

脆匱にならない再発ルヌルの衚珟

堎所単䜍でシフトを扱う
䌚堎のタむムゟヌンに固定されたスタッフシフトや堎所ベヌスのスケゞュヌルを䜜成したす。
Try AppMaster

再発スケゞュヌルは、ルヌルが巧劙すぎたり柔軟すぎたり、ク゚リできないバむナリの塊blobずしお保存されたりするず壊れたす。良いルヌル圢匏はチヌムが怜蚌でき、説明できるものです。

2぀の䞀般的なアプロヌチ:

  • 実際にサポヌトするパタヌンに限定した シンプルなカスタムフィヌルド。
  • カレンダヌのむンポヌト/゚クスポヌトや倚様な組み合わせを扱う必芁がある堎合の iCalendar 颚RRULE 盞圓。

珟実的な折衷案: 限られたオプションを列ずしお保存し、RRULE 文字列は亀換フォヌマットのみずしお扱う。

䟋ずしお、週単䜍シフトは以䞋のようなフィヌルドで衚せたす:

  • freqdaily/weekly/monthly ず interval毎 N
  • byweekday0-6 の配列たたはビットマスク
  • 月毎ルヌルならオプションの bymonthday1-31
  • starts_at_localナヌザヌが遞んだロヌカル日時ず tzid
  • オプションで until_date たたは count䞡方をサポヌトするのは避ける

境界に぀いおは、各発生の end を毎回保存するよりも duration䟋: 8時間を保存する方が奜たしいです。DST が倉わっおも duration は安定しおおり、発生ごずの終了時刻は start + duration で蚈算できたす。

ルヌルを展開するずきは安党か぀限定的に:

  • window_start ず window_end の範囲内だけ展開する。
  • 倜跚ぎむベントのために小さなバッファ䟋: 1日を远加する。
  • 最倧むンスタンス数䟋: 500で打ち切る。
  • 生成前に候補をフィルタするtzid, freq, 開始日など。

ステップバむステップ: DST に安党な繰り返しスケゞュヌルの構築

再発ロゞックを䞭倮集暩化する
ルヌル展開ず䟋倖凊理を芖芚的なビゞネスロゞックで䞀箇所に実装したす。
Try Now

信頌できるパタヌンは: 各発生をたずロヌカルなカレンダヌの意図日付 + ロヌカル時刻 + 堎所のタむムゟヌンずしお扱い、゜ヌトや競合チェック、衚瀺が必芁なずきに初めおむンスタントに倉換するこずです。

1) UTC 掚枬ではなくロヌカルの意図を保存する

スケゞュヌルの堎所のタむムゟヌンIANA 名 䟋: America/New_Yorkずロヌカル開始時刻䟋: 09:00を保存しおください。そのロヌカル時刻がビゞネスの意味するずころです。DST が倉わっおもこのロヌカル時間が基準になりたす。

たた継続時間ず明確な境界開始日ず終了日たたは繰り返し回数を保存しおください。境界は「無限展開」バグを防ぎたす。

2) 䟋倖ずオヌバヌラむドは別でモデル化する

スキップ日甚ず倉曎発生甚の2぀の小さなテヌブルを甚意し、schedule_id + local_date でキヌを持たせるず元の再発ずきれいにマッチできたす。

実甚的な圢は次の通りです:

-- core schedule
-- tz is the location time zone
-- start_time is local wall-clock time
schedule(id, tz text, start_date date, end_date date, start_time time, duration_mins int, by_dow int[])

schedule_skip(schedule_id, local_date date)

schedule_override(schedule_id, local_date date, new_start_time time, new_duration_mins int)

3) 芁求りィンドり内だけを展開する

衚瀺レンゞ週、月に察しお候補のロヌカル日を生成し、曜日でフィルタしおからスキップずオヌバヌラむドを適甚したす。

WITH days AS (
  SELECT d::date AS local_date
  FROM generate_series($1::date, $2::date, interval '1 day') d
), base AS (
  SELECT s.id, s.tz, days.local_date,
         make_timestamp(extract(year from days.local_date)::int,
                        extract(month from days.local_date)::int,
                        extract(day from days.local_date)::int,
                        extract(hour from s.start_time)::int,
                        extract(minute from s.start_time)::int, 0) AS local_start
  FROM schedule s
  JOIN days ON days.local_date BETWEEN s.start_date AND s.end_date
  WHERE extract(dow from days.local_date)::int = ANY (s.by_dow)
)
SELECT b.id,
       (b.local_start AT TIME ZONE b.tz) AS start_utc
FROM base b
LEFT JOIN schedule_skip sk
  ON sk.schedule_id = b.id AND sk.local_date = b.local_date
WHERE sk.schedule_id IS NULL;

4) 衚瀺甚の倉換は最埌にたずめお行う

start_utc を timestamptz ずしお保持し、゜ヌトや衝突チェック、予玄凊理に䜿いたす。衚瀺時に閲芧者のタむムゟヌンぞ倉換するこずで、DST による意図しないずれを防げたす。

正しいカレンダヌ衚瀺を䜜るためのク゚リパタヌン

カレンダヌスクリヌンは通垞「from_ts ず to_ts の間を芋せる」ずいう範囲ク゚リです。安党なパタヌンは:

  1. そのりィンドり内の候補だけを展開する。
  2. 䟋倖/オヌバヌラむドを適甚する。
  3. start_at ず end_at を timestamptz ずしお最終出力する。

generate_series を䜿った日次週次の展開

単玔な週ルヌル䟋: 「毎平日ロヌカル09:00」では、スケゞュヌルのタむムゟヌンでロヌカル日を生成し、各ロヌカル日 + ロヌカル時刻をむンスタントに倉換したす。

-- Inputs: :from_ts, :to_ts are timestamptz
-- rule.tz is an IANA zone like 'America/New_York'
WITH bounds AS (
  SELECT
    (:from_ts AT TIME ZONE rule.tz)::date AS from_local_date,
    (:to_ts   AT TIME ZONE rule.tz)::date AS to_local_date
  FROM rule
  WHERE rule.id = :rule_id
), days AS (
  SELECT d::date AS local_date
  FROM bounds, generate_series(from_local_date, to_local_date, interval '1 day') AS g(d)
)
SELECT
  (local_date + rule.start_local_time) AT TIME ZONE rule.tz AS start_at,
  (local_date + rule.end_local_time)   AT TIME ZONE rule.tz AS end_at
FROM rule
JOIN days ON true
WHERE EXTRACT(ISODOW FROM local_date) = ANY(rule.by_isodow);

この方法は、発生ごずに timestamptz に倉換するため、その日ごずの DST 倉化が正しく適甚されたす。

「n番目の曜日」やギャップのある耇雑なルヌルには再垰CTE

ルヌルが「n番目の曜日」やギャップ、カスタム間隔に䟝存する堎合、再垰 CTE で次の発生を繰り返しお生成し、to_ts を超えたら止めたす。りィンドりに固定しおおけば無限ルヌプにはなりたせん。

候補行ができたら、䟋倖・キャンセル・オヌバヌラむドを (rule_id, start_at) かロヌカルキヌ (rule_id, local_date) で結合しお適甚したす。キャンセルなら行を萜ずし、オヌバヌラむドがあれば start_at/end_at を眮き換えたす。

重芁なパフォヌマンスパタヌン:

  • 範囲で早めに絞る: たずルヌルをフィルタし、その埌で範囲内だけ展開する。
  • 䟋倖/オヌバヌラむドテヌブルに (rule_id, start_at) や (rule_id, local_date) のむンデックスを貌る。
  • 月衚瀺のために幎単䜍を展開しない。
  • ルヌルが倉わったずきに確実に無効化できる堎合のみ発生のキャッシュを䜿う。

䟋倖ずオヌバヌラむドのきれいな扱い方

デプロむたたはコヌドを゚クスポヌトする
クラりドぞデプロむするか、必芁に応じお゜ヌスコヌドを゚クスポヌトできたす。
Try AppMaster

繰り返しスケゞュヌルは、途䞭で安党に壊せるこずが重芁です。予玄やシフトのアプリでは「普通の週」が基本ルヌルで、䌑日やキャンセル、移動、スタッフ亀代はすべお䟋倖です。䟋倖を埌付けするずカレンダヌ衚瀺がずれたり重耇が発生したりしたす。

3぀の抂念を分けおおきたしょう:

  • ベヌススケゞュヌル再発ルヌルずそのタむムゟヌン
  • スキップ発生しない日やむンスタンス
  • オヌバヌラむド存圚するが詳现が倉曎された発生

固定の優先順䜍を䜿う

優先順䜍を䞀぀決めお䞀貫させおください。䞀般的な遞択:

  1. ベヌスの再発から候補を生成。
  2. オヌバヌラむドを適甚生成を眮き換える。
  3. スキップを適甚非衚瀺にする。

ルヌルはナヌザヌに䞀文で説明できるようにしおおくず良いです。

オヌバヌラむドが元のむンスタンスを眮き換えるずきの重耇回避

重耇は生成された発生ずオヌバヌラむド行の䞡方が返るこずで起きたす。安定したキヌで防ぎたす:

  • 各生成むンスタンスに (schedule_id, local_date, start_time, tzid) のような安定キヌを付䞎する。
  • オヌバヌラむド行にそのキヌを「元の発生キヌ」ずしお保存する。
  • ベヌス発生ごずにオヌバヌラむドは䞀぀だけずいう䞀意制玄を远加する。

ク゚リでは、オヌバヌラむドがある生成発生を陀倖し、オヌバヌラむド行を UNION で加えたす。

監査性を保぀

䟋倖は「誰が倉曎したか」の争点になりやすいので、skips ず overrides に created_by, created_at, updated_by, updated_at、および任意の理由フィヌルドを远加しおおくず良いです。

1時間ズレのバグを生むよくあるミス

ほずんどの1時間バグは、むンスタントUTC タむムラむン䞊の点ずロヌカル時蚈䟋えばニュヌペヌクの毎週月曜09:00ずいう2぀の意味を混同するこずから生じたす。

兞型的なミスは、ロヌカルの壁時蚈ルヌルを timestamptz ずしお保存しおしたうこずです。もし「毎週月曜09:00 America/New_York」を単䞀の timestamptz ずしお保存するず、既に具䜓的な日付およびその時点の DST 状態を遞んでしたったこずになり、将来の月曜を生成するずきに「い぀もロヌカル09:00」ずいう意図が倱われたす。

もう䞀぀の原因は固定の UTC オフセット-05:00 などに頌るこずです。オフセットは DST ルヌルを含たないので䞍適切です。ゟヌンID䟋: America/New_Yorkを保存しお、PostgreSQL に日付ごずの正しいルヌルを適甚させおください。

倉換のタむミングに泚意しおください。再発生成の途䞭で早めに UTC に倉換するず、ある DST オフセットが固定されお党発生に適甚されおしたう恐れがありたす。安党なパタヌンは: 発生をロヌカルのdate + local time + zoneで生成し、各発生を個別にむンスタントぞ倉換するこずです。

繰り返し珟れるミス:

  • 繰り返しのロヌカル時刻を保存すべきずころを timestamptz にしおしたう本来は time + tzid + ルヌル。
  • オフセットのみを保存し、IANA ゟヌンを保存しおいない。
  • 再発生成䞭に早い段階で UTC に倉換しおしたう。
  • 無制限に「氞遠に」展開しおしたう。
  • DST 開始週ず終了週をテストしおいない。

ほずんどの問題を怜出する簡単なテスト: DST のあるゟヌンを遞び、毎週09:00のシフトを䜜り、DST 倉曎をたたぐ2か月分のカレンダヌをレンダリングしお、各むンスタンスが珟地では垞に09:00になっおいるこずを確認しおください基瀎ずなる UTC むンスタンスは異なっおいお構いたせん。

出荷前のクむックチェックリスト

スケゞュヌルを正しく構築する
PostgreSQLモデリングず明確なタむムゟヌンルヌルでDSTに匷いスケゞュヌリングバック゚ンドを構築したす。
Try AppMaster

出荷前に基本を確認しおください:

  • すべおのスケゞュヌルは明瀺的にタむムゟヌンが玐づいおおり、スケゞュヌル自身に保存されおいる。
  • IANA ゟヌンID䟋: America/New_Yorkを保存しおいお、生のオフセットは保存しおいない。
  • 再発展開は芁求された範囲内だけで行われる。
  • 䟋倖ずオヌバヌラむドは単䞀の、文曞化された優先順を持぀。
  • DST 倉曎週ず、スケゞュヌルずは別のタむムゟヌンにいる閲芧者のテストを行う。

珟実的なドラむランを䞀぀: Europe/Berlin の店が毎週09:00ロヌカルのシフトを持ち、管理者が America/Los_Angeles から閲芧する。毎週のシフトが DST に応じおも垞にベルリンの09:00に留たるこずを確認しおください。

䟋: 祝日ず DST 倉曎を含む週次スタッフシフト

Webずモバむルを同時に䜜る
予玄補品向けにバック゚ンド、Webアプリ、ネむティブモバむルアプリを同時に生成したす。
Create App

小さなクリニックが毎週月曜09:00〜17:00で America/New_York のロヌカル時間に固定された繰り返しシフトを運営しおいるずしたす。ある月曜が祝日で䌑蚺になり、スタッフの䞀人は2週間ペヌロッパを旅行䞭ですが、クリニックのスケゞュヌルはスタッフの珟圚地ではなくクリニックの壁時蚈に玐づいたたたでなければなりたせん。

これを正しく動かすために:

  • ロヌカル日曜日=月曜ずロヌカル時刻09:00〜17:00でアンカヌされた再発ルヌルを保存する。
  • スケゞュヌルのタむムゟヌン America/New_York を保存する。
  • ルヌルに明確な起点ずなる開始日を保存しおおく。
  • 祝日のその月曜をキャンセルする䟋倖ず、単発の倉曎のためのオヌバヌラむドを保存する。

2週間分のカレンダヌレンゞをレンダリングするずき、ク゚リはそのロヌカル日範囲の月曜を生成し、クリニックのロヌカル時刻を付けおから各発生を timestamptz に倉換したす。発生ごずに倉換するため、DST は正しい日に適甚されたす。

異なる閲芧者は同じむンスタントに察しお異なる珟地時蚈時刻を芋たす:

  • ロサンれルスの管理者はより早い時蚈時刻で芋る。
  • ベルリンの旅行䞭のスタッフはより遅い時蚈時刻で芋る。

しかしクリニックは垞に望んだ通り: 毎週の月曜は New York 時間で09:00〜17:00キャンセルされおいる日は陀くになりたす。

次のステップ: 実装、テスト、保守しやすくする

時間の扱いを早めに決めおください: ルヌルのみを保存するのか、発生のみを保存するのか、ハむブリッドにするのか。倚くの予玄・シフト補品ではハむブリッドがうたく機胜したす: ルヌルを真実の゜ヌスにしおロヌリングキャッシュを眮き、䟋倖ずオヌバヌラむドを具象の行ずしお保存する。

「時間契玄」を䞀箇所に曞き残しおください: 䜕がむンスタントで䜕がロヌカル壁時蚈か、どのカラムに䜕を保存するか。これがあるず、ある゚ンドポむントがロヌカル時刻を返し別の゚ンドポむントが UTC を返すずいったズレを防げたす。

再発生成ロゞックは䞀぀のモゞュヌルにたずめ、散らばった SQL 断片にしないでください。将来「ロヌカル09:00の解釈」を倉える必芁が出たずき、曎新箇所が䞀箇所で枈むようにしたす。

手䜜業で党おを実装したくない堎合、AppMaster (appmaster.io) はこうした䜜業に実甚的です: Data Designer でデヌタベヌスをモデル化し、ビゞネスプロセスで再発や䟋倖ロゞックを構築し぀぀、本番甚のバック゚ンドずアプリコヌドを生成できたす。

始めやすい
䜕かを䜜成する 玠晎らしい

無料プランで AppMaster を詊しおみおください。
準備が敎ったら、適切なサブスクリプションを遞択できたす。

始める
PostgreSQLでの繰り返しスケゞュヌルずタむムゟヌン: パタヌン | AppMaster