Time zones

Active Record stores a datetime in UTC unless ActiveRecord.default_timezone says :local, and a value a block quotes follows it: a Time on the right of a comparison is converted the way Active Record converts one, so where { :created_at > Time.current - 1.day } is right whatever Time.zone is. What is not converted is what the database says the time is.

current_timestamp and its relatives are the server's clock in the session's zone. Active Record sets PostgreSQL's session to UTC and SQLite's clock is UTC already, so on those two the clock and the stored values agree. MySQL's session keeps the server's own zone, so a server that sits in Tokyo answers CURRENT_TIMESTAMP nine hours off the values Active Record stored; set the session in database.ymlvariables: { time_zone: "+00:00" } — or write Time.current in the block instead, which is right everywhere. SQL Server answers in its operating system's zone and Oracle in the client's, where Time.current is again the one to reach for. current_date is today in UTC, which in Tokyo is still yesterday until nine in the morning; Date.current says the day meant.

extract, date_trunc and a group by day cut at UTC's midnight. A zone without daylight saving is a fixed offset away — date_trunc("day", :created_at + 9.hours) — and one with it needs the database's own AT TIME ZONE, through sql.

A datetime computed in the query, (:created_at + 1.hour).as(:later), has no column to take a type from, so it comes back as a Time in UTC — as text on SQLite — with no Time.zone applied; in_time_zone on the Ruby side does that.