DEV Community

Hajime
Hajime

Posted on

締切・予選・本戦を同じカレンダーに入れると、締切が消える

締切・予選・本戦を同じカレンダーに入れると、締切が消える

大会の予定をカレンダーに入れる仕組みを作ると、ほぼ必ず同じ不具合が出ます。

応募の締切が、一覧から消えます。

理由は単純で、「今月の予定」を出すときに開催日で絞り込むからです。本戦が12月にあるイベントは12月の欄に入り、9月に閉まる応募期限は、9月の一覧のどこにも出てきません。

実際のデータで確かめてみます(2026年9月9日に確認した内容)。

e-Sports TODAY

大会の日程と応募期限を確認するなら e-Sports TODAY

問題が起きる実例

こういう予定がありました。

  • JeSU「全国都道府県対抗eスポーツ選手権 2026 オンライン - Road to the NEXT -」は 12月12日・13日。ただしグランツーリスモ7部門の予選は9月6日まで
  • 全日本高校eスポーツ選手権は、エントリーが9月10日18時までで、予選は9月26日開始

上のイベントは、開催日だけを持つと12月の予定になります。しかし、9月6日を過ぎたら出られません。9月上旬の画面に何も出ないまま、機会が閉じます。

下のイベントも同様で、エントリーの締切と予選の開始が別の日にあります。しかも締切には時刻まで付いています。18時。

1つのテーブルに入れない

素直にやると、こうなります。

{ "name": "…", "date": "2026-12-12" }
Enter fullscreen mode Exit fullscreen mode

ここに締切を足そうとして、フィールドを1つ増やす方向へ行くと破綻します。

{ "name": "…", "date": "2026-12-12", "deadline": "2026-09-06" }   //  部門ごとに違う
Enter fullscreen mode Exit fullscreen mode

先ほどの例では、締切はグランツーリスモ7部門のものでした。イベント全体の締切ではありません。1イベント1締切を前提にした時点で、表現できない現実が出てきます。

分けます。イベントと、そこに紐づく期限を別の実体にする。

{
  "event_id": "jesu-kokutai-2026",
  "name": "全国都道府県対抗eスポーツ選手権 2026 オンライン - Road to the NEXT -",
  "phases": [
    { "kind": "final", "from": "2026-12-12", "to": "2026-12-13" }
  ],
  "gates": [
    { "kind": "entry_close", "scope": "グランツーリスモ7部門", "at": "2026-09-06", "precision": "day" }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  • phases期間を持つもの。開催・予選・本戦
  • gates時点で閉まるもの。エントリー期限、申込期限

この2つは、カレンダー上での扱いがまったく違います。期間は「その間ずっと」ですが、時点は「そこを過ぎたら終わり」です。同じ配列に入れると、必ずどちらかの表示が壊れます。

precision を持たせる

締切に時刻が付く場合と付かない場合があります。

  • グランツーリスモ7部門の予選: 9月6日まで(日付のみ)
  • 全日本高校eスポーツ選手権のエントリー: 9月10日18時まで(時刻あり)

ここで全部を 2026-09-06T23:59:59 のように正規化すると、書かれていない情報を足したことになります。「まで」が当日いっぱいなのか、営業時間内なのかは、発表されていない限り分かりません。

precision: "day" | "minute" を持たせて、表示のときに切り替えます。

const label = (g) =>
  g.precision === 'minute'
    ? `${fmtDate(g.at)} ${fmtTime(g.at)}まで`
    : `${fmtDate(g.at)}まで`;
Enter fullscreen mode Exit fullscreen mode

推測して埋めた時刻を、確定した時刻の顔で表示しない。これが要点です。

一覧の絞り込みを2本立てにする

データを分けたら、取り出し方も分けます。

// 期間もの: その月に「かかっている」ものを拾う(またぐイベントを落とさない)
const phasesIn = (month) => events.flatMap(e =>
  e.phases.filter(p => overlaps(p, month)).map(p => ({ e, p }))
);

// 時点もの: その月に「閉まる」ものを拾う
const gatesIn = (month) => events.flatMap(e =>
  e.gates.filter(g => inMonth(g.at, month)).map(g => ({ e, g }))
);
Enter fullscreen mode Exit fullscreen mode

期間ものに overlaps を使うのが大事で、開始日だけで絞るとまたぐイベントが消えます。

たとえば、第20回アジア競技大会(2026/愛知・名古屋)のeスポーツ競技は9月23日から10日間です。開始日で絞ると10月の一覧に出ません。東京ゲームショウ2026 の9月17日〜21日のような5日間のものも同じ理屈で、月をまたぐ配置なら消えます。

締切のほうを先に出す

表示の順序も設計の一部です。

9月の画面を作るなら、こうします。

■ 今月閉まるもの
  09/06  グランツーリスモ7部門 予選エントリー(全国都道府県対抗 2026)
  09/10 18:00  全日本高校eスポーツ選手権 エントリー

■ 今月ひらくもの
  09/13        ALGS Year 6 Split 2 APAC North リージョナルファイナル
  09/17〜09/21 東京ゲームショウ2026(幕張メッセ)
  09/23〜      第20回アジア競技大会 eスポーツ競技(Aichi Sky Expo 展示ホールD)
  09/24〜      VALORANT Champions 2026(上海)
  09/26        全日本高校eスポーツ選手権 予選開始
Enter fullscreen mode Exit fullscreen mode

閉まるものを上に置く。開くものは見逃しても次があることが多いですが、閉まるものは一度きりです。

種目とタイトルが1対1ではない

もう1つ、phases の中でつまずく点があります。

アジア競技大会のeスポーツ競技は、10日間で11種目13タイトルと記載されていました。種目数とタイトル数が一致していません。

つまり、phases の1件に対して、実施される中身が複数あります。開催期間だけを持って「1イベント」として扱うか、種目単位まで割るかは、何を見せたいかで決めます。一覧に出すだけなら前者で足りますが、「この日に何があるか」を出すなら後者が要ります。

最初から後者で作ろうとすると、データが集まらずに詰まります。先に前者で動かして、必要になったら割るほうが現実的でした。

出場枠の数は、日程ではない

ついでに注意点を1つ。

  • ALGS の APAC North リージョナルファイナルは上位20チーム
  • VALORANT Champions 2026 は出場16チーム

こういう数字を phases に混ぜたくなりますが、枠の数は日程の属性ではありません。別のフィールドに置きます。混ぜると、日程を更新するたびに枠の数まで書き換えることになります。

まとめ

  • 期間もの(phases)と時点もの(gates)を分ける。同じ配列に入れない
  • 締切は部門ごとに違いうる。1イベント1締切にしない
  • 時刻の有無を precision で持つ。書かれていない時刻を補わない
  • 期間の絞り込みは overlaps。開始日だけで絞るとまたぐイベントが消える
  • 表示は閉まるものを先に出す
  • 枠の数など、日程以外の属性を phases に混ぜない

日程は変更されることがあります。取り込むなら取得日を添えて持ってください。

本ページはプロモーションが含まれています

Top comments (0)