Skip to content

Commit 59b2ea5

Browse files
Merge pull request #88 from deariary/fix/daily-fetch-current-week
fix: daily-fetch now collects current week events instead of previous week
2 parents c157166 + 5d6b9aa commit 59b2ea5

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)