150 lines
5.2 KiB
Markdown
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 (`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 <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
|