fix: calendar cache date mismtach (#20446)
* fix: calendar cache date mismtach * Update tests
This commit is contained in:
@@ -11,30 +11,21 @@ import { getTimeMax, getTimeMin } from "./lib/datesForCache";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });
|
||||
|
||||
/** Enable or disable the expanded cache. Enabled by default. */
|
||||
const ENABLE_EXPANDED_CACHE = process.env.ENABLE_EXPANDED_CACHE !== "0";
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
|
||||
const CACHING_TIME = ONE_MONTH_IN_MS;
|
||||
|
||||
export function parseKeyForCache(args: FreeBusyArgs): string {
|
||||
const { timeMin: _timeMin, timeMax: _timeMax, items } = args;
|
||||
function parseKeyForCache(args: FreeBusyArgs): string {
|
||||
// Ensure that calendarIds are unique
|
||||
const uniqueItems = uniqueBy(items, ["id"]);
|
||||
const { timeMin, timeMax } = handleMinMax(_timeMin, _timeMax);
|
||||
const key = JSON.stringify({ timeMin, timeMax, items: uniqueItems });
|
||||
const uniqueItems = uniqueBy(args.items, ["id"]);
|
||||
const key = JSON.stringify({
|
||||
timeMin: getTimeMin(args.timeMin),
|
||||
timeMax: getTimeMax(args.timeMax),
|
||||
items: uniqueItems,
|
||||
});
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* By expanding the cache to whole months, we can save round trips to the third party APIs.
|
||||
* In this case we already have the data in the database, so we can just return it.
|
||||
*/
|
||||
function handleMinMax(min: string, max: string) {
|
||||
if (!ENABLE_EXPANDED_CACHE) return { timeMin: min, timeMax: max };
|
||||
return { timeMin: getTimeMin(min), timeMax: getTimeMax(max) };
|
||||
}
|
||||
|
||||
type FreeBusyArgs = { timeMin: string; timeMax: string; items: { id: string }[] };
|
||||
|
||||
export class CalendarCacheRepository implements ICalendarCacheRepository {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getTimeMin, getTimeMax } from "./datesForCache";
|
||||
|
||||
describe("getTimeMin", () => {
|
||||
// Tested on multiple dates
|
||||
// vi.setSystemTime("2030-04-21T00:00:13Z");
|
||||
it("should return start of current month when no date is passed", () => {
|
||||
const result = getTimeMin();
|
||||
const expected = new Date();
|
||||
expected.setUTCDate(1);
|
||||
expected.setUTCHours(0, 0, 0, 0);
|
||||
expect(result).toBe(expected.toISOString());
|
||||
});
|
||||
|
||||
it("should return start of month for a given date", () => {
|
||||
const result = getTimeMin("2025-03-15T10:30:00Z");
|
||||
expect(result).toMatchInlineSnapshot(`"2025-03-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle dates at the start of month", () => {
|
||||
const result = getTimeMin("2026-03-01T00:00:00Z");
|
||||
expect(result).toMatchInlineSnapshot(`"2026-03-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle DST changes", () => {
|
||||
const result = getTimeMin("2024-10-27T01:30:00Z");
|
||||
expect(result).toMatchInlineSnapshot(`"2024-10-01T00:00:00.000Z"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTimeMax", () => {
|
||||
it("should return the start of the overnext month when no date is passed", () => {
|
||||
const result = getTimeMax();
|
||||
const expected = new Date();
|
||||
expected.setUTCMonth(expected.getUTCMonth() + 2);
|
||||
expected.setUTCDate(1);
|
||||
expected.setUTCHours(0, 0, 0, 0);
|
||||
expect(result).toBe(expected.toISOString());
|
||||
});
|
||||
|
||||
it("should return the start of overnext month for dates between start of current month and end of next month", () => {
|
||||
const testDate = "2024-03-15T10:30:00Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-05-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should return end of month for dates beyond overnext month", () => {
|
||||
const testDate = "2024-05-15T10:30:00Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-07-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle dates at the end of month", () => {
|
||||
const testDate = "2024-03-31T23:59:59Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-05-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle October correctly (31 days)", () => {
|
||||
const testDate = "2024-10-15T10:30:00Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-12-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle DST changes", () => {
|
||||
const testDate = "2024-10-27T01:30:00Z"; // DST change in Europe
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-12-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle dates around DST changes in next month", () => {
|
||||
const testDate = "2024-09-15T10:30:00Z"; // September, next month includes DST change
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-11-01T00:00:00.000Z"`);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,66 @@
|
||||
/** Expand the start date to the beginning of the current month */
|
||||
export const getTimeMin = (timeMin?: string) => {
|
||||
const date = timeMin ? new Date(timeMin) : new Date();
|
||||
date.setDate(1);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date.toISOString();
|
||||
// Set to UTC to avoid timezone issues
|
||||
const result = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
|
||||
result.setUTCHours(0, 0, 0, 0);
|
||||
return result.toISOString();
|
||||
};
|
||||
|
||||
/** Expand the end date to the end of the next month */
|
||||
/**
|
||||
* Expand the end date to the start of the overnext month if date
|
||||
* is between start of current month and end of next month,
|
||||
* otherwise return start of overnext month from the passed date
|
||||
* @example
|
||||
* Today: March 15, 2024 ▼-------------▼ getTimeMax returns May 1st 00:00:00
|
||||
* Mar Apr May Jun
|
||||
* ├────────┼────────┼────────┼────────┤
|
||||
* Current Month █████████| | | |
|
||||
* Next Month | █████████| | |
|
||||
**/
|
||||
export function getTimeMax(timeMax?: string) {
|
||||
const date = timeMax ? new Date(timeMax) : new Date();
|
||||
date.setMonth(date.getMonth() + 1);
|
||||
date.setDate(0);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date.toISOString();
|
||||
const now = new Date();
|
||||
const currentMonth = now.getUTCMonth();
|
||||
const currentYear = now.getUTCFullYear();
|
||||
|
||||
if (!timeMax) {
|
||||
// If no date passed, return start of the overnext month from current date
|
||||
const result = new Date(Date.UTC(currentYear, currentMonth + 2, 1));
|
||||
result.setUTCHours(0, 0, 0, 0);
|
||||
return result.toISOString();
|
||||
}
|
||||
|
||||
const date = new Date(timeMax);
|
||||
const dateMonth = date.getUTCMonth();
|
||||
const dateYear = date.getUTCFullYear();
|
||||
|
||||
// If the date is within current month or next month, return start of overnext month
|
||||
// Otherwise, return start of overnext month from the date
|
||||
let targetYear = dateYear;
|
||||
let targetMonth = dateMonth;
|
||||
|
||||
// Check if date is within current month or next month
|
||||
const isWithinCurrentOrNextMonth =
|
||||
(dateYear === currentYear && dateMonth <= currentMonth + 1) ||
|
||||
(dateYear === currentYear + 1 && currentMonth === 11 && dateMonth === 0);
|
||||
|
||||
if (isWithinCurrentOrNextMonth) {
|
||||
// For dates within current month or next month, return start of overnext month
|
||||
targetMonth = dateMonth + 2;
|
||||
if (targetMonth > 11) {
|
||||
targetMonth = 0;
|
||||
targetYear++;
|
||||
}
|
||||
} else {
|
||||
// For dates beyond overnext month, return start of overnext month from the date
|
||||
targetMonth = dateMonth + 2;
|
||||
if (targetMonth > 11) {
|
||||
targetMonth = 0;
|
||||
targetYear++;
|
||||
}
|
||||
}
|
||||
|
||||
const result = new Date(Date.UTC(targetYear, targetMonth, 1));
|
||||
result.setUTCHours(0, 0, 0, 0);
|
||||
return result.toISOString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user