O que é o timestamp?
Provavelmente você já ouviu falar em "data em milissegundos" ou algo assim. Esse é o tal do timestamp, vamos entender o que ele realmente é?
Provavelmente você já teve que lidar com datas e esbarrou em valores como 1556322834
ou 1556322834401
. Talvez tenham te falado que guardar esses valores é melhor que guardar a data, e você meio que acreditou, afinal, “o código funciona”. Mas o que são esses números?
Esses números possuem vários nomes: Unix timestamps, Unix Time, ou simplesmente timestamps (que é o nome que usaremos a partir de agora). Um timestamp basicamente representa um instante único, um ponto específico na linha do tempo, e seu valor corresponde a uma determinada quantidade de tempo decorrida a partir de um instante inicial.
Esse instante inicial (o “instante zero”) é chamado de Unix Epoch, cujo valor é 1970-01-01T00:00Z
(1 de janeiro de 1970, à meia-noite, em UTC 1). E o timestamp geralmente tem seu valor em segundos ou milissegundos, podendo ser um número positivo (para instantes que ocorrem depois do Unix Epoch) ou negativo (para instantes anteriores ao Unix Epoch).
O timestamp 1556322834
, por exemplo, representa um instante ocorrido 1.556.322.834 segundos depois do Unix Epoch, que corresponde a 2019-04-26T23:53:54Z
(26 de abril de 2019, às 23:53:54 em UTC).
Um detalhe importante é que o timestamp representa um único instante, que é o mesmo no mundo todo. Muitas linguagens e APIs possuem funções para retornar “a data atual”, mas retornam o valor do timestamp. E qualquer computador do mundo que rodasse uma dessas funções, naquele exato instante, obteria o mesmo valor (1556322834
)2.
O detalhe é que um mesmo valor de timestamp corresponde a uma data e hora diferente, dependendo do fuso-horário. O timestamp 1556322834
, por exemplo, corresponde às seguintes datas e horários:
Data e hora | Fuso horário |
---|---|
26/04/2019, às 20:53:54 | São Paulo |
26/04/2019, às 16:53:54 | Los Angeles |
27/04/2019, às 08:53:54 | Tóquio |
Este é um conceito importantíssimo: o timestamp 1556322834
corresponde a todas as datas e horas acima. O instante é o mesmo (1.556.322.834 segundos depois do Unix Epoch), o que muda é apenas a data e hora local, de acordo com o fuso-horário que você usa como referência.
Por isso, só faz sentido converter um timestamp para uma data e hora (e vice-versa) se você estiver usando um fuso-horário específico. Muitas linguagens possuem funções que fazem essas conversões sem pedir por um fuso-horário, mas no fundo elas usam algum predefinido (geralmente o default que está configurado no ambiente em que o código roda). Algumas permitem que você mude ou configure o fuso-horário, mas nem sempre isso é possível.
A seguir seguem alguns exemplos básicos de manipulação do timestamp em algumas linguagens. Se quiser, pode usar os links abaixo para ir direto para a linguagem de sua preferência:
Java
Se você estiver usando o Java >= 8, use a API java.time
. Para representar um timestamp, você pode usar a classe java.time.Instant
:
// timestamp correspondente ao instante atual
Instant agora = Instant.now();
// timestamp em segundos - 2019-04-26T23:53:54Z
Instant instant = Instant.ofEpochSecond(1556322834);
// timestamp em milissegundos - 2019-04-26T23:53:54.483Z
Instant instant = Instant.ofEpochMilli(1556322834483L);
// timestamp em segundos + nanossegundos - 2019-04-26T23:53:54.123456789Z
Instant instant = Instant.ofEpochSecond(1556322834, 123456789);
O método now()
retorna um Instant
contendo o timestamp atual. As classes da API java.time
possuem precisão de nanossegundos (9 casas decimais na fração de segundos), mas o método now()
usa o “melhor relógio disponível no sistema”. No Java 8, o “melhor relógio disponível” é o método System.currentTimeMillis()
, portanto Instant.now()
retornará um timestamp com precisão de milissegundos (mesmo que a JVM esteja rodando em uma máquina cujo relógio tenha precisão maior). A partir do Java 9 este método de fato usa a mesma precisão que o relógio do sistema possui.
Mas caso você precise somente do valor numérico do timestamp atual e nada mais, basta usar System.currentTimeMillis()
, que retorna esse valor em milissegundos.
Repare que também há métodos para criar um Instant
a partir de um timestamp em segundos ou milissegundos, além de uma opção para setar também o valor dos nanossegundos (mas independente da forma usada, internamente são mantidos dois valores: a quantidade de segundos e os nanossegundos). Ao imprimir um Instant
(com System.out.println
ou com sua API de log favorita), sempre é mostrado a data e hora correspondente em UTC, no formato ISO 8601 (ex: 2019-04-26T23:53:54.483Z
).
Para converter um Instant
para um fuso-horário específico, basta usar um java.time.ZoneId
, passando um nome de timezone válido:
Instant instant = Instant.ofEpochMilli(1556322834483L);
// 2019-04-26T20:53:54.483-03:00[America/Sao_Paulo]
System.out.println(instant.atZone(ZoneId.of("America/Sao_Paulo")));
// 2019-04-27T08:53:54.483+09:00[Asia/Tokyo]
System.out.println(instant.atZone(ZoneId.of("Asia/Tokyo")));
Os nomes America/Sao_Paulo
e Asia/Tokyo
são definidos pela IANA (órgão responsável pelo banco de informações de fusos horários que o Java e muitos outros sistemas, APIs e linguagens usam). Para saber todos os timezones disponíveis, use o método ZoneId.getAvailableZoneIds()
.
O método atZone
retorna um java.time.ZonedDateTime
, que possui uma data, hora e timezone (portanto, representa uma data e hora em um fuso horário específico).
Para converter uma data (somente o dia, mês e ano) para um timestamp, é preciso definir um horário e um timezone. Lembre-se que o timestamp representa um instante único, um ponto na linha do tempo. Tendo somente o dia, não é possível obter um único valor de timestamp, já que um dia possui várias horas e portanto corresponde a vários instantes diferentes. Tendo o dia e horário, também não é possível obter o timestamp, já que uma data e hora pode corresponder a um instante diferente em cada parte do mundo (por exemplo, 26 de abril de 2019 às 22h ocorreu em um instante diferente em cada parte do mundo).
A API java.time
é feita de modo que te “obriga” a fornecer estas informações. Ou seja, se eu tenho um java.time.LocalDate
(uma classe que possui somente o dia, mês e ano) e quero transformá-la em um Instant
, eu devo fornecer o horário e o timezone:
LocalDate data = LocalDate.of(2019, 4, 26); // 26 de abril de 2019
Instant instant = data
.atTime(10, 30) // 10:30 da manhã
.atZone(ZoneId.of("America/Sao_Paulo")) // setar o timezone
.toInstant(); // obter o Instant
Dependendo do horário e timezone escolhido, o valor do Instant
será diferente. Por um lado, é um pouco trabalhoso setar o horário e timezone, mas por outro lado a API te força a pensar da maneira correta com relação aos conceitos de datas, horas e timestamps, além de permitir que você tenha um controle maior sobre os valores usados (algo que muitas APIs não permitem, por exemplo, pois usam valores predefinidos e nem sempre permitem que você mude).
Para Java <= 7, existe a API legada: java.util.Date
e java.util.Calendar
.
Date
, apesar do nome, não é uma data (um dia, mês e ano específicos). Na verdade, esta classe representa um timestamp. O que pode confundir é que, ao imprimir um Date
, ele converte o timestamp para o timezone default da JVM e mostra a data e hora correspondente. Exemplo:
Date data = new Date(1556322834483L);
TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"));
System.out.println(data.getTime() + "=" + data);
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(data.getTime() + "=" + data);
Eu uso TimeZone.setDefault
para mudar o timezone default da JVM, e em seguida imprimo o valor de getTime()
(que retorna o timestamp) e o próprio Date
(que usará o timestamp default para converter o timestamp para uma data e hora). O resultado é:
1556322834483=Fri Apr 26 20:53:54 BRT 2019
1556322834483=Sat Apr 27 08:53:54 JST 2019
Repare que o valor do timestamp é o mesmo, mas a data e hora não. O valor do Date
(o timestamp) não é alterado, mas quando este é impresso, o timestamp é convertido para o timezone default que está setado no momento. Isso é algo que confunde muita gente: o Date
só possui o valor do timestamp, nada mais. Qualquer outro valor (dia, mês, ano , hora, minuto, segundo) é derivado do timestamp, levando em conta o timezone default da JVM. Mas o Date
em si não possui tais valores.
Para converter uma data específica (dia, mês e ano) em um timestamp, basta usar um Calendar
:
// 26 de abril de 2019, 10:30
Calendar cal = Calendar.getInstance();
cal.set(2019, Calendar.APRIL, 26, 10, 30, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.setTimeZone(TimeZone.getTimeZone("America/Sao_Paulo"));
long timestamp = cal.getTimeInMillis(); // 1556285400000
No exemplo acima foram setados o horário e o timezone, mas a API não nos obriga a fazer isso. Calendar.getInstance()
cria uma instância contendo a data e hora atual no timezone default da JVM, e se você só mudar o dia, mês e ano, os outros campos permanecem os mesmos, e isso faz com que o valor do timestamp retornado seja diferente.
C#
Em C# existe o struct DateTime
, mas ele não usa Unix timestamps. Na verdade ele usa um epoch diferente, ou seja, o instante zero não é o Unix Epoch, e sim 0001-01-01T00:00Z
(1 de janeiro do ano 1, à meia-noite em UTC). E a unidade de medida usada é chamada de tick, que equivale a 100 nanossegundos (ou 0,0000001 segundos). Por isso há vários construtores que recebem a quantidade de ticks e criam o DateTime
correspondente.
Para trabalhar com Unix timestamps, existe o struct DateTimeOffset
, que possui o método FromUnixTimeSeconds
, que recebe o timestamp em segundos. Também existe o método FromUnixTimeMilliseconds
que recebe o timestamp em milissegundos:
DateTimeOffset d = DateTimeOffset.FromUnixTimeMilliseconds(1556322834483);
Console.WriteLine(d); // 4/26/19 11:53:54 PM +00:00
Se quiser, também pode usar d.DateTime
para obter o DateTime
correspondente. E caso queira transformar uma data específica em um timestamp, basta fazer o processo inverso, criando um DateTimeOffset
a partir de um DateTime
:
DateTime dt = new DateTime(2019, 4, 26, 10, 30, 0);
DateTimeOffset d = new DateTimeOffset(dt);
Console.WriteLine(d.ToUnixTimeSeconds()); // 1556285400
No exemplo acima o retorno foi 1556285400
(que corresponde a 26/04/2019 às 10:30 no Horário de Brasília), pois o valor de dt.Kind
acima é Unspecified
, e neste caso o DateTimeOffset
usa o timezone configurado no sistema (e o meu sistema está usando o Horário de Brasília). Para mais detalhes e opções, veja este link.
E para obter o timestamp atual (somente o valor numérico e nada mais):
Console.WriteLine(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
Console.WriteLine(((DateTimeOffset) DateTime.UtcNow).ToUnixTimeSeconds());
Python
Em Python você pode usar o módulo datetime
, que possui o método fromtimestamp
para criar uma data a partir de um timestamp:
from datetime import datetime
d = datetime.fromtimestamp(1556322834.483)
print(d) # 2019-04-26 20:53:54.483000
O valor do timestamp deve estar em segundos, mas o método fromtimestamp
aceita valores com frações de segundos (no exemplo acima, este valor é .483
, ou seja, 483 milissegundos). O limite da API é microssegundos (6 casas decimais), e valores com mais que 6 casas decimais são arredondados (ex: datetime.fromtimestamp(1556322834.483199999)
resulta em 2019-04-26 20:53:54.483200
).
O detalhe é que por default é usado o timezone do sistema para converter o timestamp para uma data e hora. No meu caso, o timezone da minha máquina é America/Sao_Paulo
, mas rodando esse mesmo código no Ideone.com, o resultado foi 2019-04-26 23:53:54.483000
(pois o timezone do servidor deles é UTC).
Ou seja, dependendo da configuração do ambiente no qual o código está rodando, você pode obter uma data e hora diferentes. Para não depender desta configuração, você precisa usar um timezone específico.
Até o Python 3.8 uma alternativa é usar o módulo pytz
(disponível no PyPI):
# Até Python 3.8
from pytz import timezone # pip install pytz
from datetime import datetime
# obter a data e hora em um timezone específico
d = datetime.fromtimestamp(1556322834.483199999, tz=timezone('America/Sao_Paulo'))
print(d) # 2019-04-26 20:53:54.483200-03:00
# obter o timestamp a partir de uma data/hora e timezone
d = timezone('America/Sao_Paulo').localize(datetime(2019, 4, 26, 10, 30, 0, 0))
print(d.timestamp()) # 1556285400.0
E a partir do Python 3.9 você pode usar o módulo nativo zoneinfo
:
# A partir do Python 3.9, não precisa mais do pytz
from zoneinfo import ZoneInfo
from datetime import datetime
# obter a data e hora em um timezone específico
d = datetime.fromtimestamp(1556322834.483199999, tz=ZoneInfo('America/Sao_Paulo'))
print(d) # 2019-04-26 20:53:54.483200-03:00
# obter o timestamp a partir de uma data/hora e timezone
d = datetime(2019, 4, 26, 10, 30, 0, 0, tzinfo=ZoneInfo('America/Sao_Paulo'))
print(d.timestamp()) # 1556285400.0
Repare que agora, ao imprimir o datetime
, também é mostrado o offset -03:00
(que é a diferença em relação a UTC, que o timezone America/Sao_Paulo
usa naquele instante específico). No código acima também há um exemplo para converter uma data e hora específicas em um timestamp (lembrando que o timezone utilizado faz com que o valor do timestamp seja diferente, já que a mesma data e hora acontece em instantes diferentes em cada parte do mundo). E se você usar apenas o construtor de datetime
(sem usar nenhum timezone), o retorno de timestamp()
usará o timezone do ambiente no qual o código está rodando (e portanto pode variar).
Usar um timezone é importante para que o código não fique dependente da configuração de timezone do ambiente no qual o código roda - a menos que este seja o comportamento desejado, claro. Mas se quiser que o timestamp corresponda a uma data e hora em um timezone específico, é melhor usar o pytz
(ou zoneinfo
, se estiver usando Python >= 3.9) As informações de timezones mudam o tempo todo (há épocas em que há horário de verão e o offset muda, entre outros detalhes explicados neste link), e tentar manter isso manualmente é inviável. O pytz
é atualizado de acordo com as versões do TZDB da IANA (o banco de informações de fusos-horários que várias linguagens e sistemas usam) e basta você usar o timezone correto (como America/Sao_Paulo
, Europe/London
, etc), que o pytz
se encarrega de verificar qual a data e hora correspondentes. Já o módulo zoneinfo
puxa essas informações do sistema operacional, e caso estas não estejam disponíveis, ele pega do pacote tzdata
. Na documentação há mais detalhes sobre isso.
E caso você precise somente do timestamp atual (o valor numérico e nada mais), pode usar o método time
:
import time
# valor do timestamp atual em segundos (e contendo as frações de segundos)
print(time.time())
PHP
Em PHP você pode usar a classe DateTime
. Para obter uma instância a partir de um timestamp, você pode fazer de duas maneiras:
// criar um DateTime correspondente ao timestamp 1556322834
$d = DateTime::createFromFormat('U', 1556322834);
// ou
$d = new DateTime('@'. 1556322834);
Em ambos os casos, o timestamp 1556322834 está em segundos, e a data e hora resultante estará em UTC (e neste caso, não adianta mudar o timezone default com date_default_timezone_set
, pois este é ignorado quando um timestamp é usado). Se você fizer um var_dump($d)
, a saída será:
object(DateTime)#10 (3) {
["date"]=>
string(26) "2019-04-26 23:53:54.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
O offset +00:00
indica que de fato o DateTime
está em UTC. Se quiser que ela esteja em um timezone específico, basta passar um DateTimeZone
como parâmetro:
$d = DateTime::createFromFormat('U', 1556322834);
$d->setTimezone(new DateTimeZone('America/Sao_Paulo'));
// ou
$d = new DateTime('@'. 1556322834);
$d->setTimezone(new DateTimeZone('America/Sao_Paulo'));
Um detalhe chato é que no construtor, o timestamp exige que se tenha o @
antes (veja o campo “Unix Timestamp” na documentação) Em ambos os casos, a saída será:
object(DateTime)#10 (3) {
["date"]=>
string(26) "2019-04-26 20:53:54.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(17) "America/Sao_Paulo"
}
Ao ajustar o timezone, os valores de data e hora também são alterados de acordo com as regras do timezone utilizado. Um detalhe interessante é que usando createFromFormat
também é possível usar valores com frações de segundos (limitado a 6 casas decimais):
# timestamp correspondente a 1556322834 segundos e 123456 microssegundos
$d = DateTime::createFromFormat('U.u', '1556322834.123456');
Para obter o valor do timestamp a partir de uma data, hora e timezone específicos, também é simples:
$d = new DateTime();
$d->setTimezone(new DateTimeZone('America/Sao_Paulo'));
$d->setDate(2019, 4, 26); // 26 de abril de 2019
$d->setTime(10, 30); // 10:30 da manhã
echo $d->getTimestamp(); // 1556285400
// ou
$d = new DateTime('2019-04-26T10:30', new DateTimeZone('America/Sao_Paulo'));
echo $d->getTimestamp(); // 1556285400
// ou ainda
echo strtotime('2019-04-26T10:30 America/Sao_Paulo'); // 1556285400
Atenção para chamar setTimezone
antes de setDate
e setTime
. Se você mudar a data e hora primeiro, as alterações serão feitas levando em conta o timezone default. Depois, ao mudar o timezone, a data e hora serão ajustadas de acordo com o novo timezone, que não necessariamente serão os mesmos que você setou.
Por fim, se você precisa apenas do valor numérico do timestamp atual, basta usar a função time()
, que retorna o timestamp em segundos.
E a função strtotime
? Bem, ela também funciona (strtotime('now')
retorna o mesmo que time()
), e muitos costumam usar em conjunto com date
:
date_default_timezone_set('America/Sao_Paulo');
echo(date('d/m/Y H:i:s', strtotime('now')));
No caso, strtotime
retorna o timestamp, e date
converte este timestamp para uma data e hora específicas, retornando uma string no formato indicado. No exemplo acima, o retorno é a data e hora atual no formato “dia/mês/ano hora:minuto:segundo” (atenção para o fato de que date
, apesar do nome, retorna uma string - se quiser um objeto que represente uma data, use DateTime
).
O detalhe é que date
usa o timezone default que está setado no momento. Se eu quiser que ela use um timezone específico, devo mudá-lo com date_default_timezone_set
. Só que isso afeta todas as demais funções que usam o timezone default. Já usando a solução anterior com DateTime
, eu mudo somente o timezone das instâncias com as quais estou trabalhando, sem mudar a configuração do PHP.
JavaScript
Em JavaScript você pode usar Date
, passando o timestamp para o construtor. O detalhe é que o valor deve estar em milissegundos:
let d = new Date(1556322834000);
console.log(d);
A data e hora que será mostrada depende do timezone que está configurado no browser (que geralmente é obtido do sistema operacional) e não há muito mais o que fazer para controlar isso. O melhor que dá para fazer é gerar uma string contendo a data e hora correspondentes em um timezone específico:
console.log(d.toLocaleString('pt-BR', { timeZone: 'America/Los_Angeles' }));
console.log(d.toLocaleString('pt-BR', { timeZone: 'Asia/Tokyo' }));
Com isso, é usado o formato correspondente ao locale pt-BR
(que deve estar instalado no sistema, caso contrário será usado o locale default que estiver configurado no browser), e os valores de data e hora são obtidos a partir do timezone indicado. A saída é:
26/04/2019 16:53:54
27/04/2019 08:53:54
Para obter o valor do timestamp a partir de uma data e hora específica, você pode passar os valores diretamente para o construtor, e em seguida usar o método getTime()
, que retorna o timestamp em milissegundos:
let d = new Date(2019, 3, 26, 10, 30);
console.log(d.getTime());
O detalhe é que é usado o timezone do browser, e não há como controlar isso.
Uma opção para controlar o timezone é usar o Moment.js, juntamente como o Moment Timezone. Com isso, você pode escolher o timezone para o qual o timestamp será convertido:
moment.tz(1556322834000, "America/Los_Angeles").format() // 2019-04-26T16:53:54-07:00
moment.tz(1556322834000, "America/Sao_Paulo").format() // 2019-04-26T20:53:54-03:00
Repare que o valor do timestamp está em milissegundos. Também é possível obter o timestamp corresponde a uma data e hora, em um timezone específico:
moment.tz("2019-04-26 10:30", "America/Los_Angeles").valueOf() // 1556299800000
moment.tz("2019-04-26 10:30", "America/Sao_Paulo").valueOf() // 1556285400000
O método valueOf()
retorna o valor do timestamp em milissegundos. Os valores são diferentes porque as 10:30 do dia 26/04/2019 ocorreram em instantes diferentes em Los Angeles e São Paulo.
Se quiser este mesmo valor em segundos, use o método unix()
. E se quiser o valor numérico do timestamp atual, basta fazer new Date().getTime()
.
Para entender melhor o Date
do JavaScript, fiz este post mais detalhado.
Enfim, todas as linguagens possuem algum suporte a datas, horas, timezones e timestamps (algumas melhores, outras piores). Sempre que for converter uma data e hora de/para um timestamp, é importante saber qual o timezone que está sendo utilizado. Como você pôde perceber, esta informação nem sempre está clara: muitas vezes é usado um timezone default (que nem sempre dá para saber facilmente qual é) e algum valor sempre é retornado - e como não dá erro, muitos assumem que “funcionou”, sem sequer conferir se os valores estão corretos.
Não há mágica: um timestamp corresponde a uma data e hora específicas em cada timezone. Se foi retornado um timestamp, é porque as 3 informações (data, hora e timezone) estavam presentes. Se você não forneceu alguma(s) dela(s), então algum valor default foi usado. É comum, por exemplo, APIs que só recebem dia, mês e ano e retornam um timestamp. Nesse caso, algum valor foi usado para o horário (a maioria usa meia-noite) e para o timezone (o default que está configurado “no sistema”). Em alguns casos este comportamento é documentado, e dependendo da API, isso pode ser configurado ou alterado programaticamente. Sempre leia a documentação e confira os valores retornados. Não aceite qualquer data só porque “funcionou”.
-
Lembrando que “meia-noite em UTC” não significa “meia-noite” em todos os lugares. Cada fuso-horário do mundo possui um horário diferente com relação a UTC (o Horário de Brasília, por exemplo, é 3 horas a menos em relação a UTC). ↩
-
Algumas linguagens e APIs retornam o valor em segundos, enquanto outras retornam o valor em milissegundos (ou em microssegundos, ou até mesmo nanossegundos - sempre leia a documentação para saber qual a precisão máxima suportada). Mas a ideia geral é a mesma: quaisquer computadores do mundo - assumindo que estão configurados corretamente - rodando ao mesmo tempo, obtêm o mesmo valor do timestamp para a “data atual”. ↩