From 3573d1b78f2283a11f8062a896559fc516a3bb37 Mon Sep 17 00:00:00 2001 From: Kenichiro NOGI Date: Sat, 5 Sep 2026 14:29:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=97=A5=E4=BB=98=E3=83=91=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/dateParser.js | 92 +++++++++++++++++++ .../test/dateParser.test.js | 37 ++++++++ 2 files changed, 129 insertions(+) create mode 100644 NodeSrv/apps/healthcheck-survey-bot/src/lib/dateParser.js create mode 100644 NodeSrv/apps/healthcheck-survey-bot/test/dateParser.test.js diff --git a/NodeSrv/apps/healthcheck-survey-bot/src/lib/dateParser.js b/NodeSrv/apps/healthcheck-survey-bot/src/lib/dateParser.js new file mode 100644 index 00000000..8ca27bec --- /dev/null +++ b/NodeSrv/apps/healthcheck-survey-bot/src/lib/dateParser.js @@ -0,0 +1,92 @@ +const ERA_INFO = { + "令和": 2018, + "平成": 1988, + "昭和": 1925, + "大正": 1911, +}; + +const ERA_ALIASES = { R: "令和", H: "平成", S: "昭和", T: "大正" }; + +function pad2(value) { + return String(value).padStart(2, "0"); +} + +function finalizeDateParts(year, month, day) { + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + throw new Error("日付形式で回答してください(例: 2026-03-01)"); + } + const date = new Date(year, month - 1, day); + if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { + throw new Error("存在しない日付です"); + } + return `${year}-${pad2(month)}-${pad2(day)}`; +} + +function tryParseEraDate(compact) { + const match = compact.match(/^(令和|平成|昭和|大正|R|H|S|T)(.+)$/i); + if (!match) return null; + + let era = match[1]; + if (/^[RHST]$/i.test(era)) { + era = ERA_ALIASES[era.toUpperCase()] || era; + } + if (!ERA_INFO[era]) return null; + + const normalized = match[2] + .replace(/年/g, "-") + .replace(/月/g, "-") + .replace(/日/g, "") + .replace(/[.\/]/g, "-"); + const parts = normalized.split("-").filter((part) => part.length > 0); + if (parts.length < 3) { + throw new Error("月と日まで入力してください(例: 令和6年3月1日)"); + } + + const eraYear = Number(parts[0]); + const month = Number(parts[1]); + const day = Number(parts[2]); + if (!Number.isFinite(eraYear) || !Number.isFinite(month) || !Number.isFinite(day)) { + throw new Error("日付形式で回答してください(例: 令和6年3月1日)"); + } + + return finalizeDateParts(ERA_INFO[era] + eraYear, month, day); +} + +function tryParseMonthDay(compact) { + const match = compact.match(/^(\d{1,2})(?:月|\/|-|\.)(\d{1,2})(?:日)?$/); + if (!match) return null; + const currentYear = new Date().getFullYear(); + return finalizeDateParts(currentYear, Number(match[1]), Number(match[2])); +} + +function parseDateInput(value) { + const trimmed = String(value ?? "").trim(); + if (!trimmed) { + throw new Error("日付が空です"); + } + const compact = trimmed.replace(/\s+/g, ""); + + const eraResult = tryParseEraDate(compact); + if (eraResult) return eraResult; + + const monthDayResult = tryParseMonthDay(compact); + if (monthDayResult) return monthDayResult; + + const normalized = compact + .replace(/年/g, "-") + .replace(/月/g, "-") + .replace(/日/g, "") + .replace(/[.\/]/g, "-"); + const isoParts = normalized.split("-").filter((part) => part.length > 0); + if (isoParts.length === 3 && isoParts[0].length >= 4) { + return finalizeDateParts(Number(isoParts[0]), Number(isoParts[1]), Number(isoParts[2])); + } + + const parsed = new Date(trimmed); + if (Number.isNaN(parsed.getTime())) { + throw new Error("日付形式で回答してください(例: 2026-03-01 や 令和6年3月1日)"); + } + return finalizeDateParts(parsed.getFullYear(), parsed.getMonth() + 1, parsed.getDate()); +} + +module.exports = { parseDateInput }; diff --git a/NodeSrv/apps/healthcheck-survey-bot/test/dateParser.test.js b/NodeSrv/apps/healthcheck-survey-bot/test/dateParser.test.js new file mode 100644 index 00000000..701004bc --- /dev/null +++ b/NodeSrv/apps/healthcheck-survey-bot/test/dateParser.test.js @@ -0,0 +1,37 @@ +// test/dateParser.test.js +const { test } = require("node:test"); +const assert = require("node:assert"); +const { parseDateInput } = require("../src/lib/dateParser"); + +test("ISO形式の日付をそのまま解釈する", () => { + assert.strictEqual(parseDateInput("2026-03-01"), "2026-03-01"); +}); + +test("スラッシュ区切りの日付を解釈する", () => { + assert.strictEqual(parseDateInput("2026/3/1"), "2026-03-01"); +}); + +test("和暦(令和)を西暦に変換する", () => { + assert.strictEqual(parseDateInput("令和6年3月1日"), "2024-03-01"); +}); + +test("和暦の略記(R)を西暦に変換する", () => { + assert.strictEqual(parseDateInput("R6.3.1"), "2024-03-01"); +}); + +test("月日のみの入力は今年として解釈する", () => { + const currentYear = new Date().getFullYear(); + assert.strictEqual(parseDateInput("3/1"), `${currentYear}-03-01`); +}); + +test("空文字はエラーになる", () => { + assert.throws(() => parseDateInput(""), /日付が空です/); +}); + +test("存在しない日付はエラーになる", () => { + assert.throws(() => parseDateInput("2026-02-30"), /存在しない日付です/); +}); + +test("解釈不能な文字列はエラーになる", () => { + assert.throws(() => parseDateInput("あいうえお")); +});