No Elasticsearch todos os campos do tipo date são sempre armazenados em UTC.

Além disso, quando usamos o Kibana para visualizar os dados no Elasticsearch, e estamos em uma timezone diferente de UTC, o Kibana por padrão irá converter a string de data em UTC para a timezone padrão do navegador.

Quando indexamos documentos onde os campos do tipo date não foram convertidos para UTC e não possuem informação sobre a timezone na qual foram gerados, podemos causar uma confusão na hora de visualizar esses dados, já que uma string de data não UTC será interpretada como estando em UTC pelo Elasticsearch e convertida para a timezone do navegador pelo Kibana.

Para evitar esse problema devemos ajustar a string de data original durante a ingestão, convertendo para UTC ou informando a timezone original da string de data.

String de data sem timezone

Como exemplo vamos considerar que temos uma string de data com as seguintes características.

  • foi gerada na timezone UTC -3.
  • não possui informação sobre a timezone na qual foi gerada.

Nesse caso a string de data 2023-11-19T10:30:00 na timezone UTC -3 corresponde a string de data 2023-11-19T13:30:00 em UTC, um offset de -3 horas.

Se indexarmos essa string de data sem informar esse offset, ela será indexada como já estando em UTC e ao visualizarmos esse documento no Kibana, em um navegador na timezone UTC -3, a string de data original irá sofrer outro offset de -3 horas, sendo exibida como Nov 19, 2023 @ 07:30:00.000.

Para exemplificar essa situação usaramos o seguinte template:

PUT _index_template/timezone-example
{
  "index_patterns": ["timezone-example"],
  "template": {
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "dateString": {
          "type": "keyword"
        },
        "message": {
          "type": "text"
        }
      }
    }
  }
}

E o seguinte documento:

POST timezone-example/_doc/
{
  "@timestamp": "2023-11-19T10:30:00",
  "dateString": "2023-11-19T10:30:00",
  "message": "date string example"
}

Nesse caso o campo @timestamp por ser do tipo date será convertido pelo Kibana que está na timezone UTC-3, mas o campo dateString por ser do tipo keyword será tratado como uma string e não será convertido.

timezone-error

Usando agora um documento com a string de data convertida para UTC, não teremos esse problema no Kibana.

POST timezone-example/_doc/
{
  "@timestamp": "2023-11-19T13:30:00",
  "dateString": "2023-11-19T10:30:00",
  "message": "date string example"
}

timezone-fixed

String de data com timezone

Quando temos a informação da timezone na String de data podemos verificar que esse problema não ocorre, já que o Elasticsearch converte a data corretamente para UTC.

POST timezone-example/_doc/
{
  "@timestamp": "2023-11-19T10:30:00-03:00",
  "dateString": "2023-11-19T10:30:00-03:00",
  "message": "date string example with timezone"
}

timezone-fixed

Informando a timezone da string de data

A melhor forma de resolver esse problema é informar a timezone diretamente na string de data, mas nem sempre isso possível, nesses casos precisamos informar ao Elasticsearch que a string de data possui um offset de timezone e como isso será feito depende de como a ingestão é feita.

Ingestão via Logstash

Quando utilizamos o Logstash podemos usar o filtro date com a opção timezone, indicando que a string de data está em uma timezone diferente de UTC.

filter {
  date {
    match => ["dateString", "yyyy-MM-dd'T'HH:mm:ss]
    target => "@timestamp"
    timezone => "America/Sao_Paulo
  }
}

O filtro date acima irá fazer o parse do campo dateString e validar se o padrão corresponde ao especificado e considerar que a string de data foi gerada na timezone America/Sao_Paulo, em caso de sucesso o valor convertido para UTC será armazenado no campo @timestamp.

Como exemplo de output temos:

{
    "event" => {
      "original" => "2023-11-19T10:30:00"
    },
    "@version" => "1",
    "@timestamp" => 2023-11-19T13:30:00.000Z,
    "dateString" => "2023-11-19T10:30:00"
}

O valor para a opção timezone precisa estar no formato canônico (America/Sao_Paulo) ou no formato numérico (-0300).

Ingestão via Elasticsearch

Quando estamos enviando os dados diretamente ao Elasticsearch podemos utilizar um Ingest Pipeline com um date processor para informar que a string de data está em uma timezone diferente de UTC.

PUT _ingest/pipeline/parse-date
{
  "description": "parse date field",
  "processors": [
    {
      "date" : {
        "field" : "dateString",
        "target_field" : "@timestamp",
        "formats" : ["yyyy-MM-dd'T'HH:mm:ss"],
        "timezone": "America/Sao_Paulo"
      }
    }
  ]
}

O pipeline acima funciona da mesma forma que o filtro date do Logstash do exemplo anterior, para utilizar esse pipeline podemos fazer o seguinte request:

POST timezone-example/_doc?pipeline=parse-date
{
  "@timestamp": "2023-11-19T10:30:00",
  "dateString": "2023-11-19T10:30:00",
  "message": "date string with ingest pipeline"
}

timezone-fixed

Podemos também configurar esse ingest pipeline como um final pipeline, fazendo com que ele seja sempre executado para qualquer request para esse índice, fazemos isso alterando o template.

PUT _index_template/timezone-example
{
  "index_patterns": ["timezone-example"],
  "template": {
    "mappings": {
    "properties": {
        "@timestamp": {
          "type": "date"
        },
        "dateString": {
          "type": "keyword"
        },
        "message": {
          "type": "text"
        }
      }
    },
    "settings": {
      "index.final_pipeline": "parse-date"
    }
  }
}

Quando temos uma string de data que foi gerada em uma timezone diferente de UTC, é necessário informarmos de alguma forma o offset em relação a UTC.