diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bb875f..5131cef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - スコープおよび名前が同一である変数が宣言された際のエラーメッセージを修正 - ネストされた名前空間下の変数を参照できるように - `arr.every`, `arr.some`を追加 +- `Date:to_iso_str`を追加 # 0.17.0 - `package.json`を修正 diff --git a/docs/std.md b/docs/std.md index 4e888add..d857e498 100644 --- a/docs/std.md +++ b/docs/std.md @@ -95,6 +95,12 @@ _date_ を渡した場合、_date_に対応するミリ秒、 ### @Date:parse(_date_: str): num +### @Date:to_iso_str(_date_?: num, _time_offset_?: num): str +_date_ を拡張表記のISO形式にした文字列を返します。 +_date_ を渡していない場合は現在時刻を使用します。 +_time_offset_ はUTCからの時差(分単位)を指定します。 +_time_offset_ を渡していない場合はローカルのものを参照します。 + ## :: Math 数が多いため専用のページになっています。→[std-math.md](std-math.md) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 469cba07..33daec7d 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -220,6 +220,39 @@ export const std: Record = { assertString(v); return NUM(new Date(v.value).getTime()); }), + + 'Date:to_iso_str': FN_NATIVE(([v, ofs]) => { + if (v) { assertNumber(v); } + const date = new Date(v?.value ?? Date.now()); + + if (ofs) { assertNumber(ofs); } + const offset = ofs?.value ?? -date.getTimezoneOffset(); + let offset_s; + if (offset === 0) { + offset_s = 'Z'; + } else { + const sign = Math.sign(offset); + const offset_hours = Math.floor(Math.abs(offset) / 60); + const offset_minutes = Math.abs(offset) % 60; + date.setUTCHours(date.getUTCHours() + sign * offset_hours); + date.setUTCMinutes(date.getUTCMinutes() + sign * offset_minutes); + + const sign_s = (offset > 0) ? '+' : '-'; + const offset_hours_s = offset_hours.toString().padStart(2, '0'); + const offset_minutes_s = offset_minutes.toString().padStart(2, '0'); + offset_s = `${sign_s}${offset_hours_s}:${offset_minutes_s}`; + } + + const y = date.getUTCFullYear().toString().padStart(4, '0'); + const mo = (date.getUTCMonth() + 1).toString().padStart(2, '0'); + const d = date.getUTCDate().toString().padStart(2, '0'); + const h = date.getUTCHours().toString().padStart(2, '0'); + const mi = date.getUTCMinutes().toString().padStart(2, '0'); + const s = date.getUTCSeconds().toString().padStart(2, '0'); + const ms = date.getUTCMilliseconds().toString().padStart(3, '0'); + + return STR(`${y}-${mo}-${d}T${h}:${mi}:${s}.${ms}${offset_s}`); + }), //#endregion //#region Math diff --git a/test/index.ts b/test/index.ts index c28f41f2..290efa73 100644 --- a/test/index.ts +++ b/test/index.ts @@ -3185,6 +3185,52 @@ describe('std', () => { }); }); }); + + describe('Date', () => { + test.concurrent('to_iso_str', async () => { + const res = await exe(` + let d1 = Date:parse("2024-04-12T01:47:46.021+09:00") + let s1 = Date:to_iso_str(d1) + let d2 = Date:parse(s1) + <: [d1, d2, s1] + `); + eq(res.value[0], res.value[1]); + assert.match(res.value[2].value, /^[0-9]{4,4}-[0-9]{2,2}-[0-9]{2,2}T[0-9]{2,2}:[0-9]{2,2}:[0-9]{2,2}\.[0-9]{3,3}(Z|[-+][0-9]{2,2}:[0-9]{2,2})$/); + }); + + test.concurrent('to_iso_str (UTC)', async () => { + const res = await exe(` + let d1 = Date:parse("2024-04-12T01:47:46.021+09:00") + let s1 = Date:to_iso_str(d1, 0) + let d2 = Date:parse(s1) + <: [d1, d2, s1] + `); + eq(res.value[0], res.value[1]); + eq(res.value[2], STR("2024-04-11T16:47:46.021Z")); + }); + + test.concurrent('to_iso_str (+09:00)', async () => { + const res = await exe(` + let d1 = Date:parse("2024-04-12T01:47:46.021+09:00") + let s1 = Date:to_iso_str(d1, 9*60) + let d2 = Date:parse(s1) + <: [d1, d2, s1] + `); + eq(res.value[0], res.value[1]); + eq(res.value[2], STR("2024-04-12T01:47:46.021+09:00")); + }); + + test.concurrent('to_iso_str (-05:18)', async () => { + const res = await exe(` + let d1 = Date:parse("2024-04-12T01:47:46.021+09:00") + let s1 = Date:to_iso_str(d1, -5*60-18) + let d2 = Date:parse(s1) + <: [d1, d2, s1] + `); + eq(res.value[0], res.value[1]); + eq(res.value[2], STR("2024-04-11T11:29:46.021-05:18")); + }); + }); }); describe('Unicode', () => {