From d0c2417b703f84c35403c4ca275f699be67d7229 Mon Sep 17 00:00:00 2001 From: MasterJi27 Date: Fri, 12 Jun 2026 20:53:33 +0530 Subject: [PATCH] bug: Align historical year ranges to user timezone offsets --- app/api/wrapped/route.ts | 5 ++++- lib/calculate.ts | 1 - lib/github.test.ts | 31 +++++++++++++++++++++++++++++++ lib/github.ts | 6 +++--- lib/validations.ts | 1 + 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/app/api/wrapped/route.ts b/app/api/wrapped/route.ts index 19c980f79..ef4279067 100644 --- a/app/api/wrapped/route.ts +++ b/app/api/wrapped/route.ts @@ -53,6 +53,7 @@ export async function GET(request: Request) { hide_background, width, height, + tz, } = parseResult.data; const year = customYear || new Date().getFullYear().toString(); @@ -95,7 +96,9 @@ export async function GET(request: Request) { const isRefreshRequested = refresh || bypassCacheParam; // Fetch the wrapped stats for the year (calendar is included to avoid a duplicate API call) - const wrappedStats = await getWrappedData(user, year, { bypassCache: isRefreshRequested }); + const wrappedStats = tz + ? await getWrappedData(user, year, { bypassCache: isRefreshRequested }, tz) + : await getWrappedData(user, year, { bypassCache: isRefreshRequested }); const svg = generateWrappedSVG(wrappedStats, params, year, wrappedStats.calendar); diff --git a/lib/calculate.ts b/lib/calculate.ts index 2372ba375..8b1b3635c 100644 --- a/lib/calculate.ts +++ b/lib/calculate.ts @@ -84,7 +84,6 @@ export function getLocalTodayStr(now: Date, timezone: string): string { return now.toISOString().split('T')[0]; } } - export function isStreakAlive( today: { contributionCount: number }, yesterday: { contributionCount: number } | null diff --git a/lib/github.test.ts b/lib/github.test.ts index f1edf8598..d04a82744 100644 --- a/lib/github.test.ts +++ b/lib/github.test.ts @@ -2350,6 +2350,37 @@ describe('getWrappedData', () => { expect(body.variables.to).toBe('2024-12-31T23:59:59Z'); }); + it('TestCase: aligns query bounds to user timezone offset (Issue #5259)', async () => { + vi.mocked(fetch).mockImplementation(async (url) => { + const urlStr = typeof url === 'string' ? url : (url?.toString() ?? ''); + if (urlStr.includes('/repos')) { + return mockResponse([]); + } + return mockResponse({ + data: { + user: { + contributionsCollection: { + contributionCalendar: mockCalendar, + }, + }, + }, + }); + }); + + // For Pacific/Honolulu (UTC-10), local 2024-01-01T00:00:00 is UTC 2024-01-01T10:00:00Z + // and local 2024-12-31T23:59:59 is UTC 2025-01-01T09:59:59Z + await getWrappedData('octocat', '2024', undefined, 'Pacific/Honolulu'); + + const graphQLCall = vi + .mocked(fetch) + .mock.calls.find(([url]) => url.toString().includes('/graphql')); + + const body = JSON.parse(graphQLCall?.[1]?.body as string); + + expect(body.variables.from).toBe('2024-01-01T10:00:00Z'); + expect(body.variables.to).toBe('2025-01-01T09:59:59Z'); + }); + it('falls back to the current-year date range when wrapped year is missing or partial', async () => { vi.mocked(fetch).mockImplementation(async (url) => { const urlStr = typeof url === 'string' ? url : (url?.toString() ?? ''); diff --git a/lib/github.ts b/lib/github.ts index 6157c03dc..d9f6f20f4 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -7,7 +7,7 @@ import type { GraphNode, GraphLink, } from '@/types'; -import { calculateStreak, aggregateCalendars } from '@/lib/calculate'; +import { calculateStreak, aggregateCalendars, convertLocalToUtc } from '@/lib/calculate'; import { DistributedCache } from '@/lib/cache'; import { LANGUAGE_COLORS } from '@/lib/svg/languageColors'; import { CONTRIBUTION_MILESTONES, STREAK_MILESTONES } from './svg/constants'; @@ -1984,8 +1984,8 @@ export async function getWrappedData( const fallbackYear = new Date().getFullYear().toString(); const normalizedYear = /^\d{4}$/.test(trimmedYear) ? trimmedYear : fallbackYear; - const from = `${normalizedYear}-01-01T00:00:00Z`; - const to = `${normalizedYear}-12-31T23:59:59Z`; + const from = convertLocalToUtc(parseInt(normalizedYear, 10), 1, 1, 0, 0, 0, timezone); + const to = convertLocalToUtc(parseInt(normalizedYear, 10), 12, 31, 23, 59, 59, timezone); const fetchOptions: FetchOptions = { from, to, diff --git a/lib/validations.ts b/lib/validations.ts index b5119196d..63412e432 100644 --- a/lib/validations.ts +++ b/lib/validations.ts @@ -637,6 +637,7 @@ export const wrappedParamsSchema = z.object({ hide_background: z.string().optional().transform(toBooleanFlag), // ✅ Fixed: was toRefreshFlag width: dimensionParam('width', 100, 1200), height: dimensionParam('height', 80, 800), + tz: timeZoneParam, }); export const notifyPostSchema = z.object({