Sự khác biệt giữa DateTime và Time trong Ruby


218

Sự khác biệt giữa DateTimeTimecá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ác tài liệu có một phần , giải thích khi nào nên sử dụng.
x-yuri

Câu trả lời:


177

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_tgiá 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.


6
Vậy tôi có nên sử dụng DateTime không?
Tom Lehman

4
Nếu bạn đang làm việc với ngày, tôi sẽ nói sử dụng DateTime. Thời gian thuận tiện cho việc đại diện cho những thứ như thời gian hiện tại trong ngày hoặc các điểm trong tương lai gần, chẳng hạn như 10.minutes.from_now. Cả hai có rất nhiều điểm chung, mặc dù DateTime đã lưu ý có thể đại diện cho phạm vi giá trị rộng hơn nhiều.
tadman

3
Tôi tin rằng đó là vì Ruby chuyển sang Bignum có độ dài tùy ý từ Fixnum 32 bit khi nó bị tràn. Các số nằm ngoài phạm vi đó có thể không được các ứng dụng bên ngoài hỗ trợ. Có, vào năm 2038, về cơ bản chúng ta đã say sưa cho đến khi tất cả chúng ta có thể đồng ý về định dạng thời gian 64 bit thích hợp. Ban giám khảo vẫn ra ngoài.
tadman

27
Câu trả lời này có trước 1.9.2. Bỏ qua mọi thứ nó nói về các giới hạn tích hợp của Thời gian và đưa ra lựa chọn của bạn dựa trên các API của Thời gian và DateTime.
Ben Nagy

8
Câu trả lời này đã lỗi thời rồi phải không? Bây giờ nên sử dụng ActiveSupport của Time hoặc Rail :: WithTimeZone. Bạn không còn cần phải sử dụng DateTime. Nó ở đó để tương thích ngược.
Donato

102

[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 Timekhô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 Timekhi 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 DateTimekhi, 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, Timebâ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 Integers được sử dụng (so với Bignums (giá trị ngoài Integerphạm vi) hoặc Rationals (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, DateTimekhô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 DateTimecó 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, DateTimekhô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, DateTimehiể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 Timelà 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.


Điều này có còn đúng trong năm 2018 không?
Qqwy

2
Nó vẫn đúng trong Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "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." Tuy nhiên, lưu ý rằng DateTime có lợi thế khi bạn cần xử lý các ngày / lần theo lịch trước Gregorian. Điều này đã không được đề cập ở đây trước đây nhưng được ghi lại trong tài liệu tham khảo: "[Thời gian] 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 về cải cách lịch [Gabriel]." Tôi sẽ chỉnh sửa câu trả lời của tôi để cung cấp thêm một số chi tiết.
Niels Ganser

Timecũ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.
michau

@michau Việc có Timehỗ 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 +0000trong khi TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser

@NielsGanser Tuyệt vời, cảm ơn! Tôi đề nghị một chỉnh sửa để làm rõ điểm này.
michau

44

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

8
Ngoài ra, Time.new và DateTime.new đang xử lý múi giờ khác nhau. Tôi đang trên GMT + 7, vì vậy Time.new(2011, 11, 1, 10, 30)sản xuất 2011-11-01 10:30:00 +0700trong khi DateTime.new(2011, 11, 1, 10, 30)sản xuất Tue, 01 Nov 2011 10:30:00 +0000.
Phương Nguyễn

21
Và như tất cả chúng ta đều biết, sự phát triển được lên kế hoạch cẩn thận của Java đã tạo ra các API logic đơn giản.
pje

@ PhươngNguy nam: Bạn vui lòng thêm câu đó làm câu trả lời để tôi có thể bình chọn không? Đó chính xác là lý do tôi quyết định chọn Time over DateTime.
Ý thức

@Senseful Xin lỗi tôi không nhận được tin nhắn của bạn cho đến bây giờ. Mọi người đã đưa ra một số đánh giá cho nhận xét của tôi vì vậy tôi nghĩ rằng nó ở đây rất tuyệt.
Phương Nguyễn

@ PhươngNguy Nguyên Xin chào, có ý tưởng nào / tại sao lại có sự khác biệt về múi giờ này không? nó lấy phần bù từ đâu?
Joel_Blum

10

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.parsekhô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.


1
Sử dụng điều này trong sản xuất bây giờ, có bất kỳ nhược điểm nào cho kỹ thuật không?
Alex Moore-Niemi

1
DateTime không tính đến thời gian tiết kiệm ánh sáng ban ngày. Do đó, về việc tiết kiệm thời gian thay đổi, nó sẽ không hoạt động chính xác. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_daytạ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_daytạ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).
Petr '' Bubák '' Šedivý

1
Nó có thể là tuyên bố rõ ràng, nhưng Time.zone.parserấ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.
Phổ

1
@ Petr''Bubák''Šedivý DateTimeđã phân tích phần bù mà bạn đã đưa ra, đó là +0100. Bạn đã không cung cấp Timemộ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.
Mecki

4

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


Chỉ có vẻ như xảy ra với ruby ​​đơn giản (tức là không có Rails)
vemv

1

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"


Đó là một quan sát thú vị, nhưng cần giải thích tại sao nó lại xảy ra. 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 TimeTimeDatexuất phát từ thực tế là Timekhô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ỉ DateTimephân tích chính xác toàn bộ chuỗi.
michau

1

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.