Qual é a diferença entre DateTime
e Time
classes em Ruby e quais fatores me levariam a escolher uma ou outra?
Qual é a diferença entre DateTime
e Time
classes em Ruby e quais fatores me levariam a escolher uma ou outra?
Respostas:
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_t
valor 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.
[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 Time
nã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 Time
exclusivo quando se trata de datas / horas passadas, atuais ou futuras e só é usada DateTime
quando, 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, Time
agora é 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 Integer
s são usados (vs. Bignum
s (valores fora do Integer
intervalo) ou Rational
s (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, DateTime
não cobre mais uma gama maior de valores potenciais do queTime
.
Além disso, duas restrições anteriormente não mencionadas DateTime
provavelmente 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, DateTime
nã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, DateTime
possui 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 DateTime
si (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.
Time
també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
.
Time
suporte 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 +0000
Considerando TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
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
Time.new(2011, 11, 1, 10, 30)
produz 2011-11-01 10:30:00 +0700
enquanto DateTime.new(2011, 11, 1, 10, 30)
produz Tue, 01 Nov 2011 10:30:00 +0000
.
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.parse
nã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.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
produz Sun, 30 Mar 2014 23:59:59 +0100
, mas Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day
produz 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).
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.
DateTime
o deslocamento que você deu, que era +0100. Você não deu Time
um 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.
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.
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"
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 Time
e TimeDate
deriva do fato de que Time
nã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 DateTime
analisa a cadeia inteira corretamente.
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)