Skip to content

feat: timezone UI with modal, toggle, and full page coverage#2

Open
veronoicc wants to merge 3 commits into
Privex-chat:mainfrom
veronoicc:timezone
Open

feat: timezone UI with modal, toggle, and full page coverage#2
veronoicc wants to merge 3 commits into
Privex-chat:mainfrom
veronoicc:timezone

Conversation

@veronoicc
Copy link
Copy Markdown

@veronoicc veronoicc commented May 18, 2026

Summary

  • New TimezoneModal component with smart input parsing, live preview, and quick-pick buttons (UTC, EST, CET, JST, etc.)
  • Globe badge in target header opens the modal to set/clear timezone
  • Insights page (routine heatmap + sleep schedule) has a timezone toggle button to switch between target's TZ and viewer's TZ
  • Heatmap supports hourShift prop for column reordering when toggling timezone view
  • All target-scoped pages now use timezone-aware formatting:
    • Overview: first seen time
    • Timeline: event list times + timeline bar tooltips
    • Messages: message/delete timestamps
    • Alerts: alert history timestamps
    • Backfill: completion timestamps
    • Briefs: generation timestamps
    • Profile: snapshot timestamps + avatar history
    • Analytics: relationship changes + baseline computation dates
  • Added formatDateTimeInTz, formatTimeInTz, formatDateInTz, parseTimezone, getTimezoneOffsetMinutes, tzLabel utilities

Companion PR: Privex-chat/sentinel-selfbot#3 — backend timezone support (DB schema, command, analyzers, API).

Summary by CodeRabbit

  • New Features
    • Added timezone management for targets—set and update timezone via a new timezone modal in the target header.
    • All timestamps throughout the app now display in the target's configured timezone.
    • Sleep schedules and routines can toggle between the target's timezone and your local timezone using a new timezone selector.
    • Enhanced timezone-aware formatting across alerts, messages, analytics, briefs, and other views for consistent time display.

veronoicc added 2 commits May 18, 2026 20:58
Add TimezoneModal with smart input parsing, live preview, and
quick-pick buttons. Globe badge in target header opens modal.
Insights page (routine + sleep) gets timezone toggle to switch
between target's TZ and viewer's TZ. Heatmap supports hourShift
prop for column reordering. Timezone parsing/formatting utilities
added to lib/utils.ts. Target type updated with timezone field.
Replace formatDateTime/formatTime/formatDate with their
timezone-aware variants (formatDateTimeInTz, formatTimeInTz,
formatDateInTz) across all target-scoped pages: overview,
timeline, messages, alerts, backfill, briefs, profile, and
analytics. Timeline bar tooltips and analytics relationship/
baseline timestamps now also respect target timezone.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

@veronoicc is attempting to deploy a commit to the privex-chat's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces timezone awareness throughout the application, allowing users to specify their timezone and have all timestamps display in that timezone rather than the viewer's local time. It adds timezone utilities, extends the data model, integrates a modal UI for timezone selection, and threads timezone parameters through multiple pages and chart components.

Changes

Timezone Management

Layer / File(s) Summary
Timezone utility functions
lib/utils.ts
Adds parseTimezone() to normalize multiple input forms (IANA zones, UTC offsets, abbreviations), getTimezoneOffsetMinutes() to compute offset in minutes, tzLabel() for display labels, and formatTimeInTz/formatDateTimeInTz/formatDateInTz for timezone-aware timestamp formatting with fallbacks.
Timezone data model and persistence
lib/types.ts, lib/api.ts, lib/context.tsx
Target type extended with `timezone: string
Timezone modal and target header integration
components/dashboard/timezone-modal.tsx, app/targets/[userId]/target-layout-client.tsx
New TimezoneModal component validates user input via parseTimezone, renders quick-pick buttons, and calls onSave with canonical timezone. Target header adds Globe icon button that opens modal; handleTimezoneSave persists changes via API and refreshes target data.
Chart component timezone support
components/charts/heatmap.tsx, components/charts/timeline-bar.tsx
Heatmap adds optional hourShift prop to circularly reindex data rows and rotate column labels when viewing in viewer's timezone. TimelineBar adds optional tz prop for timezone-aware session time range formatting in tooltips.
Simple page timezone integration
app/targets/[userId]/alerts/page-client.tsx, app/targets/[userId]/backfill/page-client.tsx, app/targets/[userId]/briefs/page-client.tsx, app/targets/[userId]/page-client.tsx, app/targets/[userId]/profile/page-client.tsx, app/targets/[userId]/timeline/page-client.tsx
Each page derives target timezone from useSentinel().targets, threads tz through component props, and replaces formatDateTime/formatTime/formatDate calls with timezone-aware variants, updating child component signatures to accept tz.
Analytics page timezone-aware tabs
app/targets/[userId]/analytics/page-client.tsx
SocialTab and BaselinesTab each derive target timezone from useSentinel().targets and use formatDateInTz to display relationship changes and baseline dates in the target's timezone instead of the server's local date.
Messages page timezone threading
app/targets/[userId]/messages/page-client.tsx
MessagesPage derives tz from target; passes it to AllMessages, DeletedMessages, and EditedMessages components via new prop. MessageItem extended with optional tz prop and uses formatDateTimeInTz for deleted-at label and message timestamp display.
Insights page dual-timezone viewing
app/targets/[userId]/insights/page-client.tsx
SleepScheduleCard and RoutineCard derive target timezone, add viewMyTz toggle state to switch between target and viewer timezones, compute hourShift from offset delta, and render times using shifted display values. RoutineCard passes hourShift to Heatmap for hour-label rotation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A timezone tale, by the rabbit's quill:
Where targets dwell in their own timezone still,
The modal saves and the offsets align,
Each page now shows the correct time, by design!
From heatmaps that shift to messages that care,
UTC or EST—display them with flair! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: timezone UI with modal, toggle, and full page coverage' directly and comprehensively describes the main changes in the PR, which add timezone UI components, toggles, and timezone-aware formatting across all target pages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Reject fractional numeric inputs (e.g. '5.5') in parseTimezone
- Replace toLocaleString round-trip with portable formatToParts
  in getTimezoneOffsetMinutes
@Privex-chat
Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/targets/[userId]/timeline/page-client.tsx (1)

40-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use target-midnight boundaries for the live timeline.

Line 46-Line 47 and Line 73-Line 86 still build day filters in the viewer’s local timezone, but Line 202 and Line 335-Line 336 render the results in the target’s timezone. For targets that are offset from the viewer, events around midnight will show under one day while the query/windowing logic fetches another. Build since/until and the live ganttStart/ganttEnd from tz as well, otherwise the page becomes internally inconsistent.

Also applies to: 72-86, 202-202, 335-336

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/targets/`[userId]/timeline/page-client.tsx around lines 40 - 47, The
since/until query params and the live ganttStart/ganttEnd are being calculated
in the viewer’s local timezone, causing off-by-one-day mismatches for targets in
other timezones; update the code that builds timelineParams (keys: since, until)
and the live timeline window (variables ganttStart and ganttEnd) to compute
midnight boundaries using the target timezone variable tz instead of the viewer
locale—i.e., convert the target date strings into epoch millis anchored to tz
(so "YYYY-MM-DDT00:00:00" and "YYYY-MM-DDT23:59:59" are interpreted in tz)
before String()ing them and sending to the server, and use the same tz-based
boundaries when setting ganttStart/ganttEnd to keep query and rendering
consistent with target time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/targets/`[userId]/messages/page-client.tsx:
- Around line 205-207: The timestamp selection in MessageItem currently always
prefers created_at; change the ts logic to pick the primary timestamp based on
view: if deleted is true use msg.deleted_at, else if showEditHistory is true use
msg.edited_at, otherwise use msg.created_at, and then fall back to any remaining
timestamps if that primary is undefined; update the ts variable used for
formatting (and likewise adjust the same logic in the other occurrence
referenced around lines 256-260) so edited and deleted tabs display their
respective event times consistently.

In `@app/targets/`[userId]/target-layout-client.tsx:
- Around line 121-125: The handleTimezoneSave flow currently evicts only
per-target cache keys (api.clearCacheForTarget) so refreshTargets() may read a
stale /api/targets entry; modify handleTimezoneSave to also clear the
targets-list cache before calling refreshTargets() by invoking the appropriate
cache-clear method for the list (e.g., api.clearCacheForTargets or
api.clearCache('/api/targets')) immediately after api.updateTarget(userId, ...)
and before await refreshTargets(), keeping the existing
api.clearCacheForTarget(userId) call in place.

In `@components/charts/heatmap.tsx`:
- Around line 12-13: The hourShift prop currently rounds fractional values and
rotates each day's row in place, which breaks cross-day remapping; update the
rendering logic that consumes hourShift (reference the hourShift prop in
components/charts/heatmap.tsx) to either (a) reject non-integer hourShift at the
prop boundary (validate and throw/log if hourShift is not an integer) or (b)
perform proper bucket remapping across both hour and weekday boundaries before
rendering: compute a new bucket index = (originalWeekday*24 + originalHour +
hourShift) modulo (7*24), then derive target weekday and hour from that index so
times that cross midnight move to the correct next/previous day; also remove the
current per-row in-place rotation and use this global remapping for all buckets.

In `@lib/utils.ts`:
- Around line 168-174: buildOffset currently returns offsets like "UTC+2" which
Intl.DateTimeFormat rejects; update buildOffset (and ensure parseTimezone uses
it) to produce canonical offsets in the "UTC±HH:MM" form: pad hours with two
digits (String(hours).padStart(2,"0")) and always include a minutes component
(use `:MM` or `:00` when minutes === 0), e.g. canonical =
`UTC${sign}${paddedHours}${minPart || ":00"}`, while keeping the special-case
return for UTC zero as "UTC".

---

Outside diff comments:
In `@app/targets/`[userId]/timeline/page-client.tsx:
- Around line 40-47: The since/until query params and the live
ganttStart/ganttEnd are being calculated in the viewer’s local timezone, causing
off-by-one-day mismatches for targets in other timezones; update the code that
builds timelineParams (keys: since, until) and the live timeline window
(variables ganttStart and ganttEnd) to compute midnight boundaries using the
target timezone variable tz instead of the viewer locale—i.e., convert the
target date strings into epoch millis anchored to tz (so "YYYY-MM-DDT00:00:00"
and "YYYY-MM-DDT23:59:59" are interpreted in tz) before String()ing them and
sending to the server, and use the same tz-based boundaries when setting
ganttStart/ganttEnd to keep query and rendering consistent with target time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4171ae98-5ebb-4d5b-b256-a2bbc8441a0c

📥 Commits

Reviewing files that changed from the base of the PR and between 79cd834 and 123022f.

📒 Files selected for processing (17)
  • app/targets/[userId]/alerts/page-client.tsx
  • app/targets/[userId]/analytics/page-client.tsx
  • app/targets/[userId]/backfill/page-client.tsx
  • app/targets/[userId]/briefs/page-client.tsx
  • app/targets/[userId]/insights/page-client.tsx
  • app/targets/[userId]/messages/page-client.tsx
  • app/targets/[userId]/page-client.tsx
  • app/targets/[userId]/profile/page-client.tsx
  • app/targets/[userId]/target-layout-client.tsx
  • app/targets/[userId]/timeline/page-client.tsx
  • components/charts/heatmap.tsx
  • components/charts/timeline-bar.tsx
  • components/dashboard/timezone-modal.tsx
  • lib/api.ts
  • lib/context.tsx
  • lib/types.ts
  • lib/utils.ts

Comment on lines +205 to 207
function MessageItem({ msg, deleted, showEditHistory, tz }: MessageItemProps) {
const content = msg.content || "[No content]"
const ts = msg.created_at || msg.deleted_at || msg.edited_at
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Choose the primary timestamp by view before formatting it.

ts always prefers created_at, so the deleted and edited tabs still render the creation time on the right. That means edited messages never show when the edit happened, and deleted messages show two different timestamps in the same row. Pick deleted_at for deleted, edited_at for showEditHistory, and fall back to created_at otherwise.

Proposed fix
 function MessageItem({ msg, deleted, showEditHistory, tz }: MessageItemProps) {
   const content = msg.content || "[No content]"
-  const ts = msg.created_at || msg.deleted_at || msg.edited_at
+  const ts = deleted
+    ? msg.deleted_at || msg.created_at
+    : showEditHistory
+      ? msg.edited_at || msg.created_at
+      : msg.created_at

Also applies to: 256-260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/targets/`[userId]/messages/page-client.tsx around lines 205 - 207, The
timestamp selection in MessageItem currently always prefers created_at; change
the ts logic to pick the primary timestamp based on view: if deleted is true use
msg.deleted_at, else if showEditHistory is true use msg.edited_at, otherwise use
msg.created_at, and then fall back to any remaining timestamps if that primary
is undefined; update the ts variable used for formatting (and likewise adjust
the same logic in the other occurrence referenced around lines 256-260) so
edited and deleted tabs display their respective event times consistently.

Comment on lines +121 to +125
const handleTimezoneSave = async (tz: string | null) => {
await api.updateTarget(userId, { timezone: tz })
api.clearCacheForTarget(userId)
await refreshTargets()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear the targets-list cache before refreshing after a timezone save.

This only evicts per-target keys, but the updated timezone is read back from api.getTargets() via refreshTargets(). Since /api/targets is cached for 5 seconds in lib/api.ts, the header can immediately reload the stale timezone and make the save look like it failed.

Suggested fix
   const handleTimezoneSave = async (tz: string | null) => {
     await api.updateTarget(userId, { timezone: tz })
-    api.clearCacheForTarget(userId)
+    api.clearCache()
     await refreshTargets()
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/targets/`[userId]/target-layout-client.tsx around lines 121 - 125, The
handleTimezoneSave flow currently evicts only per-target cache keys
(api.clearCacheForTarget) so refreshTargets() may read a stale /api/targets
entry; modify handleTimezoneSave to also clear the targets-list cache before
calling refreshTargets() by invoking the appropriate cache-clear method for the
list (e.g., api.clearCacheForTargets or api.clearCache('/api/targets'))
immediately after api.updateTarget(userId, ...) and before await
refreshTargets(), keeping the existing api.clearCacheForTarget(userId) call in
place.

Comment on lines +12 to +13
/** Shift to apply to hour labels (viewer offset - target offset, in hours). 0 = no shift. */
hourShift?: number
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Timezone shifting needs cross-day remapping, not per-row rotation.

hourShift currently rounds to whole hours and then rotates each day row in place. That makes the viewer-TZ routine view wrong whenever the offset crosses midnight: e.g. a Monday 23:00 bucket shifted by +2 should land on Tuesday 01:00, but this code keeps it on Monday. The same path also mislabels half/quarter-hour zones because 5.5, 9.5, etc. are snapped to an integer. Please remap buckets across both hour and weekday boundaries before rendering, or reject unsupported non-integral shifts at the prop boundary.

Also applies to: 25-42

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/charts/heatmap.tsx` around lines 12 - 13, The hourShift prop
currently rounds fractional values and rotates each day's row in place, which
breaks cross-day remapping; update the rendering logic that consumes hourShift
(reference the hourShift prop in components/charts/heatmap.tsx) to either (a)
reject non-integer hourShift at the prop boundary (validate and throw/log if
hourShift is not an integer) or (b) perform proper bucket remapping across both
hour and weekday boundaries before rendering: compute a new bucket index =
(originalWeekday*24 + originalHour + hourShift) modulo (7*24), then derive
target weekday and hour from that index so times that cross midnight move to the
correct next/previous day; also remove the current per-row in-place rotation and
use this global remapping for all buckets.

Comment thread lib/utils.ts
Comment on lines +168 to +174
function buildOffset(sign: string, hours: number, minutes: number): ParsedTimezone | null {
if (hours > 14 || minutes > 59) return null
if (hours === 0 && minutes === 0) return { canonical: "UTC", display: "UTC" }
const minPart = minutes > 0 ? `:${String(minutes).padStart(2, "0")}` : ""
const canonical = `UTC${sign}${hours}${minPart}`
return { canonical, display: canonical }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Intl.DateTimeFormataccepttimeZonevalues likeUTC+2/UTC-5, or must fixed-offset zones be passed as offset identifiers like +02:00/-05:00?

💡 Result:

Intl.DateTimeFormat does not accept informal time zone strings like "UTC+2" or "UTC-5" as values for the timeZone option [1][2]. Instead, you must use either: 1. IANA time zone names (e.g., "America/New_York", "UTC", "Etc/GMT+8") [1][3]. 2. Fixed-offset identifiers, which must be formatted as specific offset strings (e.g., "+02:00", "-05:00") [1][2]. According to official documentation, the timeZone option supports IANA names, "Etc/GMT" style identifiers, and explicit offset strings [1][2]. Attempting to pass an informal format like "UTC+2" will generally result in the constructor throwing a RangeError due to an invalid time zone name.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
ls -la
rg -n "function parseTimezone|parseTimezone|function buildOffset|buildOffset|formatTimeInTz|formatDateTimeInTz|formatDateInTz" lib/utils.ts
nl -ba lib/utils.ts | sed -n '130,260p'

Repository: Privex-chat/sentinel-web

Length of output: 2117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "export function parseTimezone|function buildOffset|export function formatTimeInTz|export function formatDateTimeInTz|export function formatDateInTz" lib/utils.ts

echo "---- parseTimezone/buildOffset ----"
sed -n '130,260p' lib/utils.ts | cat -n

echo "---- formatters catch/fallback ----"
sed -n '200,250p' lib/utils.ts | cat -n

Repository: Privex-chat/sentinel-web

Length of output: 6962


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "function formatTime\\b|function formatDateTime\\b|function formatDate\\b|export function formatTime\\b|export function formatDateTime\\b|export function formatDate\\b" lib/utils.ts
sed -n '1,140p' lib/utils.ts | cat -n

Repository: Privex-chat/sentinel-web

Length of output: 5885


Fix formatting of saved fixed offsets (UTC±H[:MM]) so Intl.DateTimeFormat respects them.

parseTimezone()/buildOffset() persist strings like UTC+2 / UTC-5, but formatTimeInTz/formatDateTimeInTz/formatDateInTz pass them directly to Intl.DateTimeFormat({ timeZone: tz }). For UTC+2-style values, Intl throws and the code falls back to toLocale* formatting, so the saved offset is effectively ignored on render.

Suggested fix
+function toIntlTimeZone(tz: string): string {
+  const m = tz.match(/^UTC([+-])(\d{1,2})(?::(\d{2}))?$/)
+  if (!m) return tz
+  return `${m[1]}${m[2].padStart(2, "0")}:${m[3] ?? "00"}`
+}
+
 export function formatTimeInTz(ts: number, tz: string | null | undefined): string {
   if (!tz) return formatTime(ts)
   try {
-    return new Intl.DateTimeFormat("en-GB", { timeZone: tz, hour: "2-digit", minute: "2-digit", hour12: false }).format(new Date(ts))
+    return new Intl.DateTimeFormat("en-GB", {
+      timeZone: toIntlTimeZone(tz),
+      hour: "2-digit",
+      minute: "2-digit",
+      hour12: false,
+    }).format(new Date(ts))
   } catch { return formatTime(ts) }
 }
 
 export function formatDateTimeInTz(ts: number, tz: string | null | undefined): string {
   if (!tz) return formatDateTime(ts)
   try {
     return new Intl.DateTimeFormat("en-US", {
-      timeZone: tz,
+      timeZone: toIntlTimeZone(tz),
       month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
     }).format(new Date(ts))
   } catch { return formatDateTime(ts) }
 }
 
 export function formatDateInTz(ts: number, tz: string | null | undefined): string {
   if (!tz) return formatDate(ts)
   try {
     return new Intl.DateTimeFormat("en-US", {
-      timeZone: tz,
+      timeZone: toIntlTimeZone(tz),
       month: "short", day: "numeric",
     }).format(new Date(ts))
   } catch { return formatDate(ts) }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils.ts` around lines 168 - 174, buildOffset currently returns offsets
like "UTC+2" which Intl.DateTimeFormat rejects; update buildOffset (and ensure
parseTimezone uses it) to produce canonical offsets in the "UTC±HH:MM" form: pad
hours with two digits (String(hours).padStart(2,"0")) and always include a
minutes component (use `:MM` or `:00` when minutes === 0), e.g. canonical =
`UTC${sign}${paddedHours}${minPart || ":00"}`, while keeping the special-case
return for UTC zero as "UTC".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants