1. Introdução
2. Por que o EPM Server armazena em UTC
O EPM Server utiliza o SQL Server para o armazenamento dos dados de processo em um formato otimizado para séries temporais. Cada Data Object possui as propriedades Valor, Estampa de Tempo e Qualidade — e é essa estampa de tempo que é persistida em UTC.
UTC (Coordinated Universal Time) é o fuso horário de referência a partir do qual todos os demais fusos do mundo são calculados. Padronizar o armazenamento nele traz duas vantagens diretas:
- Continuidade da série temporal. Em formato UTC os dados são contínuos e o horário de verão é desconsiderado. Não existe, na base, a hora que se repete no fim do horário de verão nem a hora que deixa de existir no início — situações que, em armazenamento por horário local, produzem registros duplicados ou lacunas artificiais.
- Consolidação de plantas em fusos diferentes. Como todo dado é gravado na mesma referência, um mesmo EPM Server pode receber dados de sites em fusos distintos e ainda assim comparar, agregar e ordenar as séries corretamente.
Por que isso resolve o problema?
O deslocamento observado não é um erro a ser corrigido na coleta, e sim uma referência de tempo explícita a ser interpretada na apresentação. Ajustar o relógio do servidor ou a interface para “compensar” as 3 horas corrompe a base: o dado passaria a ser gravado como se fosse UTC sem o ser.
3. Como a diferença de 3 horas se forma
A conversão é uma soma simples do deslocamento do fuso. Para o horário oficial de São Paulo e Brasília (UTC-3), o valor armazenado está sempre 3 horas à frente do horário local:
| Momento da coleta (horário local, UTC-3) | Valor armazenado no EPM Server (UTC) |
|---|---|
| 05/08/2026 08:00 | 05/08/2026 11:00 |
| 05/08/2026 14:30 | 05/08/2026 17:30 |
| 05/08/2026 22:00 | 06/08/2026 01:00 |
Repare na última linha: perto do fim do dia, a conversão para UTC também avança a data. Esse é um efeito colateral frequente em consultas que filtram por “dia de hoje” sem declarar o fuso — o período pedido cobre um intervalo diferente do pretendido.
Caminho da estampa de tempo: a gravação é sempre em UTC; a conversão para o horário local depende da ferramenta que apresenta o dado.
4. Como cada ferramenta apresenta a data/hora
Por padrão, as ferramentas clientes do Sistema EPM convertem os dados para o horário local antes de apresentá-los ao analista (gráficos, tabelas e demais visualizações). Portanto, o deslocamento normalmente não aparece para o usuário final — ele fica visível quando se inspeciona o dado armazenado diretamente ou quando se consulta por uma via que não faz a conversão.
| Via de acesso | Tratamento da data/hora |
|---|---|
| EPM Studio | Os dados em UTC são convertidos automaticamente para o horário local da aplicação cliente. |
| EPM Add-in for MS Excel | Idem: conversão automática para o horário local da aplicação cliente. |
| EPM Processor / EPM Web API (Python) | Sem conversão automática. As estampas de tempo retornadas pelas consultas estão sempre em UTC; a conversão para o horário local é responsabilidade do código. |
| EPM Functions a partir do E3 | O fuso utilizado é determinado pelo parâmetro @timeZoneOffset, que deve ser informado conforme o período de horário oficial vigente (por exemplo, -3 para o horário padrão e -2 quando houver horário de verão em vigor). |
É justamente por esse contraste que o mesmo dado parece “certo” no EPM Studio e “adiantado” em um script Python: não há divergência de armazenamento, apenas ferramentas com políticas de apresentação diferentes.
5. Consultas em Python: datas Naive e Aware
Ao consultar o EPM por Python, o ponto crítico é como as datas são informadas nos parâmetros da consulta. O Python trabalha com dois tipos de data/hora:
- Naive — não possui nenhuma informação de localização. É apenas uma data/hora que poderia ser de qualquer lugar do mundo (por exemplo,
15/06/2021 16:30:00). - Aware — possui a informação de localização. Por exemplo,
15/06/2021 16:30:00(Brasil/São Paulo), que se sabe pertencer ao fuso de São Paulo (UTC-3); ou15/06/2021 16:30:00(UTC), que especifica 16:30 em UTC.
A recomendação para as consultas do EPM é que as datas informadas estejam sempre em UTC e Aware. Isso elimina a margem de interpretação dos sistemas, facilita o entendimento do código por outros usuários e o isola de eventuais mudanças de implementação nos produtos do EPM ou no próprio Python.
Opção A — partir do horário local da máquina
import
datetimetoday = datetime.date.today()
# As datas abaixo foram criadas NAIVE, sem a informação de localização.
startTime = datetime.datetime(today.year, today.month, today.day,5 ,0)
endTime = datetime.datetime(today.year, today.month, today.day, 19, 0)
# A função astimezone() adiciona o fuso horário da máquina às variáveis,
# transformando-as de NAIVE em AWARE. Caso já fossem AWARE, nada seria alterado.
# O parâmetro datetime.timezone.utc informa que a função também deve converter
# a variável de horário local para UTC.
startTimeUtc = startTime.astimezone(datetime.timezone.utc)
endTimeUtc = endTime.astimezone(datetime.timezone.utc)
# startTimeUtc e endTimeUtc estão prontos para os parâmetros das consultas do EPM
Opção B — declarar explicitamente o fuso de escolha
Preferível quando o horário informado não é o da máquina que executa o código — caso comum em EPM Processor, em que o código roda no servidor mas o período de interesse é o da planta.
import pytz import datetime today = datetime.date.today() # As datas abaixo foram criadas NAIVE, sem a informação de localização. startTime = datetime.datetime(today.year, today.month, today.day, 5, 0) endTime = datetime.datetime(today.year, today.month, today.day, 19, 0) # Neste exemplo, a Data/Hora é referente ao fuso de São Paulo, portanto # transformamos de Naive em Aware, adicionando a localização de São Paulo. startTimeAware = pytz.timezone("America/Sao_Paulo").localize(startTime) endTimeAware = pytz.timezone("America/Sao_Paulo").localize(endTime) # Abaixo, astimezone() não adicionará a localização da máquina, pois a variável # já é Aware. Portanto apenas a conversão para UTC será realizada. startTimeUtc = startTimeAware.astimezone(datetime.timezone.utc) endTimeUtc = endTimeAware.astimezone(datetime.timezone.utc)
Opção C — já criar a data Aware
import datetime from dateutil import tz # Neste exemplo, a data já é criada AWARE, através do parâmetro tzinfo. iniTimeAware = datetime.datetime(2021, 6, 15, 18, 30, 0, 0, tzinfo=tz.gettz('America/Sao_Paulo')) iniTimeUtc = iniTimeAware.astimezone(datetime.timezone.utc)
Convertendo o retorno da consulta
Do outro lado, as estampas de tempo retornadas pelas consultas do EPM sempre estarão em UTC. Para apresentar no fuso brasileiro é preciso converter após a consulta:
import datetime import pytz tzLocal = pytz.timezone("America/Sao_Paulo") # 'ts' é uma estampa de tempo retornada pela consulta do EPM — sempre em UTC. tsLocal = ts.replace(tzinfo=datetime.timezone.utc).astimezone(tzLocal)
6. Onde a estampa de tempo é definida na coleta
Quando a suspeita é de que o valor gravado está errado (e não apenas a forma de apresentá-lo), vale verificar de onde vem a estampa de tempo. As interfaces de comunicação do EPM oferecem a configuração Timestamp com duas opções:
| Opção | Comportamento |
|---|---|
| Data Source | A estampa de tempo dos valores dos tags é mantida conforme vem do equipamento ou da fonte de dados original, preservando a informação temporal fornecida pela própria fonte. |
| Local Machine | A interface adiciona a estampa de tempo aos valores antes de enviá-los ao EPM Server. Em alguns casos a fonte não coloca estampa de tempo nos dados, tornando necessária a utilização desta opção. |
A escolha depende das características da fonte. Se o equipamento fornece estampa de tempo própria e confiável, Data Source preserva o instante real do evento; se não fornece, Local Machine é a alternativa. Em ambos os casos, a gravação no EPM Server continua sendo feita em UTC.
7. Observações e boas práticas
- Não “corrija” o deslocamento na coleta. Alterar o fuso do servidor, o relógio da máquina ou a interface para compensar as 3 horas grava um horário local se passando por UTC. O resultado é uma base internamente inconsistente, com dados antigos e novos em referências diferentes — e o erro só aparece meses depois, em consultas e relatórios.
- Sempre declare o fuso nas consultas. Datas Naive deixam a interpretação a cargo de quem recebe, e o comportamento pode variar entre a máquina de desenvolvimento e o servidor de produção.
- Cuidado com filtros por “dia” ou “mês”. Como a conversão para UTC pode avançar a data, um filtro de dia civil no horário local corresponde a um intervalo que atravessa dois dias em UTC. Monte o intervalo no fuso local, converta as duas pontas para UTC e só então consulte.
- Períodos que atravessam a troca de horário de verão. Por armazenar em UTC, o EPM mantém a série contínua nesses períodos. Ao usar as EPM Functions a partir do E3, no entanto, o
@timeZoneOffsetprecisa refletir o horário oficial vigente no período consultado — um valor fixo produz um deslocamento de uma hora em parte do intervalo. - É possível alterar a apresentação padrão. A conversão automática para o horário local feita pelas ferramentas clientes é o padrão, mas esse comportamento pode ser alterado — o EPM Add-in for MS Excel, por exemplo, pode ser configurado para realizar as consultas sempre em UTC, o que é a opção mais segura em análises que cruzam fusos ou trocas de horário de verão.
- Ao instanciar datas em código, prefira classes que suportem UTC nativamente, o que permite a conversão para o horário local com o ajuste de horário de verão aplicado automaticamente.
