[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6H0H3R714TYCM63EC6JEJSZ":3,"skill-same:01M6H0H3R714TYCM63EC6JEJSZ":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":16,"sourceHtmlUrl":17,"categories":18,"keywords":19,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":38,"commitSha":39,"skillDirectory":40,"forksCount":41,"repositoryHtmlUrl":42,"repositoryHomepageUrl":6,"ownerHtmlUrl":43,"ownerBlogUrl":44,"ownerDescription":45,"updatedAt":46,"install":47,"resources":60},"01M6H0H3R714TYCM63EC6JEJSZ","store-user-timezone-and-render-locally",null,"Use when a product shows times to a user — store the IANA zone name the user chose and render every timestamp in it, never store a fixed offset.","jiayaoqijia\u002Fcryptoskill","jiayaoqijia","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1453879?v=4","01M6GMYTX7Q7S504VC6E756ENR","YQ","01M6GMYV1QKJ1CC75MTTGT9PR0",80,"MIT","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally\u002FSKILL.md",[],[20,21,22,23,24,25,26,27,28,29,30,31,32],"user","store","render","store user timezone and render locally","product","timestamp","use","never","iana","name","it","zone","when","2026-11-10 10:12:30",false,"---\nname: store-user-timezone-and-render-locally\ndescription: Use when a product shows times to a user — store the IANA zone name the user chose and render every timestamp in it, never store a fixed offset.\n---\n\n# Store the user timezone and render locally\n\nUsers think in their own local time and expect the product to do the same. Store the IANA zone name once, render every timestamp through it, and the display stays correct across DST without per-row math.\n\n## Procedure\n\n1. Capture the zone name, not the offset, and not the browser's abbreviation (`EST` is ambiguous across the world):\n```js\nIntl.DateTimeFormat().resolvedOptions().timeZone   \u002F\u002F \"America\u002FNew_York\"\n```\n2. Persist it as a column with a default of UTC and validate against the tz database:\n```\npython3 -c \"import zoneinfo,sys; zoneinfo.ZoneInfo(sys.argv[1])\" America\u002FNew_York\n```\n3. Let the user override it explicitly; do not re-detect on every login, or a travelling user's logs jump zones.\n4. Store every instant in UTC and convert only at the render edge:\n```python\nfrom zoneinfo import ZoneInfo\ndef render(dt_utc, zone):\n    return dt_utc.astimezone(ZoneInfo(zone)).strftime(\"%Y-%m-%d %H:%M %Z\")\n```\n5. Render with the zone label so the user is never guessing: `2026-03-01 09:00 EST`, not `2026-03-01 09:00`.\n6. For date-only displays (due dates, birthdays), keep the value as a *civil date*, not an instant — an instant rendered in a western zone can slip a day for users east of UTC.\n7. Re-render on zone change and cache with the zone in the key, so a user in two zones does not see another's formatting:\n```\ncache_key = f\"render:{user_id}:{zone}:{instant_epoch}\"\n```\n8. Backfill: for existing rows with no zone, default to UTC and flag them for the user to confirm at next login.\n\n## Pitfalls\n\n- Storing `-05:00` freezes the user in winter time; six months later their 09:00 shows as 08:00 because the offset never shifted.\n- `toLocaleString()` without an explicit zone uses the server's or runtime's zone in SSR, so the same page renders differently on the server and the client — a hydration mismatch.\n- Emails generated server-side with no zone land at odd hours for the recipient; resolve the recipient's zone before formatting.\n- A user who moves regions may want history in their old zone; storing only the current zone loses that. Consider a per-event zone for anything legally timestamped.\n- Date-only fields converted through UTC can move a day; keep them as `DATE` and never run them through a zone.\n\n## Verification\n\n```\npsql -c \"SELECT count(*) FROM users WHERE timezone IS NULL OR timezone !~ '^[A-Za-z]+\u002F[A-Za-z_]+$';\"\n```\nReturns `0` = every user has a valid IANA zone name. Report: \"users.timezone stores IANA names; render path converts UTC to the user's zone with a `%Z` label; server and client output match.\"\n","# Store the user timezone and render locally\n\nUsers think in their own local time and expect the product to do the same. Store the IANA zone name once, render every timestamp through it, and the display stays correct across DST without per-row math.\n\n## Procedure\n\n1. Capture the zone name, not the offset, and not the browser's abbreviation (`EST` is ambiguous across the world):\n```js\nIntl.DateTimeFormat().resolvedOptions().timeZone   \u002F\u002F \"America\u002FNew_York\"\n```\n2. Persist it as a column with a default of UTC and validate against the tz database:\n```\npython3 -c \"import zoneinfo,sys; zoneinfo.ZoneInfo(sys.argv[1])\" America\u002FNew_York\n```\n3. Let the user override it explicitly; do not re-detect on every login, or a travelling user's logs jump zones.\n4. Store every instant in UTC and convert only at the render edge:\n```python\nfrom zoneinfo import ZoneInfo\ndef render(dt_utc, zone):\n    return dt_utc.astimezone(ZoneInfo(zone)).strftime(\"%Y-%m-%d %H:%M %Z\")\n```\n5. Render with the zone label so the user is never guessing: `2026-03-01 09:00 EST`, not `2026-03-01 09:00`.\n6. For date-only displays (due dates, birthdays), keep the value as a *civil date*, not an instant — an instant rendered in a western zone can slip a day for users east of UTC.\n7. Re-render on zone change and cache with the zone in the key, so a user in two zones does not see another's formatting:\n```\ncache_key = f\"render:{user_id}:{zone}:{instant_epoch}\"\n```\n8. Backfill: for existing rows with no zone, default to UTC and flag them for the user to confirm at next login.\n\n## Pitfalls\n\n- Storing `-05:00` freezes the user in winter time; six months later their 09:00 shows as 08:00 because the offset never shifted.\n- `toLocaleString()` without an explicit zone uses the server's or runtime's zone in SSR, so the same page renders differently on the server and the client — a hydration mismatch.\n- Emails generated server-side with no zone land at odd hours for the recipient; resolve the recipient's zone before formatting.\n- A user who moves regions may want history in their old zone; storing only the current zone loses that. Consider a per-event zone for anything legally timestamped.\n- Date-only fields converted through UTC can move a day; keep them as `DATE` and never run them through a zone.\n\n## Verification\n\n```\npsql -c \"SELECT count(*) FROM users WHERE timezone IS NULL OR timezone !~ '^[A-Za-z]+\u002F[A-Za-z_]+$';\"\n```\nReturns `0` = every user has a valid IANA zone name. Report: \"users.timezone stores IANA names; render path converts UTC to the user's zone with a `%Z` label; server and client output match.\"\n",{"name":5,"description":7},"main","3df1d0d67b30ff15236561f8c0f9f06d33f42dfd","skills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally",17,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia","https:\u002F\u002Feth2030.com","Hardcore infra. Building @alt-research and applied cryptography.","2026-11-11 08:47:40",{"source":8,"skillName":5,"commands":48,"prompt":58,"zipAvailable":59},[49,52,55],{"tool":50,"command":51},"npx","npx skills add jiayaoqijia\u002Fcryptoskill --skill store-user-timezone-and-render-locally",{"tool":53,"command":54},"bunx","bunx skills add jiayaoqijia\u002Fcryptoskill --skill store-user-timezone-and-render-locally",{"tool":56,"command":57},"pnpm","pnpm dlx skills add jiayaoqijia\u002Fcryptoskill --skill store-user-timezone-and-render-locally","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally\u002FSKILL.md",true,[61,65,71],{"path":62,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":64,"sourceHtmlUrl":16},"LICENSE","许可证",1063,{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"SOURCE.md","REFERENCE","参考文档",364,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally\u002FSOURCE.md",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"TRUST.auto.yaml",2217,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-store-user-timezone-and-render-locally\u002FTRUST.auto.yaml",[76,104,127,145,158,168],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":80,"sourceHtmlUrl":81,"categories":82,"keywords":83,"publishedAt":33},"01M712C436EQYE43342Q4E7GG1","recipe-performance","Diagnose a specific MetaMask user flow's real-app performance and Sentry critical-user-flow telemetry with deterministic mm-harness recipes. Use for CUF boundaries, cold\u002Fwarm\u002Fbackground-resume behavior, latency regressions, lifecycle tags, duplicate or blocking network requests, long tasks, layout\u002Fpaint cost, screen redraws, React rerenders, or browser trace analysis where tests alone cannot prove the real app.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FSKILL.md",[],[84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103],"performance","recipe performance","recipe","mm","sentry","harness","app","deterministic","recipes","flow","cold","critical","analysis","tasks","react","browser","layout","screen","network","metamask",{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":108,"licenseFileUrl":109,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":33},"01M70XT2F8A5CQZ81JBP98KSW3","mexc-spot-websocket","MEXC Spot WebSocket workflows for public streams, protobuf handling, depth maintenance, listen keys, and private streams without exposing API keys.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FSKILL.md",[],[113,114,115,116,117,118,119,120,121,122,123,124,125,126],"mexc spot websocket","spot","websocket","api","workflows","public","listen","streams","depth","keys","for","api keys","maintenance","protobuf",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":108,"licenseFileUrl":131,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":33},"01M70XT0MBXN3WZY1BKN0CPNKR","mexc-spot-rest","MEXC Spot REST workflows for public data, signed account requests, orders, wallet\u002Fadmin actions, and secure signing without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FSKILL.md",[],[135,114,136,137,138,139,140,124,123,122,141,142,143,118,144,117,116],"mexc spot rest","REST","admin","signing","Wallet","requests","actions","secure","data","orders",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":108,"licenseFileUrl":149,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":33},"01M70XSYSVZ2N0N91XCAR2CAK0","mexc-futures-websocket","MEXC Futures WebSocket workflows for public streams, private account\u002Forder\u002Fposition streams, ping\u002Fpong, subscriptions, and depth maintenance.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FSKILL.md",[],[153,154,115,155,156,125,123,157,121,120,118,117],"mexc futures websocket","futures","position","ping","subscriptions",{"skillNo":159,"name":160,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":161,"sourceHtmlUrl":162,"categories":163,"keywords":164,"publishedAt":33},"01M6H0ZWHVE5W04WG2DKQE2SQC","0xklen-version-and-refresh-sanctions-lists","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FSKILL.md",[],[165,166,167],"refresh","0xklen version and refresh sanctions lists","version",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":172,"sourceHtmlUrl":173,"categories":174,"keywords":175,"publishedAt":33},"01M6H0Z28Q3R8J0S799GJQWXM3","verify-tests-cover-changed-lines","Use when a PR adds code that the existing suite may not exercise. Measures patch coverage — the share of newly changed lines hit by tests — before approving.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FSKILL.md",[],[176,177,178,179,180,181,182,26,183,184,185],"tests","cover","verify tests cover changed lines","verify","coverage","pr","exercise","patch","code","share"]