app/models/observation.rb
def munge_observed_on_with_chronic
    if observed_on_string.blank?
      self.observed_on = nil
      self.time_observed_at = nil
      return true
    end
    date_string = observed_on_string.strip
    tz_abbrev_pattern = /\s\(?([A-Z]{3,})\)?$/ # ends with (PDT)
    tz_offset_pattern = /([+-]\d{4})$/ # contains -0800
    tz_js_offset_pattern = /(GMT)?([+-]\d{4})/ # contains GMT-0800
    tz_colon_offset_pattern = /(GMT|HSP)([+-]\d+:\d+)/ # contains (GMT-08:00)
    tz_failed_abbrev_pattern = /\(#{tz_colon_offset_pattern}\)/
    
    if date_string =~ /#{tz_js_offset_pattern} #{tz_failed_abbrev_pattern}/
      date_string = date_string.sub(tz_failed_abbrev_pattern, '').strip
    end

    # Rails timezone support doesn't seem to recognize this abbreviation, and
    # frankly I have no idea where ActiveSupport::TimeZone::CODES comes from.
    # In case that ever stops working or a less hackish solution is required,
    # check out https://gist.github.com/kueda/3e6f77f64f792b4f119f
    tz_abbrev = date_string[tz_abbrev_pattern, 1]
    tz_abbrev = 'CET' if tz_abbrev == 'CEST'
    
    if parsed_time_zone = ActiveSupport::TimeZone::CODES[tz_abbrev]
      date_string = observed_on_string.sub(tz_abbrev_pattern, '')
      date_string = date_string.sub(tz_js_offset_pattern, '').strip
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_offset_pattern, 1]) && 
        (n = offset.to_f / 100) && 
        (key = n == 0 ? 0 : n.floor + (n%n.floor)/0.6) && 
        (parsed_time_zone = ActiveSupport::TimeZone[key])
      date_string = date_string.sub(tz_offset_pattern, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_js_offset_pattern, 2]) && 
        (n = offset.to_f / 100) && 
        (key = n == 0 ? 0 : n.floor + (n%n.floor)/0.6) && 
        (parsed_time_zone = ActiveSupport::TimeZone[key])
      date_string = date_string.sub(tz_js_offset_pattern, '')
      date_string = date_string.sub(/^(Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s+/i, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_colon_offset_pattern, 2]) && 
        (t = Time.parse(offset)) && 
        (parsed_time_zone = ActiveSupport::TimeZone[t.hour+t.min/60.0])
      date_string = date_string.sub(/#{tz_colon_offset_pattern}|#{tz_failed_abbrev_pattern}/, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    end
    
    date_string.sub!('T', ' ') if date_string =~ /\d{4}-\d{2}-\d{2}T/
    date_string.sub!(/(\d{2}:\d{2}:\d{2})\.\d+/, '\\1')
    
    # strip leading month if present
    date_string.sub!(/^[A-z]{3} ([A-z]{3})/, '\\1')

    # strip paranthesized stuff
    date_string.gsub!(/\(.*\)/, '')
    
    # Set the time zone appropriately
    old_time_zone = Time.zone
    begin
      Time.zone = time_zone || user.try(:time_zone)
    rescue ArgumentError
      # Usually this would happen b/c of an invalid time zone being specified
      self.time_zone = time_zone_was || old_time_zone.name
    end
    Chronic.time_class = Time.zone
    
    begin
      # Start parsing...
      t = begin
        Chronic.parse(date_string)
      rescue ArgumentError
        Chronic.parse(date_string.split[0..-2].join(' '))
      end
      if !t && (locale = user.locale || I18n.locale)
        date_string = englishize_month_abbrevs_for_locale(date_string, locale)
        t = Chronic.parse(date_string)
      end

      if !t
        I18N_SUPPORTED_LOCALES.each do |locale|
          date_string = englishize_month_abbrevs_for_locale(date_string, locale)
          break if t = Chronic.parse(date_string) 
        end
      end
      return true unless t
    
      # Re-interpret future dates as being in the past
      if t > Time.now
        t = Chronic.parse(date_string, :context => :past)  
      end
    
      self.observed_on = t.to_date
    
      # try to determine if the user specified a time by ask Chronic to return
      # a time range. Time ranges less than a day probably specified a time.
      if tspan = Chronic.parse(date_string, :context => :past, :guess => false)
        # If tspan is less than a day and the string wasn't 'today', set time
        if tspan.width < 86400 && date_string.strip.downcase != 'today'
          self.time_observed_at = t
        else
          self.time_observed_at = nil
        end
      end
    rescue RuntimeError, ArgumentError
      # ignore these, just don't set the date
      return true
    end
    
    # don't store relative observed_on_strings, or they will change
    # every time you save an observation!
    if date_string =~ /today|yesterday|ago|last|this|now|monday|tuesday|wednesday|thursday|friday|saturday|sunday/i
      self.observed_on_string = self.observed_on.to_s
      if self.time_observed_at
        self.observed_on_string = self.time_observed_at.strftime("%Y-%m-%d %H:%M:%S")
      end
    end
    
    # Set the time zone back the way it was
    Time.zone = old_time_zone
    true
  end
#
# Set all the time fields based on the contents of observed_on_string
#
Set all the time fields based on the contents of observed_on_string
Set all the time fields based on the contents of observed_on_string
def munge_observed_on_with_chronic
    if observed_on_string.blank?
      self.observed_on = nil
      self.time_observed_at = nil
      return true
    end
    date_string = observed_on_string.strip
    tz_abbrev_pattern = /\s\(?([A-Z]{3,})\)?$/ 
    tz_offset_pattern = /([+-]\d{4})$/ 
    tz_js_offset_pattern = /(GMT)?([+-]\d{4})/ 
    tz_colon_offset_pattern = /(GMT|HSP)([+-]\d+:\d+)/ 
    tz_failed_abbrev_pattern = /\(#{tz_colon_offset_pattern}\)/
    if date_string =~ /#{tz_js_offset_pattern} #{tz_failed_abbrev_pattern}/
      date_string = date_string.sub(tz_failed_abbrev_pattern, '').strip
    end
    tz_abbrev = date_string[tz_abbrev_pattern, 1]
    tz_abbrev = 'CET' if tz_abbrev == 'CEST'
    if parsed_time_zone = ActiveSupport::TimeZone::CODES[tz_abbrev]
      date_string = observed_on_string.sub(tz_abbrev_pattern, '')
      date_string = date_string.sub(tz_js_offset_pattern, '').strip
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_offset_pattern, 1]) && 
        (n = offset.to_f / 100) && 
        (key = n == 0 ? 0 : n.floor + (n%n.floor)/0.6) && 
        (parsed_time_zone = ActiveSupport::TimeZone[key])
      date_string = date_string.sub(tz_offset_pattern, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_js_offset_pattern, 2]) && 
        (n = offset.to_f / 100) && 
        (key = n == 0 ? 0 : n.floor + (n%n.floor)/0.6) && 
        (parsed_time_zone = ActiveSupport::TimeZone[key])
      date_string = date_string.sub(tz_js_offset_pattern, '')
      date_string = date_string.sub(/^(Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s+/i, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    elsif (offset = date_string[tz_colon_offset_pattern, 2]) && 
        (t = Time.parse(offset)) && 
        (parsed_time_zone = ActiveSupport::TimeZone[t.hour+t.min/60.0])
      date_string = date_string.sub(/#{tz_colon_offset_pattern}|#{tz_failed_abbrev_pattern}/, '')
      self.time_zone = parsed_time_zone.name if observed_on_string_changed?
    end
    date_string.sub!('T', ' ') if date_string =~ /\d{4}-\d{2}-\d{2}T/
    date_string.sub!(/(\d{2}:\d{2}:\d{2})\.\d+/, '\\1')
    date_string.sub!(/^[A-z]{3} ([A-z]{3})/, '\\1')
    date_string.gsub!(/\(.*\)/, '')
    old_time_zone = Time.zone
    begin
      Time.zone = time_zone || user.try(:time_zone)
    rescue ArgumentError
      self.time_zone = time_zone_was || old_time_zone.name
    end
    Chronic.time_class = Time.zone
    begin
      t = begin
        Chronic.parse(date_string)
      rescue ArgumentError
        Chronic.parse(date_string.split[0..-2].join(' '))
      end
      if !t && (locale = user.locale || I18n.locale)
        date_string = englishize_month_abbrevs_for_locale(date_string, locale)
        t = Chronic.parse(date_string)
      end
      if !t
        I18N_SUPPORTED_LOCALES.each do |locale|
          date_string = englishize_month_abbrevs_for_locale(date_string, locale)
          break if t = Chronic.parse(date_string) 
        end
      end
      return true unless t
      if t > Time.now
        t = Chronic.parse(date_string, :context => :past)  
      end
      self.observed_on = t.to_date
      if tspan = Chronic.parse(date_string, :context => :past, :guess => false)
        if tspan.width < 86400 && date_string.strip.downcase != 'today'
          self.time_observed_at = t
        else
          self.time_observed_at = nil
        end
      end
    rescue RuntimeError, ArgumentError
      return true
    end
    if date_string =~ /today|yesterday|ago|last|this|now|monday|tuesday|wednesday|thursday|friday|saturday|sunday/i
      self.observed_on_string = self.observed_on.to_s
      if self.time_observed_at
        self.observed_on_string = self.time_observed_at.strftime("%Y-%m-%d %H:%M:%S")
      end
    end
    Time.zone = old_time_zone
    true
  end
def munge_observed_on_with_chronic if observed_on_string . blank? self . observed_on = nil self . time_observed_at = nil return true end date_string = observed_on_string . strip tz_abbrev_pattern = / \s \( ?([A-Z]{3,}) \) ?$ / tz_offset_pattern = / ([+-] \d {4})$ / tz_js_offset_pattern = / (GMT)?([+-] \d {4}) / tz_colon_offset_pattern = / (GMT|HSP)([+-] \d +: \d +) / tz_failed_abbrev_pattern = / \( #{ tz_colon_offset_pattern } \) / if date_string =~ / #{ tz_js_offset_pattern } #{ tz_failed_abbrev_pattern } / date_string = date_string . sub ( tz_failed_abbrev_pattern , '' ) . strip end tz_abbrev = date_string [ tz_abbrev_pattern , 1 ] tz_abbrev = 'CET' if tz_abbrev == 'CEST' if parsed_time_zone = ActiveSupport :: TimeZone :: CODES [ tz_abbrev ] date_string = observed_on_string . sub ( tz_abbrev_pattern , '' ) date_string = date_string . sub ( tz_js_offset_pattern , '' ) . strip self . time_zone = parsed_time_zone . name if observed_on_string_changed? elsif ( offset = date_string [ tz_offset_pattern , 1 ] ) && ( n = offset . to_f / 100 ) && ( key = n == 0 ? 0 : n . floor + ( n % n . floor ) / 0.6 ) && ( parsed_time_zone = ActiveSupport :: TimeZone [ key ] ) date_string = date_string . sub ( tz_offset_pattern , '' ) self . time_zone = parsed_time_zone . name if observed_on_string_changed? elsif ( offset = date_string [ tz_js_offset_pattern , 2 ] ) && ( n = offset . to_f / 100 ) && ( key = n == 0 ? 0 : n . floor + ( n % n . floor ) / 0.6 ) && ( parsed_time_zone = ActiveSupport :: TimeZone [ key ] ) date_string = date_string . sub ( tz_js_offset_pattern , '' ) date_string = date_string . sub ( / ^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) \s + /i , '' ) self . time_zone = parsed_time_zone . name if observed_on_string_changed? elsif ( offset = date_string [ tz_colon_offset_pattern , 2 ] ) && ( t = Time . parse ( offset ) ) && ( parsed_time_zone = ActiveSupport :: TimeZone [ t . hour + t . min / 60.0 ] ) date_string = date_string . sub ( / #{ tz_colon_offset_pattern } | #{ tz_failed_abbrev_pattern } / , '' ) self . time_zone = parsed_time_zone . name if observed_on_string_changed? end date_string . sub! ( 'T' , ' ' ) if date_string =~ / \d {4}- \d {2}- \d {2}T / date_string . sub! ( / ( \d {2}: \d {2}: \d {2}) \. \d + / , '\\1' ) date_string . sub! ( / ^[A-z]{3} ([A-z]{3}) / , '\\1' ) date_string . gsub! ( / \( .* \) / , '' ) old_time_zone = Time . zone begin Time . zone = time_zone || user . try ( :time_zone ) rescue ArgumentError self . time_zone = time_zone_was || old_time_zone . name end Chronic . time_class = Time . zone begin t = begin Chronic . parse ( date_string ) rescue ArgumentError Chronic . parse ( date_string . split [ 0 .. - 2 ] . join ( ' ' ) ) end if ! t && ( locale = user . locale || I18n . locale ) date_string = englishize_month_abbrevs_for_locale ( date_string , locale ) t = Chronic . parse ( date_string ) end if ! t I18N_SUPPORTED_LOCALES . each do | locale | date_string = englishize_month_abbrevs_for_locale ( date_string , locale ) break if t = Chronic . parse ( date_string ) end end return true unless t if t > Time . now t = Chronic . parse ( date_string , :context => :past ) end self . observed_on = t . to_date if tspan = Chronic . parse ( date_string , :context => :past , :guess => false ) if tspan . width < 86400 && date_string . strip . downcase != 'today' self . time_observed_at = t else self . time_observed_at = nil end end rescue RuntimeError , ArgumentError return true end if date_string =~ / today|yesterday|ago|last|this|now|monday|tuesday|wednesday|thursday|friday|saturday|sunday /i self . observed_on_string = self . observed_on . to_s if self . time_observed_at self . observed_on_string = self . time_observed_at . strftime ( "%Y-%m-%d %H:%M:%S" ) end end Time . zone = old_time_zone true end
Set all the time fields based on the contents of observed_on_string
Set all the time fields based on the contents of observed_on_string
# ends with (PDT) # contains -0800 # contains GMT-0800 # contains (GMT-08:00) # Rails timezone support doesn't seem to recognize this abbreviation, and # frankly I have no idea where ActiveSupport::TimeZone::CODES comes from. # In case that ever stops working or a less hackish solution is required, # check out https://gist.github.com/kueda/3e6f77f64f792b4f119f # strip leading month if present # strip paranthesized stuff # Set the time zone appropriately # Usually this would happen b/c of an invalid time zone being specified # Start parsing... # Re-interpret future dates as being in the past # try to determine if the user specified a time by ask Chronic to return # a time range. Time ranges less than a day probably specified a time. # If tspan is less than a day and the string wasn't 'today', set time # ignore these, just don't set the date # don't store relative observed_on_strings, or they will change # every time you save an observation! # Set the time zone back the way it was