web-dev-qa-db-fra.com

Différence entre DateTime et Time en Ruby

Quelle est la différence entre les classes DateTime et Time dans Ruby et quels facteurs me poussent à choisir l'une ou l'autre?

204
Tom Lehman

Les versions les plus récentes de Ruby (2.0+) n’ont pas vraiment de différences significatives entre les deux classes. Certaines bibliothèques utiliseront l'une ou l'autre pour des raisons historiques, mais le nouveau code ne doit pas nécessairement être concerné. Il est probablement préférable d’en choisir un pour la cohérence. Essayez donc de vous adapter aux attentes de vos bibliothèques. Par exemple, ActiveRecord préfère DateTime.

Dans les versions antérieures à Ruby 1.9 et sur de nombreux systèmes, Time est représenté par une valeur signée sur 32 bits décrivant le nombre de secondes depuis le 1er janvier 1970 UTC, un wrapper fin autour d'une valeur time_t au standard POSIX.

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

Les nouvelles versions de Ruby sont capables de gérer des valeurs plus grandes sans générer d’erreurs.

DateTime est une approche basée sur un calendrier dans laquelle l'année, le mois, le jour, l'heure, la minute et la seconde sont stockés individuellement. Il s'agit d'une construction Ruby on Rails qui sert d'enveloppe aux champs DATETIME standard SQL. Ceux-ci contiennent des dates arbitraires et peuvent représenter presque n'importe quel moment car la plage d'expression est généralement très grande.

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

Il est donc rassurant de savoir que DateTime peut gérer les articles de blog d’Aristote.

Lors du choix, les différences sont maintenant quelque peu subjectives. Historiquement, DateTime offrait de meilleures options pour le manipuler selon un calendrier, mais bon nombre de ces méthodes ont également été transférées vers Time, du moins dans l'environnement Rails.

168
tadman

[Edit Juillet 2018]

Tous les éléments ci-dessous sont toujours valables dans Ruby 2.5.1. De la documentation de référence :

DateTime ne considère pas les secondes intercalaires, ne suit pas les règles de l'heure d'été.

Ce qui n’a pas été noté auparavant dans ce fil de discussion est l’un des rares avantages de DateTime: il est au courant des réformes du calendrier alors que Time n’est pas:

[…] La classe Time de Ruby met en œuvre un calendrier proleptique grégorien et n'a aucun concept de réforme du calendrier […].

La documentation de référence se termine par la recommandation d'utiliser Time pour traiter exclusivement des dates/heures quasi-antérieures, actuelles ou futures et de n'utiliser DateTime que, par exemple, lorsque l'anniversaire de Shakespeare doit être converti avec précision: (non souligné dans l'original)

Alors, quand devriez-vous utiliser DateTime dans Ruby et quand devriez-vous utiliser Time? Vous souhaiterez presque certainement utiliser Time puisque votre application traite probablement des dates et des heures actuelles. Cependant, si vous devez gérer des dates et des heures dans un contexte historique, vous voudrez utiliser DateTime […]. Si vous devez également gérer les fuseaux horaires, n'hésitez pas à garder à l'esprit que vous aurez probablement à gérer l'heure solaire locale, car ce n'est qu'au 19e siècle que l'introduction des chemins de fer a rendu nécessaire le recours à l'heure normale. et éventuellement les fuseaux horaires.

[/ Edit juillet 2018]

Depuis Ruby 2.0, la plupart des informations des autres réponses sont obsolètes.

En particulier, Time est maintenant pratiquement non lié. Cela peut être plus ou moins que même 63 bits loin de 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

L'utilisation de valeurs plus grandes devrait avoir pour seule conséquence les performances, qui sont meilleures lorsque Integers est utilisé (par rapport à Bignums (valeurs hors de la plage Integer) ou Rationals (lorsque des nanosecondes sont suivies)):

Depuis Ruby 1.9.2, l'implémentation Time utilise un entier signé de 63 bits, Bignum ou Rational. Le nombre entier est un nombre de nanosecondes depuis l’époque qui peut représenter 1823-11-12 à 2116-02-20. Lorsque Bignum ou Rational est utilisé (avant 1823, après 2116, sous nanoseconde), le temps est plus lent que lorsque l'entier est utilisé . ( http://www.Ruby-doc.org/core-2.1.0/Time.html )

Autrement dit, pour autant que je sache, DateTime NE COUVRE PLUS UNE PLAGE DE VALEURS POTENTIELLES PLUS ÉTENDUE QUE Time.

En outre, il convient probablement de noter deux restrictions de DateTime non mentionnées précédemment:

DateTime ne prend pas en compte les secondes, ne suit pas les règles de l'heure d'été . ( http://www.Ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )

Tout d'abord, DateTime n'a pas de notion de secondes intercalaires:

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_i
=> 1341100823

Deuxièmement, DateTime a une compréhension très limitée des fuseaux horaires et en particulier n'a pas de concept d'économie de jour. Il gère les fuseaux horaires comme de simples décalages 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>

Cela peut poser problème lorsque les heures sont entrées en heure DST puis converties en fuseaux horaires autres que l'heure d'été sans garder trace des décalages corrects en dehors de DateTime (de nombreux systèmes d'exploitation peuvent déjà s'en charger pour vous).

Globalement, je dirais que, de nos jours, Time est le meilleur choix pour la plupart des applications.

Notez également une différence importante lors de l'ajout: lorsque vous ajoutez un nombre à un objet Heure, il est compté en secondes, mais lorsque vous ajoutez un nombre à un DateTime, il est compté en jours.

88
Niels Ganser

Obsolète! Voir ci-dessous...

La différence de performance ne peut pas être suffisamment soulignée…

>> Benchmark.bm do |bm|
?>   bm.report('DateTime:') do
?>     n1 = DateTime.now
>>     n2 = DateTime.now
>>     1_000_000.times{ n1 < n2 }
>>   end
>>   bm.report('Time:    ') do
?>     n1 = Time.now
>>     n2 = Time.now
>>     1_000_000.times{ n1 < n2 }
>>   end
>> end
      user     system      total        real
DateTime:  4.980000   0.020000   5.000000 (  5.063963)
Time:      0.330000   0.000000   0.330000 (  0.335913)

Mise à jour (2/2012):

Comme déjà mentionné dans le commentaire, 1.9.3 a considérablement amélioré les performances DateTime:

       user     system      total        real
DateTime:  0.330000   0.000000   0.330000 (  0.333869)
Time:      0.300000   0.000000   0.300000 (  0.306444)
66
Mladen Jablanović

Je pense que la réponse à "Quelle est la différence" est l'une des réponses communes malheureuses à cette question dans les bibliothèques standard Ruby: les deux classes/bibliothèques ont été créées différemment par des personnes différentes à des moments différents. C’est l’une des conséquences malheureuses de la nature communautaire de l’évolution de Ruby par rapport au développement soigneusement planifié de quelque chose comme Java. Les développeurs veulent de nouvelles fonctionnalités mais ne souhaitent pas utiliser les API existantes, ils créent donc simplement une nouvelle classe. Pour l'utilisateur final, il n'existe aucune raison évidente de leur existence.

Ceci est vrai pour les bibliothèques de logiciels en général: souvent, la raison pour laquelle un code ou une API est d'origine est plutôt historique que logique.

La tentation est de commencer par DateTime, car cela semble plus générique. Date ... et heure, non? Faux. De plus, le temps sert mieux les dates et peut en fait analyser les fuseaux horaires, contrairement à DateTime. En outre, il fonctionne mieux.

J'ai fini par utiliser Time partout.

Pour être sûr cependant, j'ai tendance à permettre aux arguments DateTime d'être passés dans mes API Timey, et soit de convertir. Aussi, si je sais que les deux ont la méthode qui m'intéresse, je l'accepte aussi, comme cette méthode que j'ai écrite pour convertir les temps en XML (pour les fichiers 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
43
Rhubarb

J'ai découvert que des tâches telles que l'analyse et le calcul du début/fin de journée dans des fuseaux horaires différents sont plus faciles à faire avec DateTime, en supposant que vous utilisez les extensions ActiveSupport .

Dans mon cas, je devais calculer la fin de journée dans le fuseau horaire d'un utilisateur (de manière arbitraire) en fonction de l'heure locale de l'utilisateur que j'avais reçue sous forme de chaîne, par exemple. "2012-10-10 10:10 +0300"

Avec DateTime, c'est aussi simple que 

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

Essayons maintenant la même chose avec 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.

En fait, Time doit connaître le fuseau horaire avant l'analyse (notez également qu'il s'agit de Time.zone.parse et non de 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

Donc, dans ce cas, il est définitivement plus facile d’utiliser DateTime.

9
Yuriy Kharchenko

Pensez à la façon dont ils gèrent les fuseaux horaires différemment avec des instanciations personnalisées:

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

Cela peut être délicat lors de la création de plages de temps, etc.

4
gr8scott06

Il semble que dans certains cas, le comportement est très différent:

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"

0
Alexey Strizhak