mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(dates): show a stated calendar date as that date east of UTC+12
Day keys anchored at noon UTC went through the zone-aware dateShortSmart, which shows them as the next day in UTC+12..+14. A dateShortSmartCalendar formatter reads the key in UTC with the same year rule, and the predictions panel, the illness timeline, the intraday pulse day label, the nutrient chart and the coach result chart use it. The lab chart dates a date-only reading by its calendar date and a timed one in the reader's zone, as the lab lists do. The timezone formatter guard now fails on a noon anchor handed to a zone-aware formatter.
This commit is contained in:
1 parent
358ef23239
commit
9ecdda6358
9 files changed
+171
-17
No files matched your search
@@ -38,11 +38,6 @@ interface NutrientDailyBarChartProps {
|
||||
referenceValue?: number | null;
|
||||
}
|
||||
|
||||
/** Noon UTC so every viewer timezone renders the same calendar day. */
|
||||
function dayToDate(day: string): Date {
|
||||
return new Date(`${day}T12:00:00.000Z`);
|
||||
}
|
||||
|
||||
export function NutrientDailyBarChart({
|
||||
days,
|
||||
unit,
|
||||
@@ -78,7 +73,7 @@ export function NutrientDailyBarChart({
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="day"
|
||||
tickFormatter={(day: string) => fmt.dateShortSmart(dayToDate(day))}
|
||||
tickFormatter={(day: string) => fmt.dateShortSmartCalendar(day)}
|
||||
tick={{ fontSize: 11, fill: "var(--muted-foreground)" }}
|
||||
stroke="var(--border)"
|
||||
minTickGap={24}
|
||||
@@ -117,7 +112,7 @@ export function NutrientDailyBarChart({
|
||||
return (
|
||||
<RichChartTooltip
|
||||
active
|
||||
label={fmt.dateShortSmart(dayToDate(point.day))}
|
||||
label={fmt.dateShortSmartCalendar(point.day)}
|
||||
rows={rows}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -32,12 +32,12 @@ import type { CyclePrediction, CycleHistoryResponse } from "./types";
|
||||
* locale, never the app's) with no year. Predictions run 1-2 cycles out, so
|
||||
* a window straddling New Year's would otherwise print an unadorned "Jan 3"
|
||||
* in December. Routes through the app-locale conditional-year formatter
|
||||
* instead; `dateShortSmart` already renders day+month in the locale's field
|
||||
* order and only adds the year when it differs from the current one.
|
||||
* instead; it renders day+month in the locale's field order and only adds
|
||||
* the year when it differs from the current one. The calendar variant reads
|
||||
* the key in UTC, so the date never rolls a day in UTC+12..+14.
|
||||
*/
|
||||
function formatDate(d: string, fmt: Formatters): string {
|
||||
// Render at noon UTC so the YYYY-MM-DD never rolls a day across tz.
|
||||
return fmt.dateShortSmart(`${d}T12:00:00Z`);
|
||||
return fmt.dateShortSmartCalendar(d);
|
||||
}
|
||||
|
||||
function confidenceTone(c: number): {
|
||||
|
||||
@@ -38,7 +38,7 @@ function DayLogRow({ log }: { log: IllnessDayLogDTO }) {
|
||||
return (
|
||||
<div className="border-border/60 space-y-2 border-l-2 pl-3">
|
||||
<p className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
|
||||
{fmt.dateShortSmart(`${log.date}T12:00:00`)}
|
||||
{fmt.dateShortSmartCalendar(log.date)}
|
||||
</p>
|
||||
|
||||
{log.symptoms.length > 0 ? (
|
||||
|
||||
@@ -84,7 +84,7 @@ function usePeriodLabel(): (key: string) => string {
|
||||
});
|
||||
return (key: string) => {
|
||||
if (DAY_KEY.test(key)) {
|
||||
return fmt.dateShortSmart(new Date(`${key}T12:00:00Z`));
|
||||
return fmt.dateShortSmartCalendar(key);
|
||||
}
|
||||
if (MONTH_KEY.test(key)) {
|
||||
return month.format(new Date(`${key}-01T12:00:00Z`));
|
||||
|
||||
@@ -188,7 +188,7 @@ export function IntradayPulseChart({
|
||||
|
||||
const dayLabel = isToday
|
||||
? t("insights.intradayPulse.today")
|
||||
: fmt.dateShortSmart(new Date(`${dateKey}T12:00:00.000Z`));
|
||||
: fmt.dateShortSmartCalendar(dateKey);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -18,6 +18,7 @@ import { CHART_HEIGHT_PX } from "@/lib/charts/constants";
|
||||
import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
import { formatReferenceRange } from "@/lib/labs/reference-range";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import { isNoonUtcAnchor } from "@/lib/tz/date-only";
|
||||
|
||||
import { RichChartTooltip, type RichTooltipRow } from "../charts/chart-tooltip";
|
||||
import type { LabResultDto } from "./types";
|
||||
@@ -93,6 +94,15 @@ export function LabBiomarkerChart({
|
||||
const { t } = useTranslations();
|
||||
const labNumber = useLabNumber();
|
||||
const fmt = useFormatters();
|
||||
// A reading from a report that states only its date is stored at noon UTC
|
||||
// and is dated by that calendar date; one typed with its time of day is
|
||||
// dated in the reader's zone (the rule `useLabDate` applies to the lists).
|
||||
const labShortDate = (value: number | string) => {
|
||||
const instant = new Date(value);
|
||||
return isNoonUtcAnchor(instant)
|
||||
? fmt.dateShortSmartCalendar(instant)
|
||||
: fmt.dateShortSmart(instant);
|
||||
};
|
||||
const [range, setRange] = useState<RangeKey>("365");
|
||||
// Capture "now" once at mount so the recency filter stays pure across
|
||||
// re-renders (an inline `Date.now()` in render is impure).
|
||||
@@ -215,7 +225,7 @@ export function LabBiomarkerChart({
|
||||
type="number"
|
||||
scale="time"
|
||||
domain={["dataMin", "dataMax"]}
|
||||
tickFormatter={(ts: number) => fmt.dateShortSmart(new Date(ts))}
|
||||
tickFormatter={(ts: number) => labShortDate(ts)}
|
||||
tick={{ fontSize: 11, fill: "var(--muted-foreground)" }}
|
||||
stroke="var(--border)"
|
||||
minTickGap={32}
|
||||
@@ -326,7 +336,7 @@ export function LabBiomarkerChart({
|
||||
return (
|
||||
<RichChartTooltip
|
||||
active
|
||||
label={fmt.dateShortSmart(new Date(point.timestamp))}
|
||||
label={labShortDate(point.timestamp)}
|
||||
rows={rows}
|
||||
/>
|
||||
);
|
||||
@@ -348,7 +358,7 @@ export function LabBiomarkerChart({
|
||||
{sourceBand.mixed
|
||||
? t("labs.chart.sourceBandMixed")
|
||||
: t("labs.chart.sourceBandLegend", {
|
||||
date: fmt.dateShortSmart(new Date(sourceBand.takenAt)),
|
||||
date: labShortDate(sourceBand.takenAt),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
Reference in new issue
Block a user