Fix event date timezone bugs (UTC rollover + same-day status) (#148)

* Fix event date display off-by-one for late-night events in UTC-negative timezones

Events saved at 10 PM PDT (UTC-7) cross UTC midnight, so eventDate is stored
as the next UTC calendar day (e.g. March 28 10 PM PDT → eventDate "2026-03-29").
Displaying that date string via parseISO() gives March 29 local midnight, so
the events list showed "Mar 29" when the user expected "Mar 28".

Fix: when eventStartsAt is available, derive the display date from it directly
(format(new Date(eventStartsAt), ...)) rather than from the stored eventDate
string. This correctly converts the UTC timestamp to the user's local calendar
date. Date-only events (no eventStartsAt) are unchanged and continue to use
parseISO(eventDate).

Also tighten the getDisplayDate() guard: replace a misleading try/catch (new
Date() never throws) with an explicit isNaN check, and replace a non-null
assertion (eventDate!) with null-coalescing in the admin events list.

Tests cover both UTC and PDT environments and are timezone-independent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix same-day events incorrectly showing Upcoming instead of Results Pending

String date comparison (eventDate < today) is strictly less-than, so events
on the current day always showed Upcoming regardless of time. When eventStartsAt
is available, use timestamp comparison (new Date(eventStartsAt) < new Date())
for accurate past/future determination.

Fixed in three locations: admin events list (getStatusBadge call site),
admin event detail isPast check, and EventSchedule upcoming badge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-03-15 12:28:39 -07:00 committed by GitHub
parent d546585fb6
commit 67c7de1924
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 18 additions and 12 deletions

View file

@ -107,11 +107,16 @@ export function EventSchedule({ upcomingEvents, recentEvents }: EventSchedulePro
</p>
</div>
{event.eventType === "schedule_event" ? (
!event.isComplete && event.eventDate && event.eventDate < new Date().toISOString().split("T")[0] && (
<Badge variant="outline" className="text-xs shrink-0 border-amber-500/30 text-amber-400">
Results Pending
</Badge>
)
!event.isComplete && (() => {
const isPast = event.eventStartsAt
? new Date(event.eventStartsAt) < new Date()
: event.eventDate != null && event.eventDate < new Date().toISOString().split("T")[0];
return isPast && (
<Badge variant="outline" className="text-xs shrink-0 border-amber-500/30 text-amber-400">
Results Pending
</Badge>
);
})()
) : (
<Badge variant="outline" className="text-xs shrink-0">
{getEventTypeLabel(event.eventType)}

View file

@ -118,9 +118,9 @@ export default function EventResults({
// Non-scoring events have a simple view — edit name/date and toggle updated status
if (event.eventType === "schedule_event") {
const today = new Date().toISOString().split("T")[0];
const eventDateStr = event.eventDate ? String(event.eventDate).slice(0, 10) : null;
const isPast = eventDateStr != null && eventDateStr < today;
const isPast = event.eventStartsAt
? new Date(event.eventStartsAt) < new Date()
: event.eventDate != null && String(event.eventDate).slice(0, 10) < new Date().toISOString().split("T")[0];
return (
<div className="p-8">

View file

@ -60,7 +60,7 @@ export default function SportsSeasonEvents({
? "schedule_event"
: "playoff_game";
const getStatusBadge = (isComplete: boolean, eventType: string, eventDate?: string | null) => {
const getStatusBadge = (isComplete: boolean, eventType: string, eventDate?: string | null, eventStartsAt?: Date | string | null) => {
if (isComplete) {
return (
<Badge variant="default" className="bg-emerald-500">
@ -69,8 +69,9 @@ export default function SportsSeasonEvents({
);
}
if (eventType === "schedule_event") {
const today = new Date().toISOString().split("T")[0];
const isPast = eventDate && eventDate < today;
const isPast = eventStartsAt
? new Date(eventStartsAt) < new Date()
: eventDate != null && eventDate < new Date().toISOString().split("T")[0];
return isPast ? (
<Badge variant="outline" className="text-muted-foreground">Results Pending</Badge>
) : (
@ -276,7 +277,7 @@ export default function SportsSeasonEvents({
>
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold">{event.name}</h3>
{getStatusBadge(event.isComplete, event.eventType, event.eventDate)}
{getStatusBadge(event.isComplete, event.eventType, event.eventDate, event.eventStartsAt)}
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span>