lib/validates_timeliness/formats.rb in validates_timeliness-1.1.7 vs lib/validates_timeliness/formats.rb in validates_timeliness-2.0.0

- old
+ new

@@ -122,17 +122,17 @@ { 'n' => [ /n{1}/, '(\d{1,2})', :min ] }, { 'ss' => [ /s{2,}/, '(\d{2})', :sec ] }, { 's' => [ /s{1}/, '(\d{1,2})', :sec ] }, { 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] }, { 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] }, - { 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] }, + { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] }, { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] }, { '_' => [ /_/, '\s?' ] } ] - # Arguments whichs will be passed to the format proc if matched in the - # time string. The key must should the key from the format tokens. The array + # Arguments which will be passed to the format proc if matched in the + # time string. The key must be the key from the format tokens. The array # consists of the arry position of the arg, the arg name, and the code to # place in the time array slot. The position can be nil which means the arg # won't be placed in the array. # # The code can be used to manipulate the arg value if required, otherwise @@ -144,10 +144,11 @@ :day => [2, 'd', 'd'], :hour => [3, 'h', 'full_hour(h,md)'], :min => [4, 'n', 'n'], :sec => [5, 's', 's'], :usec => [6, 'u', 'microseconds(u)'], + :offset => [7, 'z', 'offset_in_seconds(z)'], :meridian => [nil, 'md', nil] } class << self @@ -158,25 +159,33 @@ end # Loop through format expressions for type and call proc on matches. Allow # pre or post match strings to exist if strict is false. Otherwise wrap # regexp in start and end anchors. - # Returns 7 part time array. - def parse(string, type, strict=true) + # Returns time array if matches a format, nil otherwise. + def parse(string, type, options={}) return string unless string.is_a?(String) + options.reverse_merge!(:strict => true) + sets = if options[:format] + [ send("#{type}_expressions").assoc(options[:format]) ] + else + expression_set(type, string) + end + matches = nil - exp, processor = expression_set(type, string).find do |regexp, proc| - full = /\A#{regexp}\Z/ if strict + processor = sets.each do |format, regexp, proc| + full = /\A#{regexp}\Z/ if options[:strict] full ||= case type when :date then /\A#{regexp}/ when :time then /#{regexp}\Z/ when :datetime then /\A#{regexp}\Z/ end - matches = full.match(string.strip) + break(proc) if matches = full.match(string.strip) end - processor.call(*matches[1..7]) if matches + last = options[:include_offset] ? 8 : 7 + processor.call(*matches[1..last]) if matches end # Delete formats of specified type. Error raised if format not found. def remove_formats(type, *remove_formats) remove_formats.each do |format| @@ -204,12 +213,11 @@ index = before ? formats.index(before) : -1 formats.insert(index, format) end compile_format_expressions end - - + # Removes formats where the 1 or 2 digit month comes first, to eliminate # formats which are ambiguous with the European style of day then month. # The mmm token is ignored as its not ambigous. def remove_us_formats us_format_regexp = /\Am{1,2}[^m]/ @@ -244,26 +252,21 @@ # Generates a proc which when executed maps the regexp capture groups to a # proc argument based on order captured. A time array is built using the proc # argument in the position indicated by the first element of the proc arg # array. # - # Examples: - # - # 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } } - # 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } } - # def format_proc(order) arg_map = format_proc_args args = order.invert.sort.map {|p| arg_map[p[1]][1] } arr = [nil] * 7 order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? } - proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }" + proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i } }" eval proc_string end def compile_formats(formats) - formats.map { |format| regexp, format_proc = format_expression_generator(format) } + formats.map { |format| [ format, *format_expression_generator(format) ] } end # Pick expression set and combine date and datetimes for # datetime attributes to allow date string as datetime def expression_set(type, string) @@ -310,9 +313,16 @@ defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES end def microseconds(usec) (".#{usec}".to_f * 1_000_000).to_i + end + + def offset_in_seconds(offset) + sign = offset =~ /^-/ ? -1 : 1 + parts = offset.scan(/\d\d/).map {|p| p.to_f } + parts[1] = parts[1].to_f / 60 + (parts[0] + parts[1]) * sign * 3600 end end end end