Sự khác biệt giữa DateTime
và Time
các lớp trong Ruby và yếu tố nào sẽ khiến tôi chọn cái này hay cái khác?
Sự khác biệt giữa DateTime
và Time
các lớp trong Ruby và yếu tố nào sẽ khiến tôi chọn cái này hay cái khác?
Câu trả lời:
Các phiên bản mới hơn của Ruby (2.0+) không thực sự có sự khác biệt đáng kể giữa hai lớp. Một số thư viện sẽ sử dụng cái này hoặc cái kia vì lý do lịch sử, nhưng mã mới không nhất thiết phải được quan tâm. Chọn một để thống nhất có lẽ là tốt nhất, vì vậy hãy thử và kết hợp với những gì thư viện của bạn mong đợi. Ví dụ: ActiveRecord thích DateTime.
Trong các phiên bản trước Ruby 1.9 và trên nhiều hệ thống Thời gian được biểu thị dưới dạng giá trị được ký 32 bit mô tả số giây kể từ ngày 1 tháng 1 năm 1970 UTC, một lớp bọc mỏng bao quanh time_t
giá trị chuẩn POSIX và được giới hạn:
Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901
Các phiên bản mới hơn của Ruby có thể xử lý các giá trị lớn hơn mà không gây ra lỗi.
DateTime là một cách tiếp cận dựa trên lịch trong đó năm, tháng, ngày, giờ, phút và giây được lưu trữ riêng lẻ. Đây là cấu trúc Ruby on Rails đóng vai trò là trình bao bọc xung quanh các trường DATETIME tiêu chuẩn SQL. Chúng chứa các ngày tùy ý và có thể biểu thị gần như bất kỳ thời điểm nào vì phạm vi biểu thức thường rất lớn.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
Vì vậy, chúng tôi yên tâm rằng DateTime có thể xử lý các bài đăng trên blog từ Aristotle.
Khi chọn một, sự khác biệt là hơi chủ quan bây giờ. Trong lịch sử DateTime đã cung cấp các tùy chọn tốt hơn để thao tác nó theo kiểu lịch, nhưng nhiều phương thức này cũng đã được chuyển sang Time, ít nhất là trong môi trường Rails.
[Chỉnh sửa tháng 7 năm 2018]
Tất cả những điều dưới đây vẫn đúng trong Ruby 2.5.1. Từ tài liệu tham khảo :
DateTime không xem xét bất kỳ giây nhuận nào, không theo dõi bất kỳ quy tắc thời gian mùa hè nào.
Điều chưa được lưu ý trong chủ đề này trước đây là một trong một vài lợi thế của DateTime
: nó nhận thức được các cải cách lịch trong khi Time
không phải là:
[Vay] Lớp Thời gian của Ruby thực hiện lịch Gregorian nguyên sinh và không có khái niệm cải cách lịch [[]].
Tài liệu tham khảo kết luận với khuyến nghị nên sử dụng Time
khi xử lý riêng các ngày / thời gian gần, hiện tại hoặc tương lai và chỉ sử dụng DateTime
khi, ví dụ, sinh nhật của Shakespeare cần được chuyển đổi chính xác: (nhấn mạnh thêm)
Vậy khi nào bạn nên sử dụng DateTime trong Ruby và khi nào bạn nên sử dụng Time? Hầu như chắc chắn bạn sẽ muốn sử dụng Thời gian vì ứng dụng của bạn có thể đang xử lý các ngày và giờ hiện tại. Tuy nhiên, nếu bạn cần xử lý ngày và giờ trong bối cảnh lịch sử, bạn sẽ muốn sử dụng DateTime [ Hồi ]. Nếu bạn cũng phải đối phó với các múi giờ thì tốt nhất là may mắn - hãy nhớ rằng có lẽ bạn sẽ phải đối phó với thời gian mặt trời địa phương, vì phải đến thế kỷ 19, việc giới thiệu đường sắt cần phải có Giờ chuẩn và cuối cùng là múi giờ.
[/ Chỉnh sửa tháng 7 năm 2018]
Kể từ ruby 2.0, hầu hết các thông tin trong các câu trả lời khác đều lỗi thời.
Đặc biệt, Time
bây giờ thực tế là không ràng buộc. Nó có thể cách Epoch nhiều hơn hoặc ít hơn 63 bit:
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
Hậu quả duy nhất của việc sử dụng các giá trị lớn hơn phải là hiệu suất, sẽ tốt hơn khi Integer
s được sử dụng (so với Bignum
s (giá trị ngoài Integer
phạm vi) hoặc Rational
s (khi nano giây được theo dõi)):
Kể từ Ruby 1.9.2, thời gian thực hiện sử dụng số nguyên 63 bit, Bignum hoặc Rational đã ký. Số nguyên là một số nano giây kể từ Kỷ nguyên có thể đại diện cho 1823-11-12 đến 2116-02-20. Khi Bignum hoặc Rational được sử dụng (trước năm 1823, sau năm 2116, dưới nano giây), Thời gian hoạt động chậm hơn khi sử dụng số nguyên. ( http://www.ruby-doc.org/core-2.1.0/Time.html )
Nói cách khác, theo như tôi hiểu, DateTime
không còn bao trùm một phạm vi giá trị tiềm năng rộng lớn hơnTime
.
Ngoài ra, hai hạn chế chưa được đề cập trước đây DateTime
có lẽ nên được lưu ý:
DateTime không xem xét bất kỳ bước nhảy vọt nào, không theo dõi bất kỳ quy tắc thời gian mùa hè nào. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#group-Date-label-DateTime )
Đầu tiên, DateTime
không có khái niệm về giây nhuận:
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
Để ví dụ trên hoạt động Time
, HĐH cần hỗ trợ giây nhuận và thông tin múi giờ cần được đặt chính xác, ví dụ thông qua TZ=right/UTC irb
(trên nhiều hệ thống Unix).
Thứ hai, DateTime
hiểu biết rất hạn chế về múi giờ và đặc biệt không có khái niệm về tiết kiệm ánh sáng ban ngày . Nó xử lý khá nhiều múi giờ như các offset UTC + X đơn giản:
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>
Điều này có thể gây rắc rối khi thời gian được nhập dưới dạng DST và sau đó được chuyển đổi thành múi giờ không phải DST mà không theo dõi các độ lệch chính xác bên ngoài DateTime
(nhiều hệ điều hành thực sự có thể xử lý vấn đề này cho bạn).
Nhìn chung, tôi muốn nói rằng ngày nay Time
là sự lựa chọn tốt hơn cho hầu hết các ứng dụng.
Cũng lưu ý một sự khác biệt quan trọng về phép cộng: khi bạn thêm số vào đối tượng Thời gian, nó được tính bằng giây, nhưng khi bạn thêm số vào DateTime, số đó được tính theo ngày.
Time
cũng không có khái niệm về giây nhuận, vì vậy nó không khác biệt DateTime
. Tôi không biết bạn đã chạy các ví dụ của mình ở đâu, nhưng tôi đã thử Time.new(2012,6,30,23,59,60,0)
các phiên bản Ruby khác nhau, từ 2.0 đến 2.7 và luôn luôn có 2012-07-01 00:00:00 +0000
.
Time
hỗ trợ giây nhuận hay không phụ thuộc vào cấu hình HĐH & múi giờ của bạn. Ví dụ: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-06-30 23:59:60 +0000
trong khi TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
Tôi nghĩ rằng câu trả lời cho "sự khác biệt" là một trong những câu trả lời chung đáng tiếc cho câu hỏi này trong các thư viện tiêu chuẩn của Ruby: hai lớp / lib được tạo ra bởi những người khác nhau ở những thời điểm khác nhau. Đó là một trong những hậu quả đáng tiếc về bản chất cộng đồng của sự tiến hóa của Ruby so với sự phát triển được lên kế hoạch cẩn thận của một thứ gì đó như Java. Các nhà phát triển muốn có chức năng mới nhưng không muốn bước vào các API hiện có để họ chỉ tạo một lớp mới - cho người dùng cuối không có lý do rõ ràng nào để cả hai tồn tại.
Điều này đúng với các thư viện phần mềm nói chung: thường thì lý do một số mã hoặc API là cách nó hóa ra là lịch sử hơn là logic.
Sự cám dỗ là bắt đầu với DateTime vì nó có vẻ chung chung hơn. Ngày ... và Thời gian, phải không? Sai lầm. Thời gian cũng có ngày tốt hơn và trên thực tế có thể phân tích các múi giờ trong đó DateTime không thể. Ngoài ra nó thực hiện tốt hơn.
Tôi đã kết thúc việc sử dụng Thời gian ở mọi nơi.
Để an toàn, tôi có xu hướng cho phép các đối số DateTime được chuyển vào API Timey của mình và chuyển đổi. Ngoài ra nếu tôi biết rằng cả hai đều có phương thức tôi quan tâm, tôi cũng chấp nhận, giống như phương pháp này tôi đã viết để chuyển đổi thời gian thành XML (đối với các tệp 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)
sản xuất 2011-11-01 10:30:00 +0700
trong khi DateTime.new(2011, 11, 1, 10, 30)
sản xuất Tue, 01 Nov 2011 10:30:00 +0000
.
Tôi thấy những việc như phân tích cú pháp và tính toán bắt đầu / kết thúc một ngày theo các múi giờ khác nhau dễ thực hiện hơn với DateTime, giả sử bạn đang sử dụng các tiện ích mở rộng ActiveSupport .
Trong trường hợp của tôi, tôi cần tính toán thời gian cuối ngày theo múi giờ của người dùng (tùy ý) dựa trên giờ địa phương của người dùng mà tôi nhận được dưới dạng chuỗi, ví dụ: "2012-10-10 10:10 +0300"
Với DateTime, nó đơn giản như
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
Bây giờ chúng ta hãy thử nó theo cách tương tự với Thời gian:
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.
Trên thực tế, Thời gian cần biết múi giờ trước khi phân tích cú pháp (cũng lưu ý là Time.zone.parse
không phải 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
Vì vậy, trong trường hợp này chắc chắn sẽ dễ dàng hơn với DateTime.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
tạo ra Sun, 30 Mar 2014 23:59:59 +0100
, nhưng Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day
tạo ra Sun, 30 Mar 2014 23:59:59 CEST +02:00
(CET = + 01: 00, CEST = + 02: 00 - chú ý phần bù đã thay đổi). Nhưng để làm điều này, bạn cần thêm thông tin về múi giờ của người dùng (không chỉ bù mà còn tiết kiệm thời gian sử dụng).
Time.zone.parse
là rất hữu ích khi phân tích cú pháp lần với vùng khác nhau - nó buộc bạn phải suy nghĩ về những vùng bạn nên sử dụng. Đôi khi, Time.find_zone hoạt động thậm chí còn tốt hơn.
DateTime
đã phân tích phần bù mà bạn đã đưa ra, đó là +0100. Bạn đã không cung cấp Time
một phần bù, nhưng múi giờ ("CET" không mô tả phần bù, đó là tên của múi giờ). Các múi giờ có thể có các độ lệch khác nhau trong suốt cả năm, nhưng phần bù là phần bù và nó luôn giống nhau.
Xem xét cách họ xử lý các múi giờ khác nhau với các cảnh báo tùy chỉnh:
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
Điều này có thể khó khăn khi tạo phạm vi thời gian, vv
Có vẻ như trong một số trường hợp, hành vi rất khác nhau:
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
(không ngạc nhiên) chỉ phân tích ngày và khi a Date
được chuyển đổi thành Time
, nó luôn sử dụng nửa đêm trong múi giờ địa phương làm thời gian. Sự khác biệt giữa Time
và TimeDate
xuất phát từ thực tế là Time
không hiểu BST, điều đáng ngạc nhiên, vì các múi giờ thường được xử lý chính xác hơn bằng cách Time
(ví dụ như liên quan đến thời gian tiết kiệm ánh sáng ban ngày). Vì vậy, trong trường hợp này, chỉ DateTime
phân tích chính xác toàn bộ chuỗi.
Ngoài câu trả lời của Niels Ganser, bạn có thể xem xét lập luận này:
Lưu ý rằng Hướng dẫn về Phong cách Ruby nêu rõ một vị trí trên này:
Không có ngày
Không sử dụng DateTime trừ khi bạn cần tính đến cải cách lịch sử - và nếu có, hãy xác định rõ ràng đối số bắt đầu để nêu rõ ý định của bạn.
# 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)