[object Object]

← back to Trending Dw

trending: pin staleness to Steve's TZ (America/Los_Angeles) so prod (UTC) and Mac2 agree

c9753c24134a4e17d956bff9105e6e3d4c5dac16 · 2026-07-06 21:40:47 -0700 · Steve Abrams

Kamatera runs UTC, so the prior local-time staleDays showed 'as of Jul 6 · 1 day ago'
on prod while Mac2 showed 'today'. spottedAt is a bare date with no tz — pin the
reference to PT + pure Date.UTC arithmetic on both endpoints. Verified staleDays=0
under PT, UTC, and Tokyo. Fixed in server.js loadMeta + tick.sh.

Files touched

Diff

commit c9753c24134a4e17d956bff9105e6e3d4c5dac16
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 6 21:40:47 2026 -0700

    trending: pin staleness to Steve's TZ (America/Los_Angeles) so prod (UTC) and Mac2 agree
    
    Kamatera runs UTC, so the prior local-time staleDays showed 'as of Jul 6 · 1 day ago'
    on prod while Mac2 showed 'today'. spottedAt is a bare date with no tz — pin the
    reference to PT + pure Date.UTC arithmetic on both endpoints. Verified staleDays=0
    under PT, UTC, and Tokyo. Fixed in server.js loadMeta + tick.sh.
---
 scripts/tick.sh |  6 ++++--
 server.js       | 14 ++++++++------
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/scripts/tick.sh b/scripts/tick.sh
index 7598eab..afa848e 100755
--- a/scripts/tick.sh
+++ b/scripts/tick.sh
@@ -33,9 +33,11 @@ read AS_OF STALE_DAYS IMG_GAP COUNT <<<"$("$NODE" -e '
   try{ const d=JSON.parse(require("fs").readFileSync("data/bestsellers.json","utf8"));
     const its=d.items||[]; const sp=its.map(i=>i.spottedAt).filter(Boolean).sort();
     const asOf=sp.length?sp[sp.length-1]:(d.generatedAt||"");
-    // local whole-day diff (Date.parse of YYYY-MM-DD is UTC midnight → off-by-one after 00:00 UTC)
+    // staleness pinned to Steve TZ (America/Los_Angeles) + pure date arithmetic, so it matches
+    // the server (Kamatera/UTC) and Mac2 identically. spottedAt is a bare date with no tz.
     let days=-1; { const p=String(asOf).split("-").map(Number);
-      if(p[0]&&p[1]&&p[2]){ const t=new Date(p[0],p[1]-1,p[2]),n=new Date(),td=new Date(n.getFullYear(),n.getMonth(),n.getDate()); days=Math.round((td-t)/86400000); } }
+      if(p[0]&&p[1]&&p[2]){ const ts=new Date().toLocaleDateString("en-CA",{timeZone:"America/Los_Angeles"}).split("-").map(Number);
+        days=Math.round((Date.UTC(ts[0],ts[1]-1,ts[2])-Date.UTC(p[0],p[1]-1,p[2]))/86400000); } }
     const gap=its.filter(i=>!i.image).length;
     process.stdout.write(asOf+" "+days+" "+gap+" "+its.length);
   }catch(e){ process.stdout.write("- -1 -1 0"); }' 2>/dev/null)"
diff --git a/server.js b/server.js
index 9d56c4f..def3d49 100644
--- a/server.js
+++ b/server.js
@@ -42,12 +42,14 @@ function loadMeta(){
     // most recent spottedAt across items = the real "as of" date
     const spotted = items.map(i=>i.spottedAt).filter(Boolean).sort();
     const asOf = spotted.length ? spotted[spotted.length-1] : generatedAt;
-    // whole-day diff in LOCAL time — parsing 'YYYY-MM-DD' via Date.parse() treats it as UTC
-    // midnight, so a today-local date read after 00:00 UTC would wrongly show "1 day ago".
-    const staleDays = asOf ? (()=>{ const [y,m,d]=String(asOf).split('-').map(Number);
-      if(!y||!m||!d) return null;
-      const then=new Date(y,m-1,d), now=new Date(), today=new Date(now.getFullYear(),now.getMonth(),now.getDate());
-      return Math.round((today-then)/86400000); })() : null;
+    // staleness must be identical whether this runs on Mac2 (Pacific) or Kamatera (UTC), so
+    // pin the reference to Steve's timezone and do pure date arithmetic (Date.UTC on both
+    // endpoints — no tz drift). spottedAt is a bare 'YYYY-MM-DD' with no tz of its own.
+    const staleDays = asOf ? (()=>{ const p=String(asOf).split('-').map(Number);
+      if(!p[0]||!p[1]||!p[2]) return null;
+      const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }); // YYYY-MM-DD in PT
+      const t = todayStr.split('-').map(Number);
+      return Math.round((Date.UTC(t[0],t[1]-1,t[2]) - Date.UTC(p[0],p[1]-1,p[2])) / 86400000); })() : null;
     const withImage = items.filter(i=>i.image).length;
     return { generatedAt, asOf, staleDays, count: items.length, imagesAttachedAt: d.imagesAttachedAt || null,
              withImage, imageGap: items.length - withImage, source: d.source || null };

← 74b3db6 trending: LIVE refresh — 49 current 2026 bestsellers via Exa  ·  back to Trending Dw  ·  trending: self-host all card images locally (scripts/localiz 00c19c0 →