feat: per-target timezone support#3
Conversation
Add timezone column to targets table (migration v6) with full parsing utility supporting IANA names, abbreviations (EST/CET/JST), UTC/GMT offsets, and bare numeric offsets. New $timezone selfbot command to view/set/clear. Routine detector, sleep analyzer, and time-displaying commands ($pattern, $history, $seen, $streak) now use target's timezone for hour/day calculations. API PATCH route validates and stores timezone via parseTimezone().
|
Warning Review limit reached
More reviews will be available in 58 minutes and 48 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR adds per-target timezone support to the selfbot application. A new timezone utility module handles parsing and formatting, the database schema gains a timezone column, the API route persists timezone settings, and activity analyzers and command handlers use timezone-aware calculations for schedules and command output display. ChangesTimezone-aware activity tracking and command output
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
supabase-schema.sql (1)
38-49:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winThis script can hard-fail on existing databases before migration runs.
For pre-existing
public.targetstables, Line 48 (COMMENT ON COLUMN ...timezone) errors if the column does not exist yet. Since the file is meant to be re-run, add an idempotentALTER TABLE ... ADD COLUMNguard before that comment.Suggested fix
CREATE TABLE IF NOT EXISTS public.targets ( @@ CONSTRAINT targets_active_bool CHECK (active IN (0, 1)) ); +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema='public' AND table_name='targets' AND column_name='timezone' + ) THEN + ALTER TABLE public.targets ADD COLUMN timezone TEXT; + END IF; +END $$; + COMMENT ON TABLE public.targets IS 'Tracked Discord user accounts.'; COMMENT ON COLUMN public.targets.user_id IS 'Discord snowflake ID (17-20 digit string).'; COMMENT ON COLUMN public.targets.added_at IS 'Unix epoch ms when the target was added.'; COMMENT ON COLUMN public.targets.active IS '1 = actively tracked, 0 = paused.'; COMMENT ON COLUMN public.targets.timezone IS 'IANA timezone identifier (e.g. Europe/Berlin) or UTC offset (e.g. UTC+2). NULL = server local time.';🤖 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 `@supabase-schema.sql` around lines 38 - 49, The COMMENT ON COLUMN for public.targets.timezone will fail if the timezone column doesn't exist; add an idempotent ALTER TABLE guard before that comment that ensures the timezone column exists (i.e., run an ALTER TABLE public.targets ... ADD COLUMN IF NOT EXISTS timezone TEXT so the script can be re-run safely) and then keep the existing COMMENT ON COLUMN public.targets.timezone line unchanged.
🤖 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 `@src/analyzers/sleep-schedule.ts`:
- Around line 90-93: The all-nighter message uses UTC formatting (new
Date(s.startMs).toISOString()) which can shift the reported day for non-UTC
targets; update the code around getHourInTimezone(s.startMs, tz) to format the
date in the target timezone (tz) instead — e.g., replace the new
Date(...).toISOString().split("T")[0] usage with a timezone-aware formatter
(toLocaleDateString with { timeZone: tz } or a small helper like
getDateInTimezone) so the string pushed into irregularities (`All-nighter on
${dateStr}`) reflects the target timezone date for s.startMs.
In `@src/api/routes/targets.ts`:
- Around line 110-117: The timezone handling should trim input before deciding
whether to clear or parse: in the update handler where body.timezone is checked
(references: body.timezone, parseTimezone, setParts, params, reply), normalize
with body.timezone = typeof body.timezone === "string" ? body.timezone.trim() :
body.timezone (or equivalent) before the null/empty check so that
whitespace-only strings are treated as empty and push null into params/setParts
to clear the setting, and only call parseTimezone on the trimmed non-empty value
and return the 400 via reply for truly unrecognized timezones.
In `@src/commands/handler.ts`:
- Line 563: Update the usage examples sent by sendTempMessage so they show
accepted mention/raw ID formats; modify the message constructed in the
sendTempMessage call (where sendTempMessage(channelId, "...") is invoked) to
replace `@user` examples with `<`@user`>` and include a raw snowflake example like
`123456789012345678`, e.g. `$timezone <`@123456789012345678`> EST` or `$timezone
123456789012345678 UTC+2`, ensuring the displayed examples match the parser that
accepts `<@...>` mentions or raw IDs.
- Around line 527-531: The loop currently slices segments using UTC-hour
boundaries (segEnd = Math.min(t + (3_600_000 - (t % 3_600_000)), end)) which
mis-allocates time for non-integer timezone offsets and DST; change the
segmentation to compute the next local-hour boundary in the target timezone and
use that as segEnd instead. Replace the UTC-based segEnd calc in the loop that
updates buckets and uses getHourInTimezone(t, tz) with a timezone-aware helper
(e.g. getStartOfNextLocalHour(timestamp, tz) or equivalent) so segEnd =
Math.min(getStartOfNextLocalHour(t, tz), end), then continue to increment
buckets[getHourInTimezone(t, tz)] by segEnd - t so hour attribution matches
local hours and correctly handles DST and fractional offsets.
In `@src/database/migrations.ts`:
- Around line 134-137: The migration currently swallows all errors when running
db.exec("ALTER TABLE targets ADD COLUMN timezone TEXT") which can mark v6 as
applied even if the column wasn't added; change the logic in the v6 migration to
(1) attempt the ALTER inside a try/catch that only ignores the known "duplicate
column" error, (2) after the ALTER (or ignore) query the schema (e.g., PRAGMA
table_info('targets') or equivalent) to assert that the "timezone" column
exists, and (3) if the post-condition check fails rethrow or log a fatal error
so the migration fails instead of logging success; refer to db.exec, the v6
migration block, and log.info("Migration v6: targets.timezone column added")
when making these changes.
In `@src/utils/timezone.ts`:
- Around line 139-143: The current branch converts a numeric string like "5.5"
by truncating via Math.trunc (numVal) which silently accepts fractional offsets;
instead require plain-number inputs to be integers. In the block handling raw →
numVal (variables raw and numVal) and before calling buildFromOffset, add a
guard using Number.isInteger(numVal) (or equivalent) so only integer numeric
values with Math.abs(numVal) <= 14 are accepted; if the value is fractional, do
not call buildFromOffset (treat as invalid input so the caller falls through to
other parsing or returns an error). Keep buildFromOffset usage and sign
calculation the same for integer values.
- Around line 186-191: The current offset calculation using toLocaleString ->
new Date (variables d, utcStr, tzStr, utcD, tzD) is non-portable; replace that
round-trip with Intl.DateTimeFormat.formatToParts: use formatToParts on the
original Date (d) for timeZone "UTC" and for the target tz, extract numeric
parts (year, month, day, hour, minute, second), build timestamps via
Date.UTC(...) for each set of parts, then compute (tzTimestamp -
utcTimestamp)/60000 and round to minutes; update the function that returns the
offset so it uses these formatToParts-derived UTC values instead of parsing
locale strings.
---
Outside diff comments:
In `@supabase-schema.sql`:
- Around line 38-49: The COMMENT ON COLUMN for public.targets.timezone will fail
if the timezone column doesn't exist; add an idempotent ALTER TABLE guard before
that comment that ensures the timezone column exists (i.e., run an ALTER TABLE
public.targets ... ADD COLUMN IF NOT EXISTS timezone TEXT so the script can be
re-run safely) and then keep the existing COMMENT ON COLUMN
public.targets.timezone line unchanged.
🪄 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: 761b6370-4352-4c56-9f5e-2f9a6c8cd268
📒 Files selected for processing (8)
src/analyzers/routine-detector.tssrc/analyzers/sleep-schedule.tssrc/api/routes/targets.tssrc/commands/handler.tssrc/database/migrations.tssrc/database/schema.tssrc/utils/timezone.tssupabase-schema.sql
- Sleep analyzer: format all-nighter dates in target tz, not UTC - API: trim whitespace-only timezone strings before parsing - Command: fix usage examples to show <@id>/snowflake formats - Pattern: use local-hour boundaries for bucket segmentation (handles DST transitions and fractional offsets correctly) - Migration v6: verify column exists post-ALTER, only ignore duplicate-column errors instead of swallowing all exceptions - Parsing: reject fractional numeric inputs (e.g. '5.5') - Offset calc: replace toLocaleString round-trip with portable Intl.DateTimeFormat.formatToParts - Supabase: add idempotent ALTER guard before timezone COMMENT
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Summary
timezonecolumn totargetstable (SQLite migration v6 + Supabase schema)$timezone <@user> [tz]selfbot command to view/set/clear timezone per targetEurope/Berlin), abbreviations (EST,CET,JST), UTC/GMT offsets (UTC+2,GMT-5), bare offsets (+3), and plain numbersIntl.DateTimeFormat)$pattern,$history,$seen,$streakdisplay times in target's timezonePATCH /api/targets/:userIdvalidates and stores timezone server-sideCompanion PR: Privex-chat/sentinel-web#2 — web UI changes (timezone modal, toggle, heatmap shifting, timezone-aware formatting across all pages).
Summary by CodeRabbit
$timezonecommand to configure or clear timezone settings