module Sequel # The offset of the current time zone from UTC, in seconds. LOCAL_DATETIME_OFFSET_SECS = Time.now.utc_offset # The offset of the current time zone from UTC, as a fraction of a day. LOCAL_DATETIME_OFFSET = respond_to?(:Rational, true) ? Rational(LOCAL_DATETIME_OFFSET_SECS, 60*60*24) : LOCAL_DATETIME_OFFSET_SECS/60/60/24.0 @application_timezone = nil @database_timezone = nil @typecast_timezone = nil module Timezones attr_reader :application_timezone, :database_timezone, :typecast_timezone %w'application database typecast'.each do |t| class_eval("def #{t}_timezone=(tz); @#{t}_timezone = convert_timezone_setter_arg(tz) end", __FILE__, __LINE__) end # Convert the given Time/DateTime object into the database timezone, used when # literalizing objects in an SQL string. def application_to_database_timestamp(v) convert_output_timestamp(v, Sequel.database_timezone) end # Convert the given object into an object of Sequel.datetime_class in the # application_timezone. Used when coverting datetime/timestamp columns # returned by the database. def database_to_application_timestamp(v) convert_timestamp(v, Sequel.database_timezone) end # Sets the database, application, and typecasting timezones to the given timezone. def default_timezone=(tz) self.database_timezone = tz self.application_timezone = tz self.typecast_timezone = tz end # Convert the given object into an object of Sequel.datetime_class in the # application_timezone. Used when typecasting values when assigning them # to model datetime attributes. def typecast_to_application_timestamp(v) convert_timestamp(v, Sequel.typecast_timezone) end private # Convert the given DateTime to the given input_timezone, keeping the # same time and just modifying the timezone. def convert_input_datetime_no_offset(v, input_timezone) case input_timezone when :utc, nil v # DateTime assumes UTC if no offset is given when :local v.new_offset(LOCAL_DATETIME_OFFSET) - LOCAL_DATETIME_OFFSET else convert_input_datetime_other(v, input_timezone) end end # Convert the given DateTime to the given input_timezone that is not supported # by default (such as nil, :local, or :utc). Raises an error by default. # Can be overridden in extensions. def convert_input_datetime_other(v, input_timezone) raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}" end # Converts the object from a String, Array, Date, DateTime, or Time into an # instance of Sequel.datetime_class. If given an array or a string that doesn't # contain an offset, assume that the array/string is already in the given input_timezone. def convert_input_timestamp(v, input_timezone) case v when String v2 = Sequel.string_to_datetime(v) if !input_timezone || Date._parse(v).has_key?(:offset) v2 else # Correct for potentially wrong offset if string doesn't include offset if v2.is_a?(DateTime) v2 = convert_input_datetime_no_offset(v2, input_timezone) else # Time assumes local time if no offset is given v2 = v2.getutc + LOCAL_DATETIME_OFFSET_SECS if input_timezone == :utc end v2 end when Array y, mo, d, h, mi, s = v if datetime_class == DateTime convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s, 0), input_timezone) else Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s) end when Time if datetime_class == DateTime v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601) else v end when DateTime if datetime_class == DateTime v else v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s) end when Date convert_input_timestamp(v.to_s, input_timezone) else raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}" end end # Convert the given DateTime to the given output_timezone that is not supported # by default (such as nil, :local, or :utc). Raises an error by default. # Can be overridden in extensions. def convert_output_datetime_other(v, output_timezone) raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}" end # Converts the object to the given output_timezone. def convert_output_timestamp(v, output_timezone) if output_timezone if v.is_a?(DateTime) case output_timezone when :utc v.new_offset(0) when :local v.new_offset(LOCAL_DATETIME_OFFSET) else convert_output_datetime_other(v, output_timezone) end else v.send(output_timezone == :utc ? :getutc : :getlocal) end else v end end # Converts the given object from the given input timezone to the # application timezone using convert_input_timestamp and # convert_output_timestamp. def convert_timestamp(v, input_timezone) begin convert_output_timestamp(convert_input_timestamp(v, input_timezone), Sequel.application_timezone) rescue InvalidValue raise rescue => e raise convert_exception_class(e, InvalidValue) end end # Convert the timezone setter argument. Returns argument given by default, # exists for easier overriding in extensions. def convert_timezone_setter_arg(tz) tz end end extend Timezones end