metatroncubeswdev 365e45406a Bundle Offers Shopify app: discount function, admin, storefront, deploy stack
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>
2026-10-13 21:35:58 -04:00

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();
}
}