Skip to content

Commit 3df3d47

Browse files
test(calculate): verify streak formulas for timezone shifts around midnight timeline
1 parent e85dcd5 commit 3df3d47

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

lib/calculate.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,53 @@ describe('calculateStreak — timezone awareness', () => {
796796
expect(result.currentStreak).toBe(3);
797797
});
798798

799+
it('handles contributions at 23:59 and 00:01 UTC across timezone boundaries', () => {
800+
// Simulate two commits that occur around the UTC midnight boundary:
801+
// - One commit at 2024-07-10T23:59:00Z (falls on 2024-07-10 UTC)
802+
// - Another commit at 2024-07-11T00:01:00Z (falls on 2024-07-11 UTC)
803+
// The flattened calendar only stores dates; these two commits appear on
804+
// consecutive dates (2024-07-10 and 2024-07-11). Depending on the
805+
// caller's timezone, the local "today" may be either 2024-07-11 or
806+
// still 2024-07-10 which can expose off-by-one errors.
807+
const calendar = {
808+
totalContributions: 2,
809+
weeks: [
810+
{
811+
contributionDays: [
812+
{ contributionCount: 0, date: '2024-07-09' },
813+
{ contributionCount: 1, date: '2024-07-10' }, // 23:59 UTC commit
814+
{ contributionCount: 1, date: '2024-07-11' }, // 00:01 UTC commit
815+
],
816+
},
817+
],
818+
};
819+
820+
// Use a UTC moment shortly after the second commit.
821+
const nowUTC = new Date('2024-07-11T00:01:00.000Z');
822+
823+
// In UTC the local date is 2024-07-11 — both days are in scope → streak=2
824+
const utcResult = calculateStreak(calendar, 'UTC', nowUTC);
825+
expect(utcResult.todayDate).toBe('2024-07-11');
826+
expect(utcResult.currentStreak).toBe(2);
827+
expect(utcResult.longestStreak).toBe(2);
828+
829+
// In a timezone ahead of UTC by 1 hour (Etc/GMT-1), local date is also 2024-07-11
830+
// and the streak remains 2 (no split occurs).
831+
const aheadResult = calculateStreak(calendar, 'Etc/GMT-1', nowUTC);
832+
expect(aheadResult.todayDate).toBe('2024-07-11');
833+
expect(aheadResult.currentStreak).toBe(2);
834+
expect(aheadResult.longestStreak).toBe(2);
835+
836+
// In a timezone behind UTC by 1 hour (Etc/GMT+1), local date is still 2024-07-10
837+
// at the same instant — only the earlier day's commit is considered "today",
838+
// so currentStreak should be 1 while longestStreak across the whole calendar
839+
// remains 2.
840+
const behindResult = calculateStreak(calendar, 'Etc/GMT+1', nowUTC);
841+
expect(behindResult.todayDate).toBe('2024-07-10');
842+
expect(behindResult.currentStreak).toBe(1);
843+
expect(behindResult.longestStreak).toBe(2);
844+
});
845+
799846
it('falls back to the last available day when the local date is ahead of the calendar data', () => {
800847
const futureNow = new Date('2024-06-16T12:00:00.000Z');
801848
const result = calculateStreak(tzCalendar, 'Etc/GMT-14', futureNow);

0 commit comments

Comments
 (0)