Convertir vers / depuis DateHeure et Heure en Ruby

132

Comment convertir entre un objet DateTime et un objet Time dans Ruby?

Lecture seulement
la source
1
Je ne sais pas si cela devrait être une question distincte, mais comment convertir entre une date et une heure?
Andrew Grimm
8
Les réponses acceptées et les mieux notées ne sont plus les plus précises sous les versions modernes de Ruby. Voir les réponses de @theTinMan et de @PatrickMcKenzie ci-dessous.
Phrogz

Réponses:

50

Vous aurez besoin de deux conversions légèrement différentes.

Pour convertir de Time en, DateTimevous pouvez modifier la classe Time comme suit:

require 'date'
class Time
  def to_datetime
    # Convert seconds + microseconds into a fractional number of seconds
    seconds = sec + Rational(usec, 10**6)

    # Convert a UTC offset measured in minutes to one measured in a
    # fraction of a day.
    offset = Rational(utc_offset, 60 * 60 * 24)
    DateTime.new(year, month, day, hour, min, seconds, offset)
  end
end

Des ajustements similaires à Date vous permettront de convertir DateTime en Time .

class Date
  def to_gm_time
    to_time(new_offset, :gm)
  end

  def to_local_time
    to_time(new_offset(DateTime.now.offset-offset), :local)
  end

  private
  def to_time(dest, method)
    #Convert a fraction of a day to a number of microseconds
    usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
    Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min,
              dest.sec, usec)
  end
end

Notez que vous devez choisir entre l'heure locale et l'heure GM / UTC.

Les deux extraits de code ci-dessus sont tirés de O'Reilly's Ruby Cookbook . Leur politique de réutilisation du code le permet.

Gordon Wilson
la source
5
Cela s'arrêtera le 1.9 où DateTime # sec_fraction renvoie le nombre de millisecondes dans une seconde. Pour la version 1.9, vous voulez utiliser: usec = dest.sec_fraction * 10 ** 6
dkubb
185
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)
Anshul
la source
13
+1 Ce n'est peut-être pas le plus efficace en exécution, mais cela fonctionne, c'est concis et c'est très lisible.
Walt Jones
6
Malheureusement, cela ne fonctionne vraiment que lorsqu'il s'agit de l'heure locale. Si vous commencez avec une date / heure ou une heure avec un fuseau horaire différent, la fonction d'analyse sera convertie en fuseau horaire local. Vous perdez essentiellement le fuseau horaire d'origine.
Bernard
6
Depuis ruby ​​1.9.1, DateTime.parse conserve le fuseau horaire. (Je n'ai pas accès aux versions antérieures.) Time.parse ne conserve pas le fuseau horaire, car il représente le time_t standard POSIX, qui, je crois, est une différence entière par rapport à l'époque. Toute conversion en temps doit avoir le même comportement.
anshul
1
Vous avez raison. DateTime.parse fonctionne dans 1.9.1 mais pas Time.parse. Dans tous les cas, il est moins sujet aux erreurs (cohérent) et probablement plus rapide à utiliser DateTime.new (...) et Time.new (..). Voir ma réponse pour un exemple de code.
Bernard
1
Salut @anshul. Je ne veux pas dire que je dis :-). Les informations de fuseau horaire ne sont pas conservées lors de l'utilisation de Time.parse (). C'est facile à tester. Dans votre code ci-dessus, remplacez simplement d = DateTime.now par d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)). tt affichera maintenant la date d convertie dans votre fuseau horaire local. Vous pouvez toujours faire de l'arithmétique de date et tout sauf les informations tz originales sont perdues. Cette information est un contexte pour la date et elle est souvent importante. Voir ici: stackoverflow.com/questions/279769/…
Bernard
63

En tant que mise à jour de l'état de l'écosystème Ruby Date, DateTimeet Timeont maintenant des méthodes pour convertir entre les différentes classes. Utilisation de Ruby 1.9.2+:

pry
[1] pry(main)> ts = 'Jan 1, 2000 12:01:01'
=> "Jan 1, 2000 12:01:01"
[2] pry(main)> require 'time'
=> true
[3] pry(main)> require 'date'
=> true
[4] pry(main)> ds = Date.parse(ts)
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[5] pry(main)> ds.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[6] pry(main)> ds.to_datetime
=> #<DateTime: 2000-01-01T00:00:00+00:00 (4903089/2,0,2299161)>
[7] pry(main)> ds.to_time
=> 2000-01-01 00:00:00 -0700
[8] pry(main)> ds.to_time.class
=> Time
[9] pry(main)> ds.to_datetime.class
=> DateTime
[10] pry(main)> ts = Time.parse(ts)
=> 2000-01-01 12:01:01 -0700
[11] pry(main)> ts.class
=> Time
[12] pry(main)> ts.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[13] pry(main)> ts.to_date.class
=> Date
[14] pry(main)> ts.to_datetime
=> #<DateTime: 2000-01-01T12:01:01-07:00 (211813513261/86400,-7/24,2299161)>
[15] pry(main)> ts.to_datetime.class
=> DateTime
l'homme d'étain
la source
1
DateTime.to_time renvoie un DateTime ... 1.9.3p327 :007 > ts = '2000-01-01 12:01:01 -0700' => "2000-01-01 12:01:01 -0700" 1.9.3p327 :009 > dt = ts.to_datetime => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :010 > dt.to_time => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :011 > dt.to_time.class => DateTime
Jesse Clark
Oups. Je viens de réaliser que c'est un problème Ruby on Rails et non un problème Ruby: stackoverflow.com/questions/11277454/… . Ils ont même déposé un bogue contre cette méthode dans la ligne 2.x et l'ont marqué «ne résoudra pas». Horrible décision à mon humble avis. Le comportement Rails rompt totalement l'interface Ruby sous-jacente.
Jesse Clark
12

Malheureusement, le DateTime.to_time, Time.to_datetimeetTime.parse fonctions ne conservent pas les informations de fuseau horaire. Tout est converti en fuseau horaire local lors de la conversion. L'arithmétique des dates fonctionne toujours, mais vous ne pourrez pas afficher les dates avec leurs fuseaux horaires d'origine. Ces informations contextuelles sont souvent importantes. Par exemple, si je souhaite voir les transactions effectuées pendant les heures de bureau à New York, je préfère probablement les voir affichées dans leur fuseau horaire d'origine, pas dans mon fuseau horaire local en Australie (qui est 12 heures avant New York).

Les méthodes de conversion ci-dessous conservent ces informations tz.

Pour Ruby 1.8, regardez la réponse de Gordon Wilson . Il provient du bon vieux livre de recettes Ruby fiable.

Pour Ruby 1.9, c'est un peu plus facile.

require 'date'

# Create a date in some foreign time zone (middle of the Atlantic)
d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))
puts d

# Convert DateTime to Time, keeping the original timezone
t = Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)
puts t

# Convert Time to DateTime, keeping the original timezone
d = DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, Rational(t.gmt_offset / 3600, 24))
puts d

Cela imprime ce qui suit

2010-01-01T10:00:00-02:00
2010-01-01 10:00:00 -0200
2010-01-01T10:00:00-02:00

Les informations DateTime d'origine complètes, y compris le fuseau horaire, sont conservées.

Bernard
la source
2
Le temps est compliqué, mais il n'y a aucune excuse pour ne pas fournir de conversion intégrée entre différentes classes de temps intégrées. Vous pouvez lancer une RangeException si vous essayez d'obtenir un time_t UNIX pour 4713 BC (bien qu'une valeur négative BigNum serait plus agréable), mais fournissez au moins une méthode pour cela.
Mark Reed
1
Time#to_datetimesemble préserver tz pour moi:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz
Le décalage UTC @Phrogz n'est pas la même chose qu'un fuseau horaire. L'un est constant, l'autre peut changer à différents moments de l'année pour l'heure d'été. DateTime n'a pas de zone, il ignore l'heure d'été. Le temps le respecte, mais uniquement dans le TZ «local» (environnement système).
Andrew Vit
1

Amélioration de la solution Gordon Wilson, voici mon essai:

def to_time
  #Convert a fraction of a day to a number of microseconds
  usec = (sec_fraction * 60 * 60 * 24 * (10**6)).to_i
  t = Time.gm(year, month, day, hour, min, sec, usec)
  t - offset.abs.div(SECONDS_IN_DAY)
end

Vous obtiendrez la même heure en UTC, perdant le fuseau horaire (malheureusement)

Aussi, si vous avez ruby ​​1.9, essayez simplement la to_timeméthode

Mildred
la source
0

Lors de ces conversions, il convient de prendre en compte le comportement des fuseaux horaires lors de la conversion d'un objet à l'autre. J'ai trouvé quelques bonnes notes et des exemples dans ce stackoverflow poste .

matou
la source