Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions frontend/__tests__/e2e/pages/Home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ test.describe('Home Page', () => {
test('should have upcoming events', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Upcoming Events' })).toBeVisible()
await expect(page.getByRole('link', { name: 'Event 1' })).toBeVisible()
await expect(page.getByText('Feb 27,')).toBeVisible()
await expect(page.getByText('Feb 28,')).toBeVisible()
await expect(page.getByText('Feb 27 — 28, 2025')).toBeVisible()
await page.getByRole('link', { name: 'Event 1' }).click()
})
})
5 changes: 1 addition & 4 deletions frontend/__tests__/unit/pages/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { mockAlgoliaData, mockGraphQLData } from '@unit/data/mockHomeData'
import { fetchAlgoliaData } from 'api/fetchAlgoliaData'
import { toast } from 'hooks/useToast'
import { Home } from 'pages'
import { formatDate } from 'utils/dateFormatter'
import { render } from 'wrappers/testUtil'

jest.mock('hooks/useToast', () => ({
Expand Down Expand Up @@ -161,9 +160,7 @@ describe('Home', () => {
expect(screen.getByText('Upcoming Events')).toBeInTheDocument()
mockGraphQLData.upcomingEvents.forEach((event) => {
expect(screen.getByText(event.name)).toBeInTheDocument()
expect(
screen.getByText(`${formatDate(event.startDate)} - ${formatDate(event.endDate)}`)
).toBeInTheDocument()
expect(screen.getByText('Feb 27 — 28, 2025')).toBeInTheDocument()
})
})
})
Expand Down
84 changes: 84 additions & 0 deletions frontend/__tests__/unit/utils/dateFormatter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { formatDate, formatDateRange } from 'utils/dateFormatter'

describe('formatDate function', () => {
beforeEach(() => {
jest.clearAllMocks()
})

test('formats ISO date string correctly', () => {
expect(formatDate('2023-09-01')).toBe('Sep 1, 2023')
})

test('formats Unix timestamp correctly', () => {
// 1630454400 is Sep 1, 2021 in Unix timestamp (seconds)
expect(formatDate(1630454400)).toBe('Sep 1, 2021')
})

test('throws error for invalid date', () => {
expect(() => formatDate('invalid-date')).toThrow('Invalid date')
})

test('handles different date formats', () => {
expect(formatDate('2023/09/01')).toBe('Sep 1, 2023')
expect(formatDate('09/01/2023')).toBe('Sep 1, 2023')
})

test('formats dates with leading zeros correctly', () => {
expect(formatDate('2023-01-05')).toBe('Jan 5, 2023')
})

test('handles different months', () => {
expect(formatDate('2023-12-25')).toBe('Dec 25, 2023')
expect(formatDate('2023-07-04')).toBe('Jul 4, 2023')
})
})

describe('formatDateRange function', () => {
beforeEach(() => {
jest.clearAllMocks()
})

test('formats date range in same month correctly', () => {
expect(formatDateRange('2023-09-01', '2023-09-04')).toBe('Sep 1 — 4, 2023')
})

test('formats date range in different months but same year correctly', () => {
expect(formatDateRange('2023-09-29', '2023-10-02')).toBe('Sep 29 — Oct 2, 2023')
})

test('formats date range in different years correctly', () => {
expect(formatDateRange('2023-12-30', '2024-01-03')).toBe('Dec 30, 2023 — Jan 3, 2024')
})

test('formats Unix timestamp date ranges correctly', () => {
// Sept 1-4, 2021
const startTimestamp = 1630454400 // Sep 1, 2021
const endTimestamp = 1630713600 // Sep 4, 2021
expect(formatDateRange(startTimestamp, endTimestamp)).toBe('Sep 1 — 4, 2021')
})

test('throws error when start date is invalid', () => {
expect(() => formatDateRange('invalid-date', '2023-09-04')).toThrow('Invalid date')
})

test('throws error when end date is invalid', () => {
expect(() => formatDateRange('2023-09-01', 'invalid-date')).toThrow('Invalid date')
})

test('handles month boundaries correctly', () => {
expect(formatDateRange('2023-09-30', '2023-10-02')).toBe('Sep 30 — Oct 2, 2023')
})

test('handles year boundaries correctly', () => {
expect(formatDateRange('2023-12-29', '2024-01-02')).toBe('Dec 29, 2023 — Jan 2, 2024')
})

test('handles single-day ranges correctly', () => {
expect(formatDateRange('2023-09-01', '2023-09-01')).toBe('Sep 1, 2023')
})

test('handles mixed input types correctly', () => {
// Sep 1, 2021 as Unix timestamp and ISO string
expect(formatDateRange(1630454400, '2021-09-04')).toBe('Sep 1 — 4, 2021')
})
})
8 changes: 2 additions & 6 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ChapterTypeAlgolia } from 'types/chapter'
import { EventType } from 'types/event'
import { MainPageData } from 'types/home'
import { capitalize } from 'utils/capitalize'
import { formatDate } from 'utils/dateFormatter'
import { formatDate, formatDateRange } from 'utils/dateFormatter'
import AnimatedCounter from 'components/AnimatedCounter'
import ChapterMap from 'components/ChapterMap'
import ItemCardList from 'components/ItemCardList'
Expand Down Expand Up @@ -146,11 +146,7 @@ export default function Home() {
<div className="flex flex-wrap items-center text-sm text-gray-600 dark:text-gray-300">
<div className="mr-4 flex items-center">
<FontAwesomeIcon icon={faCalendar} className="mr-2 h-4 w-4" />
<span>
{event.endDate && event.startDate != event.endDate
? `${formatDate(event.startDate)} - ${formatDate(event.endDate)}`
: formatDate(event.startDate)}
</span>
<span>{formatDateRange(event.startDate, event.endDate)}</span>
</div>
</div>
</div>
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/utils/dateFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,44 @@ export const formatDate = (input: number | string) => {
day: 'numeric',
})
}

export const formatDateRange = (startDate: number | string, endDate: number | string) => {
const start = typeof startDate === 'number' ? new Date(startDate * 1000) : new Date(startDate)
const end = typeof endDate === 'number' ? new Date(endDate * 1000) : new Date(endDate)

if (isNaN(start.getTime()) || isNaN(end.getTime())) {
throw new Error('Invalid date')
}

if (
start.getTime() === end.getTime() ||
(start.getFullYear() === end.getFullYear() &&
start.getMonth() === end.getMonth() &&
start.getDate() === end.getDate())
) {
return formatDate(startDate)
}

const sameMonth = start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear()
const sameYear = start.getFullYear() === end.getFullYear()

if (sameMonth) {
// Format as "Month Day - Day, Year" (e.g., "Sep 1 - 4, 2025")
return (
`${start.toLocaleDateString('en-US', { month: 'short' })} ` +
`${start.getDate()} — ${end.getDate()}, ${start.getFullYear()}`
)
} else if (sameYear) {
// Different months but same year (e.g., "Sep 29 - Oct 2, 2025")
const startMonth = start.toLocaleDateString('en-US', { month: 'short' })
const endMonth = end.toLocaleDateString('en-US', { month: 'short' })
const startDay = start.getDate()
const endDay = end.getDate()
const year = start.getFullYear()

return `${startMonth} ${startDay} — ${endMonth} ${endDay}, ${year}`
} else {
// Different years (e.g., "Dec 30, 2025 - Jan 3, 2026")
return `${formatDate(startDate)} — ${formatDate(endDate)}`
}
}