feat: 日付パースロジックを追加

This commit is contained in:
Kenichiro NOGI 2026-09-05 14:29:08 +09:00
parent 0bee14f18c
commit 3573d1b78f
2 changed files with 129 additions and 0 deletions

View File

@ -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 };

View File

@ -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("あいうえお"));
});