Chuyển đổi sang / từ DateTime và Time trong Ruby


131

Làm thế nào để bạn chuyển đổi giữa một đối tượng DateTime và Time trong Ruby?


1
Tôi không chắc đây có phải là một câu hỏi riêng biệt không, nhưng làm thế nào để bạn chuyển đổi giữa Ngày và Giờ?
Andrew Grimm

8
Các câu trả lời được chấp nhận và đánh giá cao nhất không còn là chính xác nhất trong các phiên bản hiện đại của Ruby. Xem câu trả lời của @theTinMan@PatrickMcKenzie bên dưới.
Phrogz

Câu trả lời:


50

Bạn sẽ cần hai chuyển đổi hơi khác nhau.

Để chuyển đổi từ Time sang DateTimebạn có thể sửa đổi lớp Thời gian như sau:

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

Điều chỉnh tương tự với Ngày sẽ cho phép bạn chuyển đổi DateTime sang 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

Lưu ý rằng bạn phải chọn giữa giờ địa phương và giờ GM / UTC.

Cả hai đoạn mã trên được lấy từ Ruby Cookbook của O'Reilly . Chính sách tái sử dụng mã của họ cho phép điều này.


5
Điều này sẽ phá vỡ vào ngày 1.9 trong đó DateTime # sec_fraction trả về số mili giây trong một giây. Đối với 1.9 bạn muốn sử dụng: 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)

13
+1 Đây có thể không phải là hiệu quả nhất trong thực thi, nhưng nó hoạt động, nó ngắn gọn và rất dễ đọc.
Walt Jones

6
Thật không may, điều này chỉ thực sự hoạt động khi làm việc với thời gian địa phương. Nếu bạn bắt đầu với DateTime hoặc Time với múi giờ khác, chức năng phân tích cú pháp sẽ chuyển đổi thành múi giờ cục bộ. Bạn về cơ bản mất múi giờ ban đầu.
Bernard

6
Kể từ ruby ​​1.9.1, DateTime.parse không bảo toàn múi giờ. (Tôi không có quyền truy cập vào các phiên bản trước.) Time.parse không bảo toàn múi giờ, vì nó đại diện cho time_t tiêu chuẩn POSIX, mà tôi tin là sự khác biệt về số nguyên so với epoch. Bất kỳ chuyển đổi thành Thời gian nên có hành vi tương tự.
anshul

1
Bạn đúng. DateTime.parse hoạt động trong 1.9.1 nhưng không phải Time.parse. Trong mọi trường hợp, nó ít bị lỗi hơn (nhất quán) và có thể nhanh hơn để sử dụng DateTime.new (...) và Time.new (..). Xem câu trả lời của tôi cho mã mẫu.
Bernard

1
Xin chào @anshul. Tôi không ngụ ý rằng tôi đang nói :-). Thông tin múi giờ không được lưu giữ khi sử dụng Time.parse (). Thật dễ dàng để kiểm tra. Trong mã của bạn ở trên, chỉ cần thay thế d = DateTime.now bằng d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)). Bây giờ tt sẽ hiển thị ngày d được chuyển đổi thành múi giờ địa phương của bạn. Bạn vẫn có thể làm ngày tháng và tất cả nhưng thông tin tz ban đầu bị mất. Thông tin này là một bối cảnh cho ngày và nó thường rất quan trọng. Xem tại đây: stackoverflow.com/questions/279769/ Lời
Bernard

63

Là một bản cập nhật cho trạng thái của hệ sinh thái Ruby Date, DateTimeTimehiện có các phương thức để chuyển đổi giữa các lớp khác nhau. Sử dụng 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

1
DateTime.to_time trả về một 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

Giáo sư. Chỉ cần nhận ra rằng đây là sự cố Ruby on Rails không phải là vấn đề về Ruby: stackoverflow.com/questions/11277454/ . Họ thậm chí đã có một lỗi được gửi theo phương pháp này trong dòng 2.x và đánh dấu là "sẽ không sửa". Quyết định khủng khiếp IMHO. Hành vi Rails hoàn toàn phá vỡ giao diện Ruby bên dưới.
Jesse Clark

12

Thật không may, các chức năng DateTime.to_time, Time.to_datetimeTime.parsekhông giữ lại thông tin múi giờ. Tất cả mọi thứ được chuyển đổi thành múi giờ địa phương trong quá trình chuyển đổi. Mỹ phẩm ngày vẫn hoạt động nhưng bạn sẽ không thể hiển thị ngày với múi giờ ban đầu của chúng. Thông tin bối cảnh đó thường rất quan trọng. Ví dụ: nếu tôi muốn xem các giao dịch được thực hiện trong giờ làm việc ở New York, tôi có thể thích thấy chúng được hiển thị trong các múi giờ ban đầu của chúng, chứ không phải múi giờ địa phương của tôi ở Úc (trước 12 giờ so với New York).

Các phương thức chuyển đổi dưới đây giữ thông tin tz đó.

Đối với Ruby 1.8, hãy xem câu trả lời của Gordon Wilson . Đó là từ Ruby Cookbook cũ đáng tin cậy.

Đối với Ruby 1.9, nó dễ dàng hơn một chút.

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

Điều này in như sau

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

Thông tin DateTime gốc đầy đủ bao gồm cả múi giờ được lưu giữ.


2
Thời gian rất phức tạp, nhưng không có lý do gì để không cung cấp chuyển đổi tích hợp giữa các lớp thời gian tích hợp khác nhau. Bạn có thể ném RangeException nếu bạn cố lấy UNIX time_t cho 4713 BC (mặc dù giá trị âm BigNum sẽ đẹp hơn), nhưng ít nhất cung cấp phương thức cho nó.
Mark Reed

1
Time#to_datetimexuất hiện để bảo vệ tz cho tôi:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz

@Phrogz UTC bù không giống với múi giờ. Một cái là không đổi, cái kia có thể thay đổi vào các thời điểm khác nhau trong năm để tiết kiệm thời gian ban ngày. DateTime không có vùng, nó bỏ qua DST. Thời gian tôn trọng nó, nhưng chỉ trong TZ "cục bộ" (môi trường hệ thống).
Andrew Vit

1

Cải thiện giải pháp Gordon Wilson, đây là thử của tôi:

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

Bạn sẽ nhận được cùng một thời gian trong UTC, mất múi giờ (không may)

Ngoài ra, nếu bạn có ruby ​​1.9, chỉ cần thử to_timephương pháp


0

Trong khi thực hiện các chuyển đổi như vậy, người ta phải xem xét hành vi của các múi giờ trong khi chuyển đổi từ đối tượng này sang đối tượng khác. Tôi tìm thấy một số ghi chú tốt và các ví dụ trong stackoverflow này bài .

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.