Quelle est la différence entre Ruby DateTime
et les Time
cours et quels facteurs me feraient choisir l'un ou l'autre?
Quelle est la différence entre Ruby DateTime
et les Time
cours et quels facteurs me feraient choisir l'un ou l'autre?
Réponses:
Les nouvelles versions 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, alors essayez de vous en tenir à ce que vos bibliothèques attendent. Par exemple, ActiveRecord préfère DateTime.
Dans les versions antérieures à Ruby 1.9 et sur de nombreux systèmes, le temps est représenté comme une valeur signée 32 bits décrivant le nombre de secondes depuis le 1er janvier 1970 UTC, un wrapper fin autour d'une time_t
valeur standard POSIX et est délimité:
Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901
Les versions plus récentes de Ruby sont capables de gérer des valeurs plus importantes sans produire d'erreurs.
DateTime est une approche basée sur un calendrier où 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 de wrapper autour des champs DATETIME standard SQL. Ceux-ci contiennent des dates arbitraires et peuvent représenter presque n'importe quel moment dans le temps car la plage d'expression est généralement très large.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
Il est donc rassurant que DateTime puisse gérer les articles de blog d'Aristote.
Au moment d'en choisir un, les différences sont quelque peu subjectives maintenant. Historiquement, DateTime a fourni de meilleures options pour le manipuler à la manière d'un calendrier, mais bon nombre de ces méthodes ont également été portées sur Time, au moins dans l'environnement Rails.
[Modifier juillet 2018]
Tout ce qui suit est toujours vrai dans Ruby 2.5.1. De la documentation de référence :
DateTime ne prend en compte aucune seconde intercalaire, ne suit aucune règle d'heure d'été.
Ce qui n'a pas été noté dans ce fil avant est l'un des rares avantages de DateTime
: il est conscient des réformes du calendrier alors qu'il Time
ne l'est pas:
[…] La classe de temps de Ruby met en œuvre un calendrier grégorien proleptique et n'a aucun concept de réforme du calendrier […].
La documentation de référence se termine par la recommandation à utiliser Time
lorsqu'il s'agit exclusivement de dates / heures proches du passé, actuelles ou futures et à n'utiliser que DateTime
lorsque, par exemple, l'anniversaire de Shakespeare doit être converti avec précision: (non souligné dans l'original)
Alors, quand devez-vous utiliser DateTime dans Ruby et quand devez-vous utiliser Time? Vous voudrez certainement utiliser Time, car votre application traite probablement des dates et heures actuelles. Cependant, si vous avez besoin de traiter des dates et des heures dans un contexte historique, vous voudrez utiliser DateTime […]. Si vous devez également gérer les fuseaux horaires, alors bonne chance - gardez à l'esprit que vous aurez probablement à faire face à l'heure solaire locale, car ce n'est qu'au 19e siècle que l'introduction des chemins de fer a rendu nécessaire l'heure standard. et éventuellement des fuseaux horaires.
[/ Modifier juillet 2018]
Depuis ruby 2.0, la plupart des informations des autres réponses sont obsolètes.
En particulier, il Time
est désormais pratiquement non lié. Il peut se trouver à plus ou moins 63 bits 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
La seule conséquence de l'utilisation de valeurs plus élevées devrait être la performance, qui est meilleure lorsque Integer
s est utilisé (vs Bignum
s (valeurs en dehors de la Integer
plage) ou Rational
s (lorsque les nanosecondes sont suivies)):
Depuis Ruby 1.9.2, l'implémentation de Time utilise un entier signé de 63 bits, Bignum ou Rational. L'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 la nanoseconde), le temps fonctionne plus lentement que lorsque l'entier est utilisé. ( http://www.ruby-doc.org/core-2.1.0/Time.html )
En d'autres termes, pour autant que je sache, DateTime
ne couvre plus un plus large éventail de valeurs potentielles queTime
.
En outre, deux restrictions précédemment non mentionnées de DateTime
devraient probablement être notées:
DateTime ne prend en compte aucune seconde, ne suit aucune règle d'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 aucun concept 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_time.to_i
=> 1341100823
Pour que l'exemple ci-dessus fonctionne Time
, le système d'exploitation doit prendre en charge les secondes intercalaires et les informations de fuseau horaire doivent être définies correctement, par exemple via TZ=right/UTC irb
(sur de nombreux systèmes Unix).
Deuxièmement, DateTime
a une compréhension très limitée des fuseaux horaires et en particulier n'a pas de concept d'heure d'été . Il gère à peu près 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 causer des problèmes lorsque les heures sont entrées en DST puis converties en un fuseau horaire non DST sans garder une trace des décalages corrects en dehors de DateTime
lui (de nombreux systèmes d'exploitation peuvent en fait déjà s'en occuper pour vous).
Dans l'ensemble, je dirais que c'est aujourd'hui Time
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 Time, il est compté en secondes, mais lorsque vous ajoutez un nombre à un DateTime, il est compté en jours.
Time
n'a pas non plus de notion de seconde intercalaire, donc ce n'est pas différent de DateTime
. Je ne sais pas où vous avez exécuté vos exemples, mais j'ai essayé Time.new(2012,6,30,23,59,60,0)
dans différentes versions de Ruby, de 2.0 à 2.7, et j'ai toujours réussi 2012-07-01 00:00:00 +0000
.
Time
charge ou non des secondes intercalaires dépend de la configuration de votre système d'exploitation et de votre fuseau horaire. Par exemple: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-06-30 23:59:60 +0000
alors que TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
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 de Ruby: les deux classes / bibliothèques ont été créées différemment par différentes personnes à différents moments. 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 veulent pas s'appuyer sur les API existantes, donc ils créent simplement une nouvelle classe - pour l'utilisateur final, il n'y a aucune raison évidente que les deux existent.
Cela est vrai pour les bibliothèques de logiciels en général: la raison pour laquelle un code ou une API est souvent la manière dont il se révèle être historique plutôt que logique.
La tentation est de commencer par DateTime car il semble plus générique. Date ... et heure, non? Faux. L'heure fait également mieux les dates et peut en fait analyser les fuseaux horaires là où DateTime ne le peut pas. Il fonctionne également mieux.
J'ai fini par utiliser Time partout.
Cependant, pour être sûr, j'ai tendance à autoriser les arguments DateTime à être passés dans mes API Timey, et à convertir. Aussi, si je sais que les deux ont la méthode qui m'intéresse, j'accepte non plus, 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
Time.new(2011, 11, 1, 10, 30)
produit 2011-11-01 10:30:00 +0700
tout en DateTime.new(2011, 11, 1, 10, 30)
produit Tue, 01 Nov 2011 10:30:00 +0000
.
J'ai trouvé que des choses comme l'analyse et le calcul du début / fin d'une journée dans différents fuseaux horaires sont plus faciles à faire avec DateTime, en supposant que vous utilisez les extensions ActiveSupport .
Dans mon cas, je devais calculer la fin de la journée dans le fuseau horaire d'un utilisateur (arbitraire) en fonction de l'heure locale de l'utilisateur que j'ai 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 de la même manière 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 que ce n'est Time.zone.parse
pas le cas 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 certainement plus facile d'utiliser DateTime.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
produit Sun, 30 Mar 2014 23:59:59 +0100
, mais Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day
produit Sun, 30 Mar 2014 23:59:59 CEST +02:00
(CET = + 01: 00, CEST = + 02: 00 - notez que le décalage a changé). Mais pour ce faire, vous avez besoin de plus d'informations sur le fuseau horaire de l'utilisateur (non seulement le décalage, mais également l'utilisation du gain de temps).
Time.zone.parse
est très utile lors de l'analyse des temps avec différentes zones - cela vous oblige à réfléchir à la zone que vous devez utiliser. Parfois, Time.find_zone fonctionne encore mieux.
DateTime
a analysé le décalage que vous lui avez donné, qui était de +0100. Vous n'avez pas donné de Time
décalage, mais un fuseau horaire ("CET" ne décrit pas un décalage, c'est le nom d'un fuseau horaire). Les fuseaux horaires peuvent avoir différents décalages tout au long de l'année, mais un décalage est un décalage et il est toujours le même.
Considérez comment ils gèrent les fuseaux horaires différemment avec les 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.
Il semble que dans certains cas le comportement soit 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"
Date
(sans surprise) analyse uniquement les dates, et lorsque a Date
est converti en Time
, il utilise toujours minuit dans le fuseau horaire local comme heure. La différence entre Time
et TimeDate
provient du fait que Time
ne comprend pas la BST, ce qui est surprenant, étant donné que les fuseaux horaires sont généralement traités plus correctement par Time
(en ce qui concerne l'heure d'été, par exemple). Donc, dans ce cas, DateTime
analyse uniquement la chaîne entière correctement.
En plus de la réponse de Niels Ganser, vous pourriez considérer cet argument:
Notez que le Ruby Style Guide énonce clairement une position à ce sujet:
Pas de date / heure
N'utilisez DateTime que si vous devez tenir compte de la réforme du calendrier historique - et si vous le faites, spécifiez explicitement l'argument de début pour indiquer clairement vos intentions.
# 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)