+1

Time zones trong Ruby on Rails

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí