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

GlideDateTimeで日付と経過時間を扱う


    開発ハンズオンシリーズの5回目です。①のGlideRecordで検索と更新を動かし、②で件数を集計し、③のドット・ウォークで参照先を辿り、④ではエンコードクエリで条件を組み立ててきました。ここまでで「探す・数える・辿る・絞る」の四つが手になじんだころだと思います。今回のテーマは、その次によく出会う 日付と時刻 です。

    ServiceNow を運用していると、「起票から今まで何時間経ったか」「締め切りまであと何日か」「更新が止まって何日たったか」といった、時間にまつわる計算に何度も出会います。この計算を受け持つのが GlideDateTime(グライド・デート・タイム) というオブジェクトです。今回も前回までと同じ Scripts - Background で、日付の足し引きや経過時間の求め方を、一つずつ実機で動かしてみましょう。

    今回のコードは、入力をすべてリテラルの日時文字列にしてあります。'2024-03-01 08:00:00' のように固定の値から始めるので、どなたの PDI で流しても、この記事とそっくり同じ結果が出るはずです。安心して手元でコピーして試してください。

    身構える必要はありません。日付の計算は、コツさえつかめば単純な足し算と引き算に落ち着きます。今回は入力を固定した四つの短いコードだけを、順番に流します。読み終えるころには、経過時間の求め方が手の中にあるはずですよ。

    この記事は、ServiceNow の PDI(Personal Developer Instance、無料の個人検証環境)Zurich リリース・OOTB(初期設定)で実機実行した記録です。コードの出力はすべて Scripts - Background で流した結果で、入力はリテラルの日時なので誰の環境でも再現できます(確認日 2026-07-19)。本番環境では実行しないでください。

    GlideDateTime とは

    GlideDateTime は、ひとことで言うと 日付と時刻をまとめて扱うサーバサイドのオブジェクト です。ふつうの JavaScript にも Date はありますが、ServiceNow のスクリプトでは、この GlideDateTime を使うのが定石になっています。理由は、内部の値の持ち方と計算メソッドが、プラットフォームの流儀にそろえてあるからですね。

    大事な性質が一つあります。GlideDateTime が内部で持つ時刻は、つねに UTC(協定世界時) です。画面に日本時間で表示されていても、裏側の値は UTC でそろっています。だから「サーバのどこで動いても、同じ入力なら同じ内部値になる」という一貫性が保たれるわけです。この記事で扱う getValue() が誰の環境でも一致するのは、この UTC 基準のおかげですよ。

    素の JavaScript の Date を使わず、わざわざ GlideDateTime を選ぶ理由も押さえておきましょう。ServiceNow のフィールドに入っている日時は、GlideDateTime として読み書きするのが前提になっています。だから DB のフィールドと相性がよく、選択肢の変換やタイムゾーンの扱いも、プラットフォーム側の設定に自動でそろいます。自前で Date を組み立てると、この足並みが崩れて、思わぬ時差のずれを招きがちです。サーバサイドで日時を触るなら GlideDateTime、と決めておくと迷いません。

    GlideDateTime が活躍する場所は、思っている以上に広いものです。Business Rule で保存時刻を加工したり、Scheduled Job で締め切りを判定したり、Flow のスクリプトステップで日付を組み立てたり。テーブルに日時フィールドがある以上、どの仕組みでも顔を出します。だからここで覚える基本は、今後どんなサーバサイドの日付処理を書くときにも、そっくり流用できる土台になりますね。手を動かして体になじませておく価値は、じゅうぶんにあります。

    作り方はとても簡単で、日時の文字列を渡すだけです。new GlideDateTime('2024-03-01 08:00:00') と書けば、その瞬間を指すオブジェクトが1つできあがります。あとはこのオブジェクトに、足し引きや差の計算、前後の比較を頼んでいく形になります。まずは、いちばん基本の「中身を読む」ところから始めましょう。

    基本:内部値・エポックms・部品

    最初に、作った GlideDateTime から値を三通りの形で取り出してみます。内部値そのもの、1970年からの経過ミリ秒、そして「日」だけの部品。この三つを並べると、オブジェクトが中に何を持っているのかが見えてきますね。

    var gdt = new GlideDateTime('2024-03-01 08:00:00');
    gs.info('内部値 getValue          : ' + gdt.getValue());
    gs.info('エポックms getNumericValue : ' + gdt.getNumericValue());
    gs.info('日   getDayOfMonthUTC    : ' + gdt.getDayOfMonthUTC());


    画像

    出力はこうなりました。getValue() が 2024-03-01 08:00:00、getNumericValue() が 1709280000000、getDayOfMonthUTC() が 1 です。

    一つずつ意味を見ます。getValue() は、DB に保存される形そのままの内部値で、UTC の文字列で返ります。入力した '2024-03-01 08:00:00' がそのまま出ているのは、この値を UTC として受け取ったからですね。getNumericValue() が返した 1709280000000 は、1970年1月1日を起点にした経過ミリ秒です。一見とっつきにくい数字ですが、これは「エポックからの通し番号」で、あとで差を秒に直すときに効いてきます。getDayOfMonthUTC() は日付のうち「日」だけを取り出す部品で、ここでは 1 が返りました。月や時を取り出すメソッドも、同じ調子でそろっています。

    エポックミリ秒がなぜ用意されているのかも、少しだけ触れておきます。日時を「1970年からの通し番号」に直しておくと、二つの時刻の大小や差を、ただの引き算として扱えます。文字列のままでは順序を正しく比べにくい日時も、数値に直せば計算の土俵にのせられるわけですね。あとで出てくる経過時間の秒換算も、この数値表現があるからこそ素直に求められます。人が読むのは表示値、機械が計算に使うのは数値、という役割の分かれ方だと思っておくと腑に落ちますよ。

    一点だけ補足させてください。getValue() は UTC の内部値なので、誰が実行しても同じ文字列になります。一方で、画面向けの表示を返す getDisplayValue() というメソッドもあって、そちらはセッションのタイムゾーン設定しだいで見え方が変わってきます。再現性を大切にしたいので、この記事では内部値の getValue() を軸に据えました。表示値の話は範囲外として、末尾のおことわりにもまとめておきました。

    足し引き

    日時オブジェクトの便利さがいちばん出るのが、足し引きです。「この日時の10日後は?」を、カレンダーを数えずにコード1行で出せます。

    var gdt = new GlideDateTime('2024-03-01 08:00:00');
    gdt.addDaysUTC(10);
    gs.info('10日後 : ' + gdt.getValue());


    画像

    結果は 2024-03-11 08:00:00。3月1日の10日後、たしかに3月11日になりました。月をまたぐ計算も、うるう年の判定も、こちらが気にする必要はありません。暦の面倒は GlideDateTime が引き受けてくれます。

    addDaysUTC() は「UTC 基準で日数を足す」メソッドです。仲間はほかにもあって、秒を足す addSeconds()、時間や週の単位で足すものなど、単位ごとに用意されています。引きたいときは、マイナスの数を渡せば大丈夫です。addDaysUTC(-10) なら10日前になりますね。締め切りの計算で「起票日に何日か足す」ような使い方をよくしますが、暦どおりの単純な足し引きなら、このメソッド群だけで十分こなせますよ。

    ここで気をつけたいのは、addDaysUTC() が 元のオブジェクトそのものを書き換える 点です。新しいオブジェクトを返すのではなく、gdt の中身が10日後に進みます。だから、足す前の値も残しておきたいときは、先に getValue() で控えるか、別のオブジェクトを作っておくのが安全です。ここは最初にとまどいやすいので、頭の隅に置いておいてください。

    足し引きが素直だと、日付をまたぐロジックがぐっと書きやすくなります。たとえば「受付から3日後の同じ時刻」を出したいとき、受付日時のオブジェクトに addDaysUTC(3) を頼むだけで済みます。手で曜日や月末を数える必要はありません。境界の面倒をオブジェクトに任せられるので、書き手はやりたいこと本体に集中できますね。この身軽さが、日時の計算を GlideDateTime に寄せる大きな利点です。

    経過時間=差

    二つの日時があれば、その 差=経過時間 を求められます。「起票から解決まで何時間かかったか」を出すときの、中心になる計算です。

    var d1 = new GlideDateTime('2024-03-01 08:00:00');
    var d2 = new GlideDateTime('2024-03-03 12:30:00');
    var dur = GlideDateTime.subtract(d1, d2);
    gs.info('経過(表示) : ' + dur.getDisplayValue());
    gs.info('経過(秒)   : ' + dur.getNumericValue() / 1000);


    画像


    画像

    出力は、表示が 2 Days 4 Hours 30 Minutes、秒に直すと 189000 でした。3月1日8時から3月3日12時30分まで、2日と4時間30分。図のとおり、開始と終了ではさむと、その間の長さが返ってきます。

    この差の計算は、GlideDateTime.subtract(d1, d2) という形で呼びます。d1 が先、d2 が後で、返ってくるのは GlideDuration(グライド・デュレーション) という「時間の長さ」を表す別のオブジェクトです。日時そのものではありません。あくまで「どれだけ離れているか」を持つ点が、GlideDateTime との違いですね。

    返ってきた Duration からは、二通りの読み方ができます。getDisplayValue() は「2 Days 4 Hours 30 Minutes」と、人が読みやすい形で返してくれます。getNumericValue() はミリ秒で返るので、1000で割って秒に直しました。手で検算しておくと安心できますね。2日は 2×86400 秒、4時間は 4×3600 秒、30分は 30×60 秒。足すと 172800+14400+1800=189000 秒で、表示とぴったり一致しました。数字の裏づけが取れると、コードの結果をそのまま信用できます。

    表示と数値、二つの顔を持っているのが Duration の使いどころです。ログや画面に「2 Days 4 Hours 30 Minutes」と出せば、人はひと目で長さをつかめます。一方で、しきい値と比べたり集計したりするなら、秒やミリ秒の数値のほうが扱いやすいものです。同じ経過時間でも、見せる相手が人か処理かで、取り出す形を選び分ける。この使い分けは、前回までに見た生値と表示値の話と、根っこがよく似ていますね。

    ここまではリテラルの日時から始めましたが、引数なしで new GlideDateTime() と書くと、その時点の現在時刻を持ったオブジェクトになります。実務では、この「今」を起点に差を取ったり、締め切りと比べたりする場面がほとんどです。現在時刻は流すたびに変わるので、この記事では再現性を優先してリテラルにそろえましたが、考え方はまったく同じ。起点が「今」に変わるだけで、足し引きも差も前後比較も、これまで見たとおりに動きます。

    前後を比べる

    最後は、二つの日時の 前後 を判定します。「締め切りを過ぎているか」「予定より早いか」を、素直に true / false で受け取れます。

    var d1 = new GlideDateTime('2024-03-01 08:00:00');
    var d2 = new GlideDateTime('2024-03-03 12:30:00');
    gs.info('d1 は d2 より前か before : ' + d1.before(d2));
    gs.info('d1 は d2 より後か after  : ' + d1.after(d2));


    画像

    結果は before が true、after が false でした。d1(3月1日)は d2(3月3日)より前なので、before は true になります。後ろではないので、after は false ですね。読み下したとおりの答えが返りました。

    ここで覚えておきたいのは、日時の前後は before() / after() で比べる、という点です。文字列のまま大小を比べる書き方も一見できそうに見えますが、形式が少しでも崩れると意図しない結果になりがちです。GlideDateTime どうしを before / after でつなげば、内部の UTC 値で正しく判定してくれます。安全で、読む人にも意図が伝わりやすい書き方ですね。実務でも、締め切り判定やSLAの超過チェックで、この二つのメソッドは何度も出番があります。

    before / after は、返り値が true か false かの単純な判定です。だから if 文の条件にそのまま置けて、「締め切りを過ぎていたらエスカレーションする」といった分岐が読みやすく書けます。逆に、同じか等しいかまで細かく見たい場面では、先ほどの getNumericValue() で数値に直して比べる手もあります。まずは前後の判定を before / after に任せる。それだけで、日時まわりの条件分岐はかなり安全に組めますよ。

    メソッド早見表

    今回登場したメソッドを一覧にまとめました。まずはこれだけ手になじませれば、日付まわりのスクリプトでしばらく困りません。


    画像

    表のメソッドは、名前を見れば役割がだいたい想像できます。addDaysUTC は日を足す、getNumericValue は数値で返す、といった具合です。UTC が付く名前は「内部の UTC 基準で動く」印なので、再現性を重視するなら、この UTC 付きのメソッドを選んでおくと安心ですよ。表示に寄せたメソッドはタイムゾーンで揺れるため、まずは内部値側から手になじませるのがおすすめです。

    今回の実測まとめ

    実機で流した入力と出力を、一枚の表に並べておきます。手元の PDI で同じコードを動かしたとき、答え合わせに使ってください。


    画像

    表の値は、どれも本文のコードをそのまま流して得たものです。手元で同じコードを動かして、この数字と見比べてみてください。ひとつでも食い違ったら、入力の日時文字列がずれていないか、まず疑うのが近道になります。入力がリテラルでそろっているかぎり、結果もそろうはずですからね。

    実務での使いどころ

    日付の計算が手になじむと、日々のスクリプトで書ける幅がぐっと広がります。実務でよく出会う形を、いくつか挙げてみましょう。

    一つめは「起票からの経過時間」です。インシデントの opened_at(起票日時)と現在時刻の差を Duration で求めれば、「この案件は何時間放置されているか」がすぐ分かります。ダッシュボードの色分けや、一定時間を超えた案件の洗い出しに、この経過時間が効いてきます。前回までに動かした検索と組み合わせれば、「経過が長い順に並べる」といった処理も自然に書けますね。

    二つめは「締め切りの計算」です。起票日時に addDaysUTC() で日数を足せば、「対応期限は何日の何時まで」という締め切りを機械的に作れます。求めた締め切りと現在時刻を before / after で比べれば、超過しているかどうかの判定まで一気につながります。SLA のような「約束の時間」を扱う場面で、この足し算と前後比較が土台になりますね。

    三つめは「古いレコードを探す」です。現在時刻から addDaysUTC(-30) のように引いて基準日を作り、「その基準日より前で止まっているレコード」を条件で絞れば、棚卸しの候補が拾えます。夜間バッチで定期的に回して、動きの止まった案件に気づく仕組みも作れますね。具体的な件数はインスタンスごとに違うので、ここでは考え方だけにとどめますが、日付の引き算が入口になるのは共通しています。

    三つに共通しているのは、どれも「日時を数えて、比べて、判断する」という同じ骨組みだという点です。起点の日時を作り、足し引きで別の日時を出し、差や前後で判断する。今回動かした四つのコードは、そのまま実務のロジックの部品になります。凝った仕組みに見える締め切り管理も、ばらすとこの基本の組み合わせにたどり着きますよ。

    まとめ

    GlideDateTime は、日時を UTC の内部値で持ち、足し引き・差・前後比較を素直にこなすオブジェクトでした。getValue() で内部値を、getNumericValue() でエポックミリ秒を読み、addDaysUTC() で足し引きし、GlideDateTime.subtract() で差を Duration として受け取る。前後は before / after で判定する。今日動かしたのはこれだけですが、時間にまつわる処理の土台は、この組み合わせでほぼ足ります。

    今回もいちばんの近道は、記事を読んで終わりにせず、Scripts - Background に貼って自分で流してみることです。入力はリテラルなので、この記事と同じ数字が出るはず。同じ結果が返る体験を一度くぐると、日付の計算に対する苦手意識がすっと消えますよ。

    日付を制すると、ServiceNow の運用は一段ラクになります。経過時間が見えれば、放置された案件に早く気づけますね。締め切りを先に計算できれば、超過する前に手を打てる。小さなメソッドの積み重ねですが、効き目はあなどれませんよ。

    次回は、④のエンコードクエリと対になる話として、AND と OR を「コード」で組む方法(addQuery と addOrCondition)を実機で動かします。コードで組んだ条件を getEncodedQuery で文字列に戻す小技も、あわせて紹介する予定です。

    その他、実機で確認した記録

    用語

    • GlideDateTime … 日付と時刻を扱うサーバサイドの標準オブジェクト(内部は UTC)

    • GlideDuration … 二つの日時の差=時間の長さを表すオブジェクト

    • getValue() … DB 保存形の内部値(UTC 文字列)を返す

    • getNumericValue() … 1970年からの経過ミリ秒を返す

    • addDaysUTC() / addSeconds() … 単位ごとに日時を足す(マイナスで引く)

    • GlideDateTime.subtract() … 二つの日時の差を Duration で返す

    • before() / after() … 二つの日時の前後を true / false で判定する

    実行に使ったスクリプト

    本文のコードは、そのまま Scripts - Background に貼れば動きます(実行は必ず PDI で)。入力はすべてリテラルの日時文字列なので、出力はどの環境でも同じになります。筆者の PDI(dev / Zurich / OOTB)で流した実画面を、そのまま載せました。

    調べるときの入口URL(先頭に自分のインスタンスを付ける)

    • /sys.scripts.do … Scripts - Background(今回コードを流した画面)

    • /sys_properties_list.do?sysparm_query=nameSTARTSWITHglide.sys.date … 日付書式まわりのシステムプロパティ

    • /sys_dictionary_list.do?sysparm_query=name=incident^internal_type=glide_date_time … incident の日時フィールド一覧

    実測の要点(再掲)

    • 基本 … getValue=2024-03-01 08:00:00 / getNumericValue=1709280000000 / getDayOfMonthUTC=1

    • 足し引き … addDaysUTC(10) で 2024-03-11 08:00:00(10日後)

    • 経過 … subtract の表示=2 Days 4 Hours 30 Minutes / 秒=189000(検算 2×86400+4×3600+30×60)

    • 前後 … before=true / after=false

    検証範囲とおことわり

    • 実行は PDI(Zurich / OOTB)の Scripts - Background・scope=global のみ。入力はリテラルなので誰でも再現できます。

    • 表示を返す getDisplayValue はセッションのタイムゾーンに依存するため、本記事では扱いません(内部値の getValue と Duration のみ)。

    • 営業時間(就業カレンダー)ベースの経過時間は Schedule 系の別機能なので、本記事の範囲外とします。

    参考リンク

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

    あなたへのおすすめ