Skip to content

Commit 5d6b9aa

Browse files
committed
fix: daily-fetch now collects current week events instead of previous week
daily-fetch was using buildWeeklyRange/getWeekId which target the previous ISO week. This caused events.yaml to always be empty because the GitHub Events API had no matching events for last week by the time the cron ran mid-week. Add buildCurrentWeekRange and getCurrentWeekId for the daily-fetch use case, keeping the previous-week functions for weekly-fetch/generate/ render/deploy.
1 parent c157166 commit 5d6b9aa

6 files changed

Lines changed: 168 additions & 7 deletions

File tree

src/cli/commands/fetch.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ vi.mock("../../collector/aggregate.js", () => ({
4343

4444
vi.mock("../../deployer/week.js", () => ({
4545
getWeekId: () => ({ year: 2026, week: 14, path: "2026/W14" }),
46+
getCurrentWeekId: () => ({ year: 2026, week: 14, path: "2026/W14" }),
4647
}));
4748

4849
vi.mock("@octokit/graphql", () => ({

src/cli/commands/fetch.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
55
import { join } from "node:path";
66
import { parse as parseYaml, stringify as toYaml } from "yaml";
77
import { graphql } from "@octokit/graphql";
8-
import { buildWeeklyRange, toISODate, parseLocalDate, type DateRange } from "../../collector/date-range.js";
8+
import { buildWeeklyRange, buildCurrentWeekRange, toISODate, parseLocalDate, type DateRange } from "../../collector/date-range.js";
99
import { fetchEvents, dedupeEvents } from "../../collector/fetch-events.js";
1010
import { fetchContributions } from "../../collector/fetch-contributions.js";
1111
import { fetchPRsByRefs, type PRRef } from "../../collector/fetch-repo-prs.js";
1212
import { aggregateRepositories } from "../../collector/aggregate.js";
13-
import { getWeekId } from "../../deployer/week.js";
13+
import { getWeekId, getCurrentWeekId } from "../../deployer/week.js";
1414
import type { GitHubEvent } from "../../types.js";
1515

1616
const env = (key: string): string | undefined => process.env[key];
@@ -59,10 +59,10 @@ export const extractPRRefs = (events: GitHubEvent[]): PRRef[] => {
5959
return refs;
6060
};
6161

62-
// daily-fetch: accumulate events
62+
// daily-fetch: accumulate events for the current week
6363
const runDailyFetch = async (options: BaseOptions): Promise<void> => {
64-
const weekId = getWeekId(options.date, options.timezone);
65-
const range = buildWeeklyRange(options.date, options.timezone);
64+
const weekId = getCurrentWeekId(options.date, options.timezone);
65+
const range = buildCurrentWeekRange(options.date, options.timezone);
6666
const reportDir = join(options.dataDir, weekId.path);
6767
await mkdir(reportDir, { recursive: true });
6868

src/collector/date-range.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "vitest";
2-
import { buildWeeklyRange, toISODate, parseLocalDate } from "./date-range.js";
2+
import { buildWeeklyRange, buildCurrentWeekRange, toISODate, parseLocalDate } from "./date-range.js";
33

44
// Helper: verify the range covers exactly 7 calendar days.
55
// In non-DST zones this is 7 * 86400000 - 1 ms.
@@ -366,6 +366,68 @@ describe("buildWeeklyRange", () => {
366366
});
367367
});
368368

369+
// -------------------------------------------------------------------
370+
// buildCurrentWeekRange
371+
// -------------------------------------------------------------------
372+
373+
describe("buildCurrentWeekRange", () => {
374+
it("returns current ISO week range for the given date (UTC)", () => {
375+
// 2026-04-03 is Friday W14 -> current week W14: Mar 30 (Mon) - Apr 3 (today)
376+
const now = new Date("2026-04-03T12:00:00Z");
377+
const range = buildCurrentWeekRange(now);
378+
379+
expect(toISODate(range.from)).toBe("2026-03-30");
380+
expect(toISODate(range.to)).toBe("2026-04-03");
381+
});
382+
383+
it("on Monday, from and to are both Monday", () => {
384+
// 2026-03-30 is Monday W14
385+
const now = new Date("2026-03-30T12:00:00Z");
386+
const range = buildCurrentWeekRange(now);
387+
388+
expect(toISODate(range.from)).toBe("2026-03-30");
389+
expect(toISODate(range.to)).toBe("2026-03-30");
390+
});
391+
392+
it("on Sunday, covers full Mon-Sun", () => {
393+
// 2026-04-05 is Sunday W14
394+
const now = new Date("2026-04-05T12:00:00Z");
395+
const range = buildCurrentWeekRange(now);
396+
397+
expect(toISODate(range.from)).toBe("2026-03-30");
398+
expect(toISODate(range.to)).toBe("2026-04-05");
399+
});
400+
401+
it("respects Asia/Tokyo timezone", () => {
402+
// 2026-04-04 08:00 JST = 2026-04-03 23:00 UTC
403+
// JST local: Apr 4 (Sat W14) -> current week: Mar 30 (Mon) - Apr 4 (today)
404+
const now = new Date("2026-04-03T23:00:00Z");
405+
const range = buildCurrentWeekRange(now, "Asia/Tokyo");
406+
407+
expect(toISODate(range.from, "Asia/Tokyo")).toBe("2026-03-30");
408+
expect(toISODate(range.to, "Asia/Tokyo")).toBe("2026-04-04");
409+
});
410+
411+
it("respects America/New_York timezone", () => {
412+
// 2026-04-03 20:00 EDT = 2026-04-04 00:00 UTC
413+
// NYC local: Apr 3 (Fri W14) -> current week: Mar 30 (Mon) - Apr 3 (today)
414+
const now = new Date("2026-04-04T00:00:00Z");
415+
const range = buildCurrentWeekRange(now, "America/New_York");
416+
417+
expect(toISODate(range.from, "America/New_York")).toBe("2026-03-30");
418+
expect(toISODate(range.to, "America/New_York")).toBe("2026-04-03");
419+
});
420+
421+
it("year boundary: first week of 2026", () => {
422+
// 2026-01-01 is Thursday W1. Current week: Dec 29 (Mon) - Jan 1 (today)
423+
const now = new Date("2026-01-01T12:00:00Z");
424+
const range = buildCurrentWeekRange(now);
425+
426+
expect(toISODate(range.from)).toBe("2025-12-29");
427+
expect(toISODate(range.to)).toBe("2026-01-01");
428+
});
429+
});
430+
369431
// -------------------------------------------------------------------
370432
// toISODate
371433
// -------------------------------------------------------------------

src/collector/date-range.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,38 @@ export const buildWeeklyRange = (
152152
return { from, to };
153153
};
154154

155+
// Current ISO week range (Monday to now). Used by daily-fetch to accumulate
156+
// events for the week that is still in progress.
157+
export const buildCurrentWeekRange = (
158+
now: Date = new Date(),
159+
timezone: string = "UTC",
160+
): DateRange => {
161+
const { year, month, day } = localDateParts(now, timezone);
162+
163+
// Find the Monday of the current ISO week
164+
const d = new Date(Date.UTC(year, month, day));
165+
const dow = d.getUTCDay() || 7; // 1=Mon..7=Sun
166+
const thisMonday = new Date(Date.UTC(year, month, day - (dow - 1)));
167+
168+
const fromParts = {
169+
year: thisMonday.getUTCFullYear(),
170+
month: thisMonday.getUTCMonth(),
171+
day: thisMonday.getUTCDate(),
172+
};
173+
const from = midnightInTz(fromParts.year, fromParts.month, fromParts.day, timezone);
174+
175+
// "to" is end of today (next day's midnight - 1ms)
176+
const nextDay = new Date(Date.UTC(year, month, day + 1));
177+
const toParts = {
178+
year: nextDay.getUTCFullYear(),
179+
month: nextDay.getUTCMonth(),
180+
day: nextDay.getUTCDate(),
181+
};
182+
const to = new Date(midnightInTz(toParts.year, toParts.month, toParts.day, timezone).getTime() - 1);
183+
184+
return { from, to };
185+
};
186+
155187
export const toISODate = (date: Date, timezone: string = "UTC"): string => {
156188
const fmt = new Intl.DateTimeFormat("en-CA", {
157189
timeZone: timezone,

src/deployer/week.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "vitest";
2-
import { getWeekId } from "./week.js";
2+
import { getWeekId, getCurrentWeekId } from "./week.js";
33

44
describe("getWeekId", () => {
55
// -------------------------------------------------------------------
@@ -171,3 +171,52 @@ describe("getWeekId", () => {
171171
});
172172
});
173173
});
174+
175+
describe("getCurrentWeekId", () => {
176+
it("returns current ISO week for 2026-04-04 (Sat W14) in UTC -> W14", () => {
177+
const result = getCurrentWeekId(new Date("2026-04-04T12:00:00Z"));
178+
expect(result.year).toBe(2026);
179+
expect(result.week).toBe(14);
180+
expect(result.path).toBe("2026/W14");
181+
});
182+
183+
it("returns W14 on Monday (first day of the week)", () => {
184+
// 2026-03-30 is Monday W14
185+
const result = getCurrentWeekId(new Date("2026-03-30T12:00:00Z"));
186+
expect(result.week).toBe(14);
187+
});
188+
189+
it("returns W14 on Sunday (last day of the week)", () => {
190+
// 2026-04-05 is Sunday W14
191+
const result = getCurrentWeekId(new Date("2026-04-05T12:00:00Z"));
192+
expect(result.week).toBe(14);
193+
});
194+
195+
it("current week is one ahead of previous week", () => {
196+
const now = new Date("2026-04-04T12:00:00Z");
197+
const prev = getWeekId(now);
198+
const curr = getCurrentWeekId(now);
199+
expect(curr.week).toBe(prev.week + 1);
200+
});
201+
202+
it("year boundary: Jan 1 2026 is in W01", () => {
203+
const result = getCurrentWeekId(new Date("2026-01-01T12:00:00Z"));
204+
expect(result.year).toBe(2026);
205+
expect(result.week).toBe(1);
206+
expect(result.path).toBe("2026/W01");
207+
});
208+
209+
it("timezone-aware: same instant, different weeks across week boundary", () => {
210+
// 2026-04-05T23:00:00Z = Apr 5 UTC (Sun W14), Apr 6 JST (Mon W15)
211+
const utcResult = getCurrentWeekId(new Date("2026-04-05T23:00:00Z"), "UTC");
212+
const jstResult = getCurrentWeekId(new Date("2026-04-05T23:00:00Z"), "Asia/Tokyo");
213+
214+
expect(utcResult.week).toBe(14); // Sunday W14 in UTC
215+
expect(jstResult.week).toBe(15); // Monday W15 in JST
216+
});
217+
218+
it("pads single-digit week numbers", () => {
219+
const result = getCurrentWeekId(new Date("2026-01-01T12:00:00Z"));
220+
expect(result.path).toBe("2026/W01");
221+
});
222+
});

src/deployer/week.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,20 @@ export const getWeekId = (
3131
const padded = String(prevWeek).padStart(2, "0");
3232
return { year: prevYear, week: prevWeek, path: `${prevYear}/W${padded}` };
3333
};
34+
35+
// Current ISO week ID. Used by daily-fetch to store events for the
36+
// week that is still in progress.
37+
export const getCurrentWeekId = (
38+
date: Date = new Date(),
39+
timezone: string = "UTC",
40+
): WeekId => {
41+
const { year, month, day } = localDateParts(date, timezone);
42+
const d = new Date(Date.UTC(year, month, day));
43+
const dow = d.getUTCDay() || 7; // 1=Mon..7=Sun
44+
// This week's Thursday
45+
const thisThursday = new Date(Date.UTC(year, month, day - (dow - 1) + 3));
46+
const week = getISOWeekNumber(thisThursday, timezone);
47+
const weekYear = localDateParts(thisThursday, timezone).year;
48+
const padded = String(week).padStart(2, "0");
49+
return { year: weekYear, week, path: `${weekYear}/W${padded}` };
50+
};

0 commit comments

Comments
 (0)