Rust discount function (one automatic discount evaluating all offers in priority order, parity with the Odoo engine), React Router admin with Polaris, theme app extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.8 KiB
TypeScript
45 lines
1.8 KiB
TypeScript
// Offer dates are stored in UTC and edited in the shop's timezone (brief §3).
|
|
|
|
function offsetMinutes(timeZone: string, at: Date) {
|
|
try {
|
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
timeZone, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
}).formatToParts(at);
|
|
const p = Object.fromEntries(parts.map((x) => [x.type, Number(x.value)]));
|
|
const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
return Math.round((asUtc - at.getTime()) / 60000);
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/** "2026-10-20T00:00" in the shop timezone -> UTC ISO string (null for empty). */
|
|
export function shopLocalToUtc(local: string | null | undefined, timeZone: string): string | null {
|
|
if (!local) return null;
|
|
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(local);
|
|
if (!m) return null;
|
|
const guess = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]);
|
|
let t = guess - offsetMinutes(timeZone, new Date(guess)) * 60000;
|
|
t = guess - offsetMinutes(timeZone, new Date(t)) * 60000; // second pass across DST changes
|
|
return new Date(t).toISOString();
|
|
}
|
|
|
|
/** UTC -> "YYYY-MM-DDTHH:mm" in the shop timezone, for <input type="datetime-local">. */
|
|
export function utcToShopLocal(utc: Date | string | null | undefined, timeZone: string): string {
|
|
if (!utc) return "";
|
|
const d = new Date(utc);
|
|
const local = new Date(d.getTime() + offsetMinutes(timeZone, d) * 60000);
|
|
return local.toISOString().slice(0, 16);
|
|
}
|
|
|
|
export function formatShopDate(utc: Date | string | null | undefined, timeZone: string, withTime = true) {
|
|
if (!utc) return "";
|
|
try {
|
|
return new Intl.DateTimeFormat("en", {
|
|
timeZone, dateStyle: "medium", ...(withTime ? { timeStyle: "short" } : {}),
|
|
}).format(new Date(utc));
|
|
} catch {
|
|
return new Date(utc).toISOString();
|
|
}
|
|
}
|