mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
fix(profile): read and store the date of birth as a calendar day
The iOS app sends the date of birth as the instant its date picker returned, and the profile writers stored that instant as sent. Since the report cover and the share page read the date part of the stored value, an entry made just after midnight in Berlin printed the day before, and a Californian entry made in the evening printed the day after. Both profile writers now store the calendar day the value names in the owner's zone, at noon UTC like other date-only values, so an iOS client reading the instant back in its own zone keeps the date. Older rows keep their instant: dateOfBirthKey reads a date-only anchor (UTC midnight or noon) by its date and any other instant in the owner's zone. The report collector, the targets age, and the web forms that send the date back read it through that helper.
This commit is contained in:
1 parent
82125815d5
commit
17dbb225d4
13 files changed
+275
-30
No files matched your search
@@ -90,6 +90,20 @@ describe("POST /api/onboarding/complete", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("stores a date of birth from the iOS app as its day in the owner's zone", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue({
|
||||
...SESSION_OK,
|
||||
user: { ...SESSION_OK.user, timezone: "Europe/Berlin" },
|
||||
} as never);
|
||||
// 14 May picked at 00:30 in Berlin.
|
||||
const res = await POST(req({ dateOfBirth: "1960-05-13T23:30:00Z" }));
|
||||
expect(res.status).toBe(200);
|
||||
const data = vi.mocked(prisma.user.update).mock.calls[0]![0]!.data as {
|
||||
dateOfBirth: Date;
|
||||
};
|
||||
expect(data.dateOfBirth.toISOString()).toBe("1960-05-14T12:00:00.000Z");
|
||||
});
|
||||
|
||||
it("still refuses an unsupported gender", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
const res = await POST(req({ gender: "diverse" }));
|
||||
|
||||
@@ -29,6 +29,8 @@ import { readManagedProfileForGuardian } from "@/lib/managed-profiles/lifecycle"
|
||||
import { writeRecordModulePreferences } from "@/lib/record-settings/modules";
|
||||
import { NextRequest } from "next/server";
|
||||
import { onboardingCompleteSchema } from "@/lib/validations/onboarding";
|
||||
import { dateOfBirthForStorage } from "@/lib/tz/date-only";
|
||||
import { resolveStoredTimezone } from "@/lib/tz/resolver";
|
||||
|
||||
/**
|
||||
* Complete the onboarding flow. Saves optional profile data and marks
|
||||
@@ -84,10 +86,12 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
}
|
||||
|
||||
if (result.data.dateOfBirth) {
|
||||
const dob = new Date(result.data.dateOfBirth);
|
||||
if (!isNaN(dob.getTime())) {
|
||||
data.dateOfBirth = dob;
|
||||
}
|
||||
// The calendar day in the owner's zone: the iOS app sends an instant.
|
||||
const dob = dateOfBirthForStorage(
|
||||
result.data.dateOfBirth,
|
||||
await resolveStoredTimezone(user.timezone),
|
||||
);
|
||||
if (dob) data.dateOfBirth = dob;
|
||||
}
|
||||
|
||||
if (result.data.gender) {
|
||||
|
||||
Reference in new issue
Block a user