Plugin WordPress com fix de timezone (UTC)

Plugin com datas deslocadas 3h em cenários de cron. Regra única: UTC como fonte da verdade, conversão só no display. Zero tickets pós-deploy.

Cliente
Plataforma de pós-graduação médica
Setor
Educação
Categoria
Papel
Plugin developer e debug
Duração
2025
Stack
PHP 8.2WordPress Plugin APIDateTimeImmutable
S01

Contexto

O que é: fix de bug de timezone em plugin WordPress que integrava plataforma educacional externa (Lyceum) ao site institucional. Datas de turmas apareciam deslocadas 3 horas para alguns usuários, derrubando matrículas. Solução: UTC como single source of truth, conversão só no display layer com DateTimeImmutable.

Contexto: cascata de inconsistência típica de plugins que tratam data como string. Usuários em horário-padrão viam uma data; cron rodando em UTC persistia outra; página renderizada com date() do PHP mostrava uma terceira.

S02

Problema

O plugin armazenava datas no timezone do servidor (America/Sao_Paulo) e exibia com date() do PHP, que usa o timezone atual do runtime. Em operações batch rodando em UTC via cron, as datas saíam erradas. Cascata de inconsistência.

S03

Abordagem

Regra única: armazenar tudo em UTC, converter no display layer. DateTime nunca string. Parse e format sempre explícitos.

S04

Execução

Criada classe dedicada a horários. Toda leitura/escrita passa por ela. Testes unitários simples cobrindo edge cases (DST, virada de dia, virada de mês).

phpinc/class-course-schedule.php
<?php
declare( strict_types=1 );

final class Course_Schedule {
    private const DISPLAY_TZ = 'America/Sao_Paulo';

    public function save( string $local_datetime, string $tz = self::DISPLAY_TZ ): string {
        $local = new DateTimeImmutable( $local_datetime, new DateTimeZone( $tz ) );
        return $local
            ->setTimezone( new DateTimeZone( 'UTC' ) )
            ->format( DATE_ATOM );
    }

    public function display( string $utc_datetime ): string {
        $utc = new DateTimeImmutable( $utc_datetime, new DateTimeZone( 'UTC' ) );
        return $utc
            ->setTimezone( new DateTimeZone( self::DISPLAY_TZ ) )
            ->format( 'd/m/Y \à\s H:i' );
    }
}
UTC como single source of truth. Conversão só no display.
S05

Resultados

100%
Datas corretas em produção
Em todos os cenários de cron e request
0
Tickets de suporte sobre horário
Após o deploy
S06

Output reutilizável

Padrão "UTC as single source of truth, convert at display" documentado para o time. Classe portável para outros plugins internos.