อะไรคือความแตกต่างระหว่างDateTime
และTime
คลาสของ Ruby และปัจจัยใดที่ทำให้ฉันเลือกอย่างใดอย่างหนึ่ง
อะไรคือความแตกต่างระหว่างDateTime
และTime
คลาสของ Ruby และปัจจัยใดที่ทำให้ฉันเลือกอย่างใดอย่างหนึ่ง
คำตอบ:
Ruby รุ่นใหม่กว่า (2.0+) ไม่มีความแตกต่างอย่างมีนัยสำคัญระหว่างสองคลาส ห้องสมุดบางแห่งจะใช้อย่างใดอย่างหนึ่งเพื่อเหตุผลทางประวัติศาสตร์ แต่รหัสใหม่ไม่จำเป็นต้องเกี่ยวข้อง การเลือกหนึ่งรายการเพื่อความมั่นคงน่าจะดีที่สุดดังนั้นให้ลองทำตามสิ่งที่ห้องสมุดคาดหวัง ตัวอย่างเช่น ActiveRecord ชอบ DateTime
ในรุ่นก่อน Ruby 1.9 และในหลาย ๆ ระบบเวลาจะแสดงเป็นค่าที่เซ็นชื่อแบบ 32 บิตที่อธิบายจำนวนวินาทีตั้งแต่ 1 มกราคม 1970 UTC UTC ซึ่งเป็นตัวหุ้มที่บางรอบค่ามาตรฐาน 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 เป็นวิธีการที่ใช้ปฏิทินซึ่งเก็บปี, เดือน, วัน, ชั่วโมง, นาทีและวินาทีตามลำดับ นี่คือโครงสร้าง Ruby on Rails ที่ทำหน้าที่เป็นตัวล้อมรอบฟิลด์ DATETIME มาตรฐาน SQL สิ่งเหล่านี้มีวันที่ตามอำเภอใจและสามารถเป็นตัวแทนของเกือบทุกจุดในขณะที่ช่วงของการแสดงออกมีขนาดใหญ่
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
ดังนั้นจึงมั่นใจได้ว่า DateTime สามารถจัดการโพสต์บล็อกจาก Aristotle
เมื่อเลือกอย่างใดอย่างหนึ่งความแตกต่างค่อนข้างเป็นอัตวิสัยในขณะนี้ ในอดีต DateTime ได้ให้ตัวเลือกที่ดีกว่าสำหรับการจัดการในรูปแบบปฏิทิน แต่วิธีการเหล่านี้จำนวนมากได้ถูกย้ายไปยัง Time เช่นกันอย่างน้อยภายในสภาพแวดล้อมของ Rails
[แก้ไขกรกฎาคม 2018]
ด้านล่างทั้งหมดยังคงเป็นจริงใน Ruby 2.5.1 จากเอกสารอ้างอิง :
DateTime ไม่พิจารณาวินาทีกระโดดใด ๆ ไม่ติดตามกฎเวลาฤดูร้อนใด ๆ
สิ่งที่ไม่ได้บันทึกไว้ในหัวข้อนี้มาก่อนเป็นหนึ่งในข้อดีสองสามข้อของDateTime
: มันตระหนักถึงการปฏิรูปปฏิทินในขณะที่Time
ไม่ได้:
[…] ชั้นเรียนรูบี้ใช้ปฏิทินเกรกอเรียนที่มีความคิดสร้างสรรค์และไม่มีแนวคิดเรื่องการปฏิรูปปฏิทิน […]
เอกสารอ้างอิงสรุปด้วยข้อเสนอแนะที่จะใช้Time
เฉพาะเมื่อจัดการกับวันที่ / เวลาใกล้อดีตปัจจุบันหรืออนาคตและใช้เฉพาะDateTime
เมื่อเช่นวันเกิดของเช็คสเปียร์จะต้องถูกแปลงอย่างถูกต้อง: (เน้นเพิ่ม)
ดังนั้นเมื่อใดที่คุณควรใช้ DateTime ใน Ruby และเมื่อใดที่คุณควรใช้ Time เกือบจะแน่นอนว่าคุณต้องการใช้เวลาเนื่องจากแอปของคุณอาจเกี่ยวข้องกับวันที่และเวลาปัจจุบัน อย่างไรก็ตามหากคุณต้องการจัดการกับวันที่และเวลาในบริบททางประวัติศาสตร์คุณจะต้องใช้ DateTime […] หากคุณต้องจัดการกับเขตเวลาดังนั้นขอให้คุณโชคดี - โปรดจำไว้ว่าคุณอาจต้องเผชิญกับเวลาสุริยจักรวาลเนื่องจากไม่ใช่จนกระทั่งศตวรรษที่ 19 ที่การแนะนำทางรถไฟจำเป็นต้องใช้เวลามาตรฐาน และในที่สุดเขตเวลา
[/ แก้ไขกรกฎาคม 2018]
ตั้งแต่ ruby 2.0 ข้อมูลส่วนใหญ่ในคำตอบอื่น ๆ นั้นล้าสมัย
โดยเฉพาะอย่างยิ่งTime
ตอนนี้ไม่ได้ผูกมัดจริง มันสามารถมากหรือน้อยกว่า 63 บิตจาก 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
ผลลัพธ์เดียวของการใช้ค่าที่มากขึ้นควรเป็นประสิทธิภาพซึ่งจะดีกว่าเมื่อใช้Integer
s (กับBignum
s (ค่านอกInteger
ช่วง) หรือRational
s (เมื่อมีการติดตาม nanoseconds)):
ตั้งแต่ Ruby 1.9.2 การใช้งานเวลาใช้จำนวนเต็มบิต 63 บิต Bignum หรือ Rational จำนวนเต็มเป็นจำนวน nanoseconds ตั้งแต่ Epoch ซึ่งสามารถแทน 1823-11-12 ถึง 2116-02-20 เมื่อใช้ Bignum หรือ Rational (ก่อนปี 1823 หลังจาก 2116 ภายใต้นาโนวินาที) เวลาทำงานช้าลงเมื่อใช้จำนวนเต็ม ( http://www.ruby-doc.org/core-2.1.0/Time.html )
ในคำอื่น ๆ เท่าที่ผมเข้าใจไม่ครอบคลุมช่วงกว้างของค่าที่มีศักยภาพกว่าDateTime
Time
นอกจากนี้DateTime
ควรสังเกตข้อ จำกัด ที่ยังไม่ได้กล่าวถึงสองข้อก่อนหน้านี้:
DateTime ไม่พิจารณา leapseconds ใด ๆ ไม่ติดตามกฎเวลาฤดูร้อนใด ๆ ( 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
ระบบปฏิบัติการจะต้องสนับสนุนการกระโดดวินาทีและข้อมูลเขตเวลาที่จะต้องตั้งค่าอย่างถูกต้องเช่นผ่าน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
ตัวเอง (ระบบปฏิบัติการหลายระบบอาจดูแลเรื่องนี้ให้คุณแล้ว)
โดยรวมแล้วฉันบอกว่าทุกวันนี้Time
เป็นตัวเลือกที่ดีกว่าสำหรับแอปพลิเคชันส่วนใหญ่
นอกจากนี้ให้สังเกตถึงความแตกต่างที่สำคัญของการเพิ่ม: เมื่อคุณเพิ่มตัวเลขลงในวัตถุ Time นั้นจะถูกนับเป็นวินาที แต่เมื่อคุณเพิ่มตัวเลขลงใน DateTime มันจะถูกนับเป็นวัน
Time
DateTime
มีแนวคิดของวินาทีกระโดดอย่างใดอย่างหนึ่งเพื่อให้มันไม่ได้แตกต่างจาก ผมไม่ทราบว่าคุณวิ่งตัวอย่างของคุณ แต่ฉันพยายามTime.new(2012,6,30,23,59,60,0)
ในรุ่นทับทิมที่แตกต่างกัน 2.0-2.7 2012-07-01 00:00:00 +0000
และได้เสมอ
Time
สนับสนุนวินาทีกระโดดหรือไม่ขึ้นอยู่กับการกำหนดค่าระบบปฏิบัติการ & เขตเวลาของคุณ ตัวอย่างเช่น: 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
ฉันคิดว่าคำตอบของ "ความแตกต่างคืออะไร" เป็นหนึ่งในคำตอบที่โชคร้ายสำหรับคำถามนี้ในไลบรารีมาตรฐานของ Ruby: ทั้งสองคลาส / libs ถูกสร้างขึ้นแตกต่างกันโดยผู้คนต่างกันในเวลาที่ต่างกัน มันเป็นหนึ่งในผลที่ตามมาไม่ดีของธรรมชาติของวิวัฒนาการของรูบี้เมื่อเทียบกับการพัฒนาอย่างระมัดระวังของ Java นักพัฒนาต้องการฟังก์ชั่นใหม่ แต่ไม่ต้องการก้าวไปสู่ API ที่มีอยู่ดังนั้นพวกเขาเพียงแค่สร้างคลาสใหม่ - สำหรับผู้ใช้ปลายทางไม่มีเหตุผลที่ชัดเจนสำหรับทั้งสองที่มีอยู่
นี่เป็นเรื่องจริงสำหรับไลบรารี่ซอฟต์แวร์โดยทั่วไป: บ่อยครั้งที่เหตุผลบางรหัสหรือ API เป็นวิธีที่มันกลายเป็นประวัติศาสตร์มากกว่าตรรกะ
สิ่งล่อใจคือการเริ่มต้นด้วย DateTime เพราะดูเหมือนทั่วไปมากขึ้น วันที่ ... และเวลาใช่มั้ย ไม่ถูกต้อง. เวลาก็ทำวันที่ดีกว่าและในความเป็นจริงสามารถแยกเขตเวลาที่ DateTime ไม่สามารถ นอกจากนี้ยังทำงานได้ดีขึ้น
ฉันลงเอยด้วยการใช้เวลาทุกที่
เพื่อความปลอดภัยแม้ว่าฉันมักจะอนุญาตให้อาร์กิวเมนต์ 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
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
ผมพบว่าสิ่งดังกล่าวเช่นการแยกและการคำนวณจุดเริ่มต้น / สิ้นสุดของวันในเขตเวลาที่แตกต่างกันได้ง่ายขึ้นจะทำอย่างไรกับ DateTime, สมมติว่าคุณกำลังใช้ส่วนขยาย ActiveSupport
ในกรณีของฉันฉันต้องการคำนวณการสิ้นสุดของวันในเขตเวลาของผู้ใช้ (ตามอำเภอใจ) ตามเวลาท้องถิ่นของผู้ใช้ที่ฉันได้รับเป็นสตริงเช่น "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
ตอนนี้ลองทำแบบเดียวกันกับเวลา:
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.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
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 - สังเกตการเปลี่ยนแปลงที่มีการชดเชย) แต่การทำเช่นนี้คุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับเขตเวลาของผู้ใช้ (ไม่เพียง แต่ชดเชย แต่ยังรวมถึงการใช้เวลาที่ประหยัด)
Time.zone.parse
มีประโยชน์มากเมื่อแยกเวลากับโซนต่าง ๆ - มันบังคับให้คุณคิดเกี่ยวกับโซนที่คุณควรใช้ บางครั้ง Time.find_zone ทำงานได้ดียิ่งขึ้น
DateTime
แยกวิเคราะห์ออฟเซ็ตที่คุณให้ไว้ซึ่งเท่ากับ +100 คุณไม่ได้ให้Time
การชดเชย แต่เขตเวลา ("CET" ไม่ได้อธิบายการชดเชย แต่เป็นชื่อของเขตเวลา) เขตเวลาสามารถมีการชดเชยที่แตกต่างกันตลอดทั้งปี แต่การชดเชยคือการชดเชยและมันก็เหมือนกันเสมอ
พิจารณาวิธีที่พวกเขาจัดการเขตเวลาแตกต่างกันด้วยอินสแตนซ์ที่กำหนดเอง:
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
สิ่งนี้อาจเป็นเรื่องยากเมื่อสร้างช่วงเวลาเป็นต้น
ดูเหมือนว่าในบางกรณีพฤติกรรมจะแตกต่างกันมาก:
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
เป็นจะใช้เวลาเที่ยงคืนในเขตเวลาท้องถิ่นเป็นเวลา ความแตกต่างระหว่างTime
และTimeDate
เกิดจากความจริงที่Time
ไม่เข้าใจ BST ซึ่งเป็นที่น่าแปลกใจเนื่องจากมักจะมีการจัดการเขตเวลาที่ถูกต้องมากขึ้นโดยTime
(เกี่ยวกับการปรับเวลาตามฤดูกาลเป็นต้น) ดังนั้นในกรณีนี้DateTime
แยกวิเคราะห์เฉพาะสายอักขระทั้งหมดอย่างถูกต้อง
นอกจากคำตอบของ Niels Ganserคุณอาจพิจารณาอาร์กิวเมนต์นี้:
โปรดทราบว่าThe Ruby Style Guideค่อนข้างระบุตำแหน่งไว้อย่างชัดเจน:
ไม่มีวันที่
อย่าใช้ 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)