แปลงเป็น / จาก DateTime และ Time ใน Ruby


133

คุณแปลงระหว่างวัตถุ DateTime และ Time ใน Ruby ได้อย่างไร?


1
ฉันไม่แน่ใจว่านี่ควรเป็นคำถามแยกกันหรือไม่ แต่คุณจะแปลงระหว่างวันที่และเวลาได้อย่างไร
Andrew Grimm

8
คำตอบที่ได้รับการยอมรับและมีคะแนนสูงสุดไม่ใช่คำตอบที่ถูกต้องที่สุดอีกต่อไปสำหรับ Ruby เวอร์ชันใหม่ ดูคำตอบโดย @theTinManและโดย@PatrickMcKenzieด้านล่าง
Phrogz

คำตอบ:


51

คุณจะต้องมีการแปลงสองรายการที่แตกต่างกันเล็กน้อย

ในการแปลงจาก Time เป็น DateTimeคุณสามารถแก้ไขคลาสเวลาได้ดังนี้:

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

การปรับคล้ายกับวันที่จะช่วยให้คุณสามารถแปลงไป DateTime 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

โปรดทราบว่าคุณต้องเลือกระหว่างเวลาท้องถิ่นและเวลา GM / UTC

ทั้งโค้ดข้างต้นถูกนำมาจากโอเรลลีทับทิมตำรา นโยบายการใช้รหัสซ้ำของพวกเขาอนุญาตสิ่งนี้


5
สิ่งนี้จะหยุดในวันที่ 1.9 โดยที่ DateTime # sec_fraction ส่งกลับจำนวนมิลลิวินาทีในหนึ่งวินาที สำหรับ 1.9 ที่คุณต้องการใช้: usec = dest.sec_fraction * 10 ** 6
dkubb

186
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)

13
+1 สิ่งนี้อาจไม่ได้ประสิทธิภาพสูงสุดในการดำเนินการ แต่ใช้งานได้กระชับและอ่านง่ายมาก
Walt Jones

6
น่าเสียดายที่สิ่งนี้ใช้ได้ผลเฉพาะเมื่อต้องรับมือกับเวลาท้องถิ่นเท่านั้น หากคุณเริ่มต้นด้วย DateTime หรือ Time ด้วยเขตเวลาอื่นฟังก์ชันแยกวิเคราะห์จะแปลงเป็นเขตเวลาท้องถิ่น โดยพื้นฐานแล้วคุณจะสูญเสียเขตเวลาเดิมไป
เบอร์นาร์ด

6
สำหรับ Ruby 1.9.1 DateTime.parse จะรักษาเขตเวลา (ฉันไม่สามารถเข้าถึงเวอร์ชันก่อนหน้านี้ได้) Time.parse ไม่สงวนเขตเวลาเนื่องจากเป็นเขตเวลามาตรฐาน POSIX ซึ่งฉันเชื่อว่าเป็นผลต่างจำนวนเต็มจาก epoch การแปลงเป็นเวลาควรมีลักษณะการทำงานเหมือนกัน
anshul

1
คุณถูก. DateTime.parse ทำงานใน 1.9.1 แต่ไม่ใช่ Time.parse ไม่ว่าในกรณีใดข้อผิดพลาดจะน้อยกว่า (สม่ำเสมอ) และมีแนวโน้มที่จะใช้ DateTime.new (... ) และ Time.new (.. ) ได้เร็วขึ้น ดูคำตอบของฉันสำหรับรหัสตัวอย่าง
Bernard

1
สวัสดี @anshul ฉันไม่ได้หมายความว่าฉันกำลังระบุ :-) ข้อมูลเขตเวลาจะไม่ถูกเก็บไว้เมื่อใช้ Time.parse () ง่ายต่อการทดสอบ ในโค้ดของคุณด้านบนให้แทนที่ d = DateTime.now ด้วย d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)) tt จะแสดงวันที่ d แปลงเป็นเขตเวลาท้องถิ่นของคุณ คุณยังคงสามารถคำนวณวันที่ได้ แต่ข้อมูล tz เดิมจะหายไป ข้อมูลนี้เป็นบริบทของวันที่และมักมีความสำคัญ ดูได้ที่นี่: stackoverflow.com/questions/279769/…
Bernard

63

ในฐานะที่เป็นอัปเดตสถานะของระบบนิเวศทับทิม, Date, DateTimeและTimeตอนนี้มีวิธีการที่จะแปลงระหว่างเรียนต่างๆ การใช้ 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 ส่งกลับ 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

อ๊ะ. เพิ่งรู้ว่านี่คือปัญหา Ruby on Rails ไม่ใช่ปัญหา Ruby: stackoverflow.com/questions/11277454/… . พวกเขายังมีข้อบกพร่องที่ยื่นต่อวิธีนี้ในบรรทัด 2.x และทำเครื่องหมายว่า "จะไม่แก้ไข" การตัดสินใจที่น่ากลัว IMHO ลักษณะการทำงานของ Rails ทำลายอินเทอร์เฟซ Ruby พื้นฐานโดยสิ้นเชิง
Jesse Clark

12

น่าเสียดายที่ฟังก์ชันDateTime.to_time, Time.to_datetimeและTime.parseฟังก์ชันไม่เก็บข้อมูลเขตเวลาไว้ ทุกอย่างจะถูกแปลงเป็นเขตเวลาท้องถิ่นในระหว่างการแปลง การคำนวณวันที่ยังคงใช้งานได้ แต่คุณจะไม่สามารถแสดงวันที่ด้วยเขตเวลาเดิมได้ ข้อมูลบริบทนั้นมักมีความสำคัญ ตัวอย่างเช่นหากฉันต้องการดูธุรกรรมที่ดำเนินการในช่วงเวลาทำการในนิวยอร์กฉันอาจต้องการเห็นการทำธุรกรรมดังกล่าวแสดงในเขตเวลาเดิมไม่ใช่เขตเวลาท้องถิ่นของฉันในออสเตรเลีย (ซึ่งก่อนนิวยอร์ก 12 ชั่วโมง)

วิธีการแปลงด้านล่างจะเก็บข้อมูล tz นั้นไว้

สำหรับ Ruby 1.8 ดูที่คำตอบกอร์ดอนวิลสัน มันมาจาก Ruby Cookbook เก่าแก่ที่น่าเชื่อถือ

สำหรับ Ruby 1.9 นั้นง่ายกว่าเล็กน้อย

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

สิ่งนี้จะพิมพ์สิ่งต่อไปนี้

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

ข้อมูล DateTime ดั้งเดิมเต็มรูปแบบรวมถึงเขตเวลาจะถูกเก็บไว้


2
เวลามีความซับซ้อน แต่ไม่มีข้อแก้ตัวใด ๆ ที่จะไม่ให้การแปลงในตัวระหว่างคลาสเวลาในตัวที่แตกต่างกัน คุณสามารถโยน RangeException ได้หากคุณพยายามรับ UNIX time_t สำหรับ 4713 BC (แม้ว่าค่าเชิงลบของ BigNum จะดีกว่า) แต่อย่างน้อยก็ให้วิธีการสำหรับมัน
Mark Reed

1
Time#to_datetimeดูเหมือนจะรักษา tz สำหรับฉัน:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz

@Phrogz UTC offset ไม่เหมือนกับเขตเวลา ค่าหนึ่งคงที่ส่วนอีกค่าหนึ่งสามารถเปลี่ยนแปลงได้ในช่วงเวลาต่างๆของปีสำหรับเวลาออมแสง DateTime ไม่มีโซนจะละเว้น DST เวลาเคารพมัน แต่เฉพาะใน TZ "ท้องถิ่น" (สภาพแวดล้อมระบบ)
Andrew Vit

1

การปรับปรุงโซลูชัน Gordon Wilson นี่คือความพยายามของฉัน:

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

คุณจะได้รับเวลาเดียวกันใน UTC โดยสูญเสียเขตเวลา (น่าเสียดาย)

นอกจากนี้หากคุณมีทับทิม 1.9 ให้ลองใช้to_timeวิธีนี้


0

ในขณะที่ทำการแปลงดังกล่าวเราควรคำนึงถึงพฤติกรรมของเขตเวลาในขณะที่แปลงจากวัตถุหนึ่งไปเป็นอีกวัตถุหนึ่ง ฉันพบบันทึกย่อและตัวอย่างที่ดีในโพสต์ stackoverflow นี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.