knowledge-base/records/plugin-dev/KB-PLUGIN-025-mcprotocol-date-timezone-handling.md
Rodolpho Lopes 200cd6c2ce kb: KB-PLUGIN-031 runbook de workflow dev/deploy de plugins + commit dos registros 021-030 pendentes
- Novo runbook KB-PLUGIN-031: nascimento do plugin ate validacao E2E no
  GLPI dev (scaffold, Forgejo local, deploy CT100, console, bootstrap
  Kernel para testes CLI). Validado de ponta a ponta com o assetinherit.
- Registros KB-PLUGIN-021..030 existiam apenas no disco (drift) e foram
  incluidos no versionamento; index.json sincronizado via kb-fix.
- Ignora lixo AppleDouble/.DS_Store do macOS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:59:47 +00:00

5.2 KiB

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-025 mcprotocol — Comportamento de timezone em datas sem hora explícita plugin-dev
mcp
tools
validation
timezone
gotcha
active low 2026-05-26 2026-05-26
glpi-11
mcprotocol-plugin
KB-PLUGIN-022
KB-PLUGIN-023

mcprotocol — Comportamento de timezone em datas sem hora explícita

Resumo

InputValidation::normalizeDate aceita YYYY-MM-DD e expande para YYYY-MM-DD 00:00:00 sem informação de timezone. O PHP do container GLPI está em date.timezone=UTC, então o valor enviado ao MariaDB é tratado como UTC e gravado como tal em colunas timestamp.

Para usuários sem TZ pessoal configurado (caso padrão do GLPI), esse comportamento é transparente — a UI exibe a data exatamente como recebida. Para usuários com TZ regional configurado (ex: America/Sao_Paulo), a data aparece deslocada nas horas correspondentes (-03:00 → "24/05 21:00" em vez de "25/05 00:00").

Detalhamento técnico

Cadeia de TZ no ambiente atual:

Camada TZ
Container app (glpi11-app) — clock -03 (São Paulo)
PHP (date.timezone) UTC
Container MariaDB (glpi11-mariadb) — clock -03
MariaDB session (@@session.time_zone) SYSTEM (= -03)
GLPI config (glpi_configs.timezone) 0 (UTC default)
Usuário glpi.timezone NULL (sem override)

Quando normalizeDate('2026-05-25') retorna 2026-05-25 00:00:00:

  1. PHP envia a string para o ORM do GLPI
  2. ORM passa adiante para MariaDB
  3. MariaDB com sessão em SYSTEM interpretaria a string como horário local — mas o GLPI executa SET time_zone='+00:00' ao conectar (verificado em Glpi\System\Diagnostic), então a sessão fica UTC
  4. Valor gravado: 2026-05-25 00:00:00 UTC

Confirmado por probe:

SET @@session.time_zone='+00:00';
SELECT plan_start_date FROM glpi_projecttasks WHERE id=33;
-- 2026-05-25 00:00:00  ← correto

REST API v2 retorna com sufixo explícito:

"plan_start_date": "2026-05-25T00:00:00+00:00"

Gotcha — Usuários com TZ regional

Cenário problemático:

  1. Admin cria usuário "joao" e seta joao.timezone = 'America/Sao_Paulo' no perfil
  2. LLM cria tarefa via plugin: plan_start_date: "2026-05-25"
  3. Banco grava: 2026-05-25 00:00:00 UTC
  4. UI do João renderiza convertendo pra SP: 24/05/2026 21:00:00

O João vê dia 24 em vez de dia 25. Confusão garantida.

Mitigações

Curto prazo — documentação

Adicionar nota na descrição do schema dos campos plan_start_date/plan_end_date:

"Datas sem hora são interpretadas como 00:00 UTC. Para usuários em fusos regionais, recomenda-se enviar com hora explícita no fuso de interesse, ex: '2026-05-25 03:00:00' para meia-noite em São Paulo (UTC-3)."

Médio prazo — normalização inteligente

Estender InputValidation::normalizeDate para aceitar um TZ contextual e, quando data vier sem hora, expandir como meia-noite naquele TZ convertido para UTC:

public static function normalizeDate(
    string $value,
    string $field,
    array &$errors,
    ?string $tz = null  // novo parâmetro opcional
): ?string {
    $tz = $tz ?? self::resolveContextTz();
    if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
        $dt = new \DateTimeImmutable("$value 00:00:00", new \DateTimeZone($tz));
        return $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
    }
    // ... resto igual
}

private static function resolveContextTz(): string {
    // Ordem: user preference → glpi config → UTC
    $userTz = $_SESSION['glpi_tz'] ?? null;
    if ($userTz) return $userTz;
    global $CFG_GLPI;
    return $CFG_GLPI['timezone'] ?? 'UTC';
}

Assim a data "2026-05-25" enviada por um LLM operando em nome de um usuário com TZ=SP resultaria em 2026-05-25 03:00:00 UTC, que renderiza corretamente como 25/05/2026 00:00 em SP.

Longo prazo — schema MCP com TZ embutido

Para datas com hora, padronizar input em ISO 8601 com TZ explícito: "2026-05-25T00:00:00-03:00" ou "2026-05-25T00:00:00Z".

normalizeDate aceitaria também esse formato e converteria para UTC.

Diagnóstico de campo

Comando para auditar o estado real vs. UI:

# 1. Confirma TZ do user atual
SELECT name, timezone FROM glpi_users WHERE name = 'X';

# 2. Lê data forçando UTC (valor real armazenado)
SET @@session.time_zone='+00:00';
SELECT plan_start_date FROM <tabela> WHERE id=N;

# 3. Lê data no TZ do user (o que a UI vai mostrar)
SET @@session.time_zone='America/Sao_Paulo';
SELECT plan_start_date FROM <tabela> WHERE id=N;

Status

  • Comportamento atual: correto para o caso padrão (sem TZ regional)
  • Não há bug — é uma gap de UX prevista da extensão de schema
  • Mitigação curto prazo: documentação no schema
  • Mitigação ideal: TZ contextual no normalizeDate (backlog do plugin)

Lições

  1. timestamp no MariaDB sempre armazena em UTC, independente da sessão
  2. Strings de data sem TZ explícito dependem do @@session.time_zone para interpretação
  3. GLPI já normaliza a sessão para UTC ao conectar — bom comportamento
  4. Mas o input do plugin precisa de TZ contextual para ser semanticamente correto