Como converter um carimbo de data / hora unix (segundos desde a época) para Ruby DateTime?

369

Como você converte um carimbo de data / hora Unix (segundos desde a época) para Ruby DateTime?

Tronathan
fonte

Respostas:

354

DateTime.strptimepode lidar com segundos desde a época. O número deve ser convertido em uma sequência:

require 'date'
DateTime.strptime("1318996912",'%s')
steenslag
fonte
4
Isso não lida com segundos fracionários
Dan Sandberg
45
Ele lida com milissegundos com %Qtho.
Mini John
3
Para acompanhar a resposta de @ TheMiniJohn. Parece que Timeé necessário em vez de DateTime. Portanto, use Time.strptime("1318996912345",'%Q').to_fe você verá os milissegundos preservados, enquanto DateTime.strptime("1318996912345",'%Q').to_fnão o preserva.
Skinsell
625

Desculpe, breve momento de falha na sinapse. Aqui está a resposta real.

require 'date'

Time.at(seconds_since_epoch_integer).to_datetime

Breve exemplo (isso leva em consideração o fuso horário do sistema atual):

$ date +%s
1318996912

$ irb

ruby-1.9.2-p180 :001 > require 'date'
 => true 

ruby-1.9.2-p180 :002 > Time.at(1318996912).to_datetime
 => #<DateTime: 2011-10-18T23:01:52-05:00 (13261609807/5400,-5/24,2299161)> 

Atualização adicional (para UTC):

ruby-1.9.2-p180 :003 > Time.at(1318996912).utc.to_datetime
 => #<DateTime: 2011-10-19T04:01:52+00:00 (13261609807/5400,0/1,2299161)>

Atualização recente : comparei as principais soluções neste segmento enquanto trabalhava em um serviço de alta disponibilidade há uma ou duas semanas e fiquei surpreso ao descobrir que Time.at(..)supera DateTime.strptime(..)(atualização: adicionamos mais benchmarks).

# ~ % ruby -v
#  => ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin13.0]

irb(main):038:0> Benchmark.measure do
irb(main):039:1*   ["1318996912", "1318496912"].each do |s|
irb(main):040:2*     DateTime.strptime(s, '%s')
irb(main):041:2>   end
irb(main):042:1> end

=> #<Benchmark ... @real=2.9e-05 ... @total=0.0>

irb(main):044:0> Benchmark.measure do
irb(main):045:1>   [1318996912, 1318496912].each do |i|
irb(main):046:2>     DateTime.strptime(i.to_s, '%s')
irb(main):047:2>   end
irb(main):048:1> end

=> #<Benchmark ... @real=2.0e-05 ... @total=0.0>

irb(main):050:0* Benchmark.measure do
irb(main):051:1*   ["1318996912", "1318496912"].each do |s|
irb(main):052:2*     Time.at(s.to_i).to_datetime
irb(main):053:2>   end
irb(main):054:1> end

=> #<Benchmark ... @real=1.5e-05 ... @total=0.0>

irb(main):056:0* Benchmark.measure do
irb(main):057:1*   [1318996912, 1318496912].each do |i|
irb(main):058:2*     Time.at(i).to_datetime
irb(main):059:2>   end
irb(main):060:1> end

=> #<Benchmark ... @real=2.0e-05 ... @total=0.0>
Adam Eberlin
fonte
11
Obrigado ... A resposta a seguir é um pouco mais sucinta. Encontrei Time.at, mas estava tentando encontrar um equivalente em DateTime.
Tronathan 19/10/11
27
É engraçado, mas Time.at () to_datetime parece mais agradável do que DateTime.strptime () simplesmente por causa de legibilidade ... Pelo menos para mim de qualquer maneira.
tybro0103
34
Não é o mesmo que o anterior, Time.at assume o fuso horário atual, em que DateTime.strptime usa UTC.
precisa saber é o seguinte
5
Não é de surpreender que Time.atsupere DateTime.strptime. O último tem que analisar uma string, que geralmente é muito mais lenta do que receber um número diretamente.
Garra
2
Sua referência não é exatamente um teste, DateTime.strptimepois está criando duas novas Strings a cada iteração, o que é muito caro. Não é apenas a seqüência de analisar como @claw disse
WattsInABox
67

Tratamento de fuso horário

Eu só quero esclarecer, mesmo que isso tenha sido comentado para que futuras pessoas não percam essa distinção muito importante.

DateTime.strptime("1318996912",'%s') # => Wed, 19 Oct 2011 04:01:52 +0000

exibe um valor de retorno em UTC e requer que os segundos sejam uma String e gera um objeto UTC Time, enquanto

Time.at(1318996912) # => 2011-10-19 00:01:52 -0400

exibe um valor de retorno no fuso horário LOCAL, normalmente requer um argumento FixNum, mas o próprio objeto Time ainda está no UTC, mesmo que a exibição não esteja.

Portanto, mesmo que eu tenha passado o mesmo número inteiro para os dois métodos, aparentemente tenho dois resultados diferentes por causa de como o #to_smétodo da classe funciona. No entanto, como o @Eero teve que me lembrar duas vezes:

Time.at(1318996912) == DateTime.strptime("1318996912",'%s') # => true

Uma comparação de igualdade entre os dois valores de retorno ainda retorna true. Novamente, isso ocorre porque os valores são basicamente os mesmos (embora classes diferentes, o #==método cuide disso para você), mas o #to_smétodo imprime cadeias drasticamente diferentes. Embora, se olharmos para as strings, podemos ver que elas são de fato a mesma hora, apenas impressas em fusos horários diferentes.

Método Argumento Esclarecimento

Os documentos também dizem "Se um argumento numérico for fornecido, o resultado será no horário local". o que faz sentido, mas foi um pouco confuso para mim porque eles não dão exemplos de argumentos não inteiros nos documentos. Portanto, para alguns exemplos de argumentos não inteiros:

Time.at("1318996912")
TypeError: can't convert String into an exact number

você não pode usar um argumento String, mas pode usar um argumento Time Time.ate ele retornará o resultado no fuso horário do argumento:

Time.at(Time.new(2007,11,1,15,25,0, "+09:00"))
=> 2007-11-01 15:25:00 +0900

Benchmarks

Após uma discussão com o @AdamEberlin sobre sua resposta, decidi publicar benchmarks ligeiramente alterados para tornar tudo o mais igual possível. Além disso, nunca quero ter que construí-las novamente, para que este seja o melhor lugar para salvá-las.

Time.at (int) .to_datetime ~ 2.8x mais rápido

09:10:58-watsw018:~$ ruby -v
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]
09:11:00-watsw018:~$ irb
irb(main):001:0> require 'benchmark'
=> true
irb(main):002:0> require 'date'
=> true
irb(main):003:0>
irb(main):004:0* format = '%s'
=> "%s"
irb(main):005:0> times = ['1318996912', '1318496913']
=> ["1318996912", "1318496913"]
irb(main):006:0> int_times = times.map(&:to_i)
=> [1318996912, 1318496913]
irb(main):007:0>
irb(main):008:0* datetime_from_strptime = DateTime.strptime(times.first, format)
=> #<DateTime: 2011-10-19T04:01:52+00:00 ((2455854j,14512s,0n),+0s,2299161j)>
irb(main):009:0> datetime_from_time = Time.at(int_times.first).to_datetime
=> #<DateTime: 2011-10-19T00:01:52-04:00 ((2455854j,14512s,0n),-14400s,2299161j)>
irb(main):010:0>
irb(main):011:0* datetime_from_strptime === datetime_from_time
=> true
irb(main):012:0>
irb(main):013:0* Benchmark.measure do
irb(main):014:1*   100_000.times {
irb(main):015:2*     times.each do |i|
irb(main):016:3*       DateTime.strptime(i, format)
irb(main):017:3>     end
irb(main):018:2>   }
irb(main):019:1> end
=> #<Benchmark::Tms:0x00007fbdc18f0d28 @label="", @real=0.8680500000045868, @cstime=0.0, @cutime=0.0, @stime=0.009999999999999998, @utime=0.86, @total=0.87>
irb(main):020:0>
irb(main):021:0* Benchmark.measure do
irb(main):022:1*   100_000.times {
irb(main):023:2*     int_times.each do |i|
irb(main):024:3*       Time.at(i).to_datetime
irb(main):025:3>     end
irb(main):026:2>   }
irb(main):027:1> end
=> #<Benchmark::Tms:0x00007fbdc3108be0 @label="", @real=0.33059399999910966, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.32000000000000006, @total=0.32000000000000006>

**** editado para não ser completamente e totalmente incorreto em todos os sentidos ****

**** benchmarks adicionados ****

WattsInABox
fonte
3
Parecia plausível, e eu já votei (não é possível rescindir agora), mas após uma verificação mais aprofundada, sua reivindicação em relação à UTC é falsa. O objeto DateTime / Time resultante estará no UTC versus local, sim, mas o carimbo de data / hora original é interpretado como estando no UTC nos dois casos! Portanto, o momento é igual, independentemente do método. Experimente Time.at(1318996912) == DateTime.strptime("1318996912",'%s')em um fuso horário não UTC e você verá!
Eero
4
Sinto muito, mas o que você corrigiu ainda está errado! :-) Corra Time.use_zone "Samoa" do Time.at(1318996912) == DateTime.strptime("1318996912",'%s') endpara verificar se os horários são iguais, se não existe um registro de data e hora LOCAL e, em ambos os casos, o registro de data e hora do Unix é interpretado como estando no UTC. Time.at apresenta o objeto Time resultante no fuso horário local e DateTime.strptime apresenta o objeto DateTime resultante no UTC, mas independentemente da apresentação, eles são iguais, pois são o momento equivalente no tempo.
Eero
A declaração que Time.at(1318996912) # => 2011-10-19 00:01:52 -0400exibe um valor de retorno no fuso horário LOCAL não parece ser precisa ... Você pode verificar? Acredito que sua declaração só seria verdade se você usouTime.zone.at(1318996912)
BigRon
Sim, isso parece ser preciso. Minha máquina local está definida como EST e os horários são exibidos em EST.
precisa saber é o seguinte
Você pode fornecer um exemplo em que esse não é o caso @BigRon? Qual fuso horário, versão ruby, etc, não se comporta dessa maneira?
precisa saber é o seguinte
10

Um comando para converter data e hora no formato Unix e depois em string

    DateTime.strptime(Time.now.utc.to_i.to_s,'%s').strftime("%d %m %y")

    Time.now.utc.to_i #Converts time from Unix format
    DateTime.strptime(Time.now.utc.to_i.to_s,'%s') #Converts date and time from unix format to DateTime

finalmente strftime é usado para formatar a data

Exemplo:

    irb(main):034:0> DateTime.strptime("1410321600",'%s').strftime("%d %m %y")
    "10 09 14"
Tejasvi Manmatha
fonte
Uma coisa a observar é que o formato da época não possui um fuso horário, portanto, encadear utc antes de encadear to_i não é necessário Time.now.utc.to_i.
Trevor McCasland 31/01
1

Isso informa a data do número de segundos no futuro a partir do momento em que você executa o código.

time = Time.new + 1000000000 #date in 1 billion seconds

põe (hora)

de acordo com o horário atual, estou respondendo à pergunta impressa 047-05-14 05:16:16 +0000(1 bilhão de segundos no futuro)

ou se você quiser contar bilhões de segundos de um horário específico, está no formato Time.mktime(year, month,date,hours,minutes)

time = Time.mktime(1987,8,18,6,45) + 1000000000

puts ("Eu teria 1 bilhão de segundos em:" + hora)

Sbutterworth
fonte
0

Se você queria apenas uma Data, pode fazer de Date.strptime(invoice.date.to_s, '%s')onde invoice.datevem na forma de um Fixnume depois convertido em um String.

pjammer
fonte
3
Time.at(1500923406).to_date.to_s=>"2017-07-24"
Chloe