Module: Incline::Extensions::DateTimeValue

Defined in:
lib/incline/extensions/date_time_value.rb

Overview

Patches the ActiveRecord DateTime value to accept more date formats.

Specifically this will allow ActiveRecord models to receive dates in US format or ISO format.

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Patches the ActiveRecord DateTime value type.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/incline/extensions/date_time_value.rb', line 15

def self.included(base)
  base.class_eval do

    private

    undef cast_value

    def cast_value(string)
      return nil if string.blank?
      return string if string.is_a?(::Time)

      if string.is_a?(::String)

        begin
          # if it matches either of our formats, we can try using it.
          if (match = (Incline::DateTimeFormats::US_DATE_FORMAT.match(string) || Incline::DateTimeFormats::ALMOST_ISO_DATE_FORMAT.match(string)))

            year = match['YEAR'].to_s.to_i
            year += 2000 if year < 50
            year += 1900 if year < 100
            month = match['MONTH'].to_s.to_i
            mday = match['DAY'].to_s.to_i

            # ensure the date portion is valid.
            dt =
                begin
                  Time.utc(year, month, mday)
                rescue
                  raise "Invalid date (#{$!.message})."
                end

            raise 'Invalid date (day of month is invalid for month).' unless dt.year == year && dt.month == month && dt.mday == mday

            hour = match['HOUR'].to_s.to_i
            minute = match['MINUTE'].to_s.to_i
            second = match['SECOND'].to_s.to_i

            # make sure the fraction is 6 chars in length, then convert to microseconds.
            micros = match['FRACTION'].to_s[0...6].ljust(6,'0').to_i

            if match.names.include?('AMPM')
              if match['AMPM'].to_s.upcase == 'P' && hour < 12
                hour += 12
              elsif match['AMPM'].to_s.upcase == 'A' && hour == 12
                hour = 0
              end
            end

            raise 'Invalid time (hour must be 0 to 24).' unless (0..24) === hour
            raise 'Invalid time (minute must be 0 to 59).' unless (0...60) === minute
            raise 'Invalid time (second must be 0 to 59).' unless (0...60) === second
            raise 'Invalid time (minute and second must be 0 if hour is 24).' if hour == 24 && (minute != 0 || second != 0)

            if hour == 24
              dt += 86400
              year = dt.year
              month = dt.month
              mday = dt.mday
              hour = 0
            end

            # compute the tz offset in seconds.
            offset =
                if match.names.include?('TZ')
                  if match['TZ']
                    tz = match['TZ'].to_s.gsub(':','').upcase
                    if %w(Z +0000 -0000).include?(tz)
                      0
                    else
                      (tz[0] == '-' ? -1 : 1) * ((tz[1..2].to_i * 3600) + (tz[3..4].to_i * 60))
                    end
                  else
                    nil
                  end
                else
                  nil
                end

            # use the new_time method to honor the ActiveRecord::Base.default_timezone
            new_time(year, month, mday, hour, minute, second, micros, offset)
          else
            # use the fallback if it doesn't match our formats.
            fallback_string_to_time(string)
          end
        rescue
          Incline::Log::warn "Failed to parse #{string.inspect}: #{$!.message}"
          nil
        end

      elsif string.respond_to?(:to_time)
        begin
          string.to_time
        rescue
          Incline::Log::warn "Failed to convert #{string.inspect} to time: #{$!.message}"
          nil
        end
      else
        nil
      end
    end
  end
end