Diferença entre DateTime e Time em Ruby

218

Qual é a diferença entre DateTimee Timeclasses em Ruby e quais fatores me levariam a escolher uma ou outra?

Tom Lehman
fonte
Os documentos têm uma seção , explicando quando usar quais.
x-yuri

Respostas:

177

As versões mais recentes do Ruby (2.0+) realmente não têm diferenças significativas entre as duas classes. Algumas bibliotecas usarão uma ou outra por razões históricas, mas o novo código não precisa necessariamente se preocupar. Escolher um por consistência é provavelmente o melhor, então tente combinar com o que suas bibliotecas esperam. Por exemplo, o ActiveRecord prefere DateTime.

Nas versões anteriores ao Ruby 1.9 e em muitos sistemas, o tempo é representado como um valor assinado de 32 bits que descreve o número de segundos desde 1 de janeiro de 1970 UTC, um invólucro fino em torno de um time_tvalor padrão POSIX e é limitado:

Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901

As versões mais recentes do Ruby são capazes de lidar com valores maiores sem gerar erros.

DateTime é uma abordagem baseada em calendário, onde o ano, mês, dia, hora, minuto e segundo são armazenados individualmente. Esta é uma construção Ruby on Rails que serve como um invólucro em torno dos campos DATETIME padrão do SQL. Eles contêm datas arbitrárias e podem representar praticamente qualquer ponto no tempo, pois o intervalo de expressão é geralmente muito grande.

DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000

Portanto, é reconfortante que o DateTime possa lidar com postagens de Aristóteles.

Ao escolher um, as diferenças são um pouco subjetivas agora. Historicamente, o DateTime oferece melhores opções para manipulá-lo de maneira calendário, mas muitos desses métodos também foram portados para o Time, pelo menos no ambiente do Rails.

tadman
fonte
6
Então, eu devo sempre usar o DateTime?
21430 Tom
4
Se você estiver trabalhando com datas, eu diria que use DateTime. A hora é conveniente para representar coisas como a hora atual do dia ou pontos no futuro próximo, como 10.minutes.from_now. Os dois têm muito em comum, embora, como observado, o DateTime possa representar uma faixa muito maior de valores.
Tadman
3
Acredito que isso ocorre porque o Ruby muda para o Bignum de comprimento arbitrário do Fixnum de 32 bits quando ocorre um estouro. Números fora desse intervalo podem não ser suportados por aplicativos externos. Sim, em 2038 estamos basicamente ferrados até que todos possamos concordar com um formato de tempo de 64 bits adequado. O júri ainda está fora.
Tadman
27
Esta resposta é anterior a 1.9.2. Ignore tudo o que diz sobre as limitações posix de Time e faça sua escolha com base nas APIs de Time e DateTime.
Ben Nagy
8
Esta resposta está desatualizada, certo? Agora é recomendável usar o Time ou o ActiveSupport :: WithTimeZone da Rail. Você não precisa mais usar o DateTime. Está lá para compatibilidade com versões anteriores.
Donato
102

[Editar julho de 2018]

Tudo abaixo ainda é válido no Ruby 2.5.1. A partir da documentação de referência :

O DateTime não considera segundos bissextos, não controla nenhuma regra de horário de verão.

O que não foi observado neste tópico antes é uma das poucas vantagens de DateTime: está ciente das reformas do calendário, enquanto Timenão é:

[…] A classe Time de Ruby implementa um calendário gregoriano pró-séptico e não tem conceito de reforma do calendário […].

A documentação de referência conclui com a recomendação de uso Timeexclusivo quando se trata de datas / horas passadas, atuais ou futuras e só é usada DateTimequando, por exemplo, o aniversário de Shakespeare precisa ser convertido com precisão: (ênfase adicionada)

Então, quando você deve usar o DateTime no Ruby e quando você deve usar o Time? Certamente, você desejará usar o Time, pois seu aplicativo provavelmente está lidando com datas e horários atuais. No entanto, se você precisar lidar com datas e horas em um contexto histórico, precisará usar o DateTime […]. Se você também tiver que lidar com os fusos horários, então boa sorte - lembre-se de que provavelmente estará lidando com as horas solares locais, já que foi somente no século 19 que a introdução das ferrovias exigiu a necessidade do Horário Padrão e, eventualmente, fusos horários.

[/ Editar julho de 2018]

A partir do ruby ​​2.0, a maioria das informações nas outras respostas está desatualizada.

Em particular, Timeagora é praticamente ilimitado. Pode estar a mais ou menos do que 63 bits do Epoch:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> Time.at(2**62-1).utc # within Integer range
=> 146138514283-06-19 07:44:38 UTC
irb(main):003:0> Time.at(2**128).utc # outside of Integer range
=> 10783118943836478994022445751222-08-06 08:03:51 UTC
irb(main):004:0> Time.at(-2**128).utc # outside of Integer range
=> -10783118943836478994022445747283-05-28 15:55:44 UTC

A única consequência do uso de valores maiores deve ser o desempenho, que é melhor quando Integers são usados ​​(vs. Bignums (valores fora do Integerintervalo) ou Rationals (quando os nanossegundos são rastreados)):

Desde o Ruby 1.9.2, a implementação do Time usa um número inteiro de 63 bits assinado, Bignum ou Rational. O número inteiro é um número de nanossegundos desde a época que pode representar 1823-11-12 a 2116-02-20. Quando Bignum ou Rational é usado (antes de 1823, após 2116, em nanossegundos), o Time trabalha mais devagar como quando o número inteiro é usado. ( http://www.ruby-doc.org/core-2.1.0/Time.html )

Em outras palavras, tanto quanto eu entendo, DateTimenão cobre mais uma gama maior de valores potenciais do queTime .

Além disso, duas restrições anteriormente não mencionadas DateTimeprovavelmente devem ser observadas:

O DateTime não considera nenhum segundo, não controla nenhuma regra de horário de verão. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )

Primeiro, DateTimenão tem conceito de segundos bissextos:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.new(2012,6,30,23,59,60,0)
=> 2012-06-30 23:59:60 +0000
irb(main):004:0> dt = t.to_datetime; dt.to_s
=> "2012-06-30T23:59:59+00:00"
irb(main):005:0> t == dt.to_time
=> false
irb(main):006:0> t.to_i
=> 1341100824
irb(main):007:0> dt.to_time.to_i
=> 1341100823

Para que o exemplo acima funcione Time, o sistema operacional precisa suportar segundos bissextos e as informações do fuso horário precisam ser definidas corretamente, por exemplo, através TZ=right/UTC irb(em muitos sistemas Unix).

Segundo, DateTimepossui um entendimento muito limitado dos fusos horários e, em particular , não tem conceito de horário de verão . Ele lida praticamente com fusos horários como deslocamentos simples do UTC + X:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.local(2012,7,1)
=> 2012-07-01 00:00:00 +0200
irb(main):004:0> t.zone
=> "CEST"
irb(main):005:0> t.dst?
=> true
irb(main):006:0> dt = t.to_datetime; dt.to_s
=> "2012-07-01T00:00:00+02:00"
irb(main):007:0> dt.zone
=> "+02:00"
irb(main):008:0> dt.dst?
NoMethodError: undefined method `dst?' for #<DateTime:0x007f34ea6c3cb8>

Isso pode causar problemas quando os horários são inseridos como horário de verão e, em seguida, convertidos em um fuso horário fora do horário de verão sem acompanhar as compensações corretas fora de DateTimesi (muitos sistemas operacionais já podem cuidar disso para você).

No geral, eu diria que hoje em dia Timeé a melhor escolha para a maioria dos aplicativos.

Observe também uma diferença importante na adição: quando você adiciona um número a um objeto Time, ele é contado em segundos, mas quando você adiciona um número a um DateTime, ele é contado em dias.

Niels Ganser
fonte
Isso ainda é verdade em 2018?
Qqwy
2
Ainda é verdade no Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime não considera segundos bissextos, não rastreia nenhuma regra de horário de verão". No entanto, observe que o DateTime possui vantagens quando você precisa lidar com datas / horários do calendário pré-gregoriano. Isso não foi mencionado aqui antes, mas está documentado na documentação de referência: "[...] a classe Time de Ruby implementa um calendário gregoriano pró-séptico e não tem nenhum conceito de reforma do calendário [...]". Vou editar minha resposta para fornecer mais alguns detalhes.
Niels Ganser
Timetambém não tem conceito de segundos bissextos, portanto não é diferente DateTime. Não sei onde você executou seus exemplos, mas tentei Time.new(2012,6,30,23,59,60,0)em diferentes versões do Ruby, de 2.0 a 2.7, e sempre consegui 2012-07-01 00:00:00 +0000.
michau 22/07/19
@michau O Timesuporte ou não aos segundos bissextos depende da configuração do seu SO e fuso horário. Por exemplo: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-06-30 23:59:60 +0000Considerando TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser
@NielsGanser Ótimo, obrigado! Sugeri uma edição para esclarecer esse ponto.
Michau
44

Acho que a resposta para "qual é a diferença" é uma das infelizes respostas comuns a essa pergunta nas bibliotecas padrão do Ruby: as duas classes / bibliotecas foram criadas de maneira diferente por pessoas diferentes em momentos diferentes. É uma das conseqüências infelizes da natureza da comunidade da evolução do Ruby em comparação com o desenvolvimento cuidadosamente planejado de algo como Java. Os desenvolvedores desejam novas funcionalidades, mas não querem adotar as APIs existentes, de modo que apenas criem uma nova classe - para o usuário final, não há razão óbvia para a existência das duas.

Isso é verdade para as bibliotecas de software em geral: geralmente a razão pela qual algum código ou API é o histórico é mais do que lógico.

A tentação é começar com o DateTime porque parece mais genérico. Data ... e Hora, certo? Errado. O tempo também melhora as datas e, de fato, pode analisar fusos horários onde o DateTime não pode. Também tem um desempenho melhor.

Acabei usando o Time em todos os lugares.

Para estar seguro, eu tendem a permitir que os argumentos DateTime sejam transmitidos para minhas APIs do Timey e os convertemos. Além disso, se eu souber que ambos têm o método no qual estou interessado, também aceito, como este método que escrevi para converter tempos em XML (para arquivos XMLTV)

# Will take a date time as a string or as a Time or DateTime object and
# format it appropriately for xmtlv. 
# For example, the 22nd of August, 2006 at 20 past midnight in the British Summertime
# timezone (i.e. GMT plus one hour for DST) gives: "20060822002000 +0100"
def self.format_date_time(date_time)
  if (date_time.respond_to?(:rfc822)) then
    return format_time(date_time)
  else 
    time = Time.parse(date_time.to_s)
    return format_time(time)
  end    
end

# Note must use a Time, not a String, nor a DateTime, nor Date.
# see format_date_time for the more general version
def self.format_time(time)
  # The timezone feature of DateTime doesn't work with parsed times for some reason
  # and the timezone of Time is verbose like "GMT Daylight Saving Time", so the only
  # way I've discovered of getting the timezone in the form "+0100" is to use 
  # Time.rfc822 and look at the last five chars
  return "#{time.strftime( '%Y%m%d%H%M%S' )} #{time.rfc822[-5..-1]}"
end
Ruibarbo
fonte
8
Além disso, Time.new e DateTime.new estão lidando com o fuso horário de maneira diferente. Estou no GMT + 7, então Time.new(2011, 11, 1, 10, 30)produz 2011-11-01 10:30:00 +0700enquanto DateTime.new(2011, 11, 1, 10, 30)produz Tue, 01 Nov 2011 10:30:00 +0000.
Ph Nng Nguyễn 25/10/11
21
E como todos sabemos, o desenvolvimento cuidadosamente planejado de Java resultou em APIs lógicas exclusivamente simples.
Pje 16/10/12
@ PhươngNguyễn: Você pode adicionar isso como resposta para que eu possa votar novamente? Essa é precisamente a razão pela qual decidi escolher Hora em vez de DateTime.
Senseful
@Senseful Desculpe, não recebi sua mensagem até agora. As pessoas já dão votos positivos ao meu comentário, então eu acho legal aqui.
Ph Nng Nguyễn
@ PhươngNguyễn Oi, alguma idéia de como / por que essa diferença de fuso horário? de onde está tirando a compensação?
Joel_Blum 24/07/19
10

Descobri coisas como analisar e calcular o início / fim de um dia em diferentes fusos horários são mais fáceis de fazer com o DateTime, supondo que você esteja usando as extensões do ActiveSupport .

No meu caso, eu precisava calcular o final do dia no fuso horário de um usuário (arbitrário) com base na hora local do usuário que recebi como uma sequência, por exemplo, "2012-10-10 10:10 +0300"

Com o DateTime, é tão simples quanto

irb(main):034:0> DateTime.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 +0300
# it preserved the timezone +0300

Agora vamos tentar da mesma maneira com o Time:

irb(main):035:0> Time.parse('2012-10-10 10:10 +0300').end_of_day
=> 2012-10-10 23:59:59 +0000
# the timezone got changed to the server's default UTC (+0000), 
# which is not what we want to see here.

Na verdade, o tempo precisa conhecer o fuso horário antes de analisar (observe também que Time.zone.parsenão Time.parse):

irb(main):044:0> Time.zone = 'EET'
=> "EET"
irb(main):045:0> Time.zone.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 EEST +03:00

Portanto, nesse caso, é definitivamente mais fácil usar o DateTime.

Yuriy Kharchenko
fonte
1
Usando isso na produção agora, existem desvantagens na técnica?
Alex Moore-Niemi
1
O DateTime não leva em consideração o horário de verão. Assim, ao economizar tempo, não funcionará corretamente. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_dayproduz Sun, 30 Mar 2014 23:59:59 +0100, mas Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_dayproduz Sun, 30 Mar 2014 23:59:59 CEST +02:00(CET = + 01: 00, CEST = + 02: 00 - observe o deslocamento alterado). Mas, para fazer isso, você precisa de mais informações sobre o fuso horário do usuário (não apenas o deslocamento, mas também se a economia de tempo é usada).
Petr '' Bubák '' Šedivý
1
Pode estar afirmando o óbvio, mas Time.zone.parseé muito útil ao analisar horários com zonas diferentes - obriga a pensar em qual zona você deve usar. Às vezes, Time.find_zone funciona ainda melhor.
precisa saber é
1
O @ Petr''Bubák''Šedivý analisou DateTimeo deslocamento que você deu, que era +0100. Você não deu Timeum deslocamento, mas um fuso horário ("CET" não descreve um deslocamento, é o nome de um fuso horário). Os fusos horários podem ter deslocamentos diferentes ao longo do ano, mas um deslocamento é um deslocamento e é sempre o mesmo.
Mecki
4

Considere como eles lidam com fusos horários de maneira diferente com instanciações personalizadas:

irb(main):001:0> Time.new(2016,9,1)
=> 2016-09-01 00:00:00 -0400
irb(main):002:0> DateTime.new(2016,9,1)
=> Thu, 01 Sep 2016 00:00:00 +0000
irb(main):003:0> Time.new(2016,9,1).to_i
=> 1472702400
irb(main):004:0> DateTime.new(2016,9,1).to_i
=> 1472688000

Isso pode ser complicado ao criar intervalos de tempo etc.

gr8scott06
fonte
Só parece acontecer com rubi simples (ou seja, sem Rails)
vemv
1

Parece que em alguns casos o comportamento é muito diferente:

Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s

"2018-06-28 09:00:00 UTC"

Date.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

"2018-06-27 21:00:00 UTC"

DateTime.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

"2018-06-28 11:00:00 UTC"

Alexey Strizhak
fonte
Essa é uma observação interessante, mas deve ser explicada por que isso acontece. Date(sem surpresa) analisa apenas datas e, quando a Dateé convertido Time, sempre usa meia-noite no fuso horário local como a hora. A diferença entre Timee TimeDatederiva do fato de que Timenão entende o BST, o que é surpreendente, dado que os fusos horários geralmente são tratados de maneira mais correta por Time(em relação ao horário de verão, por exemplo). Portanto, neste caso, apenas DateTimeanalisa a cadeia inteira corretamente.
michau 22/07/19
1

Além da resposta de Niels Ganser, você pode considerar este argumento:

Observe que o Ruby Style Guide indica claramente uma posição sobre isso:

No DateTime

Não use DateTime, a menos que precise prestar contas da reforma histórica do calendário - e, se precisar, especifique explicitamente o argumento de início para indicar claramente suas intenções.

# bad - uses DateTime for current time
DateTime.now

# good - uses Time for current time
Time.now

# bad - uses DateTime for modern date
DateTime.iso8601('2016-06-29')

# good - uses Date for modern date
Date.iso8601('2016-06-29')

# good - uses DateTime with start argument for historical date
DateTime.iso8601('1751-04-23', Date::ENGLAND)
Paul van Leeuwen
fonte