PHP

IntlDateFormatter: data por extenso em pt-BR sem array de meses

Toda vez que preciso imprimir “31 de agosto de 2026” aparece de novo aquele array com os doze meses no meio do template. Dá para apagar essa herança: a extensão intl já sabe escrever data em português, e o IntlDateFormatter aceita um pattern seu no lugar dos formatos prontos.

$tz  = new DateTimeZone('America/Sao_Paulo');
$fmt = new IntlDateFormatter(
    'pt_BR',
    IntlDateFormatter::NONE,
    IntlDateFormatter::NONE,
    $tz,
    IntlDateFormatter::GREGORIAN,
    "d 'de' MMMM 'de' y"
);

echo $fmt->format(new DateTimeImmutable('2026-08-31', $tz));
// 31 de agosto de 2026

As aspas simples dentro do pattern marcam texto literal do ICU, por isso o “de” sai como palavra e não como código de campo. MMMM é o mês por extenso, MMM abrevia para “ago.” (com ponto, em pt-BR) e EEEE traz o dia da semana. Os dois NONE no meio existem porque, com pattern explícito, os estilos prontos de data e hora ficam fora do jogo. O mês vem minúsculo: se precisar de “Agosto” no começo da frase, resolva com mb_convert_case() na saída, não trocando o pattern.

A pegadinha do fuso

date_default_timezone_get();  // UTC na maioria dos servidores

$fmt->format(new DateTimeImmutable('2026-08-31'));
// 30 de agosto de 2026  <- um dia a menos

$fmt->format(new DateTimeImmutable('2026-08-31', $tz));
// 31 de agosto de 2026

Data sem hora vira meia-noite no fuso padrão do PHP. Se o servidor roda em UTC e o formatter converte para São Paulo, aquelas três horas a menos jogam o resultado para o dia anterior. Passe o mesmo DateTimeZone na criação e na formatação e o problema some. Vale o mesmo cuidado ao formatar um post_date vindo do MySQL, que é hora local sem fuso nenhum embutido.

Antes de subir isso para produção rode php -m | grep intl. Sem a extensão instalada a classe nem existe e o deploy quebra com Error: Class “IntlDateFormatter” not found.

Post Recentes

Creation of dynamic property: deprecated no PHP 8.3 com WordPress 6.9

debug.log crescendo em gigabytes com avisos de propriedade dinâmica. Veja como isolar o plugin culpado, corrigir a classe e silenciar…

4 horas atrás

Plugin do Magento 2 não executa: método final, private ou objeto novo

O before ou around declarado no di.xml nunca dispara. Confira o Interceptor em generated e descarte as três causas que…

9 horas atrás

Criar swapfile no Ubuntu: parar o OOM killer de matar o MySQL

VPS de 2 GB onde o mysqld morre de madrugada e o site volta com erro de conexão. Como confirmar…

2 dias atrás

Grupos nomeados no preg_match: pare de contar índice de $matches

Regex com cinco parênteses e ninguém lembra se a data é $matches[2] ou $matches[3]. Nomeie os grupos e leia o…

3 dias atrás

Token de integração da API do Magento 2: gerar e usar no header Bearer

Erro The consumer isn't authorized to access resources ao chamar a REST do Magento 2.4.8? Ative a integração, libere os…

3 dias atrás

Rate limit no wp-login.php com nginx: barrar força bruta antes do PHP

Bots batendo centenas de POST por minuto no login derrubam a CPU. Configure limit_req_zone no nginx e devolva 429 antes…

4 dias atrás