--- id: KB-PLUGIN-025 title: mcprotocol — Comportamento de timezone em datas sem hora explícita domain: plugin-dev tags: - mcp - tools - validation - timezone - gotcha status: active severity: low created_at: 2026-05-26 updated_at: 2026-05-26 applies_to: - glpi-11 - mcprotocol-plugin related_records: - 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 (`devportal_glpi`) — 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: ```sql 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: ```json "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: ```php 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: ```bash # 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 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 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