Fix email timing and show fallback data when Garmin sync pending
All checks were successful
Deploy / deploy (push) Successful in 2m31s
All checks were successful
Deploy / deploy (push) Successful in 2m31s
- Add 15-minute notification granularity (*/15 cron) so users get emails at their configured time instead of rounding to the nearest hour - Add DailyLog fallback to most recent when today's log doesn't exist, preventing 100/100/Unknown default values before morning sync - Show "Last synced" indicator when displaying stale data - Change Garmin sync to 6-hour intervals (0,6,12,18 UTC) to ensure data is available before European morning notifications Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,19 +17,40 @@ interface NotificationResult {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// Get the current hour in a specific timezone
|
||||
function getCurrentHourInTimezone(timezone: string): number {
|
||||
// Get current quarter-hour slot (0, 15, 30, 45) in user's timezone
|
||||
function getCurrentQuarterHourSlot(timezone: string): {
|
||||
hour: number;
|
||||
minute: number;
|
||||
} {
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: timezone,
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false,
|
||||
});
|
||||
return parseInt(formatter.format(new Date()), 10);
|
||||
const parts = formatter.formatToParts(new Date());
|
||||
const hour = Number.parseInt(
|
||||
parts.find((p) => p.type === "hour")?.value ?? "0",
|
||||
10,
|
||||
);
|
||||
const minute = Number.parseInt(
|
||||
parts.find((p) => p.type === "minute")?.value ?? "0",
|
||||
10,
|
||||
);
|
||||
// Round down to nearest 15-min slot
|
||||
const slot = Math.floor(minute / 15) * 15;
|
||||
return { hour, minute: slot };
|
||||
}
|
||||
|
||||
// Extract hour from "HH:MM" format
|
||||
function getNotificationHour(notificationTime: string): number {
|
||||
return parseInt(notificationTime.split(":")[0], 10);
|
||||
// Extract quarter-hour slot from "HH:MM" format
|
||||
function getNotificationSlot(notificationTime: string): {
|
||||
hour: number;
|
||||
minute: number;
|
||||
} {
|
||||
const [h, m] = notificationTime.split(":").map(Number);
|
||||
// Round down to nearest 15-min slot
|
||||
const slot = Math.floor(m / 15) * 15;
|
||||
return { hour: h, minute: slot };
|
||||
}
|
||||
|
||||
// Map decision status to icon
|
||||
@@ -95,11 +116,14 @@ export async function POST(request: Request) {
|
||||
|
||||
for (const user of users) {
|
||||
try {
|
||||
// Check if current hour in user's timezone matches their notification time
|
||||
const currentHour = getCurrentHourInTimezone(user.timezone);
|
||||
const notificationHour = getNotificationHour(user.notificationTime);
|
||||
// Check if current quarter-hour slot in user's timezone matches their notification time
|
||||
const currentSlot = getCurrentQuarterHourSlot(user.timezone);
|
||||
const notificationSlot = getNotificationSlot(user.notificationTime);
|
||||
|
||||
if (currentHour !== notificationHour) {
|
||||
if (
|
||||
currentSlot.hour !== notificationSlot.hour ||
|
||||
currentSlot.minute !== notificationSlot.minute
|
||||
) {
|
||||
result.skippedWrongTime++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user