From: nobu@... Date: 2006-08-06T21:03:55+09:00 Subject: Re: [ ruby-Bugs-5263 ] strptime fails to properly parse certain inputs Hi, At Wed, 2 Aug 2006 17:40:13 +0900, Yukihiro Matsumoto wrote in [ruby-core:08485]: > |Date.strptime and DateTime.strptime do not properly parse > |certain forms of input in which there are no non-numeric > |separators between the fields. > | > |For example, the following code fails: > | > |Date.strptime("20060401", "%Y%m%d") > > Just because strptime() %Y parsed 20060401 as a year of 20,060,401 > (year twenty million sixty thousand four hundred and one a.d.), and > did not find month and day. How about to accept an optional width? Index: lib/date/format.rb =================================================================== RCS file: /cvs/ruby/src/ruby/lib/date/format.rb,v retrieving revision 1.15 diff -p -u -2 -r1.15 format.rb --- lib/date/format.rb 6 Aug 2006 11:38:58 -0000 1.15 +++ lib/date/format.rb 6 Aug 2006 11:58:25 -0000 @@ -52,134 +52,139 @@ class Date def self.__strptime(str, fmt, elem) - fmt.scan(/%[EO]?.|./mo) do |c| - cc = c.sub(/\A%[EO]?(.)\z/mo, '%\\1') - case cc - when /\A\s/o - str.sub!(/\A[\s\v]+/o, '') - when '%A', '%a' - return unless str.sub!(/\A([a-z]+)\b/io, '') + fmt.scan(/%([EO]|\d+)?(.)|(\s+)|([^%]+)/mo) do |w, c, sp, s| + if sp + next str.sub!(/\A[\s\v]+/o, '') + end + if s + return unless str.sub!(/\A#{Regexp.quote(s)}/, '') + next + end + w = nil unless w and /\d/ =~ w # ignore E and O flag + case c + when 'A', 'a' + return unless str.sub!(w ? /\A([a-z]{1,#{w}})/i : /\A([a-z]+)\b/io, '') val = DAYS[$1.downcase] || ABBR_DAYS[$1.downcase] return unless val elem[:wday] = val - when '%B', '%b', '%h' - return unless str.sub!(/\A([a-z]+)\b/io, '') + when 'B', 'b', 'h' + return unless str.sub!(w ? /\A([a-z]{1,#{w}})/i : /\A([a-z]+)\b/io, '') val = MONTHS[$1.downcase] || ABBR_MONTHS[$1.downcase] return unless val elem[:mon] = val - when '%C' - return unless str.sub!(/\A(\d+)/o, '') + when 'C' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i elem[:cent] = val - when '%c' + when 'c' return unless __strptime(str, '%a %b %e %H:%M:%S %Y', elem) - when '%D' + when 'D' return unless __strptime(str, '%m/%d/%y', elem) - when '%d', '%e' - return unless str.sub!(/\A ?(\d+)/o, '') + when 'd', 'e' + return unless str.sub!(w ? /\A ?(\d{1,#{w}})/ : /\A ?(\d+)/o, '') val = $1.to_i return unless (1..31) === val elem[:mday] = val - when '%F' + when 'F' return unless __strptime(str, '%Y-%m-%d', elem) - when '%G' - return unless str.sub!(/\A([-+]?\d+)/o, '') + when 'G' + return unless str.sub!(w ? /\A([-+]?\d{1,#{w}})/ : /\A([-+]?\d+)/o, '') val = $1.to_i elem[:cwyear] = val - when '%g' - return unless str.sub!(/\A(\d+)/o, '') + when 'g' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..99) === val elem[:cwyear] = val elem[:cent] ||= if val >= 69 then 19 else 20 end - when '%H', '%k' - return unless str.sub!(/\A ?(\d+)/o, '') + when 'H', 'k' + return unless str.sub!(w ? /\A ?(\d{1,#{w}})/ : /\A ?(\d+)/o, '') val = $1.to_i return unless (0..24) === val elem[:hour] = val - when '%I', '%l' - return unless str.sub!(/\A ?(\d+)/o, '') + when 'I', 'l' + return unless str.sub!(w ? /\A ?(\d{1,#{w}})/ : /\A ?(\d+)/o, '') val = $1.to_i return unless (1..12) === val elem[:hour] = val - when '%j' - return unless str.sub!(/\A(\d+)/o, '') + when 'j' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (1..366) === val elem[:yday] = val - when '%M' - return unless str.sub!(/\A(\d+)/o, '') + when 'M' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..59) === val elem[:min] = val - when '%m' - return unless str.sub!(/\A(\d+)/o, '') + when 'm' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (1..12) === val elem[:mon] = val =begin - when '%N' - return unless str.sub!(/\A(\d+)/o, '') + when 'N' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i.to_r / (10**9) elem[:sec_fraction] = val =end - when '%n' + when 'n' return unless __strptime(str, ' ', elem) - when '%p', '%P' + when 'p', 'P' return unless str.sub!(/\A([ap])(?:m\b|\.m\.)/io, '') elem[:merid] = if $1.downcase == 'a' then 0 else 12 end - when '%R' + when 'R' return unless __strptime(str, '%H:%M', elem) - when '%r' + when 'r' return unless __strptime(str, '%I:%M:%S %p', elem) - when '%S' - return unless str.sub!(/\A(\d+)/o, '') + when 'S' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..60) === val elem[:sec] = val - when '%s' - return unless str.sub!(/\A(\d+)/o, '') + when 's' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i elem[:seconds] = val - when '%T' + when 'T' return unless __strptime(str, '%H:%M:%S', elem) - when '%t' + when 't' return unless __strptime(str, ' ', elem) - when '%U', '%W' - return unless str.sub!(/\A(\d+)/o, '') + when 'U', 'W' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..53) === val - elem[if c[-1,1] == 'U' then :wnum0 else :wnum1 end] = val - when '%u' - return unless str.sub!(/\A(\d+)/o, '') + elem[if c == 'U' then :wnum0 else :wnum1 end] = val + when 'u' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (1..7) === val elem[:cwday] = val - when '%V' - return unless str.sub!(/\A(\d+)/o, '') + when 'V' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (1..53) === val elem[:cweek] = val - when '%v' + when 'v' return unless __strptime(str, '%e-%b-%Y', elem) - when '%w' - return unless str.sub!(/\A(\d+)/o, '') + when 'w' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..6) === val elem[:wday] = val - when '%X' + when 'X' return unless __strptime(str, '%H:%M:%S', elem) - when '%x' + when 'x' return unless __strptime(str, '%m/%d/%y', elem) - when '%Y' - return unless str.sub!(/\A([-+]?\d+)/o, '') + when 'Y' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A([-+]?\d+)/o, '') val = $1.to_i elem[:year] = val - when '%y' - return unless str.sub!(/\A(\d+)/o, '') + when 'y' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i return unless (0..99) === val elem[:year] = val elem[:cent] ||= if val >= 69 then 19 else 20 end - when '%Z', '%z' + when 'Z', 'z' return unless str.sub!(/\A([-+:a-z0-9]+(?:\s+dst\b)?)/io, '') val = $1 @@ -187,35 +192,16 @@ class Date offset = zone_to_diff(val) elem[:offset] = offset - when '%%' + when '%' return unless str.sub!(/\A%/o, '') - when '%+' + when '+' return unless __strptime(str, '%a %b %e %H:%M:%S %Z %Y', elem) =begin - when '%.' - return unless str.sub!(/\A(\d+)/o, '') + when '.' + return unless str.sub!(w ? /\A(\d{1,#{w}})/ : /\A(\d+)/o, '') val = $1.to_i.to_r / (10**$1.size) elem[:sec_fraction] = val =end - when '%1' - if $VERBOSE - warn("warning: %1 is deprecated; forget this") - end - return unless str.sub!(/\A(\d+)/o, '') - val = $1.to_i - elem[:jd] = val - when '%2' - if $VERBOSE - warn("warning: %2 is deprecated; use '%Y-%j'") - end - return unless __strptime(str, '%Y-%j', elem) - when '%3' - if $VERBOSE - warn("warning: %3 is deprecated; use '%F'") - end - return unless __strptime(str, '%F', elem) - when /\A%(.)/m - return unless str.sub!(Regexp.new('\\A' + Regexp.quote($1)), '') else - return unless str.sub!(Regexp.new('\\A' + Regexp.quote(c)), '') + return unless str.sub!(/\A%#{Regexp.quote(c)}/, '') end end @@ -493,58 +479,62 @@ class Date def strftime(fmt='%F') o = '' - fmt.scan(/%[EO]?.|./mo) do |c| - cc = c.sub(/\A%[EO]?(.)\z/mo, '%\\1') - case cc - when '%A'; o << DAYNAMES[wday] - when '%a'; o << ABBR_DAYNAMES[wday] - when '%B'; o << MONTHNAMES[mon] - when '%b'; o << ABBR_MONTHNAMES[mon] - when '%C'; o << '%02d' % (year / 100.0).floor # P2,ID - when '%c'; o << strftime('%a %b %e %H:%M:%S %Y') - when '%D'; o << strftime('%m/%d/%y') # P2,ID - when '%d'; o << '%02d' % mday - when '%e'; o << '%2d' % mday - when '%F'; o << strftime('%Y-%m-%d') # ID - when '%G'; o << '%.4d' % cwyear # ID - when '%g'; o << '%02d' % (cwyear % 100) # ID - when '%H'; o << '%02d' % hour - when '%h'; o << strftime('%b') # P2,ID - when '%I'; o << '%02d' % ((hour % 12).nonzero? or 12) - when '%j'; o << '%03d' % yday - when '%k'; o << '%2d' % hour # AR,TZ,GL - when '%l'; o << '%2d' % ((hour % 12).nonzero? or 12) # AR,TZ,GL - when '%M'; o << '%02d' % min - when '%m'; o << '%02d' % mon + m = nil + form = proc {|m0, f, v| "%#{m || m0}#{f}" % v} + fmt.scan(/%([EO]|[-+\#]?\d*\.?\d+)?(.)|([^%]+)/mo) do |m, c, s| + next o << s if s + s = $& + m = "" unless m and /\d/ =~ m # ignore E and O flag + case c + when 'A'; o << DAYNAMES[wday] + when 'a'; o << ABBR_DAYNAMES[wday] + when 'B'; o << MONTHNAMES[mon] + when 'b'; o << ABBR_MONTHNAMES[mon] + when 'C'; o << form['02', 'd', (year / 100.0).floor] # P2,ID + when 'c'; o << strftime('%a %b %e %H:%M:%S %Y') + when 'D'; o << strftime('%m/%d/%y') # P2,ID + when 'd'; o << form['02', 'd', mday] + when 'e'; o << form['2', 'd', mday] + when 'F'; o << strftime('%Y-%m-%d') # ID + when 'G'; o << form['.4', 'd', cwyear] # ID + when 'g'; o << form['02', 'd', (cwyear % 100)] # ID + when 'H'; o << form['02', 'd', hour] + when 'h'; o << strftime('%b') # P2,ID + when 'I'; o << form['02', 'd', ((hour % 12).nonzero? or 12)] + when 'j'; o << form['03', 'd', yday] + when 'k'; o << form['2', 'd', hour] # AR,TZ,GL + when 'l'; o << form['2', 'd', ((hour % 12).nonzero? or 12)] # AR,TZ,GL + when 'M'; o << form['02', 'd', min] + when 'm'; o << form['02', 'd', mon] =begin - when '%N' # GNU date - o << '%09d' % (sec_fraction / (1.to_r/86400/(10**9))) + when 'N' # GNU date + o << form['09', 'd', (sec_fraction / (1.to_r/86400/(10**9)))] =end - when '%n'; o << "\n" # P2,ID - when '%P'; o << if hour < 12 then 'am' else 'pm' end # GL - when '%p'; o << if hour < 12 then 'AM' else 'PM' end - when '%R'; o << strftime('%H:%M') # ID - when '%r'; o << strftime('%I:%M:%S %p') # P2,ID - when '%S'; o << '%02d' % sec - when '%s' # TZ,GL + when 'n'; o << "\n" # P2,ID + when 'P'; o << if hour < 12 then 'am' else 'pm' end # GL + when 'p'; o << if hour < 12 then 'AM' else 'PM' end + when 'R'; o << strftime('%H:%M') # ID + when 'r'; o << strftime('%I:%M:%S %p') # P2,ID + when 'S'; o << form['02', 'd', sec] + when 's' # TZ,GL d = ajd - self.class.jd_to_ajd(self.class.civil_to_jd(1970,1,1), 0) s = (d * 86400).to_i - o << '%d' % s - when '%T'; o << strftime('%H:%M:%S') # P2,ID - when '%t'; o << "\t" # P2,ID - when '%U', '%W' + o << form['', 'd', s] + when 'T'; o << strftime('%H:%M:%S') # P2,ID + when 't'; o << "\t" # P2,ID + when 'U', 'W' a = self.class.civil_to_jd(year, 1, 1, ns?) + 6 k = if c[-1,1] == 'U' then 0 else 1 end w = (jd - (a - ((a - k) + 1) % 7) + 7) / 7 - o << '%02d' % w - when '%u'; o << '%d' % cwday # P2,ID - when '%V'; o << '%02d' % cweek # P2,ID - when '%v'; o << strftime('%e-%b-%Y') # AR,TZ - when '%w'; o << '%d' % wday - when '%X'; o << strftime('%H:%M:%S') - when '%x'; o << strftime('%m/%d/%y') - when '%Y'; o << '%.4d' % year - when '%y'; o << '%02d' % (year % 100) - when '%Z'; o << (if offset.zero? then 'Z' else strftime('%z') end) - when '%z' # ID + o << form['02', 'd', w] + when 'u'; o << form[ '', 'd', cwday] # P2,ID + when 'V'; o << form['02', 'd', cweek] # P2,ID + when 'v'; o << strftime('%e-%b-%Y') # AR,TZ + when 'w'; o << form[ '', 'd', wday] + when 'X'; o << strftime('%H:%M:%S') + when 'x'; o << strftime('%m/%d/%y') + when 'Y'; o << form['.4', 'd', year] + when 'y'; p year; o << form['02', 'd', (year % 100)] + when 'Z'; o << (if offset.zero? then 'Z' else strftime('%z') end) + when 'z' # ID o << if offset < 0 then '-' else '+' end of = offset.abs @@ -553,29 +543,12 @@ class Date o << '%02d' % hh o << '%02d' % mm - when '%%'; o << '%' - when '%+'; o << strftime('%a %b %e %H:%M:%S %Z %Y') # TZ + when '%'; o << '%' + when '+'; o << strftime('%a %b %e %H:%M:%S %Z %Y') # TZ =begin when '%.' o << '%06d' % (sec_fraction / (1.to_r/86400/(10**6))) =end - when '%1' - if $VERBOSE - warn("warning: %1 is deprecated; forget this") - end - o << '%d' % jd - when '%2' - if $VERBOSE - warn("warning: %2 is deprecated; use '%Y-%j'") - end - o << strftime('%Y-%j') - when '%3' - if $VERBOSE - warn("warning: %3 is deprecated; use '%F'") - end - o << strftime('%F') - when /\A%(.)/m - o << $1 else - o << c + o << s end end -- Nobu Nakada