プリザンターのサーバスクリプトで日付を簡単に扱う方法

## はじめに
プリザンターのサーバスクリプトだと、日付項目などのDateTime型はタイムゾーンはUTCのデータしか取得が出来ません。
これは、サーバスクリプトの実装に使われているClearScriptのライブラリの制約によるもので、C#のDateTime型をJavaScriptのDateTime型に変換をおこなうときに、タイムゾーンがUTCとなってしまうためです。

> EnableDateTimeConversion
> Specifies that the script engine is to perform automatic conversion between .NET DateTime objects and JavaScript Date objects. This conversion is bidirectional and lossy. A DateTime object constructed from a JavaScript Date object always represents a Coordinated Universal Time (UTC) and has its Kind property set to Utc.
>> https://microsoft.github.io/ClearScript/Reference/html/T_Microsoft_ClearScript_V8_V8ScriptEngineFlags.htm

ただし、気をつけないといけないことが一つあります。サーバスクリプト内で日付の書き換え行う場合など、DateTimeの更新などを行う場合は、それを実行するユーザーにセットされているタイムゾーンのデータをセットする必要があります。
どこでどのタイムゾーンになるか簡単にまとめてみました。

|実行環境|データベース|ユーザー設定|API Get/Set[^1]|スクリプトGet/Set[^2]|サーバスクリプトGet|サーバスクリプトSet[^2]|
|--|--|--|--|--|--|--|
|JST|JST|JST|JST|JST|UTC|JST|
|UTC|UTC|JST|JST|JST|UTC|JST|

普通に日本向けに運用している場合などは、何をするにしても一旦JSTに変換する必要がありかなりめんどくさい・・・。

## 解決してみた
### サーバスクリプトに実装してみた
サーバスクリプトから取得するときだけ、UTCになるのが不便なので、一括でタイムゾーンを変換してやりましょう。
`model`を直接触ると面倒なので、DateTimeなプロパティだけを`model_jst`という変数を作ってそちらにセットしてみます。「画面表示の前」や「秒表示の前」にセットすればOKです。

```js
const regex = new RegExp('(^Date|Time$)');
let model_jst = {};

for (const [key, value] of Object.entries(model)) {
    if (regex.test(key) && value) {
        model_jst[key] = value.toLocaleString("ja-JP", {
             timeZone: "JST"
        });
    }
}
```

`DateA～Z`と`Date001～999`、`UpdateTime`と`CreatedTime`のうちページに表されているもの(=modelに含まれているもの)だけが、model_jstにセットされます。表示されていないものを触りたいときはview.AlwaysGetColumnsを使って対象を追加する方法も吊しのまま使えるようになっています。
デバッグ用に`context.Log`を追加して実行してみるとこんな感じになります。

```console
model:CreatedTime/Thu Dec 05 2024 06:19:08 GMT+0000 (Coordinated Universal Time)
model:UpdatedTime/Thu Dec 05 2024 06:19:08 GMT+0000 (Coordinated Universal Time)
model:DateY/Wed Dec 04 2024 15:00:00 GMT+0000 (Coordinated Universal Time)
model:DateZ/Wed Dec 04 2024 15:00:00 GMT+0000 (Coordinated Universal Time)
model:Date001/Wed Dec 04 2024 15:00:00 GMT+0000 (Coordinated Universal Time)
model:Date002/Wed Dec 04 2024 15:00:00 GMT+0000 (Coordinated Universal Time)
model_jst:CreatedTime/2024/12/5 15:19:08
model_jst:UpdatedTime/2024/12/5 15:19:08
model_jst:DateY/2024/12/5 0:00:00
model_jst:DateZ/2024/12/5 0:00:00
model_jst:Date001/2024/12/5 0:00:00
model_jst:Date002/2024/12/5 0:00:00
```
お、いい感じに変換できてますね。

### 拡張サーバスクリプトにしてみた
それぞれのサイトに追加していくもの面倒なので、拡張サーバスクリプトにして組み込んでみました。

```json:DateTimeTimeZoneConvert.json
{
    "Name": "modelのタイムゾーンを変換する",
    "BeforeOpeningPage": true,
    "BeforeOpeningRow": true,
    "Body": "// Write an arbitrary script."
}
```
```js:DateTimeTimeZoneConvert.json.js
let model_jst = {};
let success_model_jst = false;
//エラーが出ると面倒なのでtry-catchで囲っておく
try {
    const regex = new RegExp('(^Date|Time$)');
    for (const [key, value] of Object.entries(model)) {
        if (regex.test(key) && value) {
            model_jst[key] = value.toLocaleString("ja-JP", {
　               timeZone: "JST"
            });
        }
    }
    success_model_jst = true;
} catch(e) {
    success_model_jst = false;
    context.Error(e.Stack);
}
```
拡張サーバスクリプトはエラーを吐くと全部が止まってしまって面倒なので、try-catchで囲っています。また、正常に実行されることを確認するために、`success_model_jst`に実行に成功したかどうかを`true/false`でセットしています。

## まとめ
サーバスクリプトで常に頭を悩ませるタイムゾーン問題を簡単に解決してみました。マルチロケーションで異なるタイムゾーンのユーザーが混在している環境の場合は、`user.UserId`で実行ユーザのIDを取得した後にAPIの`users/get`でユーザーの`TimeZone`と`Language`[^3]を取得することで対応が可能です。

[^1]:APIキーにセットされているユーザーと同じ
[^2]:実行したユーザーのタイムゾーンと同じ
[^3]:ロケールはプリザンターでは管理していないので、Languageから類推してセットする必要あり