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

150 lines
5.2 KiB
Markdown

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