RubyでのDateTimeとTimeの違い


回答:


177

Rubyの新しいバージョン(2.0以降)では、2つのクラスに大きな違いはありません。一部のライブラリは歴史的な理由でどちらか一方を使用しますが、新しいコードは必ずしも考慮する必要はありません。一貫性を保つために1つを選択するのがおそらく最善です。ライブラリが期待することを試してみてください。たとえば、ActiveRecordはDateTimeを優先します。

Ruby 1.9より前のバージョンと多くのシステムでは、Timeは1970年1月1日からの秒数を表す32ビットの符号付き値として表され、POSIX標準time_t値の薄いラッパーであり、制限されています。

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

Rubyの新しいバージョンでは、エラーを発生させることなく、より大きな値を処理できます。

DateTimeは、年、月、日、時、分、秒が個別に保存されるカレンダーベースのアプローチです。これは、SQL標準のDATETIMEフィールドのラッパーとして機能するRuby on Railsコンストラクトです。これらは任意の日付を含み、式の範囲は通常非常に大きいため、ほぼすべての時点を表すことができます。

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

つまり、DateTimeがアリストテレスからのブログ投稿を処理できることは安心です。

いずれかを選択すると、違いはやや主観的になります。歴史的に、DateTimeはカレンダー形式で操作するためのより良いオプションを提供してきましたが、これらのメソッドの多くは、少なくともRails環境内でもTimeに移植されています。


6
だから私はいつもDateTimeを使うべきですか?
トムリーマン

4
日付で作業している場合は、DateTimeを使用します。時間は、現在の時刻、または10.minutes.from_nowなどの近い将来のポイントなどを表すのに便利です。前述のように、DateTimeははるかに広い範囲の値を表すことができますが、2つには多くの共通点があります。
tadman 2009

3
Rubyでオーバーフローが発生すると、32ビットのFixnumから任意の長さのBignumに切り替わるからだと思います。この範囲外の数値は、外部アプリケーションでサポートされていない場合があります。はい、2038年には、適切な64ビット時間形式に全員が同意できるようになるまで、基本的にねじ込まれています。陪審はまだ出ていません。
tadman

27
この回答は1.9.2より前のものです。Timeのposix制限について述べていることはすべて無視し、TimeとDateTimeのAPIに基づいて選択してください。
Ben Nagy

8
この答えは時代遅れですよね?現在は、TimeまたはRailのActiveSupport :: WithTimeZoneを使用することをお勧めします。DateTimeを使用する必要がなくなりました。下位互換性のためにあります。
Donato

102

[2018年7月を編集]

以下のすべてがRuby 2.5.1でも当てはまります。参照ドキュメントから:

DateTimeはうるう秒を考慮せず、夏時間のルールを追跡しません。

何このスレッドに記載されていないと、前のいくつかの利点の一つであるDateTime:一方で、それはカレンダーの改革を認識しているTimeではありません。

[…] RubyのTimeクラスは、前向きなグレゴリオ暦を実装し、カレンダーの改革という概念はありません[…]。

参照ドキュメントは、Time過去、現在、または将来の日付/時刻のみを処理するDateTime場合に使用し、たとえばシェイクスピアの誕生日を正確に変換する必要がある場合にのみ使用することを推奨しています:(強調を追加)

では、RubyでいつDateTimeを使用し、いつTimeを使用する必要があるのでしょうか。アプリはおそらく現在の日付と時刻を扱っているため、ほぼ間違いなくTimeを使用する必要があります。ただし、履歴のコンテキストで日付と時刻を処理する必要がある場合は、DateTime […] を使用する必要があります。タイムゾーンも処理する必要がある場合は、幸運を祈る-鉄道の導入により標準時の必要性が必要となったのは19世紀まででなかったため、おそらく現地の太陽時を扱うことになることを覚えておいてください。そして最終的にはタイムゾーン。

[/ 2018年7月を編集]

Ruby 2.0以降、他の回答のほとんどの情報は古くなっています。

特に、Time今は実質的に拘束力がない。エポックから63ビットも離れていても、離れていてもかまいません。

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

より大きな値を使用した場合の唯一の結果はパフォーマンスであるはずです。これは、Integersを使用した場合(v Bignums(Integer範囲外の値)またはRationals(ナノ秒を追跡した場合))の方が優れています。

Ruby 1.9.2以降、Timeの実装では、符号付き63ビット整数、BignumまたはRationalを使用しています。整数は、エポック以降のナノ秒数で、1823-11-12〜2116-02-20を表すことができます。BignumまたはRationalが使用されている場合(1823より前、2116より後、ナノ秒未満)、整数が使用されている場合よりも時間がかかります。(http://www.ruby-doc.org/core-2.1.0/Time.html

言い換えれば、私が理解している限りでDateTimeは、は潜在的な値のより広い範囲をもはやカバーしていませんTime

さらに、前述の2つの制限にDateTimeおそらく注意する必要があります。

DateTimeはうるう秒を考慮せず、夏時間のルールを追跡しません。(http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime

まず、DateTimeうるう秒の概念はありません。

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

上記の例がで動作するTimeためには、OSがうるう秒をサポートし、タイムゾーン情報がTZ=right/UTC irb(たとえば多くのUnixシステムで)正しく設定されている必要があります。

第二に、DateTimeタイムゾーンの理解が非常に限られており、特に夏時間の概念がありません。タイムゾーンを単純な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>

これにより、時刻がDSTとして入力され、DateTimeそれ自体の外部で正しいオフセットを追跡せずにDST以外のタイムゾーンに変換されると、問題が発生する可能性があります(多くのオペレーティングシステムが実際に既にこの処理を行っている場合があります)。

全体として、今日でTimeはほとんどのアプリケーションに適しています。

また、加算に関する重要な違いにも注意してください。Timeオブジェクトに数値を追加すると、秒単位でカウントされますが、DateTimeに数値を追加すると、日単位でカウントされます。


これは2018年にも当てはまりますか?
Qqwy 2018

2
Ruby 2.5.1でも同じです。ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html:「DateTimeはうるう秒を考慮せず、夏時間のルールを追跡しません。」ただし、プレグレゴリオ暦の日付/時刻を処理する必要がある場合、DateTimeには利点があることに注意してください。これについてはここでは触れていませんが、リファレンスドキュメントに記載されています。「[…] RubyのTimeクラスは、発作的なグレゴリオ暦を実装し、カレンダーのリフォームの概念はありません[…]。回答を編集して、もう少し詳しく説明します。
Niels Ganser

Timeうるう秒の概念もないため、と違いはありませんDateTime。あなたがあなたの例をどこで実行したかはわかりませんが、私はTime.new(2012,6,30,23,59,60,0)2.0から2.7までのさまざまなRubyバージョンで試しましたが、常にを手に入れました2012-07-01 00:00:00 +0000
michau

@michau Timeうるう秒をサポートするかどうかは、OSとタイムゾーンの構成によって異なります。例:TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-06-30 23:59:60 +0000一方TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000
Niels Ganser

@NielsGanserありがとうございます。この点を明確にするために編集を提案しました。
michau

44

「違いは何ですか」への答えは、Ruby標準ライブラリにおけるこの質問への不幸な一般的な答えの1つだと思います。2つのクラス/ライブラリは、異なる人々によって異なる時期に異なる方法で作成されました。これは、Javaのようなものの慎重に計画された開発と比較した、Rubyの進化のコミュニティの性質の不幸な結果の1つです。開発者は新しい機能を望んでいますが、既存のAPIを踏襲したくないので、新しいクラスを作成するだけです。エンドユーザーにとって、2つが存在する明確な理由はありません。

これは、ソフトウェアライブラリ全般に当てはまります。多くの場合、一部のコードまたはAPIが論理的ではなく歴史的であることが判明するのはそのためです。

より一般的であるように思われるので、誘惑はDateTimeから始めることです。日付と時間ですよね?違う。Timeは日付も適切に処理し、実際にはDateTimeができないタイムゾーンを解析できます。また、パフォーマンスも向上します。

どこでもTimeを使用してしまいました。

ただし、念のため、DateTime引数をTimey APIに渡して変換できるようにする傾向があります。また、両方に興味のあるメソッドがあることがわかっている場合は、どちらかを受け入れます。このメソッドのように、時間をXMLに変換するために記述しました(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

8
また、Time.newとDateTime.newはタイムゾーンの扱いが異なります。私はGMT + 7にいるので、Time.new(2011, 11, 1, 10, 30)プロデュースし2011-11-01 10:30:00 +0700ながらDateTime.new(2011, 11, 1, 10, 30)プロデュースしTue, 01 Nov 2011 10:30:00 +0000ます。
フオングエン

21
そして、誰もが知っているように、慎重に計画されたJavaの開発により、排他的で論理的なAPIが生まれました。
2012年

@PhươngNguyễn:賛成投票できるように、それを回答として追加してくれませんか?それこそが、DateTimeよりTimeを選ぶことにした理由です。
センスフルな2013

@Senseful申し訳ありませんが、今までメッセージを受け取っていません。人々は私のコメントにすでにいくつかの賛成票を与えているので、ここでそれはクールだと思います。
フオングエン

@PhươngNguyễnこんにちは、どのように/なぜこのタイムゾーンの違いがあるのですか?それはどこからオフセットを取っていますか?
Joel_Blum

10

ActiveSupport拡張機能を使用している場合、異なるタイムゾーンでの1日の開始/終了の解析や計算などがDateTimeで行う方が簡単であることがわかりました。

私の場合、文字列として受信したユーザーのローカル時間に基づいて、ユーザーのタイムゾーン(任意)で1日の終わりを計算する必要がありました。たとえば、 "2012-10-10 10:10 +0300"

DateTimeを使用すると、次のように簡単です

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

次に、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.

実際、Timeは解析前にタイムゾーンを知る必要があります(またTime.zone.parse、ではなくであることに注意してください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

したがって、この場合は、DateTimeを使用する方が間違いなく簡単です。


1
これを本番環境で使用していますが、このテクニックにはマイナス面がありますか?
Alex Moore-Niemi 2014年

1
DateTimeは夏時間を考慮しません。したがって、時間の変更を保存すると、正しく機能しなくなります。DateTime.parse('2014-03-30 01:00:00 +0100').end_of_dayは生成されますSun, 30 Mar 2014 23:59:59 +0100が、Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day生成されますSun, 30 Mar 2014 23:59:59 CEST +02:00(CET = + 01:00、CEST = + 02:00-オフセットが変更されていることに注意してください)。ただし、これを行うには、ユーザーのタイムゾーンに関する詳細情報(オフセットだけでなく、時間の節約が使用されているかどうかも必要)が必要です。
Petr ''Bubák ''Šedivý

1
それは明白なことを述べてもよいが、Time.zone.parseある非常に異なるゾーンで時間を解析するときに便利-それはあなたが使用する必要のある区域について考えるようにあなたを強制します。Time.find_zoneの方が優れている場合があります。
prusswan、2015年

1
@Petr''Bubák''Šedivý DateTimeは、指定したオフセット(+0100)を解析しました。Timeオフセットを指定しませんでしたが、タイムゾーン( "CET"はオフセットを記述せず、タイムゾーンの名前です)。タイムゾーンは1年を通して異なるオフセットを持つことができますが、オフセットはオフセットであり、常に同じです。
Mecki

4

カスタムのインスタンス化でタイムゾーンの扱い方を検討してください。

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

これは、時間範囲などを作成するときに注意が必要です。


単純なルビ(Railsなし)でのみ発生するようです
vemv 2016年

1

場合によっては動作が大きく異なるようです:

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(当然のことながら)は日付のみを解析し、a Dateがに変換されるときはTime、常にローカルタイムゾーンの午前0時を時刻として使用します。との違いはTime、BSTを理解しないTimeDateという事実に由来しますTime。これは、タイムゾーンがTime(たとえば、夏時間に関して)通常より正確に処理されることを考えると、驚くべきことです。したがって、この場合、DateTime文字列全体を正しく解析するだけです。
michau

1

Niels Ganserの回答に加えて次の議論を検討してください。

ことに注意してくださいルビースタイルガイドは、非常に明確にこの上の位置を述べています:

日時なし

過去のカレンダーの改革を説明する必要がない限り、DateTimeを使用しないでください。その場合は、開始引数を明示的に指定して、意図を明確に示してください。

# 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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.