Time zones trong Ruby on Rails
Bài đăng này đã không được cập nhật trong 8 năm
Rails cung cấp công cụ tuyệt vời để làm việc với các múi giờ nhưng trong quá trình sử dụng, vẫn có nhiều lỗi dễ gặp phải. Bài viết này nhằm mục đích làm sáng tỏ về những lỗi đó và cung cấp cách giải quyết.
Tôi đã mất khá lớn thời gian nghĩ rằng mình có thể xử lý tất cả các vấn đề về thời gian với Rails. Đừng như vậy. Bạn nên dành nhiều thời gian tìm hiểu trước khi hoàn toàn tin tưởng nó. Hãy xem xét những điều sau đây: cơ sở dữ liệu, máy chủ, máy dev, hệ thống cấu hình, người dùng và trình duyệt.
Cấu hình ứng dụng Rails
Để sử dụng các công cụ như một lập trình viên Rails, cần cấu hình config.time_zone
trong file config/application.rb
. ActiveRecord sẽ giúp bạn chuyển đổi thời gian qua lại giữa các múi giờ tùy chọn.
Xử lý các thông tin thời gian
Các vấn đề sẽ xảy ra khi cần xử lý thông tin thời gian trước khi lưu lại, thường gặp ở các xử lý sau:
Parsing
Chú ý là không bao giờ chuyển đổi thời gian khi không xác định múi giờ. Cách tốt nhất là dùng Time.zone.parse (phương thức này sẽ sử dụng múi giờ trong config.time_zone) thay thế cho Time.parse (sử dụng múi giờ trên thiết bị).
Numeric và ActiveRecord attributes
Phương thức gọi 2.hours.ago
sử dụng múi giờ mà bạn đã cấu hình, điều này cũng đúng với các thuộc tính giờ trên ActiveRecord model:
post = Post.first
post.published_at #=> Thu, 22 Mar 2012 00:00:00 CDT -05:00
ActiveRecord dẫn đến giờ UTC trong cơ sở dữ liệu và chuyển đổi sang config.time_zone
của bạn.
Date & time
Time
chứa thông tin về Date
nhưng Date
không chứa thông tin về Time
.
Thậm chí nếu bạn nghĩ không cần quan tâm đến điều này thì sớm muộn bạn cũng sẽ nhận ra nó. Sẽ an toàn nếu sử dụng Time
(hoặc DateTime
nếu cần sử dụng khoảng thời gian xa hơn).
Nhưng ví dụ bạn đang trong một ngày mà bạn cần biết thời gian của nó, để đảm bảo chuyển nó sang múi giờ cấu hình của bạn, sử dụng:
1.day.from_now # => Fri, 03 Mar 2012 22:04:47 JST +09:00
Date.current.in_time_zone # => Fri, 02 Mar 2012 00:00:00 JST +09:00
Không dùng:
Date.today.to_time # => 2012-03-02 00:00:00 +0100
Querying
Rails biết chắc chắn rằng thời gian được lưu trong cơ sở dữ liệu là UTC nên nó luôn chuyển đổi thời gian từ các múi giờ khác sang UTC.
Post.where(["posts.published_at > ?", Time.current])
Chỉ cần bạn không thực hiện cấu trúc lại truy vấn bằng tay mà luôn sử dụng Time.current
là căn cứ thì bạn sẽ luôn an toàn.
Làm việc với API
Supplying
Bạn muốn tạo một API để sử dụng? Hãy chắc chắn sẽ gửi toàn bộ thông tin như UTC (và chỉ rõ như trường hợp dưới đây).
Time.current.utc.iso8601 #=> "2012-03-16T14:55:33Z"
Đọc thêm về iso8601 tại đây.
Consuming
Khi bạn có được những thông tin từ một API
bên ngoài mà bạn không có quyền kiểm soát nó, đơn giản bạn chỉ cần chuyển định dạng và múi giờ của nó. Bởi vì Time.zone.parse
có thể không làm việc với các định dạng mà bạn nhận được, bạn có thể cần dùng:
Time.strptime(time_string, "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Làm việc với nhiều múi giờ
Nhiều hệ thống hỗ trợ môi trường làm việc quốc tế, tức là người dùng có thể ở nhiều múi giờ khác nhau. Để làm được điều này, cần phải lưu trữ múi giờ của mỗi người dùng (có thể chỉ là một trong những chuỗi múi giờ trong rake time:zones:all
). Sau đó, để sử dụng time zone theo dạng này thì mô hình phổ biến nhất là tạo một private method trong ActionController.
around_action :user_time_zone, if: :current_user
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
Điều này tương tự như config.time_zone
nhưng mỗi yêu cầu có một cơ sở khác nhau. Tuy nhiên vẫn khuyến nghị sử dụng config.time_zone
như là cài đặt mặc định tốt nhất cho người dùng.
Kiểm thử
Tất cả những điều trên là những gì bạn phải kiểm tra trong ứng dụng của mình. Vấn đề là bạn sử dụng chính máy của mình làm server nên client và server luôn cùng múi giờ. Đây là trường hợp hiếm khi xảy ra khi bạn phát hành sản phẩm.
Zonebie là một gem giúp bạn kiểm tra điều đó, ít nhất bạn phải đảm bảo rằng chương trình test của bạn chạy với Time.zone
cài đặt những múi giờ khác máy tính của bạn.
Cheat sheet
Nên
2.hours.ago # => Thu, 27 Aug 2015 14:39:36 AFT +04:30
1.day.from_now # => Fri, 28 Aug 2015 16:39:36 AFT +04:30
Time.zone.parse("2015-08-27T12:09:36Z") # => Thu, 27 Aug 2015 16:39:36 AFT +04:30
Time.current # => Thu, 27 Aug 2015 16:39:36 AFT +04:30
Time.current.utc.iso8601 # When supliyng an API ("2015-08-27T12:09:36Z")
Time.strptime("2015-08-27T12:09:36Z", "%Y-%m-%dT%H:%M:%S%z").in_time_zone # If you can’t use Time.zone.parse (Thu, 27 Aug 2015 16:39:36 AFT +04:30)
Date.current # If you really can’t have a Time or DateTime for some reason (Thu, 27 Aug 2015)
Date.current.in_time_zone # If you have a date and want to make the best out of it (Thu, 27 Aug 2015 00:00:00 AFT +04:30)
Không nên
Time.now # Returns system time and ignores your configured time zone. (2015-08-27 14:09:36 +0200)
Time.parse("2015-08-27T12:09:36Z") # Will assume time string given is in the system’s time zone. (2015-08-27 12:09:36 UTC)
Time.strptime("2015-08-27T12:09:36Z", "%Y-%m-%dT%H:%M:%S%z") # Same problem as with Time.parse. (2015-08-27 12:09:36 UTC)
Date.today # This could be yesterday or tomorrow depending on the machine’s time zone, see https://github.com/ramhoj/time-zone-article/issues/1 for more info. (Thu, 27 Aug 2015)
Kết luận
Trên đây là những chú ý trong quá trình sử dụng time zone trong Rails. Hy vọng bạn có thể sử dụng được điều gì đó từ bài viết này.
- Ruby and Rails version
- Ngày xuất bản: 2012/03/20
- Ngày cập nhật cuối cùng: 2016/06/22
- Rails version: 4. 2. 6
- Ruby version: 2. 3. 1p112 (54.768)
- Hệ điều hành: Mac OS X 10. 11. 4 (El Capitan)
Đây là git repository bạn có thể clone:
git clone git@github.com:ramhoj/time-zone-article.git
cd time-zone-article
bundle install
rake db:create:all db:migrate db:test:prepare
rspec spec/
Ứng dụng đang được chạy trên những phiên bản liệt kê bên trên. Nếu bạn muốn chắc chắn ứng dụng chạy được trên các phiên bản mà bạn đang sử dụng thì vui lòng fork repository và chạy thử để kiểm tra.
Nguồn: https://www.varvet.com/blog/working-with-time-zones-in-ruby-on-rails/
All rights reserved