In-process read surface: albums, photos, and timeline grouping #64
+28
-3
@@ -151,8 +151,8 @@ export interface PhotoFilter {
|
||||
}
|
||||
|
||||
export interface TimelineGroup {
|
||||
// The period's identity: `YYYY-MM-DD` for day and week (the Monday), and
|
||||
// `YYYY-MM` for month.
|
||||
// The period's identity: `YYYY-MM-DD` for day, `YYYY-Www` (ISO 8601 week,
|
||||
// e.g. `2025-W32`) for week, and `YYYY-MM` for month.
|
||||
key: string;
|
||||
// Local-time milliseconds at the start of the period.
|
||||
startsAt: number;
|
||||
@@ -292,6 +292,31 @@ const pad = (n: number): string => String(n).padStart(2, "0");
|
||||
const dateKey = (d: Date): string =>
|
||||
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
|
||||
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// The ISO 8601 week key `YYYY-Www` for the week starting at the given Monday.
|
||||
// The week-year is the year of that week's Thursday, so it can differ from the
|
||||
// calendar year at the January/December boundary (e.g. 2024-12-30 is 2025-W01).
|
||||
const isoWeekKey = (monday: Date): string => {
|
||||
const thursday = new Date(
|
||||
monday.getFullYear(),
|
||||
monday.getMonth(),
|
||||
monday.getDate() + 3,
|
||||
);
|
||||
const isoYear = thursday.getFullYear();
|
||||
// Thursday of ISO week 1 is the Thursday of the week containing January 4.
|
||||
const jan4 = new Date(isoYear, 0, 4);
|
||||
const week1Thursday = new Date(
|
||||
isoYear,
|
||||
0,
|
||||
4 + 3 - ((jan4.getDay() + 6) % 7),
|
||||
);
|
||||
const week =
|
||||
1 +
|
||||
Math.round((thursday.getTime() - week1Thursday.getTime()) / WEEK_MS);
|
||||
return `${isoYear}-W${pad(week)}`;
|
||||
};
|
||||
|
||||
// The period a millisecond instant falls in, in local time. Weeks start on
|
||||
// Monday. `Date` normalizes out-of-range day arguments, so the week's Monday
|
||||
// is correct across month and year boundaries.
|
||||
@@ -312,7 +337,7 @@ const periodOf = (
|
||||
// getDay(): 0=Sunday..6=Saturday; shift so Monday is the week start.
|
||||
const fromMonday = (d.getDay() + 6) % 7;
|
||||
const start = new Date(year, month, day - fromMonday);
|
||||
return { key: dateKey(start), startsAt: start.getTime() };
|
||||
return { key: isoWeekKey(start), startsAt: start.getTime() };
|
||||
}
|
||||
const start = new Date(year, month, day);
|
||||
return { key: dateKey(start), startsAt: start.getTime() };
|
||||
|
||||
@@ -287,8 +287,9 @@ describe("lib.timeline grouping", () => {
|
||||
],
|
||||
);
|
||||
const groups = apis(records).timeline.groups({ groupBy: "week" });
|
||||
expect(groups.map((g) => g.key)).toEqual(["2024-01-22", "2024-01-15"]);
|
||||
const first = groups.find((g) => g.key === "2024-01-15")!;
|
||||
// ISO week keys: 2024-01-15 is in 2024-W03, the next Monday in 2024-W04.
|
||||
expect(groups.map((g) => g.key)).toEqual(["2024-W04", "2024-W03"]);
|
||||
const first = groups.find((g) => g.key === "2024-W03")!;
|
||||
expect(first.startsAt).toBe(Date.UTC(2024, 0, 15));
|
||||
// The Sunday belongs to the Monday-started week, not the next one.
|
||||
expect(first.fileIDs.sort((a, b) => a - b)).toEqual([1, 2, 3]);
|
||||
@@ -301,7 +302,7 @@ describe("lib.timeline grouping", () => {
|
||||
[file(1, 1, { creationTime: micros(Date.UTC(2024, 0, 14, 12)) })],
|
||||
);
|
||||
const groups = apis(records).timeline.groups({ groupBy: "week" });
|
||||
expect(groups.map((g) => g.key)).toEqual(["2024-01-08"]);
|
||||
expect(groups.map((g) => g.key)).toEqual(["2024-W02"]);
|
||||
expect(groups[0]!.startsAt).toBe(Date.UTC(2024, 0, 8));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user