2013-05-29 30 views
67

Tôi có bản ghi foo trong cơ sở dữ liệu có thuộc tính :start_time:timezone.Ruby/Rails - Thay đổi múi giờ của Thời gian, mà không thay đổi giá trị

Ví dụ: :start_time Ví dụ: UTC - 2001-01-01 14:20:00. Ví dụ: :timezone là một chuỗi - America/New_York.

Tôi muốn tạo đối tượng Thời gian mới với giá trị :start_time nhưng có múi giờ được chỉ định bởi :timezone. Tôi không muốn tải :start_time và sau đó chuyển đổi thành :timezone, vì Rails sẽ thông minh và cập nhật thời gian từ UTC để nhất quán với múi giờ đó.

Hiện nay,

t = foo.start_time 
=> 2000-01-01 14:20:00 UTC 
t.zone 
=> "UTC" 
t.in_time_zone("America/New_York") 
=> Sat, 01 Jan 2000 09:20:00 EST -05:00 

Thay vào đó, tôi muốn nhìn thấy

=> Sat, 01 Jan 2000 14:20:00 EST -05:00 

tức. Tôi muốn làm:

t 
=> 2000-01-01 14:20:00 UTC 
t.zone = "America/New_York" 
=> "America/New_York" 
t 
=> 2000-01-01 14:20:00 EST 
+1

Có lẽ điều này sẽ giúp: http: //api.rubyonrails. org/classes/Time.html # method-c-use_zone – MrYoshiji

+2

Tôi không nghĩ rằng bạn đang sử dụng múi giờ chính xác. Nếu bạn lưu nó vào db của bạn dưới dạng UTC từ địa phương, có gì sai khi phân tích cú pháp thông qua thời gian địa phương và lưu nó qua utc tương đối của nó? – Trip

+0

Ya ... Tôi nghĩ rằng để nhận được sự trợ giúp tốt nhất bạn có thể cần giải thích * tại sao * bạn sẽ cần phải làm điều này? Tại sao bạn lưu trữ sai thời gian cho cơ sở dữ liệu ở nơi đầu tiên? – nzifnab

Trả lời

46

Âm thanh như bạn muốn một cái gì đó dọc theo dòng của

ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t) 

này nói chuyển đổi theo giờ địa phương này (sử dụng vùng) để utc. Nếu bạn đã Time.zone thiết lập sau đó bạn có thể của khóa học để

Time.zone.local_to_utc(t) 

này sẽ không sử dụng múi giờ gắn liền với t - nó giả định rằng đó là địa phương để các múi giờ bạn đang chuyển đổi từ.

Một trường hợp cạnh để bảo vệ chống lại ở đây là chuyển tiếp DST: giờ địa phương bạn chỉ định có thể không tồn tại hoặc có thể không rõ ràng.

+11

Sự kết hợp giữa local_to_utc và Time.use_zone là những gì tôi cần: 'Time.use_zone (self.timezone) {Time.zone.local_to_utc (t)} .localtime' – rwb

+0

Bạn đề xuất gì đối với chuyển tiếp DST? Giả sử thời gian đích cần chuyển đổi là sau khi chuyển đổi D/ST trong khi Time.now là trước khi thay đổi. Liệu điều đó có hiệu quả ? –

6

bạn cần phải thêm thời gian bù đắp cho thời gian của mình sau khi bạn chuyển đổi.

cách dễ nhất để làm điều này là

t = Foo.start_time.in_time_zone("America/New_York") 
t -= t.utc_offset 

không chắc chắn lý do tại sao bạn sẽ muốn làm điều này .... tho lẽ tốt nhất để thực sự làm việc với thời gian theo cách mà họ đang xây dựng. Tôi đoán một số nền tảng về lý do tại sao bạn cần phải thay đổi thời gian và múi giờ sẽ hữu ích.

+0

Điều này làm việc cho tôi. Điều này sẽ chính xác trong các ca tiết kiệm ánh sáng ban ngày, miễn là giá trị offset được thiết lập khi sử dụng. Nếu sử dụng các đối tượng DateTime, người ta có thể cộng hoặc trừ "offset.seconds" từ nó. – JosephK

4

Thực ra, tôi nghĩ rằng bạn cần phải trừ đi bù đắp sau khi bạn chuyển đổi nó, như trong:

1.9.3p194 :042 > utc_time = Time.now.utc 
=> 2013-05-29 16:37:36 UTC 
1.9.3p194 :043 > local_time = utc_time.in_time_zone('America/New_York') 
=> Wed, 29 May 2013 12:37:36 EDT -04:00 
1.9.3p194 :044 > desired_time = local_time-local_time.utc_offset 
=> Wed, 29 May 2013 16:37:36 EDT -04:00 
3

Phụ thuộc vào nơi bạn sẽ sử dụng Thời gian này.

Khi thời gian của bạn là một thuộc tính

Nếu thời gian được sử dụng như một thuộc tính, bạn có thể sử dụng cùng một date_time_attribute gem:

class Task 
    include DateTimeAttribute 
    date_time_attribute :due_at 
end 

task = Task.new 
task.due_at_time_zone = 'Moscow' 
task.due_at      # => Mon, 03 Feb 2013 22:00:00 MSK +04:00 
task.due_at_time_zone = 'London' 
task.due_at      # => Mon, 03 Feb 2013 22:00:00 GMT +00:00 

Khi bạn thiết lập một riêng biệt biến

Sử dụng cùng một số date_time_attribute gem:

my_date_time = DateTimeAttribute::Container.new(Time.zone.now) 
my_date_time.date_time   # => 2001-02-03 22:00:00 KRAT +0700 
my_date_time.time_zone = 'Moscow' 
my_date_time.date_time   # => 2001-02-03 22:00:00 MSK +0400 
1
def relative_time_in_time_zone(time, zone) 
    DateTime.parse(time.strftime("%d %b %Y %H:%M:%S #{time.in_time_zone(zone).formatted_offset}")) 
end 

Chức năng nhỏ nhanh Tôi đã giải quyết công việc. Nếu ai đó có một cách hiệu quả hơn để làm điều này xin vui lòng gửi nó!

15

Nếu bạn đang sử dụng Rails, đây là một phương pháp dọc theo dòng của Eric Walsh của câu trả lời:

def set_in_timezone(time, zone) 
    Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) } 
end 
+1

Và để chuyển đổi đối tượng DateTime trở lại đối tượng TimeWithZone, chỉ cần tack vào '.in_time_zone' đến cuối. – Sooie

+0

@Brian Murphy-Dye Tôi gặp sự cố với Daylight Saving Time khi sử dụng chức năng này. Bạn có thể chỉnh sửa câu hỏi của mình để cung cấp giải pháp phù hợp với DST không? Có thể thay thế 'Time.zone.now' thành nội dung gần nhất với thời gian bạn muốn thay đổi sẽ hoạt động? –

0

Tôi đã tạo ra vài phương pháp helper một trong số đó chỉ làm điều tương tự như được yêu cầu bởi các gốc tác giả của bài đăng tại Ruby/Rails - Change the timezone of a Time, without changing the value.

Ngoài ra tôi đã ghi nhận vài đặc thù tôi quan sát và cũng những người giúp đỡ chứa phương pháp để hoàn toàn bỏ qua tiết kiệm tự động ngày ánh sáng được áp dụng trong khi thời gian chuyển đổi mà không phải là có sẵn out-of-the-box trong khuôn khổ Rails:

def utc_offset_of_given_time(time, ignore_dst: false) 
    # Correcting the utc_offset below 
    utc_offset = time.utc_offset 

    if !!ignore_dst && time.dst? 
     utc_offset_ignoring_dst = utc_offset - 3600 # 3600 seconds = 1 hour 
     utc_offset = utc_offset_ignoring_dst 
    end 

    utc_offset 
    end 

    def utc_offset_of_given_time_ignoring_dst(time) 
    utc_offset_of_given_time(time, ignore_dst: true) 
    end 

    def change_offset_in_given_time_to_given_utc_offset(time, utc_offset) 
    formatted_utc_offset = ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, false) 

    # change method accepts :offset option only on DateTime instances. 
    # and also offset option works only when given formatted utc_offset 
    # like -0500. If giving it number of seconds like -18000 it is not 
    # taken into account. This is not mentioned clearly in the documentation 
    # , though. 
    # Hence the conversion to DateTime instance first using to_datetime. 
    datetime_with_changed_offset = time.to_datetime.change(offset: formatted_utc_offset) 

    Time.parse(datetime_with_changed_offset.to_s) 
    end 

    def ignore_dst_in_given_time(time) 
    return time unless time.dst? 

    utc_offset = time.utc_offset 

    if utc_offset < 0 
     dst_ignored_time = time - 1.hour 
    elsif utc_offset > 0 
     dst_ignored_time = time + 1.hour 
    end 

    utc_offset_ignoring_dst = utc_offset_of_given_time_ignoring_dst(time) 

    dst_ignored_time_with_corrected_offset = 
     change_offset_in_given_time_to_given_utc_offset(dst_ignored_time, utc_offset_ignoring_dst) 

    # A special case for time in timezones observing DST and which are 
    # ahead of UTC. For e.g. Tehran city whose timezone is Iran Standard Time 
    # and which observes DST and which is UTC +03:30. But when DST is active 
    # it becomes UTC +04:30. Thus when a IRDT (Iran Daylight Saving Time) 
    # is given to this method say '05-04-2016 4:00pm' then this will convert 
    # it to '05-04-2016 5:00pm' and update its offset to +0330 which is incorrect. 
    # The updated UTC offset is correct but the hour should retain as 4. 
    if utc_offset > 0 
     dst_ignored_time_with_corrected_offset -= 1.hour 
    end 

    dst_ignored_time_with_corrected_offset 
    end 

Ví dụ có thể được thử trên đường ray giao diện điều khiển hoặc một kịch bản ruby ​​sau khi gói các phương pháp trên trong một lớp học hoặc mô-đun:

dd1 = '05-04-2016 4:00pm' 
dd2 = '07-11-2016 4:00pm' 

utc_zone = ActiveSupport::TimeZone['UTC'] 
est_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] 
tehran_zone = ActiveSupport::TimeZone['Tehran'] 

utc_dd1 = utc_zone.parse(dd1) 
est_dd1 = est_zone.parse(dd1) 
tehran_dd1 = tehran_zone.parse(dd1) 

utc_dd1.dst? 
est_dd1.dst? 
tehran_dd1.dst? 

ignore_dst = true 
utc_to_est_time = utc_dd1.in_time_zone(est_zone.name) 
if utc_to_est_time.dst? && !!ignore_dst 
    utc_to_est_time = ignore_dst_in_given_time(utc_to_est_time) 
end 

puts utc_to_est_time 

Hope this helps.

1

tôi đã dành thời gian đáng kể phải vật lộn với các múi giờ là tốt, và sau khi mày mò với Ruby 1.9.3 nhận ra rằng bạn không cần phải chuyển đổi sang một biểu tượng múi giờ được đặt tên trước khi chuyển đổi:

my_time = Time.now 
west_coast_time = my_time.in_time_zone(-8) # Pacific Standard Time 
east_coast_time = my_time.in_time_zone(-5) # Eastern Standard Time 

Điều này có nghĩa là bạn có thể tập trung vào việc thiết lập thời gian thích hợp trước tiên trong vùng bạn muốn, cách bạn nghĩ về nó (ít nhất trong đầu tôi phân vùng theo cách này), và sau đó chuyển đổi ở cuối vùng bạn muốn xác minh logic kinh doanh của bạn với.

Điều này cũng hoạt động đối với Ruby 2.3.1.

2

Tôi vừa phải đối mặt với cùng một vấn đề và đây là những gì tôi sẽ làm:

t = t.asctime.in_time_zone("America/New York") 

Đây là documentation on asctime

+0

Nó chỉ làm những gì tôi mong đợi – Kaz