Re: [ ruby-Bugs-5263 ] strptime fails to properly parse certain inputs
From:
nobu@...
Date:
2006-08-06 12:03:55 UTC
List:
ruby-core #8538
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