Skip to content

feat(db): convert timestamp to timestamptz#314

Merged
gvieira18 merged 6 commits into
4.xfrom
feature/timestamp-to-timestamptz
Jun 7, 2026
Merged

feat(db): convert timestamp to timestamptz#314
gvieira18 merged 6 commits into
4.xfrom
feature/timestamp-to-timestamptz

Conversation

@gvieira18
Copy link
Copy Markdown
Member

@gvieira18 gvieira18 commented Jun 7, 2026

Summary

  • Convert 77 timestamp columns to timestamptz across 9 modules using Option D (AT TIME ZONE 'America/Sao_Paulo' + fix post-switch window)
  • Add Artisan command maintenance:fix-post-switch-timestamps for data correction (~190k rows) with progress display, dry-run, and module/table filters
  • Update 49 original migrations to use timestampsTz()/timestampTz()/softDeletesTz()/dateTimeTz() for fresh installs

Context

After the APP_TIMEZONE switch from America/Sao_Paulo to UTC (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):

  • Category A: pre-switch columns → AT TIME ZONE 'America/Sao_Paulo'
  • Category B: Discord API columns → AT TIME ZONE 'UTC' (always were UTC)
  • Category C: post-switch tables → AT TIME ZONE 'UTC'
  • Category D: already timestamptz → skip

Artisan command maintenance:fix-post-switch-timestamps:

  • Corrects post-switch data: UPDATE col = col - interval '3 hours'
  • Future-dated columns use WHERE created_at >= CUTOFF instead of WHERE col >= CUTOFF
  • Progress display via Laravel Prompts (task/label/line)

Extras:

  • Drop orphaned tmi_cluster_* tables (removed package)
  • Fix Review.received_at cast from 'timestamp' (int) to 'datetime'
  • Add .ai/guidelines/domain/05-timezone-aware-dates.blade.php

Production procedure

php artisan down
# stop Discord bot
php artisan migrate                                    # ~65s (ALTER)
php artisan maintenance:fix-post-switch-timestamps     # ~3s (UPDATE)
# run verification checklist
php artisan up
# restart Discord bot

Validation (local prod dump)

  • 0 timestamp without time zone columns remaining
  • Pre-switch spot-check: SP times show 14h-16h ✓
  • Post-switch spot-check: no +3h shift ✓
  • Discord API: joined_at ↔ created_at ±1s ✓
  • Idempotent: re-running is no-op ✓
  • 620/620 tests passing, pint + phpstan clean

Report

https://waifuvault.moe/f/f1f61a41-f995-4043-9c8a-8b544106c995/2026-06-06-timestamp-to-timestamptz-report.html

Test plan

  • Run php artisan migrate on local prod dump
  • Run php artisan maintenance:fix-post-switch-timestamps --dry-run
  • Run php artisan maintenance:fix-post-switch-timestamps
  • Verify 0 timestamp columns remaining
  • Spot-check pre/post-switch data
  • Run php artisan migrate:fresh (fresh install creates timestamptz)
  • Run full test suite

gvieira18 added 4 commits June 6, 2026 21:05
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
@gvieira18 gvieira18 requested a review from a team June 7, 2026 01:33
@gvieira18 gvieira18 self-assigned this Jun 7, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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

  • he4rt/heartdevs.com#264: Aligns SQL display-timezone usage and parameterized AT TIME ZONE conversions with this PR’s display/query rules.
  • he4rt/heartdevs.com#223: Overlaps changes to activity timeline migration (activity_timeline) affecting timestamp column variants.
  • he4rt/heartdevs.com#274: Related to user_profiles table creation and subsequent timestamp conversion.

Suggested reviewers

  • danielhe4rt
  • Clintonrocha98
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.69% 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 Description is comprehensive and directly related to the changeset, covering the conversion strategy, ALTER migrations, new Artisan command, updated migrations, and validation approach.
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.
Title check ✅ Passed Title accurately reflects the main changeset: converting timestamp columns to timestamptz across multiple database migrations and adding related infrastructure.

✏️ 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.

❤️ Share

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

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3d375ba and b50351e.

📒 Files selected for processing (62)
  • .ai/guidelines/domain/05-timezone-aware-dates.blade.php
  • app-modules/activity/database/migrations/2023_01_18_211845_create_messages_table.php
  • app-modules/activity/database/migrations/2023_02_10_224951_create_voice_messages_table.php
  • app-modules/activity/database/migrations/2026_03_18_000000_create_interactions_table.php
  • app-modules/activity/database/migrations/2026_04_17_000001_add_metadata_to_messages_table.php
  • app-modules/activity/database/migrations/2026_04_17_000002_create_moderation_events_table.php
  • app-modules/activity/database/migrations/2026_04_17_000003_create_activity_reactions_table.php
  • app-modules/activity/database/migrations/2026_04_19_222819_add_provider_message_id_to_voice_messages_table.php
  • app-modules/activity/database/migrations/2026_04_20_110053_create_message_mentions_and_threads.php
  • app-modules/activity/database/migrations/2026_04_20_111057_create_fase3_activity_tables.php
  • app-modules/activity/database/migrations/2026_05_09_154113_create_activity_timeline_table.php
  • app-modules/activity/database/migrations/2026_05_09_155946_create_activity_post_entries_table.php
  • app-modules/activity/database/migrations/2026_06_06_190902_alter_activity_timestamps_to_timestamptz.php
  • app-modules/community/database/migrations/2022_12_07_005119_create_meeting_types_table.php
  • app-modules/community/database/migrations/2022_12_07_005347_create_meetings_table.php
  • app-modules/community/database/migrations/2022_12_07_005627_create_meeting_participants_table.php
  • app-modules/community/database/migrations/2023_01_28_183013_create_feedbacks_table.php
  • app-modules/community/database/migrations/2023_01_28_202610_create_feedback_reviews_table.php
  • app-modules/community/database/migrations/2026_06_06_190906_alter_community_timestamps_to_timestamptz.php
  • app-modules/community/src/Feedback/Models/Review.php
  • app-modules/economy/database/migrations/2026_03_16_220000_create_wallets_table.php
  • app-modules/economy/database/migrations/2026_03_16_220001_create_transactions_table.php
  • app-modules/economy/database/migrations/2026_06_06_190907_alter_economy_timestamps_to_timestamptz.php
  • app-modules/gamification/database/migrations/2023_01_14_053138_create_characters_table.php
  • app-modules/gamification/database/migrations/2023_01_20_193234_create_badges_table.php
  • app-modules/gamification/database/migrations/2023_01_22_152940_create_characters_badges_table.php
  • app-modules/gamification/database/migrations/2023_01_26_200555_create_seasons_rankings_table.php
  • app-modules/gamification/database/migrations/2023_01_30_174411_create_seasons_table.php
  • app-modules/gamification/database/migrations/2023_01_31_220125_create_character_levelup_table.php
  • app-modules/gamification/database/migrations/2026_06_06_190903_alter_gamification_timestamps_to_timestamptz.php
  • app-modules/identity/database/migrations/2014_10_12_000000_create_users_table.php
  • app-modules/identity/database/migrations/2014_10_12_100000_create_password_resets_table.php
  • app-modules/identity/database/migrations/2023_01_18_210724_create_providers_table.php
  • app-modules/identity/database/migrations/2023_01_26_155712_create_user_address_table.php
  • app-modules/identity/database/migrations/2023_01_26_193201_create_user_information_table.php
  • app-modules/identity/database/migrations/2025_11_02_172528_create_tenants_table.php
  • app-modules/identity/database/migrations/2025_11_07_162624_create_providers_tokens_table.php
  • app-modules/identity/database/migrations/2025_11_08_161609_create_tenant_users_table.php
  • app-modules/identity/database/migrations/2026_03_21_000001_migrate_providers_to_external_identities.php
  • app-modules/identity/database/migrations/2026_05_26_001934_add_first_login_at_to_users_table.php
  • app-modules/identity/database/migrations/2026_06_06_190901_alter_identity_timestamps_to_timestamptz.php
  • app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200000_create_discord_guilds_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200001_create_discord_channels_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200002_create_discord_roles_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200003_create_discord_members_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200004_create_discord_member_roles_table.php
  • app-modules/integration-discord/database/migrations/2026_05_19_200005_create_discord_member_role_history_table.php
  • app-modules/integration-discord/database/migrations/2026_06_06_190904_alter_discord_timestamps_to_timestamptz.php
  • app-modules/integration-twitch/database/migrations/2026_05_20_000001_create_twitch_event_logs_table.php
  • app-modules/integration-twitch/database/migrations/2026_05_22_000001_create_twitch_subscriptions_table.php
  • app-modules/integration-twitch/database/migrations/2026_06_06_190905_alter_twitch_timestamps_to_timestamptz.php
  • app-modules/profile/database/migrations/2026_05_21_000000_create_user_profiles_table.php
  • app-modules/profile/database/migrations/2026_06_06_190908_alter_profile_timestamps_to_timestamptz.php
  • app/Console/Commands/FixPostSwitchTimestampsCommand.php
  • database/migrations/2019_08_19_000000_create_failed_jobs_table.php
  • database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
  • database/migrations/2025_10_30_072218_create_notifications_table.php
  • database/migrations/2025_11_05_135059_create_media_table.php
  • database/migrations/2025_12_17_151114_create_telescope_entries_table.php
  • database/migrations/2026_06_06_190909_alter_main_timestamps_to_timestamptz.php
  • database/migrations/2026_06_06_191000_drop_orphaned_tmi_cluster_tables.php

Comment thread .ai/guidelines/domain/05-timezone-aware-dates.blade.php Outdated
Comment thread app/Console/Commands/FixPostSwitchTimestampsCommand.php
Comment thread app/Console/Commands/FixPostSwitchTimestampsCommand.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)
@gvieira18 gvieira18 changed the title Convert timestamp to timestamptz (Option D) feat(db): convert timestamp to timestamptz Jun 7, 2026
@gvieira18 gvieira18 merged commit 8a78def into 4.x Jun 7, 2026
6 checks passed
@gvieira18 gvieira18 deleted the feature/timestamp-to-timestamptz branch June 7, 2026 15:18
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.

3 participants