From cf2099f683f33506c4cb6c65ca1acb2a57e77007 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Fri, 17 Jul 2026 21:02:18 +0800 Subject: [PATCH] feat: add birth date value adapter --- frontend/package-lock.json | 11 +++++++++++ frontend/package.json | 1 + frontend/src/lib/birth-time-intake-model.ts | 14 ++++++++++++++ frontend/tests/birth-time-intake.test.ts | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index abbfb260..bf21968b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@tailwindcss/postcss": "^4.3.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.4.0", "lucide-react": "^1.24.0", "next": "16.2.10", "react": "19.2.4", @@ -4131,6 +4132,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 570bfe21..6e70a79b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@tailwindcss/postcss": "^4.3.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.4.0", "lucide-react": "^1.24.0", "next": "16.2.10", "react": "19.2.4", diff --git a/frontend/src/lib/birth-time-intake-model.ts b/frontend/src/lib/birth-time-intake-model.ts index 8a178458..6a2a6715 100644 --- a/frontend/src/lib/birth-time-intake-model.ts +++ b/frontend/src/lib/birth-time-intake-model.ts @@ -1,5 +1,8 @@ +import { format, isValid, parse } from "date-fns"; import type { JourneySnapshot } from "./birth-time-journey.ts"; +const birthDatePattern = "yyyy-MM-dd"; + export type BirthTimeSource = | "" | "hospital_record" @@ -39,6 +42,17 @@ export type BirthTimeDraft = { export type BirthTimeDraftPatch = Partial; +export function parseBirthDate(value: string): Date | undefined { + if (value === "") return undefined; + const parsed = parse(value, birthDatePattern, new Date(2000, 0, 1)); + if (!isValid(parsed) || format(parsed, birthDatePattern) !== value) return undefined; + return parsed; +} + +export function formatBirthDate(value: Date): string { + return format(value, birthDatePattern); +} + export const birthTimeSourceOptions = [ { value: "hospital_record", label: "出生证明或医院记录", hint: "先检查前后两分钟是否稳定" }, { value: "family_exact", label: "家人明确记得具体时间", hint: "进行 5—15 分钟轻量校正" }, diff --git a/frontend/tests/birth-time-intake.test.ts b/frontend/tests/birth-time-intake.test.ts index 4b17aca9..45892252 100644 --- a/frontend/tests/birth-time-intake.test.ts +++ b/frontend/tests/birth-time-intake.test.ts @@ -4,7 +4,9 @@ import { assistantIntentCopy, birthTimePersistenceValues, describeBirthTimeDraft, + formatBirthDate, isBirthTimeDraftReady, + parseBirthDate, type BirthTimeDraft, } from "../src/lib/birth-time-intake-model.ts"; @@ -79,3 +81,19 @@ test("birth time intake describes uncertainty without claiming false precision", "目前只能保存候选范围,还没有足够证据应用到具体分钟。", ); }); + +test("birth date parsing preserves the local calendar day", () => { + const parsed = parseBirthDate("1993-04-17"); + + assert.equal(parsed?.getFullYear(), 1993); + assert.equal(parsed?.getMonth(), 3); + assert.equal(parsed?.getDate(), 17); +}); + +test("birth date values round trip leap days and reject invalid input", () => { + const leapDay = parseBirthDate("2000-02-29"); + + assert.equal(leapDay === undefined ? undefined : formatBirthDate(leapDay), "2000-02-29"); + assert.equal(parseBirthDate(""), undefined); + assert.equal(parseBirthDate("2001-02-29"), undefined); +});