From b0a5b92fac498c2aa6fa92853f9d2df6b51bd697 Mon Sep 17 00:00:00 2001 From: EdiWeeks <121399761+EdiWeeks@users.noreply.github.com> Date: Tue, 5 May 2026 12:40:35 +0100 Subject: [PATCH 1/2] fix: handle BC null-date sentinel on project header dates The project header used a local formatDate that wrapped `new Date(...).toLocaleDateString('en-GB', ...)`. BC returns "0001-01-01" as the null-date sentinel for projects with no start/end set. The empty-string guard didn't catch it, so toLocaleDateString rendered the value as "31 Dec 1" (timezone shift in negative-UTC zones flips the day). - Add a sentinel guard for any "0001-..." date string. - Use the shared formatDate util + DATE_FORMAT_FULL ("d MMM yyyy"), which goes through date-fns parseISO and avoids the timezone shift. Fixes #199 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/projects/ProjectHeader.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/projects/ProjectHeader.tsx b/src/components/projects/ProjectHeader.tsx index 2ce9db5..aa40781 100644 --- a/src/components/projects/ProjectHeader.tsx +++ b/src/components/projects/ProjectHeader.tsx @@ -15,22 +15,22 @@ import { } from '@heroicons/react/24/outline'; import { useProjectDetailsStore } from '@/hooks/useProjectDetailsStore'; import { useCompanyStore } from '@/hooks'; -import { getBCJobUrl } from '@/utils'; -import { cn } from '@/utils'; +import { cn, DATE_FORMAT_FULL, formatDate as formatDateUtil, getBCJobUrl } from '@/utils'; import type { BillingMode } from '@/services/bc/projectDetailsService'; /** - * Format a date string for display (e.g., "15 Jan 2025") + * Format a date string for display (e.g., "15 Jan 2025"). + * + * Treats BC's null-date sentinel ("0001-..." — typically "0001-01-01") as + * unspecified. The sentinel arrives as a non-empty ISO string, so the + * empty-check alone misses it and `new Date()` would render it as a year-1 + * garbage date (a timezone shift can also flip it to "31 Dec 1" in negative + * UTC zones). */ function formatDate(dateStr?: string): string { - if (!dateStr) return 'Unspecified'; + if (!dateStr || dateStr.startsWith('0001-')) return 'Unspecified'; try { - const date = new Date(dateStr); - return date.toLocaleDateString('en-GB', { - day: 'numeric', - month: 'short', - year: 'numeric', - }); + return formatDateUtil(dateStr, DATE_FORMAT_FULL); } catch { return 'Unspecified'; } From 3f6adae37f478763b95cee98a5ef2ea58bbc82e9 Mon Sep 17 00:00:00 2001 From: EdiWeeks <121399761+EdiWeeks@users.noreply.github.com> Date: Tue, 5 May 2026 12:46:30 +0100 Subject: [PATCH 2/2] docs: reword formatDate comment to lead with observed symptom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Copilot review on PR #200 — describe the actual "31 Dec 1" output (timezone-shifted day + unpadded year) rather than the abstract "year-1 garbage date" framing, which made the symptom less obvious to readers. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/projects/ProjectHeader.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/projects/ProjectHeader.tsx b/src/components/projects/ProjectHeader.tsx index aa40781..eb7e1f7 100644 --- a/src/components/projects/ProjectHeader.tsx +++ b/src/components/projects/ProjectHeader.tsx @@ -23,9 +23,9 @@ import type { BillingMode } from '@/services/bc/projectDetailsService'; * * Treats BC's null-date sentinel ("0001-..." — typically "0001-01-01") as * unspecified. The sentinel arrives as a non-empty ISO string, so the - * empty-check alone misses it and `new Date()` would render it as a year-1 - * garbage date (a timezone shift can also flip it to "31 Dec 1" in negative - * UTC zones). + * empty-check alone misses it; `new Date(...).toLocaleDateString` then + * renders it as e.g. "31 Dec 1" — the timezone shift in negative-UTC zones + * flips the calendar day, and the year prints unpadded as "1". */ function formatDate(dateStr?: string): string { if (!dateStr || dateStr.startsWith('0001-')) return 'Unspecified';