Skip to content

dates fix#3993

Open
chpy04 wants to merge 6 commits intodevelopfrom
dates-fix
Open

dates fix#3993
chpy04 wants to merge 6 commits intodevelopfrom
dates-fix

Conversation

@chpy04
Copy link
Contributor

@chpy04 chpy04 commented Feb 28, 2026

Changes

Handle dates properly by splitting datetimes and dates in db and logic

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates date handling across shared/frontend/backend by introducing centralized date formatting utilities and migrating several Prisma DateTime fields intended to be “date-only” to @db.Date, reducing timezone-related off-by-one issues.

Changes:

  • Add dayjs-backed shared formatting helpers for date-only vs timestamp rendering and for Slack formatting.
  • Migrate multiple Prisma models’ date-only fields to @db.Date and add a SQL migration to cast existing data.
  • Refactor frontend/backend usages to use shared date formatting / toDateString and remove legacy date transformation helpers.

Reviewed changes

Copilot reviewed 38 out of 39 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
yarn.lock Adds/updates lock entries for dayjs across workspaces.
src/shared/src/date-format.ts New shared date formatting utilities (date-only, timestamps, Slack) using dayjs.
src/shared/package.json Adds dayjs as a shared dependency.
src/shared/index.ts Exports the new date-format module from shared.
src/frontend/src/utils/pipes.ts Switches UI pipes to shared formatting helpers and simplifies date logic.
src/frontend/src/utils/part.utils.ts Uses shared date-only formatting for part history display.
src/frontend/src/utils/datetime.utils.ts Removes several formatting helpers, keeping only remaining date utilities.
src/frontend/src/pages/WorkPackageForm/WorkPackageFormView.tsx Uses shared toDateString for start date payloads.
src/frontend/src/pages/SettingsPage/UserScheduleSettings/UserScheduleSettingsView.tsx Uses shared formatting for scheduled date display in modal title.
src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/v2/TaskColumn.tsx Uses shared toDateString for task create payload date-only fields.
src/frontend/src/pages/HomePage/components/TimelineSection.tsx Uses shared date-only formatting for milestone display.
src/frontend/src/pages/HomePage/components/TeamTaskCard.tsx Uses shared date-only formatting for task deadlines.
src/frontend/src/pages/HomePage/components/TaskDetailCard.tsx Uses shared date-only formatting for task deadlines.
src/frontend/src/pages/HomePage/components/EventCard.tsx Refactors event date display to distinguish date-only vs datetime.
src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttToolTip.tsx Uses shared date-only formatting in tooltip.
src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/GanttTaskBar.tsx Replaces dateToString with shared toDateString for comparisons.
src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttChartTimeline.tsx Replaces dateToString with shared toDateString for “current week” logic.
src/frontend/src/pages/GanttPage/GanttChart/GanttChart.tsx Replaces dateToString with shared toDateString for “current week” column.
src/frontend/src/pages/FinancePage/FinanceComponents/GenerateReceiptsModal.tsx Uses shared startOfDay for date filtering comparisons.
src/frontend/src/pages/CalendarPage/UpcomingMeetingsCard.tsx Uses shared formatEventTime for meeting time display.
src/frontend/src/pages/CalendarPage/TaskClickPopup.tsx Uses shared date-only formatting for task deadline display.
src/frontend/src/pages/CalendarPage/EventPartialInfoView.tsx Uses shared formatEventTime for event time ranges.
src/frontend/src/pages/CalendarPage/EventClickPopup.tsx Uses shared formatEventTime for event time ranges.
src/frontend/src/pages/CalendarPage/Components/ScheduleEventModal.tsx Uses shared formatEventTime for time range display.
src/frontend/src/pages/CalendarPage/CalendarPage.tsx Removes manual timezone adjustments when keying day dictionaries.
src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/MilestoneTable.tsx Uses shared date-only formatting for milestone date display.
src/frontend/src/pages/AdminToolsPage/AdminToolsSlackUpcomingDeadlines.tsx Sends deadline Date directly instead of reconstructing via toDateString().
src/backend/src/utils/slack.utils.ts Switches scheduled-event Slack formatting to shared Slack formatters.
src/backend/src/utils/google-integration.utils.ts Uses shared toDateString when creating all-day Google Calendar events.
src/backend/src/utils/datetime.utils.ts Removes backend transformDate helper (now relying on shared).
src/backend/src/utils/changes.utils.ts Uses shared date-only formatting for change details.
src/backend/src/utils/change-requests.utils.ts Uses shared toDateString and date-only formatting in change request application/details.
src/backend/src/services/work-packages.services.ts Removes “add 12 hours” workaround and uses new Date(startDate) for date-only storage.
src/backend/src/services/reimbursement-requests.services.ts Removes “add 12 hours” workaround for reimbursements (date-only handling).
src/backend/src/services/notifications.services.ts Uses shared Slack time formatting for event notification messages.
src/backend/src/prisma/seed.ts Uses shared toDateString for seeded date-only strings.
src/backend/src/prisma/schema.prisma Migrates multiple fields to @db.Date (date-only semantics).
src/backend/src/prisma/migrations/20260228000000_change_date_only_fields_to_db_date/migration.sql SQL migration casting timestamp/date-time columns to date.
src/backend/package.json Adds dayjs dependency to backend workspace.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 55 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (2)

src/frontend/src/pages/CalendarPage/Components/EventAvailabilityPage.tsx:132

  • displayDate can become undefined when there is no date query param and event.initialDateScheduled is missing. It is then used as a Date (e.g., passed into getMostRecentAvailabilities, toLocaleDateString, and modals), which will throw at runtime. Restore a safe fallback (e.g., event?.initialDateScheduled ?? new Date()), and if initialDateScheduled is a date-only field, normalize it (e.g., dbDateToLocalDate(...)) before using it for local date arithmetic.
  const displayDate = useMemo(() => {
    if (dateParam) {
      return new Date(dateParam);
    }
    return event?.initialDateScheduled ?? new Date();

src/backend/src/utils/google-integration.utils.ts:246

  • Same issue for the end date: switching from UTC (toISOString().split('T')[0]) to local formatting can shift the all-day end date depending on server timezone. Use a UTC-based conversion for Google all-day events, or a dedicated helper that makes the intended timezone explicit.
        end: slot.allDay
          ? { date: slot.endTime ? toDateString(slot.endTime) : undefined }
          : {
              dateTime: slot.endTime ? slot.endTime.toISOString() : undefined,
              timeZone: 'America/New_York'
            },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI commented Feb 28, 2026

@chpy04 I've opened a new pull request, #3994, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +17
* Format a date-only value (deadlines, start dates stored as @db.Date).
* Uses UTC because @db.Date columns return midnight UTC, and we want the UTC date (which IS the stored date).
*/
export const formatDateOnly = (date: Date, format = 'MM/DD/YYYY'): string => dayjs.utc(date).format(format);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatDateOnly currently formats with dayjs.utc(date). That only works correctly if the input Date represents midnight UTC (as returned directly from a Postgres DATE / Prisma @db.Date). In the frontend you’re now normalizing @db.Date values to local-midnight via dbDateToLocalDate, and passing those into formatDateOnly; in positive-offset timezones this will format the previous calendar day. Consider either (a) changing formatDateOnly to format in local time and updating the docstring, or (b) splitting into two helpers (e.g., formatDbDateOnly vs formatLocalDateOnly, or a utc flag) so call sites can be explicit and consistent.

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 17
const calculateEndDate = (start: Date, weeks: number) => {
const end = new Date(start);
end.setDate(start.getDate() + weeks * 7);
end.setUTCDate(start.getUTCDate() + weeks * 7);
return end;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateEndDate now uses getUTCDate/setUTCDate. This function is exported from shared and is used in the frontend (e.g., to display work package end dates) where startDate/endDate are now normalized to local-midnight via dbDateToLocalDate. For users in UTC+ timezones, UTC date arithmetic on a local-midnight Date can shift by one day and produce an incorrect end date. Consider adding a utc/dbDate flag (similar to getMonday) or reverting to local getDate/setDate for the default, and only using UTC arithmetic in backend-only paths.

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +235
// Task deadlines are date-only values (@db.Date, midnight UTC).
// Extract UTC components from deadline, local components from weekDay, and compare.
const tasksByDay: CalendarTask[][] = weekDays.map((day) =>
tasks.filter((t) => {
if (!t.deadline) return false;
return isSameDay(new Date(t.deadline), day);
const d = new Date(t.deadline);
return (
d.getUTCFullYear() === day.getFullYear() && d.getUTCMonth() === day.getMonth() && d.getUTCDate() === day.getDate()
);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tasksByDay compares the task deadline using UTC getters against day using local getters. However, filteredTasks are keyed in CalendarPage via datePipe(new Date(task.deadline)) (local formatting), and task deadlines are now transformed to local-midnight via dbDateToLocalDate. With the current UTC/local mix, tasks can land on the wrong day (especially in non-UTC timezones). Use a consistent comparison basis (e.g., isSameDay(d, day) or compare local Y/M/D on both sides) that matches how deadlines are normalized and displayed elsewhere in the calendar.

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +215
start.getUTCFullYear() === day.getFullYear() &&
start.getUTCMonth() === day.getMonth() &&
start.getUTCDate() === day.getDate()
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For single-point all-day events (start.getTime() === end.getTime()), the code compares UTC components from start against local components from day. This mixes time bases and can misclassify events on the wrong day around timezone boundaries. Either compare on local time for both (e.g., isSameDay(start, day)) or compare UTC components on both sides (day.getUTC*).

Suggested change
start.getUTCFullYear() === day.getFullYear() &&
start.getUTCMonth() === day.getMonth() &&
start.getUTCDate() === day.getDate()
start.getUTCFullYear() === day.getUTCFullYear() &&
start.getUTCMonth() === day.getUTCMonth() &&
start.getUTCDate() === day.getUTCDate()

Copilot uses AI. Check for mistakes.
Comment on lines 68 to 71
const firstScheduledDate = event.initialDateScheduled || event.scheduledTimes[0]?.startTime;
const timezoneAdjustedDate = firstScheduledDate ? timezoneOffset(firstScheduledDate) : new Date();
const displayDate = firstScheduledDate ? new Date(firstScheduledDate) : new Date();
const formattedDate = formatDateOnly(displayDate);

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedDate is derived with formatDateOnly(displayDate), but displayDate comes from initialDateScheduled / scheduledTimes[0].startTime (a full timestamp). Formatting a timestamp with a date-only UTC formatter can disagree with getWeekday(displayDate) and with the displayed meeting time in some timezones. Consider using a timestamp-aware formatter here (e.g., formatEventDate(displayDate) / formatTimestamp) so the weekday/date/time are all derived in the same timezone basis.

Copilot uses AI. Check for mistakes.
changeRequestProject1Id,
WorkPackageStage.Design,
weeksAgo(12).toISOString().split('T')[0],
toDateString(weeksAgo(12)),
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toDateString(weeksAgo(...)) formats using local timezone rules, but weeksAgo() retains the current time-of-day. That means the resulting YYYY-MM-DD string can vary depending on the machine timezone and the time the seed is run (e.g., around midnight), making seed data non-deterministic. For seed data, consider using a UTC-based conversion (like the previous toISOString().split('T')[0]) or add a dedicated UTC date-string helper for date-only fields.

Suggested change
toDateString(weeksAgo(12)),
weeksAgo(12).toISOString().split('T')[0],

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants