diff --git a/utils/dateHelpers.test.ts b/utils/dateHelpers.test.ts index 95efa1ffe..86a20b48a 100644 --- a/utils/dateHelpers.test.ts +++ b/utils/dateHelpers.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { getAuthorLocalHour, getViewerLocalHour } from './dateHelpers'; +import { getAuthorLocalHour, getViewerLocalHour, processCommitTimestamps } from './dateHelpers'; describe('dateHelpers', () => { describe('getAuthorLocalHour', () => { @@ -24,6 +24,15 @@ describe('dateHelpers', () => { expect(getAuthorLocalHour('invalid-date')).toBe(0); expect(getAuthorLocalHour('')).toBe(0); }); + + it('validates ISO 8601 date format before substring extraction', () => { + // Valid ISO format extracts hour from substring + expect(getAuthorLocalHour('2024-03-10T15:30:00Z')).toBe(15); + // Non-ISO format strings fall back to Date parsing + const hour = getAuthorLocalHour('March 10, 2024 15:30'); + expect(hour).toBeGreaterThanOrEqual(0); + expect(hour).toBeLessThanOrEqual(23); + }); }); describe('getViewerLocalHour', () => { @@ -38,4 +47,62 @@ describe('dateHelpers', () => { expect(getViewerLocalHour('')).toBe(0); }); }); + + describe('processCommitTimestamps', () => { + it('returns zero metrics for an empty array', () => { + const result = processCommitTimestamps([]); + expect(result).toEqual({ morning: 0, afternoon: 0, evening: 0, night: 0 }); + }); + + it('returns zero metrics when all dates are invalid', () => { + const result = processCommitTimestamps(['invalid-date', 'not-a-date', '']); + expect(result).toEqual({ morning: 0, afternoon: 0, evening: 0, night: 0 }); + }); + + it('returns zero metrics when all dates are null or undefined', () => { + const result = processCommitTimestamps([null as unknown as string, undefined as unknown as string]); + expect(result).toEqual({ morning: 0, afternoon: 0, evening: 0, night: 0 }); + }); + + it('returns zero metrics for an array containing only Invalid Date strings', () => { + const result = processCommitTimestamps(['2024-13-99T25:99:00Z', 'hello world']); + expect(result).toEqual({ morning: 0, afternoon: 0, evening: 0, night: 0 }); + }); + + it('counts valid morning commits correctly', () => { + const result = processCommitTimestamps(['2024-03-10T09:00:00Z', '2024-03-10T11:30:00Z']); + expect(result.morning).toBe(2); + expect(result.afternoon).toBe(0); + expect(result.evening).toBe(0); + expect(result.night).toBe(0); + }); + + it('counts valid afternoon commits correctly', () => { + const result = processCommitTimestamps(['2024-03-10T12:00:00Z', '2024-03-10T17:59:00Z']); + expect(result.morning).toBe(0); + expect(result.afternoon).toBe(2); + }); + + it('counts valid evening commits correctly', () => { + const result = processCommitTimestamps(['2024-03-10T18:00:00Z', '2024-03-10T23:59:00Z']); + expect(result.evening).toBe(2); + }); + + it('counts valid night commits correctly', () => { + const result = processCommitTimestamps(['2024-03-10T00:00:00Z', '2024-03-10T05:59:00Z']); + expect(result.night).toBe(2); + }); + + it('ignores invalid dates while counting valid ones', () => { + const result = processCommitTimestamps([ + '2024-03-10T09:00:00Z', + 'invalid-date', + '2024-03-10T14:00:00Z', + ]); + expect(result.morning).toBe(1); + expect(result.afternoon).toBe(1); + expect(result.night).toBe(0); + expect(result.evening).toBe(0); + }); + }); }); diff --git a/utils/dateHelpers.ts b/utils/dateHelpers.ts index b1d7a25e4..b4db3be4d 100644 --- a/utils/dateHelpers.ts +++ b/utils/dateHelpers.ts @@ -11,6 +11,7 @@ export function processCommitTimestamps(commitDates: string[] | Date[]): TimeOfD commitDates.forEach((dateString) => { if (!dateString) return; const date = new Date(dateString); + if (isNaN(date.getTime())) return; const hour = date.getHours(); if (hour >= 6 && hour < 12) { @@ -34,6 +35,12 @@ export function processCommitTimestamps(commitDates: string[] | Date[]): TimeOfD export function getAuthorLocalHour(isoDate: string): number { if (!isoDate || typeof isoDate !== 'string') return 0; + // Validate ISO 8601 date format: YYYY-MM-DDTHH:MM:SS... + if (!/^\d{4}-\d{2}-\d{2}T/.test(isoDate)) { + const parsed = new Date(isoDate); + return isNaN(parsed.getTime()) ? 0 : parsed.getHours(); + } + if (isoDate.length >= 13) { const hourStr = isoDate.substring(11, 13); const hour = parseInt(hourStr, 10);