Skip to content
Open
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
69 changes: 68 additions & 1 deletion utils/dateHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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);
});
});
});
7 changes: 7 additions & 0 deletions utils/dateHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
Loading