module Sequel::Timezones

def application_to_database_timestamp(v)

literalizing objects in an SQL string.
Convert the given +Time+/+DateTime+ object into the database timezone, used when
def application_to_database_timestamp(v)
  convert_output_timestamp(v, Sequel.database_timezone)
end

def convert_input_datetime_no_offset(v, input_timezone)

same time and just modifying the timezone.
Convert the given +DateTime+ to the given input_timezone, keeping the
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
    offset = local_offset_for_datetime(v)
    v.new_offset(offset) - offset
  else
    convert_input_datetime_other(v, input_timezone)
  end
end

def convert_input_datetime_other(v, input_timezone)

Can be overridden in extensions.
by default (i.e. one other than +nil+, :local, or :utc). Raises an +InvalidValue+ by default.
Convert the given +DateTime+ to the given input_timezone that is not supported
def convert_input_datetime_other(v, input_timezone)
  raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
end

def convert_input_timestamp(v, input_timezone)

contain an offset, assume that the array/string is already in the given +input_timezone+.
instance of Sequel.datetime_class. If given an array or a string that doesn't
Converts the object from a +String+, +Array+, +Date+, +DateTime+, or +Time+ into an
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 + v2.utc_offset if input_timezone == :utc
      end
      v2
    end
  when Array
    y, mo, d, h, mi, s, ns, off = v
    if datetime_class == DateTime
      s += (defined?(Rational) ? Rational(ns, 1000000000) : ns/1000000000.0) if ns
      if off
        DateTime.civil(y, mo, d, h, mi, s, off)
      else
        convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s), input_timezone)
      end
    else
      Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
    end
  when Hash
    ary = [:year, :month, :day, :hour, :minute, :second, :nanos].map{|x| (v[x] || v[x.to_s]).to_i}
    if (offset = (v[:offset] || v['offset']))
      ary << offset
    end
    convert_input_timestamp(ary, input_timezone)
    convert_input_timestamp(ary, input_timezone)
  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
  else
    raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
  end
end

def convert_output_datetime_other(v, output_timezone)

Can be overridden in extensions.
by default (i.e. one other than +nil+, :local, or :utc). Raises an +InvalidValue+ by default.
Convert the given +DateTime+ to the given output_timezone that is not supported
def convert_output_datetime_other(v, output_timezone)
  raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
end

def convert_output_timestamp(v, output_timezone)

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_offset_for_datetime(v))
      else
        convert_output_datetime_other(v, output_timezone)
      end
    else
      v.send(output_timezone == :utc ? :getutc : :getlocal)
    end
  else
    v
  end
end

def convert_timestamp(v, input_timezone)

+convert_output_timestamp+.
+application_timezone+ using +convert_input_timestamp+ and
Converts the given object from the given input timezone to the
def convert_timestamp(v, input_timezone)
  begin
    if v.is_a?(Date) && !v.is_a?(DateTime)
      # Dates handled specially as they are assumed to already be in the application_timezone
      if datetime_class == DateTime
        DateTime.civil(v.year, v.month, v.day, 0, 0, 0, application_timezone == :local ? (defined?(Rational) ? Rational(Time.local(v.year, v.month, v.day).utc_offset, 86400) : Time.local(v.year, v.month, v.day).utc_offset/86400.0) : 0)
      else
        Time.send(application_timezone == :utc ? :utc : :local, v.year, v.month, v.day)
      end
    else
      convert_output_timestamp(convert_input_timestamp(v, input_timezone), application_timezone)
    end
  rescue InvalidValue
    raise
  rescue => e
    raise convert_exception_class(e, InvalidValue)
  end
end

def convert_timezone_setter_arg(tz)

exists for easier overriding in extensions.
Convert the timezone setter argument. Returns argument given by default,
def convert_timezone_setter_arg(tz)
  tz
end

def database_to_application_timestamp(v)

returned by the database.
+application_timezone+. Used when coverting datetime/timestamp columns
Convert the given object into an object of Sequel.datetime_class in the
def database_to_application_timestamp(v)
  convert_timestamp(v, Sequel.database_timezone)
end

def default_timezone=(tz)

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

def local_offset_for_datetime(dt)

Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included.
def local_offset_for_datetime(dt)
  time_offset_to_datetime_offset Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec).utc_offset
end

def time_offset_to_datetime_offset(offset_secs)

Caches offset conversions to avoid excess Rational math.
def time_offset_to_datetime_offset(offset_secs)
  @local_offsets ||= {}
  @local_offsets[offset_secs] ||= respond_to?(:Rational, true) ? Rational(offset_secs, 60*60*24) : offset_secs/60/60/24.0
end

def typecast_to_application_timestamp(v)

to model datetime attributes.
+application_timezone+. Used when typecasting values when assigning them
Convert the given object into an object of Sequel.datetime_class in the
def typecast_to_application_timestamp(v)
  convert_timestamp(v, Sequel.typecast_timezone)
end