Skip to content

Commit 2bf7c86

Browse files
authored
Merge pull request #98 from cortex-reply/fix-timezone-holiday-issue
Fix timezone holiday issue
2 parents bb86f65 + bc76d01 commit 2bf7c86

5 files changed

Lines changed: 36 additions & 16 deletions

File tree

src/components/Holidays/CalendarView.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,19 @@ const mockHolidays: Holiday[] = [
8888
},
8989
]
9090

91+
9192
export const Default: Story = {
9293
render: () => {
9394
const [currentDate, setCurrentDate] = useState(new Date(2025, 0, 1)) // January 2025
95+
const today = TimeUtil.toUtcMidnight(new Date(2025, 0, 1)) // Server's "today"
9496

9597
const correctedCurrentDate = TimeUtil.toUtcMidnight(currentDate)
9698
return (
9799
<CalendarView
98100
currentDate={correctedCurrentDate}
99101
setCurrentDate={setCurrentDate}
100102
holidays={mockHolidays}
103+
today={today}
101104
/>
102105
)
103106
},

src/components/Holidays/CalendarView.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface CalendarViewProps {
1111
currentDate: Date
1212
setCurrentDate: (date: Date) => void
1313
holidays: Holiday[]
14+
today: Date // Server-determined "today" to avoid hydration mismatch
1415
}
1516

1617
interface Holiday {
@@ -24,18 +25,25 @@ interface Holiday {
2425
leaveType: 'Full Day' | 'Morning' | 'Afternoon'
2526
}
2627

27-
export function CalendarView({ currentDate, setCurrentDate, holidays }: CalendarViewProps) {
28+
export function CalendarView({ currentDate, setCurrentDate, holidays, today }: CalendarViewProps) {
2829
const [selectedDay, setSelectedDay] = useState<Date | null>(null)
2930

31+
// Helper to compare dates by their UTC year/month/day values
32+
const isSameUtcDay = (a: Date, b: Date) =>
33+
a.getUTCFullYear() === b.getUTCFullYear() &&
34+
a.getUTCMonth() === b.getUTCMonth() &&
35+
a.getUTCDate() === b.getUTCDate()
36+
3037
const firstDayOfMonth =
31-
(new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getDay() + 6) % 7
38+
(new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth(), 1)).getUTCDay() + 6) % 7
3239

3340
const days = Array.from({ length: 42 }, (_, i) => {
3441
const day = new Date(
35-
currentDate.getFullYear(),
36-
currentDate.getMonth(),
37-
i - firstDayOfMonth + 1,
38-
currentDate.getHours(),
42+
Date.UTC(
43+
currentDate.getUTCFullYear(),
44+
currentDate.getUTCMonth(),
45+
i - firstDayOfMonth + 1,
46+
),
3947
)
4048
const filteredHolidays = holidays.filter(
4149
(h) =>
@@ -44,28 +52,32 @@ export function CalendarView({ currentDate, setCurrentDate, holidays }: Calendar
4452
)
4553
return {
4654
date: day,
47-
isCurrentMonth: day.getMonth() === currentDate.getMonth(),
48-
isToday: day.toDateString() === new Date().toDateString(),
55+
isCurrentMonth: day.getUTCMonth() === currentDate.getUTCMonth(),
56+
isToday: isSameUtcDay(day, today),
4957
holidays: filteredHolidays,
5058
}
5159
})
5260

5361
const prevMonth = () => {
54-
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1))
62+
setCurrentDate(new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth() - 1, 1)))
5563
}
5664
const nextMonth = () => {
57-
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1))
65+
setCurrentDate(new Date(Date.UTC(currentDate.getUTCFullYear(), currentDate.getUTCMonth() + 1, 1)))
5866
}
5967
const setToday = () => {
60-
setCurrentDate(new Date())
68+
setCurrentDate(TimeUtil.toUtcMidnight(new Date()))
6169
}
6270

71+
// Format month/year using UTC values to avoid hydration mismatch
72+
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
73+
const monthYearDisplay = `${months[currentDate.getUTCMonth()]} ${currentDate.getUTCFullYear()}`
74+
6375
return (
6476
<div className="lg:flex lg:h-full lg:flex-col">
6577
<header className="flex items-center justify-between border-b border-gray-200 px-6 py-4 lg:flex-none">
6678
<h1 className="text-base font-semibold text-foreground">
6779
<time dateTime={currentDate.toISOString()}>
68-
{currentDate.toLocaleString('default', { month: 'long', year: 'numeric' })}
80+
{monthYearDisplay}
6981
</time>
7082
</h1>
7183
<div className="flex items-center">
@@ -139,7 +151,7 @@ export function CalendarView({ currentDate, setCurrentDate, holidays }: Calendar
139151
day.isToday && 'bg-indigo-600 font-semibold text-white',
140152
)}
141153
>
142-
{day.date.getDate()}
154+
{day.date.getUTCDate()}
143155
</time>
144156
{day.holidays.length > 0 && (
145157
<ol className="mt-2 space-y-1 overflow-visible">
@@ -189,7 +201,7 @@ export function CalendarView({ currentDate, setCurrentDate, holidays }: Calendar
189201
'flex h-6 w-6 items-center justify-center rounded-full bg-gray-900',
190202
)}
191203
>
192-
{day.date.getDate()}
204+
{day.date.getUTCDate()}
193205
</time>
194206
<span className="sr-only">{day.holidays.length} holidays</span>
195207
{day.holidays.length > 0 && (

src/components/Holidays/HolidayTracker.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const Default: Story = {
2929
args: {
3030
// Mock holidays data
3131
currentDate: format(new Date(2025, 0, 2), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"), // January 2025
32+
today: format(new Date(2025, 0, 2), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"), // Today's date for highlighting
3233
holidays: [
3334
{
3435
id: '1',

src/components/Holidays/HolidayTracker.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ interface HolidayTrackerProps {
1919
holidays: Holiday[]
2020
leaveApprovals: LeaveRequest[]
2121
employees: Employee[]
22-
currentDate: string // ISO 8601 string
22+
currentDate: string // ISO 8601 string - the date being viewed
23+
today: string // ISO 8601 string - server's current date for "today" highlighting
2324
currentUser: { grade: string; remainingLeaveDays: number }
2425
submitLeaveRequest?: (formData: FormData) => Promise<{ success: boolean; message: string }>
2526
approveLeave: (ids: string[]) => Promise<{ success: boolean; message: string }>
@@ -34,6 +35,7 @@ export function HolidayTracker({
3435
holidays,
3536
currentUser,
3637
currentDate,
38+
today,
3739
leaveApprovals,
3840
employees,
3941
submitLeaveRequest,
@@ -46,6 +48,7 @@ export function HolidayTracker({
4648
const isLoading = false
4749

4850
const parsedCurrentDate = TimeUtil.toUtcMidnight(parseISO(currentDate))
51+
const parsedToday = TimeUtil.toUtcMidnight(parseISO(today))
4952

5053
const setCurrentDate = async (date: Date) => {
5154
const formattedDate = format(date, 'dd-MM-yyyy')
@@ -147,6 +150,7 @@ export function HolidayTracker({
147150
currentDate={parsedCurrentDate}
148151
setCurrentDate={setCurrentDate}
149152
holidays={holidays}
153+
today={parsedToday}
150154
/>
151155
)}
152156
{currentTab === 'Request Leave' && (

src/components/Holidays/RequestLeave.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function RequestLeave({ remainingDays, submitLeaveRequest }: RequestLeave
7171
}, [])
7272
const [leaveType, setLeaveType] = useState('Full Day')
7373
const [isMultipleDays, setIsMultipleDays] = useState(false)
74-
const [totalDays, setTotalDays] = useState(1)
74+
const [totalDays, setTotalDays] = useState(0)
7575
const router = useRouter()
7676

7777
useEffect(() => {

0 commit comments

Comments
 (0)