Différence entre DateTime et Time en Ruby

219

Quelle est la différence entre Ruby DateTimeet les Timecours et quels facteurs me feraient choisir l'un ou l'autre?

Tom Lehman
la source
La documentation a une section , expliquant quand l'utiliser.
x-yuri

Réponses:

177

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_tvaleur 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.

tadman
la source
6
Dois-je donc toujours utiliser DateTime?
Tom Lehman
4
Si vous travaillez avec des dates, je dirais utiliser DateTime. Le temps est pratique pour représenter des choses telles que l'heure actuelle de la journée ou des points dans un avenir proche tels que 10.minutes.from_now. Les deux ont beaucoup en commun, bien que, comme indiqué, DateTime puisse représenter une plage de valeurs beaucoup plus large.
tadman
3
Je pense que c'est parce que Ruby passe au Bignum de longueur arbitraire à partir du Fixnum 32 bits lorsqu'il subit un débordement. Les nombres en dehors de cette plage peuvent ne pas être pris en charge par les applications externes. Oui, en 2038, nous sommes fondamentalement foutus jusqu'à ce que nous puissions tous nous mettre d'accord sur un format d'heure 64 bits approprié. Le jury est toujours absent.
tadman
28
Cette réponse est antérieure à 1.9.2. Ignorez tout ce qu'il dit sur les limitations posix de Time et faites votre choix en fonction des API de Time et DateTime.
Ben Nagy
8
Cette réponse est obsolète non? Il est maintenant recommandé d'utiliser TimeSupport :: WithTimeZone de Time ou Rail. Vous n'avez plus besoin d'utiliser DateTime. Il est là pour la compatibilité descendante.
Donato
103

[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 Timene 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 Timelorsqu'il s'agit exclusivement de dates / heures proches du passé, actuelles ou futures et à n'utiliser que DateTimelorsque, 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 Timeest 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 Integers est utilisé (vs Bignums (valeurs en dehors de la Integerplage) ou Rationals (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, DateTimene couvre plus un plus large éventail de valeurs potentielles queTime .

En outre, deux restrictions précédemment non mentionnées de DateTimedevraient 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, DateTimen'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, DateTimea 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 DateTimelui (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 Timele 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.

Niels Ganser
la source
Est-ce toujours vrai en 2018?
Qqwy
2
C'est toujours vrai dans Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime ne considère aucune seconde intercalaire , ne suit aucune règle d'heure d'été." Cependant, notez que DateTime présente des avantages lorsque vous devez gérer des dates / heures de calendrier pré-grégorien. Cela n'a pas été mentionné ici auparavant mais est documenté dans la documentation de référence: "[…] La classe Time de Ruby implémente un calendrier grégorien proleptique et n'a aucun concept de réforme du calendrier […]." Je vais modifier ma réponse pour fournir plus de détails.
Niels Ganser
Timen'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.
michau
@michau La prise en Timecharge 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 +0000alors que TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser
@NielsGanser Super, merci! J'ai suggéré une modification pour clarifier ce point.
michau
44

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
Rhubarbe
la source
8
En outre, Time.new et DateTime.new traitent différemment le fuseau horaire. Je suis sur GMT + 7, donc Time.new(2011, 11, 1, 10, 30)produit 2011-11-01 10:30:00 +0700tout en DateTime.new(2011, 11, 1, 10, 30)produit Tue, 01 Nov 2011 10:30:00 +0000.
Phương Nguyễn
21
Et comme nous le savons tous, le développement soigneusement planifié de Java a abouti à des API logiques exclusivement simples.
pje
@ PhươngNguyễn: Pourriez-vous s'il vous plaît ajouter cela comme réponse afin que je puisse le voter? C'est précisément la raison pour laquelle j'ai décidé de choisir l'heure plutôt que la date et l'heure.
Senseful
@Senseful Désolé, je n'ai pas encore reçu votre message. Les gens donnent déjà quelques votes positifs à mon commentaire, donc je pense que c'est cool ici.
Phương Nguyễn
@ PhươngNguyễn Salut, des idées comment / pourquoi cette différence de fuseaux horaires? d'où prend-elle le décalage?
Joel_Blum
10

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.parsepas 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.

Yuriy Kharchenko
la source
1
En utilisant cela en production maintenant, y a-t-il des inconvénients à la technique?
Alex Moore-Niemi
1
DateTime ne prend pas en compte l'heure d'été. Ainsi, en économisant le changement d'heure, cela ne fonctionnera pas correctement. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_dayproduit Sun, 30 Mar 2014 23:59:59 +0100, mais Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_dayproduit 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).
Petr '' Bubák '' Šedivý
1
Cela peut être évident, mais Time.zone.parseest 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.
prusswan
1
@ Petr''Bubák''Šedivý DateTimea analysé le décalage que vous lui avez donné, qui était de +0100. Vous n'avez pas donné de Timedé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.
Mecki
4

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.

gr8scott06
la source
Semble ne se produire qu'avec du rubis uni (c'est-à-dire sans Rails)
vemv
1

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"

Alexey Strizhak
la source
C'est une observation intéressante, mais il faut expliquer pourquoi cela se produit. Date(sans surprise) analyse uniquement les dates, et lorsque a Dateest converti en Time, il utilise toujours minuit dans le fuseau horaire local comme heure. La différence entre Timeet TimeDateprovient du fait que Timene 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, DateTimeanalyse uniquement la chaîne entière correctement.
michau
1

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)
Paul van Leeuwen
la source