メインコンテンツへスキップ
見出し画像

Business Calendar:30年分の「期間」が最初から入っている


    「今年の第3四半期の件数を出したい」。レポートでよくある要望です。ここで必要になるのが、「第3四半期とはいつからいつまでか」という定義になります。

    ServiceNow はこれを Business Calendar(business_calendar)というテーブルで持っています。しかも、期間の実体は計算するのではなく あらかじめレコードとして作られていました。実測すると2,197件。今回はこの珍しい持ち方を読み解きます。

    この記事は、ServiceNow の PDI(Personal Developer Instance、無料の個人検証環境)Zurich リリース・OOTB(初期設定)で確認した実測メモです。件数・項目は REST と実画面で確認しました(確認日 2026-07-26)。本番環境では設定が異なります。

    期間は計算せず、行として持つ

    普通に考えれば、「2026年の第3四半期」は計算で求まります。年と四半期の番号から、開始日と終了日を導けばよい。

    ところが ServiceNow はそうしていません。business_calendar_span(期間)というテーブルに、1期間=1行 として実体を持ちます。実測は2,197件でした。


    画像

    行として持つ利点は明快です。参照できて、集計の軸に使えて、レポートで結合もできます。計算式のままでは、レポートの列にできません。

    そして何より、期間の定義を変えられる。会計年度が4月始まりの会社なら、その定義に合わせて行を作り直せばよい。計算式が固定されていたら、こうはいきません。

    4つの期間カレンダー

    business_calendar は98件ありました。ただし名前が入っているのは 13件 だけです。


    画像


    画像

    このうち期間を表すのは4件でした。Year / Quarter / Month / Week。

    残る9件は性格が違います。Office Hours と、ITSM App-Dev という名前で始まる8件。どれも is_legacy_schedule が true で、タイムゾーンに Europe/London が入っていました。オンコールの当番表として作られた旧来のスケジュールが、このテーブルに同居しています。

    名前が空の85件は、label も cal_type も time_zone もすべて空でした。何のためのレコードかは特定できていません。

    こうした「中身の見えないレコード」は、標準データを読むときによく出てきます。プラグインが将来のために置いた枠なのか、過去の機能の名残なのかは分かりません。分からないものを無理に解釈せず、分からないまま記録しておくのが安全です。

    件数の見え方にも影響します。「業務カレンダーは98件ある」と報告すると、実際に使える13件との差で誤解が生じかねません。名前で絞った件数を示すほうが実態に近くなります。

    この差は7倍以上です。テーブルの総件数をそのまま報告に使うと、規模の印象が実態とずれる。件数を出すときは、意味のある条件で絞ってから数えるのが基本になります。

    親子で階層になっている

    4つの期間カレンダーには、parent フィールドで親子関係が付いています。

    • Year … 親なし

    • Quarter … 親は Year

    • Month … 親は Quarter

    • Week … 親なし

    年の下に四半期、その下に月。素直な入れ子です。

    Week だけが独立している のが目を引きます。週は月をまたぎます。2026年8月31日から始まる週は、8月と9月にまたがる。だから月の子にはできません。

    この扱いは、実務でも同じ悩みを生みます。「週次の件数を月で合計する」と、境界の週をどちらに入れるかで数字が変わる。ServiceNow の設計は、その曖昧さをテーブル構造で正直に表していました。

    期間2,197件の内訳


    画像


    画像

    内訳を数えると、きれいな対応が見えます。

    • Year … 30件

    • Quarter … 120件(= 30 × 4)

    • Month … 360件(= 30 × 12)

    • Week … 1,567件

    • カレンダー未設定 … 120件

    Year の30件に対して、Quarter がちょうど4倍、Month がちょうど12倍。30年分の期間が、すべて行として作られています。

    Week の1,567件も、30年分(30 × 52.18 ≒ 1,565)とほぼ一致します。閏週の扱いで数件ずれる程度です。

    カレンダーが設定されていない120件が残りますが、これが何かは特定できませんでした。数が Quarter と同じ120件ですが、偶然なのか対応関係があるのかは確認できていません。

    この規則性は、期間が自動生成されたものだと示しています。人が1件ずつ作ったなら、こんなにきれいな倍数にはなりません。インストール時か、あるいは初回の利用時に、まとめて生成されたと考えるのが自然です。

    裏を返せば、追加の生成も仕組みで行えるということです。2030年以降が必要になったとき、手作業で360件を作る必要はありません。同じ生成の仕組みを走らせれば済みます。

    30年分とは、いつからいつまでか

    Year の期間を開いてみます。


    画像


    画像

    始まりは 2000年 でした。30件なので、2029年までになります。

    start と end の値も素直です。2000年の期間は 2000-01-01 00:00:00 から 2001-01-01 00:00:00 まで。終わりが翌年の元日ちょうどで、次の期間の開始と同じ値です。

    この持ち方は「以上・未満」を前提にしています。ある日時がどの期間に属するかを判定するとき、start <= 対象 < end で切れば重複しません。終端を「12月31日 23:59:59」にすると、秒未満の扱いで漏れが出ます。設計としてきれいな選び方でした。

    期間の名前は別テーブル

    business_calendar_span の span_name は、文字列ではなく 参照 でした。参照先は business_calendar_span_name で、実測 2,077件 です。


    画像

    名前を別テーブルに持つ理由は、翻訳だと読めます。「2013 Week 9」という表記を、日本語では「2013年 第9週」と出したい。名前をレコードにしておけば、翻訳の対象にできます。

    期間が2,197件で名前が2,077件と、数がわずかに違います。同じ名前を複数の期間で共有しているか、名前のないものがあるか。差の120件は、カレンダー未設定の期間120件と同じ数でした。関係があるかどうかは確認していません。

    期間が持つ10本のインデックス

    business_calendar_span のフィールドを見ると、変わった項目がありました。index0 から index9 まで、整数のフィールドが 10本 並んでいます。


    画像

    実測した Year の期間では、index0 は空でした。用途は確認できていません。

    推測できるのは、階層のどの位置にあるかを数字で持つための項目だということです。「2026年 第3四半期 8月」なら、年・四半期・月それぞれの番号を別の列に持てば、絞り込みや並べ替えが速くなります。

    counter という整数フィールドもあり、こちらは実測で 687 という値が入っていました。Week カレンダーの「2013 Week 9」という期間で、この値です。1,567件の週に通し番号を振ったときの位置と考えると、辻褄が合います。

    期間を行として持つ以上、大量のレコードから目的の1件を素早く見つける必要があります。インデックス用の列を10本も用意しているのは、そのための備えと読めます。

    旧スケジュールが同居している理由

    名前のある13件のうち9件が is_legacy_schedule=true でした。Office Hours と、担当者名の付いた8件です。

    名前をよく見ると、規則性があります。「ITSM App-Dev: 氏名: Primary」と「同: Secondary」。4名分の一次担当と二次担当で8件です。親はすべて Office Hours でした。

    オンコールの当番表は、期間の集合です。「この人はこの週の一次担当」という区切りが並びます。年や四半期と形は同じなので、同じテーブルに入る。理屈は通っています。

    ただし性格は違います。年や四半期は全社共通の物差しですが、当番表は特定のチームの取り決めです。混ざったままだと、カレンダーの一覧を開いたときに何が何だか分かりません。

    is_legacy_schedule というフィールドがあるのは、そのための区別でした。同じ形をしているが用途が違うものを、1つのテーブルに置きつつ見分ける。 ServiceNow のテーブル設計でよく見る割り切り方です。

    親が Office Hours になっているのも読み取れます。営業時間という土台があり、その中で誰が当番かを分ける。時間の区切りが二段構えになっています。

    タイムゾーンはカレンダーが持つ

    business_calendar には time_zone フィールドがあります。期間カレンダー4件では空でしたが、旧スケジュール9件には Europe/London が入っていました。

    タイムゾーンをカレンダー側に持つのは理にかなっています。「営業時間は9時から17時」と決めても、どの地域の9時かで実際の時刻が変わるからです。

    期間カレンダー(Year / Quarter / Month / Week)でタイムゾーンが空なのも納得できます。「2026年」は地域によって始まる瞬間が違いますが、レポートの集計軸としては地域を問わず同じ扱いで足りることが多い。厳密さより扱いやすさを取った設計に見えます。

    週が1,567件である意味

    Week の期間が1,567件あるのは、一見すると半端な数です。しかし計算してみると理由が見えます。

    30年は約1,565週です(30 × 365.25 ÷ 7 ≒ 1,565)。実測の1,567件は、これとほぼ一致します。

    年と違い、週は年をまたぎます。2029年の最終週が2030年に食い込む。だから「30年分ぴったり」にはならず、端数が出ます。

    business_calendar_span_name の実測例に「2013 Week 9」という名前がありました。年と週番号の組み合わせです。週番号の付け方(年の第1週をどう決めるか)は国や規格によって違うので、この名前の付き方も確認しておくべき点になります。

    日本の業務では「第◯週」の数え方が組織ごとに違うことがあります。月曜始まりか日曜始まりか、1月1日を含む週を第1週とするか。標準の期間をそのまま使う前に、自社の数え方と合っているかを確かめておくと安全です。

    何に使われるか

    業務カレンダーは、単体で使う機能ではありません。他の仕組みの「期間の軸」として使われます。

    レポートと分析 … 四半期別、月別の集計。Performance Analytics の期間もここに紐づきます。

    SLA と稼働時間 … 「営業時間内で4時間以内」という取り決めは、営業時間の定義が要ります。旧スケジュール系のカレンダーがこの役割を持っていました。

    オンコールの当番 … ITSM App-Dev の8件が、担当者ごとの Primary / Secondary として作られていました。誰がいつ当番かを期間で表しています。

    この3つが同じテーブルに同居しているのは、「時間の区切り」という点で共通するためと読めます。ただし性格は違うので、is_legacy_schedule で区別されています。

    期間を行として持つことの代償

    利点ばかり書いてきましたが、この設計には代償もあります。

    まず件数が増えること。30年分でも2,197件です。日単位のカレンダーを足せば、一気に1万件を超えます。テーブルが太ると、検索も集計も重くなります。

    次に生成が要ること。2030年以降を使いたければ、誰かが期間を作らなければなりません。計算で求めるなら不要な作業です。

    そして定義変更の影響が広いこと。四半期の区切りを変えると、既存の期間レコードを作り直すことになります。過去のレポートと数字が変わる可能性もあります。

    それでも行として持つ設計が選ばれているのは、参照できることの価値が大きいからです。レポートの軸として選べる、他のレコードから参照できる、条件で絞れる。計算式ではできないことが、行にすればすべてできます。

    index0〜index9 のようなインデックス列が10本も用意されているのは、件数が増えることを織り込んだ設計です。太るのは前提で、そのうえで速く引ける工夫をしてある。

    実務で気をつけるところ

    30年分で足りるか確認する

    2000年から2029年まで。この記事を書いている時点では十分ですが、2030年以降のデータを扱うときには足りません。

    期間が行として存在しない年は、集計の軸に使えない可能性があります。長期の計画を扱うなら、期間を追加で生成する必要が出てきます。

    会計年度は別に考える

    Year / Quarter / Month は暦年(1月始まり)で作られていました。4月始まりの会計年度を使う組織では、そのままでは合いません。

    ServiceNow には会計年度(fiscal calendar)の仕組みが別にあります。業務カレンダーの期間をそのまま会計期間として使わないよう、設計の初期に切り分けておく必要があります。

    週の集計はまたぎに注意する

    Week が月の子になっていないのは、構造上の正直さです。ただし運用では「この週は何月ぶんか」を決める必要が出ます。

    開始日で決めるのか、終了日で決めるのか、日数の多いほうで決めるのか。数字を出す前に決めておかないと、月次と週次の合計が合わない相談が来ます。

    集計の軸に使う前に件数を確かめる

    レポートで四半期別の集計を作るとき、期間レコードが存在しない範囲は空欄になります。2030年以降のデータを入れても、Quarter の期間が無ければ四半期に割り当てられません。

    長期のデータを扱う予定があるなら、着手前に期間の範囲を確認しておいてください。business_calendar_span を Year で絞り、start の最大値を見れば足ります。

    同じことが過去にも言えます。2000年より前のデータを移行する場合、その年の期間は存在しません。移行の前に、必要な範囲の期間が揃っているかを確かめる工程を入れておくと安全です。

    名前が空のレコードは触らない

    実測で85件あった名前なしのカレンダーは、用途が分かりませんでした。用途不明のレコードは、消さずにそのまま置いておくのが安全です。

    同じことが、カレンダー未設定の期間120件にも言えます。理由が分からないものを掃除すると、思わぬところが壊れます。

    どこから触るか

    • /business_calendar_list.do?sysparm_query=calendar_nameISNOTEMPTY … 名前のあるカレンダー13件

    • /business_calendar_span_list.do?sysparm_query=GROUPBYcalendar … 期間をカレンダー別に数える

    • /business_calendar_span_list.do?sysparm_query=calendar=<Yearのsys_id>^ORDERBYstart … Year の30件

    • /business_calendar_span_name_list.do … 期間の名前2,077件

    • /business_calendar.do?sys_id=... … 1件のカレンダー

    期間の一覧では start と end を列に出しておくと、境界の持ち方が見えます。カレンダーでグループ化すれば、どの種類が何件あるかも一目で分かります。

    なお business_calendar の一覧をそのまま開くと、名前が空の85件が混ざって読みにくくなります。calendar_nameISNOTEMPTY で絞ってから見ると読みやすくなります。

    まとめ

    Business Calendar は、期間を計算せずに行として持つ仕組みでした。

    カレンダーは98件のうち名前があるのが13件。そのうち4件が Year / Quarter / Month / Week の期間カレンダーで、残る9件は旧来のスケジュールでした。

    期間は2,197件。Year 30件を基準に、Quarter が4倍の120件、Month が12倍の360件。2000年から2029年までの30年分が、あらかじめレコードとして用意されています。

    Week だけが階層から独立しているのは、週が月をまたぐという現実をそのまま表したものでした。期間の終端を次の期間の開始と同じ値にしているのも、判定を単純にするための設計です。

    普段は意識しない機能ですが、レポートやSLAの土台としてどこでも使われています。「四半期の集計が合わない」という相談を受けたら、まずここを見ると早いです。

    動いているうちは誰も見ない仕組みです。止まって初めて存在に気づく。業務カレンダーはその典型で、2030年が近づいたときに思い出される種類の機能です。

    今のうちに、期間がどこまで作られているかを確認しておく。それだけで、数年後の慌ただしさを1つ減らせます。

    用語

    • 業務カレンダー(business_calendar) … 期間の種類を表す。Year / Quarter / Month / Week ほか

    • 期間(business_calendar_span) … 1期間=1行。start と end を持つ

    • 期間の名前(business_calendar_span_name) … 期間の表示名。翻訳のために別テーブル

    • parent … カレンダーの親子関係。Year → Quarter → Month

    • is_legacy_schedule … 旧来のスケジュール(営業時間・オンコール)かどうか

    • index0〜index9 … 期間に付く整数の列。10本ある

    自分の環境で確認するときのURL

    • /business_calendar_list.do?sysparm_query=calendar_nameISNOTEMPTY^ORDERBYcalendar_name(名前あり13件)

    • /business_calendar_span_list.do?sysparm_query=GROUPBYcalendar(期間のカレンダー別内訳)

    • /business_calendar_span_list.do?sysparm_query=calendar=...^ORDERBYstart(Year の30件)

    • /business_calendar_span_name_list.do(期間の名前2,077件)

    • /business_calendar.do?sys_id=...(1件のカレンダー)

    実測の要点(再掲)

    • business_calendar 98件。名前ありは13件、うち期間カレンダーは Year / Quarter / Month / Week の4件

    • 残る9件は is_legacy_schedule=true(Office Hours とオンコール由来の8件)

    • 名前が空の85件は label・cal_type・time_zone もすべて空

    • business_calendar_span 2,197件=Week 1,567 / Month 360 / Quarter 120 / Year 30 / 未設定 120

    • Year は2000年から30件。start は当年1月1日、end は翌年1月1日

    • 親子は Year → Quarter → Month。Week だけ親なし

    • business_calendar_span_name は2,077件。期間との差は120件

    検証範囲とおことわり

    • 件数・項目・値は PDI(Zurich / OOTB)の実測です。本番環境の設定とは異なります。

    • 名前が空のカレンダー85件、カレンダー未設定の期間120件は、用途を特定していません。

    • index0〜index9 の使われ方は未検証です。実測した Year の期間では空でした。

    • cal_type は98件すべて空で、選択肢の値も確認していません。

    • 会計年度(fiscal calendar)との関係は本記事の検証範囲外です。

    参考リンク

     
     
    ServiceNowのPDI(Personal Developer Instance)を実機で観察して、Noteに記事を書いています 実装・運用・教育の現場視点で淡々と記録中です リクエストがあればその記事も書きます 個人の観察日記です

    あなたへのおすすめ