feat(db): convert timestamp to timestamptz#314
Conversation
Ensures fresh installs create timestamptz columns directly.
One migration per module. Converts timestamp columns to timestamptz using AT TIME ZONE 'America/Sao_Paulo' for pre-switch data and 'UTC' for Discord API and post-switch tables. Idempotent — skips columns already converted.
Artisan command to correct post-switch data after ALTER migrations. Subtracts 3h from rows inserted after the APP_TIMEZONE switch. Includes dry-run, module/table filters, progress display, and verification.
- Drop orphaned tmi_cluster tables (removed package) - Add timezone-aware dates guideline for LLM agents - Fix Review.received_at cast from 'timestamp' to 'datetime' and update PHPDoc to CarbonInterface
📝 WalkthroughWalkthroughAdds a guideline enforcing timezone-aware schema usage and display conversions; switches many create migrations to Tz variants; adds conditional ALTER migrations that convert timestamp columns to timestamptz (checking information_schema before changing); updates a Review model’s types/casts; and adds an Artisan maintenance command to detect and optionally fix row-level timestamp drift after conversion. Possibly related PRs
Suggested reviewers
🚥 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. 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: 3
🤖 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 @.ai/guidelines/domain/05-timezone-aware-dates.blade.php:
- Line 41: Replace the non-timezone-aware nullable mapping that uses
`$table->nullableTimestamps()` with the timezone-aware timestamps call: locate
the line referencing `nullableTimestamps()` and change it to use
`$table->timestampsTz()` (do not chain `->nullable()` since `timestampsTz()`
already creates nullable `created_at` and `updated_at`).
In `@app/Console/Commands/FixPostSwitchTimestampsCommand.php`:
- Around line 203-213: The row-scope path currently updates rows where
created_at >= CUTOFF even when the target timestamp column is NULL, causing
NULL→NULL writes; update the SQL used in both the pre-check count and the
DB::affectingStatement update to add an additional predicate that the target
column is NOT NULL (e.g. AND "%s" IS NOT NULL) when fixScope is 'row' so the
queries that reference $table, $column, $whereCol and use self::CUTOFF skip NULL
targets and avoid unnecessary rewrites/lock contention.
- Around line 200-213: The current WHERE clause in
FixPostSwitchTimestampsCommand (variables $whereCol, $fixScope, CUTOFF) is
non‑idempotent and will reapply "- 3 hours" on reruns; make the operation
idempotent by restricting both the dry-run count query and the UPDATE to only
target timestamps in [CUTOFF, CUTOFF + interval '3 hours') so already-fixed rows
are skipped. Concretely, update the SQL for the SELECT count(*) and the UPDATE
(the queries that use $whereCol and $column and pass self::CUTOFF) to add an
upper bound like "%s < (? + interval '3 hours')" (pass self::CUTOFF for both
params) so the UPDATE/COUNT only touches rows whose timestamp is between CUTOFF
and CUTOFF + 3 hours. Ensure the same condition is used in the dry-run branch
and in the DB::affectingStatement call so reruns are safe.
🪄 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: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 92c0c4d1-38ee-4535-a649-9d7df58fbb6b
📒 Files selected for processing (62)
.ai/guidelines/domain/05-timezone-aware-dates.blade.phpapp-modules/activity/database/migrations/2023_01_18_211845_create_messages_table.phpapp-modules/activity/database/migrations/2023_02_10_224951_create_voice_messages_table.phpapp-modules/activity/database/migrations/2026_03_18_000000_create_interactions_table.phpapp-modules/activity/database/migrations/2026_04_17_000001_add_metadata_to_messages_table.phpapp-modules/activity/database/migrations/2026_04_17_000002_create_moderation_events_table.phpapp-modules/activity/database/migrations/2026_04_17_000003_create_activity_reactions_table.phpapp-modules/activity/database/migrations/2026_04_19_222819_add_provider_message_id_to_voice_messages_table.phpapp-modules/activity/database/migrations/2026_04_20_110053_create_message_mentions_and_threads.phpapp-modules/activity/database/migrations/2026_04_20_111057_create_fase3_activity_tables.phpapp-modules/activity/database/migrations/2026_05_09_154113_create_activity_timeline_table.phpapp-modules/activity/database/migrations/2026_05_09_155946_create_activity_post_entries_table.phpapp-modules/activity/database/migrations/2026_06_06_190902_alter_activity_timestamps_to_timestamptz.phpapp-modules/community/database/migrations/2022_12_07_005119_create_meeting_types_table.phpapp-modules/community/database/migrations/2022_12_07_005347_create_meetings_table.phpapp-modules/community/database/migrations/2022_12_07_005627_create_meeting_participants_table.phpapp-modules/community/database/migrations/2023_01_28_183013_create_feedbacks_table.phpapp-modules/community/database/migrations/2023_01_28_202610_create_feedback_reviews_table.phpapp-modules/community/database/migrations/2026_06_06_190906_alter_community_timestamps_to_timestamptz.phpapp-modules/community/src/Feedback/Models/Review.phpapp-modules/economy/database/migrations/2026_03_16_220000_create_wallets_table.phpapp-modules/economy/database/migrations/2026_03_16_220001_create_transactions_table.phpapp-modules/economy/database/migrations/2026_06_06_190907_alter_economy_timestamps_to_timestamptz.phpapp-modules/gamification/database/migrations/2023_01_14_053138_create_characters_table.phpapp-modules/gamification/database/migrations/2023_01_20_193234_create_badges_table.phpapp-modules/gamification/database/migrations/2023_01_22_152940_create_characters_badges_table.phpapp-modules/gamification/database/migrations/2023_01_26_200555_create_seasons_rankings_table.phpapp-modules/gamification/database/migrations/2023_01_30_174411_create_seasons_table.phpapp-modules/gamification/database/migrations/2023_01_31_220125_create_character_levelup_table.phpapp-modules/gamification/database/migrations/2026_06_06_190903_alter_gamification_timestamps_to_timestamptz.phpapp-modules/identity/database/migrations/2014_10_12_000000_create_users_table.phpapp-modules/identity/database/migrations/2014_10_12_100000_create_password_resets_table.phpapp-modules/identity/database/migrations/2023_01_18_210724_create_providers_table.phpapp-modules/identity/database/migrations/2023_01_26_155712_create_user_address_table.phpapp-modules/identity/database/migrations/2023_01_26_193201_create_user_information_table.phpapp-modules/identity/database/migrations/2025_11_02_172528_create_tenants_table.phpapp-modules/identity/database/migrations/2025_11_07_162624_create_providers_tokens_table.phpapp-modules/identity/database/migrations/2025_11_08_161609_create_tenant_users_table.phpapp-modules/identity/database/migrations/2026_03_21_000001_migrate_providers_to_external_identities.phpapp-modules/identity/database/migrations/2026_05_26_001934_add_first_login_at_to_users_table.phpapp-modules/identity/database/migrations/2026_06_06_190901_alter_identity_timestamps_to_timestamptz.phpapp-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200000_create_discord_guilds_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200001_create_discord_channels_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200002_create_discord_roles_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200003_create_discord_members_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200004_create_discord_member_roles_table.phpapp-modules/integration-discord/database/migrations/2026_05_19_200005_create_discord_member_role_history_table.phpapp-modules/integration-discord/database/migrations/2026_06_06_190904_alter_discord_timestamps_to_timestamptz.phpapp-modules/integration-twitch/database/migrations/2026_05_20_000001_create_twitch_event_logs_table.phpapp-modules/integration-twitch/database/migrations/2026_05_22_000001_create_twitch_subscriptions_table.phpapp-modules/integration-twitch/database/migrations/2026_06_06_190905_alter_twitch_timestamps_to_timestamptz.phpapp-modules/profile/database/migrations/2026_05_21_000000_create_user_profiles_table.phpapp-modules/profile/database/migrations/2026_06_06_190908_alter_profile_timestamps_to_timestamptz.phpapp/Console/Commands/FixPostSwitchTimestampsCommand.phpdatabase/migrations/2019_08_19_000000_create_failed_jobs_table.phpdatabase/migrations/2019_12_14_000001_create_personal_access_tokens_table.phpdatabase/migrations/2025_10_30_072218_create_notifications_table.phpdatabase/migrations/2025_11_05_135059_create_media_table.phpdatabase/migrations/2025_12_17_151114_create_telescope_entries_table.phpdatabase/migrations/2026_06_06_190909_alter_main_timestamps_to_timestamptz.phpdatabase/migrations/2026_06_06_191000_drop_orphaned_tmi_cluster_tables.php
- Add IS NOT NULL guard for row-scope UPDATE to skip unnecessary NULL→NULL writes - Remove redundant ->nullable() from guideline mapping (timestampsTz() already creates nullable columns)
Summary
timestampcolumns totimestamptzacross 9 modules using Option D (AT TIME ZONE 'America/Sao_Paulo' + fix post-switch window)maintenance:fix-post-switch-timestampsfor data correction (~190k rows) with progress display, dry-run, and module/table filterstimestampsTz()/timestampTz()/softDeletesTz()/dateTimeTz()for fresh installsContext
After the APP_TIMEZONE switch from
America/Sao_PaulotoUTC(2026-05-20, commit c35082e), the database has mixed-era data: ~3 years stored in SP time and ~2 weeks in UTC. This causes ±3h display bugs.Option D was chosen: interpret all data as SP time during ALTER (fixes 3 years), then UPDATE only the ~2 week post-switch window to subtract 3h.
What changed
9 ALTER migrations (1 per module, idempotent):
AT TIME ZONE 'America/Sao_Paulo'AT TIME ZONE 'UTC'(always were UTC)AT TIME ZONE 'UTC'Artisan command
maintenance:fix-post-switch-timestamps:UPDATE col = col - interval '3 hours'WHERE created_at >= CUTOFFinstead ofWHERE col >= CUTOFFExtras:
tmi_cluster_*tables (removed package)Review.received_atcast from'timestamp'(int) to'datetime'.ai/guidelines/domain/05-timezone-aware-dates.blade.phpProduction procedure
Validation (local prod dump)
timestamp without time zonecolumns remainingReport
https://waifuvault.moe/f/f1f61a41-f995-4043-9c8a-8b544106c995/2026-06-06-timestamp-to-timestamptz-report.html
Test plan
php artisan migrateon local prod dumpphp artisan maintenance:fix-post-switch-timestamps --dry-runphp artisan maintenance:fix-post-switch-timestampsphp artisan migrate:fresh(fresh install creates timestamptz)