Re-enable spec/library for full CI runs.
[rbx.git] / lib / date / format.rb
blobd361b67e1102fcd59e0f41ec932bdbdee11587ee
1 # format.rb: Written by Tadayoshi Funaba 1999-2008
2 # $Id: format.rb,v 2.43 2008-01-17 20:16:31+09 tadf Exp $
4 require 'rational'
6 class Date
8   module Format # :nodoc:
10     MONTHS = {
11       'january'  => 1, 'february' => 2, 'march'    => 3, 'april'    => 4,
12       'may'      => 5, 'june'     => 6, 'july'     => 7, 'august'   => 8,
13       'september'=> 9, 'october'  =>10, 'november' =>11, 'december' =>12
14     }
16     DAYS = {
17       'sunday'   => 0, 'monday'   => 1, 'tuesday'  => 2, 'wednesday'=> 3,
18       'thursday' => 4, 'friday'   => 5, 'saturday' => 6
19     }
21     ABBR_MONTHS = {
22       'jan'      => 1, 'feb'      => 2, 'mar'      => 3, 'apr'      => 4,
23       'may'      => 5, 'jun'      => 6, 'jul'      => 7, 'aug'      => 8,
24       'sep'      => 9, 'oct'      =>10, 'nov'      =>11, 'dec'      =>12
25     }
27     ABBR_DAYS = {
28       'sun'      => 0, 'mon'      => 1, 'tue'      => 2, 'wed'      => 3,
29       'thu'      => 4, 'fri'      => 5, 'sat'      => 6
30     }
32     ZONES = {
33       'ut'  =>  0*3600, 'gmt' =>  0*3600, 'est' => -5*3600, 'edt' => -4*3600,
34       'cst' => -6*3600, 'cdt' => -5*3600, 'mst' => -7*3600, 'mdt' => -6*3600,
35       'pst' => -8*3600, 'pdt' => -7*3600,
36       'a'   =>  1*3600, 'b'   =>  2*3600, 'c'   =>  3*3600, 'd'   =>  4*3600,
37       'e'   =>  5*3600, 'f'   =>  6*3600, 'g'   =>  7*3600, 'h'   =>  8*3600,
38       'i'   =>  9*3600, 'k'   => 10*3600, 'l'   => 11*3600, 'm'   => 12*3600,
39       'n'   => -1*3600, 'o'   => -2*3600, 'p'   => -3*3600, 'q'   => -4*3600,
40       'r'   => -5*3600, 's'   => -6*3600, 't'   => -7*3600, 'u'   => -8*3600,
41       'v'   => -9*3600, 'w'   =>-10*3600, 'x'   =>-11*3600, 'y'   =>-12*3600,
42       'z'   =>  0*3600,
44       'utc' =>  0*3600, 'wet' =>  0*3600,
45       'at'  => -2*3600, 'brst'=> -2*3600, 'ndt' => -(2*3600+1800),
46       'art' => -3*3600, 'adt' => -3*3600, 'brt' => -3*3600, 'clst'=> -3*3600,
47       'nst' => -(3*3600+1800),
48       'ast' => -4*3600, 'clt' => -4*3600,
49       'akdt'=> -8*3600, 'ydt' => -8*3600,
50       'akst'=> -9*3600, 'hadt'=> -9*3600, 'hdt' => -9*3600, 'yst' => -9*3600,
51       'ahst'=>-10*3600, 'cat' =>-10*3600, 'hast'=>-10*3600, 'hst' =>-10*3600,
52       'nt'  =>-11*3600,
53       'idlw'=>-12*3600,
54       'bst' =>  1*3600, 'cet' =>  1*3600, 'fwt' =>  1*3600, 'met' =>  1*3600,
55       'mewt'=>  1*3600, 'mez' =>  1*3600, 'swt' =>  1*3600, 'wat' =>  1*3600,
56       'west'=>  1*3600,
57       'cest'=>  2*3600, 'eet' =>  2*3600, 'fst' =>  2*3600, 'mest'=>  2*3600,
58       'mesz'=>  2*3600, 'sast'=>  2*3600, 'sst' =>  2*3600,
59       'bt'  =>  3*3600, 'eat' =>  3*3600, 'eest'=>  3*3600, 'msk' =>  3*3600,
60       'msd' =>  4*3600, 'zp4' =>  4*3600,
61       'zp5' =>  5*3600, 'ist' =>  (5*3600+1800),
62       'zp6' =>  6*3600,
63       'wast'=>  7*3600,
64       'cct' =>  8*3600, 'sgt' =>  8*3600, 'wadt'=>  8*3600,
65       'jst' =>  9*3600, 'kst' =>  9*3600,
66       'east'=> 10*3600, 'gst' => 10*3600,
67       'eadt'=> 11*3600,
68       'idle'=> 12*3600, 'nzst'=> 12*3600, 'nzt' => 12*3600,
69       'nzdt'=> 13*3600,
71       'afghanistan'           =>   16200, 'alaskan'               =>  -32400,
72       'arab'                  =>   10800, 'arabian'               =>   14400,
73       'arabic'                =>   10800, 'atlantic'              =>  -14400,
74       'aus central'           =>   34200, 'aus eastern'           =>   36000,
75       'azores'                =>   -3600, 'canada central'        =>  -21600,
76       'cape verde'            =>   -3600, 'caucasus'              =>   14400,
77       'cen. australia'        =>   34200, 'central america'       =>  -21600,
78       'central asia'          =>   21600, 'central europe'        =>    3600,
79       'central european'      =>    3600, 'central pacific'       =>   39600,
80       'central'               =>  -21600, 'china'                 =>   28800,
81       'dateline'              =>  -43200, 'e. africa'             =>   10800,
82       'e. australia'          =>   36000, 'e. europe'             =>    7200,
83       'e. south america'      =>  -10800, 'eastern'               =>  -18000,
84       'egypt'                 =>    7200, 'ekaterinburg'          =>   18000,
85       'fiji'                  =>   43200, 'fle'                   =>    7200,
86       'greenland'             =>  -10800, 'greenwich'             =>       0,
87       'gtb'                   =>    7200, 'hawaiian'              =>  -36000,
88       'india'                 =>   19800, 'iran'                  =>   12600,
89       'jerusalem'             =>    7200, 'korea'                 =>   32400,
90       'mexico'                =>  -21600, 'mid-atlantic'          =>   -7200,
91       'mountain'              =>  -25200, 'myanmar'               =>   23400,
92       'n. central asia'       =>   21600, 'nepal'                 =>   20700,
93       'new zealand'           =>   43200, 'newfoundland'          =>  -12600,
94       'north asia east'       =>   28800, 'north asia'            =>   25200,
95       'pacific sa'            =>  -14400, 'pacific'               =>  -28800,
96       'romance'               =>    3600, 'russian'               =>   10800,
97       'sa eastern'            =>  -10800, 'sa pacific'            =>  -18000,
98       'sa western'            =>  -14400, 'samoa'                 =>  -39600,
99       'se asia'               =>   25200, 'malay peninsula'       =>   28800,
100       'south africa'          =>    7200, 'sri lanka'             =>   21600,
101       'taipei'                =>   28800, 'tasmania'              =>   36000,
102       'tokyo'                 =>   32400, 'tonga'                 =>   46800,
103       'us eastern'            =>  -18000, 'us mountain'           =>  -25200,
104       'vladivostok'           =>   36000, 'w. australia'          =>   28800,
105       'w. central africa'     =>    3600, 'w. europe'             =>    3600,
106       'west asia'             =>   18000, 'west pacific'          =>   36000,
107       'yakutsk'               =>   32400
108     }
110     [MONTHS, DAYS, ABBR_MONTHS, ABBR_DAYS, ZONES].each do |x|
111       x.freeze
112     end
114     class Bag # :nodoc:
116       def initialize
117         @elem = {}
118       end
120       def method_missing(t, *args, &block)
121         t = t.to_s
122         set = t.chomp!('=')
123         t = t.intern
124         if set
125           @elem[t] = args[0]
126         else
127           @elem[t]
128         end
129       end
131       def to_hash
132         @elem.reject{|k, v| /\A_/ =~ k.to_s || v.nil?}
133       end
135     end
137   end
139   def emit(e, f) # :nodoc:
140     case e
141     when Numeric
142       sign = %w(+ + -)[e <=> 0]
143       e = e.abs
144     end
146     s = e.to_s
148     if f[:s] && f[:p] == '0'
149       f[:w] -= 1
150     end
152     if f[:s] && f[:p] == "\s"
153       s[0,0] = sign
154     end
156     if f[:p] != '-'
157       s = s.rjust(f[:w], f[:p])
158     end
160     if f[:s] && f[:p] != "\s"
161       s[0,0] = sign
162     end
164     s = s.upcase if f[:u]
165     s = s.downcase if f[:d]
166     s
167   end
169   def emit_w(e, w, f) # :nodoc:
170     f[:w] = [f[:w], w].compact.max
171     emit(e, f)
172   end
174   def emit_n(e, w, f) # :nodoc:
175     f[:p] ||= '0'
176     emit_w(e, w, f)
177   end
179   def emit_sn(e, w, f) # :nodoc:
180     if e < 0
181       w += 1
182       f[:s] = true
183     end
184     emit_n(e, w, f)
185   end
187   def emit_z(e, w, f) # :nodoc:
188     w += 1
189     f[:s] = true
190     emit_n(e, w, f)
191   end
193   def emit_a(e, w, f) # :nodoc:
194     f[:p] ||= "\s"
195     emit_w(e, w, f)
196   end
198   def emit_ad(e, w, f) # :nodoc:
199     if f[:x]
200       f[:u] = true
201       f[:d] = false
202     end
203     emit_a(e, w, f)
204   end
206   def emit_au(e, w, f) # :nodoc:
207     if f[:x]
208       f[:u] = false
209       f[:d] = true
210     end
211     emit_a(e, w, f)
212   end
214   private :emit, :emit_w, :emit_n, :emit_sn, :emit_z,
215           :emit_a, :emit_ad, :emit_au
217   def strftime(fmt='%F')
218     fmt.gsub(/%([-_0^#]+)?(\d+)?([EO]?(?::{1,3}z|.))/m) do |m|
219       f = {}
220       a = $&
221       s, w, c = $1, $2, $3
222       if s
223         s.scan(/./) do |k|
224           case k
225           when '-'; f[:p] = '-'
226           when '_'; f[:p] = "\s"
227           when '0'; f[:p] = '0'
228           when '^'; f[:u] = true
229           when '#'; f[:x] = true
230           end
231         end
232       end
233       if w
234         f[:w] = w.to_i
235       end
236       case c
237       when 'A'; emit_ad(DAYNAMES[wday], 0, f)
238       when 'a'; emit_ad(ABBR_DAYNAMES[wday], 0, f)
239       when 'B'; emit_ad(MONTHNAMES[mon], 0, f)
240       when 'b'; emit_ad(ABBR_MONTHNAMES[mon], 0, f)
241       when 'C', 'EC'; emit_sn((year / 100).floor, 2, f)
242       when 'c', 'Ec'; emit_a(strftime('%a %b %e %H:%M:%S %Y'), 0, f)
243       when 'D'; emit_a(strftime('%m/%d/%y'), 0, f)
244       when 'd', 'Od'; emit_n(mday, 2, f)
245       when 'e', 'Oe'; emit_a(mday, 2, f)
246       when 'F'
247         if m == '%F'
248           format('%.4d-%02d-%02d', year, mon, mday) # 4p
249         else
250           emit_a(strftime('%Y-%m-%d'), 0, f)
251         end
252       when 'G'; emit_sn(cwyear, 4, f)
253       when 'g'; emit_n(cwyear % 100, 2, f)
254       when 'H', 'OH'; emit_n(hour, 2, f)
255       when 'h'; emit_ad(strftime('%b'), 0, f)
256       when 'I', 'OI'; emit_n((hour % 12).nonzero? || 12, 2, f)
257       when 'j'; emit_n(yday, 3, f)
258       when 'k'; emit_a(hour, 2, f)
259       when 'L'
260         emit_n((sec_fraction / MILLISECONDS_IN_SECOND).floor, 3, f)
261       when 'l'; emit_a((hour % 12).nonzero? || 12, 2, f)
262       when 'M', 'OM'; emit_n(min, 2, f)
263       when 'm', 'Om'; emit_n(mon, 2, f)
264       when 'N'
265         emit_n((sec_fraction / NANOSECONDS_IN_SECOND).floor, 9, f)
266       when 'n'; "\n"
267       when 'P'; emit_ad(strftime('%p').downcase, 0, f)
268       when 'p'; emit_au(if hour < 12 then 'AM' else 'PM' end, 0, f)
269       when 'Q'
270         s = ((ajd - UNIX_EPOCH_IN_AJD) / MILLISECONDS_IN_DAY).round
271         emit_sn(s, 1, f)
272       when 'R'; emit_a(strftime('%H:%M'), 0, f)
273       when 'r'; emit_a(strftime('%I:%M:%S %p'), 0, f)
274       when 'S', 'OS'; emit_n(sec, 2, f)
275       when 's'
276         s = ((ajd - UNIX_EPOCH_IN_AJD) / SECONDS_IN_DAY).round
277         emit_sn(s, 1, f)
278       when 'T'
279         if m == '%T'
280           format('%02d:%02d:%02d', hour, min, sec) # 4p
281         else
282           emit_a(strftime('%H:%M:%S'), 0, f)
283         end
284       when 't'; "\t"
285       when 'U', 'W', 'OU', 'OW'
286         emit_n(if c[-1,1] == 'U' then wnum0 else wnum1 end, 2, f)
287       when 'u', 'Ou'; emit_n(cwday, 1, f)
288       when 'V', 'OV'; emit_n(cweek, 2, f)
289       when 'v'; emit_a(strftime('%e-%b-%Y'), 0, f)
290       when 'w', 'Ow'; emit_n(wday, 1, f)
291       when 'X', 'EX'; emit_a(strftime('%H:%M:%S'), 0, f)
292       when 'x', 'Ex'; emit_a(strftime('%m/%d/%y'), 0, f)
293       when 'Y', 'EY'; emit_sn(year, 4, f)
294       when 'y', 'Ey', 'Oy'; emit_n(year % 100, 2, f)
295       when 'Z'; emit_au(strftime('%:z'), 0, f)
296       when /\A(:{0,3})z/
297         t = $1.size
298         sign = if offset < 0 then -1 else +1 end
299         fr = offset.abs
300         ss = fr.div(SECONDS_IN_DAY) # 4p
301         hh, ss = ss.divmod(3600)
302         mm, ss = ss.divmod(60)
303         if t == 3
304           if    ss.nonzero? then t =  2
305           elsif mm.nonzero? then t =  1
306           else                   t = -1
307           end
308         end
309         case t
310         when -1
311           tail = []
312           sep = ''
313         when 0
314           f[:w] -= 2 if f[:w]
315           tail = ['%02d' % mm]
316           sep = ''
317         when 1
318           f[:w] -= 3 if f[:w]
319           tail = ['%02d' % mm]
320           sep = ':'
321         when 2
322           f[:w] -= 6 if f[:w]
323           tail = ['%02d' % mm, '%02d' % ss]
324           sep = ':'
325         end
326         ([emit_z(sign * hh, 2, f)] + tail).join(sep)
327       when '%'; emit_a('%', 0, f)
328       when '+'; emit_a(strftime('%a %b %e %H:%M:%S %Z %Y'), 0, f)
329       else
330         a
331       end
332     end
333   end
335 # alias_method :format, :strftime
337   def asctime() strftime('%c') end
339   alias_method :ctime, :asctime
341   def iso8601() strftime('%F') end
343   def rfc3339() iso8601 end
345   def xmlschema() iso8601 end # :nodoc:
347   def rfc2822() strftime('%a, %-d %b %Y %T %z') end
349   alias_method :rfc822, :rfc2822
351   def httpdate() new_offset(0).strftime('%a, %d %b %Y %T GMT') end # :nodoc:
353   def jisx0301
354     if jd < 2405160
355       iso8601
356     else
357       case jd
358       when 2405160...2419614
359         g = 'M%02d' % (year - 1867)
360       when 2419614...2424875
361         g = 'T%02d' % (year - 1911)
362       when 2424875...2447535
363         g = 'S%02d' % (year - 1925)
364       else
365         g = 'H%02d' % (year - 1988)
366       end
367       g + strftime('.%m.%d')
368     end
369   end
371 =begin
372   def beat(n=0)
373     i, f = (new_offset(HOURS_IN_DAY).day_fraction * 1000).divmod(1)
374     ('@%03d' % i) +
375       if n < 1
376         ''
377       else
378         '.%0*d' % [n, (f / Rational(1, 10**n)).round]
379       end
380   end
381 =end
383   def self.num_pattern? (s) # :nodoc:
384     /\A%[EO]?[CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy\d]/ =~ s || /\A\d/ =~ s
385   end
387   private_class_method :num_pattern?
389   def self._strptime_i(str, fmt, e) # :nodoc:
390     fmt.scan(/%([EO]?(?::{1,3}z|.))|(.)/m) do |s, c|
391       a = $&
392       if s
393         case s
394         when 'A', 'a'
395           return unless str.sub!(/\A(#{Format::DAYS.keys.join('|')})/io, '') ||
396                         str.sub!(/\A(#{Format::ABBR_DAYS.keys.join('|')})/io, '')
397           val = Format::DAYS[$1.downcase] || Format::ABBR_DAYS[$1.downcase]
398           return unless val
399           e.wday = val
400         when 'B', 'b', 'h'
401           return unless str.sub!(/\A(#{Format::MONTHS.keys.join('|')})/io, '') ||
402                         str.sub!(/\A(#{Format::ABBR_MONTHS.keys.join('|')})/io, '')
403           val = Format::MONTHS[$1.downcase] || Format::ABBR_MONTHS[$1.downcase]
404           return unless val
405           e.mon = val
406         when 'C', 'EC'
407           return unless str.sub!(if num_pattern?($')
408                                  then /\A([-+]?\d{1,2})/
409                                  else /\A([-+]?\d{1,})/
410                                  end, '')
411           val = $1.to_i
412           e._cent = val
413         when 'c', 'Ec'
414           return unless _strptime_i(str, '%a %b %e %H:%M:%S %Y', e)
415         when 'D'
416           return unless _strptime_i(str, '%m/%d/%y', e)
417         when 'd', 'e', 'Od', 'Oe'
418           return unless str.sub!(/\A( \d|\d{1,2})/, '')
419           val = $1.to_i
420           return unless (1..31) === val
421           e.mday = val
422         when 'F'
423           return unless _strptime_i(str, '%Y-%m-%d', e)
424         when 'G'
425           return unless str.sub!(if num_pattern?($')
426                                  then /\A([-+]?\d{1,4})/
427                                  else /\A([-+]?\d{1,})/
428                                  end, '')
429           val = $1.to_i
430           e.cwyear = val
431         when 'g'
432           return unless str.sub!(/\A(\d{1,2})/, '')
433           val = $1.to_i
434           return unless (0..99) === val
435           e.cwyear = val
436           e._cent ||= if val >= 69 then 19 else 20 end
437         when 'H', 'k', 'OH'
438           return unless str.sub!(/\A( \d|\d{1,2})/, '')
439           val = $1.to_i
440           return unless (0..24) === val
441           e.hour = val
442         when 'I', 'l', 'OI'
443           return unless str.sub!(/\A( \d|\d{1,2})/, '')
444           val = $1.to_i
445           return unless (1..12) === val
446           e.hour = val
447         when 'j'
448           return unless str.sub!(/\A(\d{1,3})/, '')
449           val = $1.to_i
450           return unless (1..366) === val
451           e.yday = val
452         when 'L'
453           return unless str.sub!(if num_pattern?($')
454                                  then /\A([-+]?\d{1,3})/
455                                  else /\A([-+]?\d{1,})/
456                                  end, '')
457 #         val = Rational($1.to_i, 10**3)
458           val = Rational($1.to_i, 10**$1.size)
459           e.sec_fraction = val
460         when 'M', 'OM'
461           return unless str.sub!(/\A(\d{1,2})/, '')
462           val = $1.to_i
463           return unless (0..59) === val
464           e.min = val
465         when 'm', 'Om'
466           return unless str.sub!(/\A(\d{1,2})/, '')
467           val = $1.to_i
468           return unless (1..12) === val
469           e.mon = val
470         when 'N'
471           return unless str.sub!(if num_pattern?($')
472                                  then /\A([-+]?\d{1,9})/
473                                  else /\A([-+]?\d{1,})/
474                                  end, '')
475 #         val = Rational($1.to_i, 10**9)
476           val = Rational($1.to_i, 10**$1.size)
477           e.sec_fraction = val
478         when 'n', 't'
479           return unless _strptime_i(str, "\s", e)
480         when 'P', 'p'
481           return unless str.sub!(/\A([ap])(?:m\b|\.m\.)/i, '')
482           e._merid = if $1.downcase == 'a' then 0 else 12 end
483         when 'Q'
484           return unless str.sub!(/\A(-?\d{1,})/, '')
485           val = Rational($1.to_i, 10**3)
486           e.seconds = val
487         when 'R'
488           return unless _strptime_i(str, '%H:%M', e)
489         when 'r'
490           return unless _strptime_i(str, '%I:%M:%S %p', e)
491         when 'S', 'OS'
492           return unless str.sub!(/\A(\d{1,2})/, '')
493           val = $1.to_i
494           return unless (0..60) === val
495           e.sec = val
496         when 's'
497           return unless str.sub!(/\A(-?\d{1,})/, '')
498           val = $1.to_i
499           e.seconds = val
500         when 'T'
501           return unless _strptime_i(str, '%H:%M:%S', e)
502         when 'U', 'W', 'OU', 'OW'
503           return unless str.sub!(/\A(\d{1,2})/, '')
504           val = $1.to_i
505           return unless (0..53) === val
506           e.__send__(if s[-1,1] == 'U' then :wnum0= else :wnum1= end, val)
507         when 'u', 'Ou'
508           return unless str.sub!(/\A(\d{1})/, '')
509           val = $1.to_i
510           return unless (1..7) === val
511           e.cwday = val
512         when 'V', 'OV'
513           return unless str.sub!(/\A(\d{1,2})/, '')
514           val = $1.to_i
515           return unless (1..53) === val
516           e.cweek = val
517         when 'v'
518           return unless _strptime_i(str, '%e-%b-%Y', e)
519         when 'w'
520           return unless str.sub!(/\A(\d{1})/, '')
521           val = $1.to_i
522           return unless (0..6) === val
523           e.wday = val
524         when 'X', 'EX'
525           return unless _strptime_i(str, '%H:%M:%S', e)
526         when 'x', 'Ex'
527           return unless _strptime_i(str, '%m/%d/%y', e)
528         when 'Y', 'EY'
529           return unless str.sub!(if num_pattern?($')
530                                  then /\A([-+]?\d{1,4})/
531                                  else /\A([-+]?\d{1,})/
532                                  end, '')
533           val = $1.to_i
534           e.year = val
535         when 'y', 'Ey', 'Oy'
536           return unless str.sub!(/\A(\d{1,2})/, '')
537           val = $1.to_i
538           return unless (0..99) === val
539           e.year = val
540           e._cent ||= if val >= 69 then 19 else 20 end
541         when 'Z', /\A:{0,3}z/
542           return unless str.sub!(/\A((?:gmt|utc?)?[-+]\d+(?:[,.:]\d+(?::\d+)?)?
543                                     |[[:alpha:].\s]+(?:standard|daylight)\s+time\b
544                                     |[[:alpha:]]+(?:\s+dst)?\b
545                                     )/ix, '')
546           val = $1
547           e.zone = val
548           offset = zone_to_diff(val)
549           e.offset = offset
550         when '%'
551           return unless str.sub!(/\A%/, '')
552         when '+'
553           return unless _strptime_i(str, '%a %b %e %H:%M:%S %Z %Y', e)
554         else
555           return unless str.sub!(Regexp.new('\\A' + Regexp.quote(a)), '')
556         end
557       else
558         case c
559         when /\A[\s\v]/
560           str.sub!(/\A[\s\v]+/, '')
561         else
562           return unless str.sub!(Regexp.new('\\A' + Regexp.quote(a)), '')
563         end
564       end
565     end
566   end
568   private_class_method :_strptime_i
570   def self._strptime(str, fmt='%F')
571     str = str.dup
572     e = Format::Bag.new
573     return unless _strptime_i(str, fmt, e)
575     if e._cent
576       if e.cwyear
577         e.cwyear += e._cent * 100
578       end
579       if e.year
580         e.  year += e._cent * 100
581       end
582     end
584     if e._merid
585       if e.hour
586         e.hour %= 12
587         e.hour += e._merid
588       end
589     end
591     unless str.empty?
592       e.leftover = str
593     end
595     e.to_hash
596   end
598   def self.s3e(e, y, m, d, bc=false)
599     unless String === m
600       m = m.to_s
601     end
603     if y && m && !d
604       y, m, d = d, y, m
605     end
607     if y == nil
608       if d && d.size > 2
609         y = d
610         d = nil
611       end
612       if d && d[0,1] == "'"
613         y = d
614         d = nil
615       end
616     end
618     if y
619       y.scan(/(\d+)(.+)?/)
620       if $2
621         y, d = d, $1
622       end
623     end
625     if m
626       if m[0,1] == "'" || m.size > 2
627         y, m, d = m, d, y # us -> be
628       end
629     end
631     if d
632       if d[0,1] == "'" || d.size > 2
633         y, d = d, y
634       end
635     end
637     if y
638       y =~ /([-+])?(\d+)/
639       if $1 || $2.size > 2
640         c = false
641       end
642       iy = $&.to_i
643       if bc
644         iy = -iy + 1
645       end
646       
647       e.year = iy
648     end
650     if m
651       m =~ /\d+/
652       e.mon = $&.to_i
653     end
655     if d
656       d =~ /\d+/
657       e.mday = $&.to_i
658     end
660     if c != nil
661       e._comp = c
662     end
664   end
666   private_class_method :s3e
668   def self._parse_day(str, e) # :nodoc:
669     if str.sub!(/\b(#{Format::ABBR_DAYS.keys.join('|')})[^-\d\s]*/ino, ' ')
670       e.wday = Format::ABBR_DAYS[$1.downcase]
671       true
672 =begin
673     elsif str.sub!(/\b(?!\dth)(su|mo|tu|we|th|fr|sa)\b/in, ' ')
674       e.wday = %w(su mo tu we th fr sa).index($1.downcase)
675       true
676 =end
677     end
678   end
680   def self._parse_time(str, e) # :nodoc:
681     if str.sub!(
682                 /(
683                    (?:
684                      \d+\s*:\s*\d+
685                      (?:
686                        \s*:\s*\d+(?:[,.]\d*)?
687                      )?
688                    |
689                      \d+\s*h(?:\s*\d+m?(?:\s*\d+s?)?)?
690                    )
691                    (?:
692                      \s*
693                      [ap](?:m\b|\.m\.)
694                    )?
695                  |
696                    \d+\s*[ap](?:m\b|\.m\.)
697                  )
698                  (?:
699                    \s*
700                    (
701                      (?:gmt|utc?)?[-+]\d+(?:[,.:]\d+(?::\d+)?)?
702                    |
703                      [[:alpha:].\s]+(?:standard|daylight)\stime\b
704                    |
705                      [[:alpha:]]+(?:\sdst)?\b
706                    )
707                  )?
708                 /inx,
709                 ' ')
711       t = $1
712       e.zone = $2 if $2
714       t =~ /\A(\d+)h?
715               (?:\s*:?\s*(\d+)m?
716                 (?:
717                   \s*:?\s*(\d+)(?:[,.](\d+))?s?
718                 )?
719               )?
720             (?:\s*([ap])(?:m\b|\.m\.))?/inx
722       e.hour = $1.to_i
723       e.min = $2.to_i if $2
724       e.sec = $3.to_i if $3
725       e.sec_fraction = Rational($4.to_i, 10**$4.size) if $4
727       if $5
728         e.hour %= 12
729         if $5.downcase == 'p'
730           e.hour += 12
731         end
732       end
733       true
734     end
735   end
737 =begin
738   def self._parse_beat(str, e) # :nodoc:
739     if str.sub!(/@\s*(\d+)(?:[,.](\d*))?/, ' ')
740       beat = Rational($1.to_i)
741       beat += Rational($2.to_i, 10**$2.size) if $2
742       secs = Rational(beat, 1000)
743       h, min, s, fr = self.day_fraction_to_time(secs)
744       e.hour = h
745       e.min = min
746       e.sec = s
747       e.sec_fraction = fr * 86400
748       e.zone = '+01:00'
749       true
750     end
751   end
752 =end
754   def self._parse_eu(str, e) # :nodoc:
755     if str.sub!(
756                 /'?(\d+)[^-\d\s]*
757                  \s*
758                  (#{Format::ABBR_MONTHS.keys.join('|')})[^-\d\s']*
759                  (?:
760                    \s*
761                    (c(?:e|\.e\.)|b(?:ce|\.c\.e\.)|a(?:d|\.d\.)|b(?:c|\.c\.))?
762                    \s*
763                    ('?-?\d+(?:(?:st|nd|rd|th)\b)?)
764                  )?
765                 /inox,
766                 ' ') # '
767       s3e(e, $4, Format::ABBR_MONTHS[$2.downcase], $1,
768           $3 && $3[0,1].downcase == 'b')
769       true
770     end
771   end
773   def self._parse_us(str, e) # :nodoc:
774     if str.sub!(
775                 /\b(#{Format::ABBR_MONTHS.keys.join('|')})[^-\d\s']*
776                  \s*
777                  ('?\d+)[^-\d\s']*
778                  (?:
779                    \s*
780                    (c(?:e|\.e\.)|b(?:ce|\.c\.e\.)|a(?:d|\.d\.)|b(?:c|\.c\.))?
781                    \s*
782                    ('?-?\d+)
783                  )?
784                 /inox,
785                 ' ') # '
786       s3e(e, $4, Format::ABBR_MONTHS[$1.downcase], $2,
787           $3 && $3[0,1].downcase == 'b')
788       true
789     end
790   end
792   def self._parse_iso(str, e) # :nodoc:
793     if str.sub!(/('?[-+]?\d+)-(\d+)-('?-?\d+)/n, ' ')
794       s3e(e, $1, $2, $3)
795       true
796     end
797   end
799   def self._parse_iso2(str, e) # :nodoc:
800     if str.sub!(/\b(\d{2}|\d{4})?-?w(\d{2})(?:-?(\d))?\b/in, ' ')
801       e.cwyear = $1.to_i if $1
802       e.cweek = $2.to_i
803       e.cwday = $3.to_i if $3
804       true
805     elsif str.sub!(/-w-(\d)\b/in, ' ')
806       e.cwday = $1.to_i
807       true
808     elsif str.sub!(/--(\d{2})?-(\d{2})\b/n, ' ')
809       e.mon = $1.to_i if $1
810       e.mday = $2.to_i
811       true
812     elsif str.sub!(/--(\d{2})(\d{2})?\b/n, ' ')
813       e.mon = $1.to_i
814       e.mday = $2.to_i if $2
815       true
816     elsif /[,.](\d{2}|\d{4})-\d{3}\b/n !~ str &&
817         str.sub!(/\b(\d{2}|\d{4})-(\d{3})\b/n, ' ')
818       e.year = $1.to_i
819       e.yday = $2.to_i
820       true
821     elsif /\d-\d{3}\b/n !~ str &&
822         str.sub!(/\b-(\d{3})\b/n, ' ')
823       e.yday = $1.to_i
824       true
825     end
826   end
828   def self._parse_jis(str, e) # :nodoc:
829     if str.sub!(/\b([mtsh])(\d+)\.(\d+)\.(\d+)/in, ' ')
830       era = { 'm'=>1867,
831               't'=>1911,
832               's'=>1925,
833               'h'=>1988
834           }[$1.downcase]
835       e.year = $2.to_i + era
836       e.mon = $3.to_i
837       e.mday = $4.to_i
838       true
839     end
840   end
842   def self._parse_vms(str, e) # :nodoc:
843     if str.sub!(/('?-?\d+)-(#{Format::ABBR_MONTHS.keys.join('|')})[^-]*
844                 -('?-?\d+)/inox, ' ')
845       s3e(e, $3, Format::ABBR_MONTHS[$2.downcase], $1)
846       true
847     elsif str.sub!(/\b(#{Format::ABBR_MONTHS.keys.join('|')})[^-]*
848                 -('?-?\d+)(?:-('?-?\d+))?/inox, ' ')
849       s3e(e, $3, Format::ABBR_MONTHS[$1.downcase], $2)
850       true
851     end
852   end
854   def self._parse_sla_ja(str, e) # :nodoc:
855     if str.sub!(%r|('?-?\d+)[/.]\s*('?\d+)(?:[^\d]\s*('?-?\d+))?|n, ' ') # '
856       s3e(e, $1, $2, $3)
857       true
858     end
859   end
861   def self._parse_sla_eu(str, e) # :nodoc:
862     if str.sub!(%r|('?-?\d+)[/.]\s*('?\d+)(?:[^\d]\s*('?-?\d+))?|n, ' ') # '
863       s3e(e, $3, $2, $1)
864       true
865     end
866   end
868   def self._parse_sla_us(str, e) # :nodoc:
869     if str.sub!(%r|('?-?\d+)[/.]\s*('?\d+)(?:[^\d]\s*('?-?\d+))?|n, ' ') # '
870       s3e(e, $3, $1, $2)
871       true
872     end
873   end
876   def self._parse_sla_jp(str, e) # :nodoc:
877     if str.sub!(%r|('?-?\d+)[/.]\s*('?\d+)(?:\D\s*('?-?\d+))?|n, ' ') # '
878       s3e(e, $1, $2, $3)
879       true
880     end
881   end
882   
883   def self._parse_year(str, e) # :nodoc:
884     if str.sub!(/'(\d+)\b/n, ' ')
885       e.year = $1.to_i
886       true
887     end
888   end
890   def self._parse_mon(str, e) # :nodoc:
891     if str.sub!(/\b(#{Format::ABBR_MONTHS.keys.join('|')})\S*/ino, ' ')
892       e.mon = Format::ABBR_MONTHS[$1.downcase]
893       true
894     end
895   end
897   def self._parse_mday(str, e) # :nodoc:
898     if str.sub!(/(\d+)(st|nd|rd|th)\b/in, ' ')
899       e.mday = $1.to_i
900       true
901     end
902   end
904   def self._parse_ddd(str, e) # :nodoc:
905     if str.sub!(
906                 /([-+]?)(\d{2,14})
907                   (?:
908                     \s*
909                     t?
910                     \s*
911                     (\d{2,6})?(?:[,.](\d*))?
912                   )?
913                   (?:
914                     \s*
915                     (
916                       z\b
917                     |
918                       [-+]\d{1,4}\b
919                     |
920                       \[[-+]?\d[^\]]*\]
921                     )
922                   )?
923                 /inx,
924                 ' ')
925       case $2.size
926       when 2
927         if $3.nil? && $4
928           e.sec  = $2[-2, 2].to_i
929         else
930           e.mday = $2[ 0, 2].to_i
931         end
932       when 4
933         if $3.nil? && $4
934           e.sec  = $2[-2, 2].to_i
935           e.min  = $2[-4, 2].to_i
936         else
937           e.mon  = $2[ 0, 2].to_i
938           e.mday = $2[ 2, 2].to_i
939         end
940       when 6
941         if $3.nil? && $4
942           e.sec  = $2[-2, 2].to_i
943           e.min  = $2[-4, 2].to_i
944           e.hour = $2[-6, 2].to_i
945         else
946           e.year = ($1 + $2[ 0, 2]).to_i
947           e.mon  = $2[ 2, 2].to_i
948           e.mday = $2[ 4, 2].to_i
949         end
950       when 8, 10, 12, 14
951         if $3.nil? && $4
952           e.sec  = $2[-2, 2].to_i
953           e.min  = $2[-4, 2].to_i
954           e.hour = $2[-6, 2].to_i
955           e.mday = $2[-8, 2].to_i
956           if $2.size >= 10
957             e.mon  = $2[-10, 2].to_i
958           end
959           if $2.size == 12
960             e.year = ($1 + $2[-12, 2]).to_i
961           end
962           if $2.size == 14
963             e.year = ($1 + $2[-14, 4]).to_i
964             e._comp = false
965           end
966         else
967           e.year = ($1 + $2[ 0, 4]).to_i
968           e.mon  = $2[ 4, 2].to_i
969           e.mday = $2[ 6, 2].to_i
970           e.hour = $2[ 8, 2].to_i if $2.size >= 10
971           e.min  = $2[10, 2].to_i if $2.size >= 12
972           e.sec  = $2[12, 2].to_i if $2.size >= 14
973           e._comp = false
974         end
975       when 3
976         if $3.nil? && $4
977           e.sec  = $2[-2, 2].to_i
978           e.min  = $2[-3, 1].to_i
979         else
980           e.yday = $2[ 0, 3].to_i
981         end
982       when 5
983         if $3.nil? && $4
984           e.sec  = $2[-2, 2].to_i
985           e.min  = $2[-4, 2].to_i
986           e.hour = $2[-5, 1].to_i
987         else
988           e.year = ($1 + $2[ 0, 2]).to_i
989           e.yday = $2[ 2, 3].to_i
990         end
991       when 7
992         if $3.nil? && $4
993           e.sec  = $2[-2, 2].to_i
994           e.min  = $2[-4, 2].to_i
995           e.hour = $2[-6, 2].to_i
996           e.mday = $2[-7, 1].to_i
997         else
998           e.year = ($1 + $2[ 0, 4]).to_i
999           e.yday = $2[ 4, 3].to_i
1000         end
1001       end
1002       if $3
1003         if $4
1004           case $3.size
1005           when 2, 4, 6
1006             e.sec  = $3[-2, 2].to_i
1007             e.min  = $3[-4, 2].to_i if $3.size >= 4
1008             e.hour = $3[-6, 2].to_i if $3.size >= 6
1009           end
1010         else
1011           case $3.size
1012           when 2, 4, 6
1013             e.hour = $3[ 0, 2].to_i
1014             e.min  = $3[ 2, 2].to_i if $3.size >= 4
1015             e.sec  = $3[ 4, 2].to_i if $3.size >= 6
1016           end
1017         end
1018       end
1019       if $4
1020         e.sec_fraction = Rational($4.to_i, 10**$4.size)
1021       end
1022       if $5
1023         e.zone = $5
1024         if e.zone[0,1] == '['
1025           o, n, = e.zone[1..-2].split(':')
1026           e.zone = n || o
1027           if /\A\d/ =~ o
1028             o = format('+%s', o)
1029           end
1030           e.offset = zone_to_diff(o)
1031         end
1032       end
1033       true
1034     end
1035   end
1037   private_class_method :_parse_day, :_parse_time, # :_parse_beat,
1038         :_parse_eu, :_parse_us, :_parse_iso, :_parse_iso2,
1039         :_parse_jis, :_parse_vms, :_parse_sla_jp, :_parse_sla_eu, :_parse_sla_us,
1040         :_parse_year, :_parse_mon, :_parse_mday, :_parse_ddd
1042   def self._parse(str, comp=true)
1043     str = str.dup
1045     e = Format::Bag.new
1047     e._comp = comp
1049     str.gsub!(/[^-+',.\/:@[:alnum:]\[\]\x80-\xff]+/n, ' ')
1051     _parse_time(str, e) # || _parse_beat(str, e)
1052     _parse_day(str, e)
1054     _parse_eu(str, e)     ||
1055     _parse_us(str, e)     ||
1056     _parse_iso(str, e)    ||
1057     _parse_jis(str, e)    ||
1058     _parse_vms(str, e)    ||
1059     _parse_sla_us(str, e) ||
1060     _parse_iso2(str, e)   ||
1061     _parse_year(str, e)   ||
1062     _parse_mon(str, e)    ||
1063     _parse_mday(str, e)   ||
1064     _parse_ddd(str, e)
1066     if str.sub!(/\b(bc\b|bce\b|b\.c\.|b\.c\.e\.)/in, ' ')
1067       if e.year
1068         e.year = -e.year + 1
1069       end
1070     end
1072     if str.sub!(/\A\s*(\d{1,2})\s*\z/n, ' ')
1073       if e.hour && !e.mday
1074         v = $1.to_i
1075         if (1..31) === v
1076           e.mday = v
1077         end
1078       end
1079       if e.mday && !e.hour
1080         v = $1.to_i
1081         if (0..24) === v
1082           e.hour = v
1083         end
1084       end
1085     end
1087     if e._comp
1088       if e.cwyear
1089         if e.cwyear >= 0 && e.cwyear <= 99
1090           e.cwyear += if e.cwyear >= 69
1091                       then 1900 else 2000 end
1092         end
1093       end
1094       if e.year
1095         if e.year >= 0 && e.year <= 99
1096           e.year += if e.year >= 69
1097                     then 1900 else 2000 end
1098         end
1099       end
1100     end
1102     e.offset ||= zone_to_diff(e.zone) if e.zone
1104     e.to_hash
1105   end
1107   def self._iso8601(str) # :nodoc:
1108     if /\A\s*(([-+]?\d{2,}|-)-\d{2}-\d{2}|
1109               ([-+]?\d{2,})?-\d{3}|
1110               (\d{2}|\d{4})?-w\d{2}-\d|
1111               -w-\d)
1112         (t
1113         \d{2}:\d{2}(:\d{2}([,.]\d+)?)?
1114         (z|[-+]\d{2}(:?\d{2})?)?)?\s*\z/inx =~ str
1115       _parse(str)
1116     elsif /\A\s*(([-+]?(\d{2}|\d{4})|--)\d{2}\d{2}|
1117               ([-+]?(\d{2}|\d{4}))?\d{3}|-\d{3}|
1118               (\d{2}|\d{4})?w\d{2}\d)
1119         (t?
1120         \d{2}\d{2}(\d{2}([,.]\d+)?)?
1121         (z|[-+]\d{2}(\d{2})?)?)?\s*\z/inx =~ str
1122       _parse(str)
1123     elsif /\A\s*(\d{2}:\d{2}(:\d{2}([,.]\d+)?)?
1124         (z|[-+]\d{2}(:?\d{2})?)?)?\s*\z/inx =~ str
1125       _parse(str)
1126     elsif /\A\s*(\d{2}\d{2}(\d{2}([,.]\d+)?)?
1127         (z|[-+]\d{2}(\d{2})?)?)?\s*\z/inx =~ str
1128       _parse(str)
1129     end
1130   end
1132   def self._rfc3339(str) # :nodoc:
1133     if /\A\s*-?\d{4}-\d{2}-\d{2} # allow minus, anyway
1134         (t|\s)
1135         \d{2}:\d{2}:\d{2}(\.\d+)?
1136         (z|[-+]\d{2}:\d{2})\s*\z/inx =~ str
1137       _parse(str)
1138     end
1139   end
1141   def self._xmlschema(str) # :nodoc:
1142     if /\A\s*(-?\d{4,})(?:-(\d{2})(?:-(\d{2}))?)?
1143         (?:t
1144           (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?
1145         (z|[-+]\d{2}:\d{2})?\s*\z/inx =~ str
1146       e = Format::Bag.new
1147       e.year = $1.to_i
1148       e.mon = $2.to_i if $2
1149       e.mday = $3.to_i if $3
1150       e.hour = $4.to_i if $4
1151       e.min = $5.to_i if $5
1152       e.sec = $6.to_i if $6
1153       e.sec_fraction = Rational($7.to_i, 10**$7.size) if $7
1154       if $8
1155         e.zone = $8
1156         e.offset = zone_to_diff($8)
1157       end
1158       e.to_hash
1159     elsif /\A\s*(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?
1160         (z|[-+]\d{2}:\d{2})?\s*\z/inx =~ str
1161       e = Format::Bag.new
1162       e.hour = $1.to_i if $1
1163       e.min = $2.to_i if $2
1164       e.sec = $3.to_i if $3
1165       e.sec_fraction = Rational($4.to_i, 10**$4.size) if $4
1166       if $5
1167         e.zone = $5
1168         e.offset = zone_to_diff($5)
1169       end
1170       e.to_hash
1171     elsif /\A\s*(?:--(\d{2})(?:-(\d{2}))?|---(\d{2}))
1172         (z|[-+]\d{2}:\d{2})?\s*\z/inx =~ str
1173       e = Format::Bag.new
1174       e.mon = $1.to_i if $1
1175       e.mday = $2.to_i if $2
1176       e.mday = $3.to_i if $3
1177       if $4
1178         e.zone = $4
1179         e.offset = zone_to_diff($4)
1180       end
1181       e.to_hash
1182     end
1183   end
1185   def self._rfc2822(str) # :nodoc:
1186     if /\A\s*(?:(?:#{Format::ABBR_DAYS.keys.join('|')})\s*,\s+)?
1187         \d{1,2}\s+
1188         (?:#{Format::ABBR_MONTHS.keys.join('|')})\s+
1189         -?(\d{2,})\s+ # allow minus, anyway
1190         \d{2}:\d{2}(:\d{2})?\s*
1191         (?:[-+]\d{4}|ut|gmt|e[sd]t|c[sd]t|m[sd]t|p[sd]t|[a-ik-z])\s*\z/inox =~ str
1192       e = _parse(str, false)
1193       if $1.size < 4
1194         if e[:year] < 50
1195           e[:year] += 2000
1196         elsif e[:year] < 1000
1197           e[:year] += 1900
1198         end
1199       end
1200       e
1201     end
1202   end
1204   class << self; alias_method :_rfc822, :_rfc2822 end
1206   def self._httpdate(str) # :nodoc:
1207     if /\A\s*(#{Format::ABBR_DAYS.keys.join('|')})\s*,\s+
1208         \d{2}\s+
1209         (#{Format::ABBR_MONTHS.keys.join('|')})\s+
1210         -?\d{4}\s+ # allow minus, anyway
1211         \d{2}:\d{2}:\d{2}\s+
1212         gmt\s*\z/inox =~ str
1213       _rfc2822(str)
1214     elsif /\A\s*(#{Format::DAYS.keys.join('|')})\s*,\s+
1215         \d{2}\s*-\s*
1216         (#{Format::ABBR_MONTHS.keys.join('|')})\s*-\s*
1217         \d{2}\s+
1218         \d{2}:\d{2}:\d{2}\s+
1219         gmt\s*\z/inox =~ str
1220       _parse(str)
1221     elsif /\A\s*(#{Format::ABBR_DAYS.keys.join('|')})\s+
1222         (#{Format::ABBR_MONTHS.keys.join('|')})\s+
1223         \d{1,2}\s+
1224         \d{2}:\d{2}:\d{2}\s+
1225         \d{4}\s*\z/inox =~ str
1226       _parse(str)
1227     end
1228   end
1230   def self._jisx0301(str) # :nodoc:
1231     if /\A\s*[mtsh]?\d{2}\.\d{2}\.\d{2}
1232         (t
1233         (\d{2}:\d{2}(:\d{2}([,.]\d*)?)?
1234         (z|[-+]\d{2}(:?\d{2})?)?)?)?\s*\z/inx =~ str
1235       if /\A\s*\d/ =~ str
1236         _parse(str.sub(/\A\s*(\d)/, 'h\1'))
1237       else
1238         _parse(str)
1239       end
1240     else
1241       _iso8601(str)
1242     end
1243   end
1245   t = Module.new do
1247     private
1249     def zone_to_diff(zone) # :nodoc:
1250       zone = zone.downcase
1251       if zone.sub!(/\s+(standard|daylight)\s+time\z/, '')
1252         dst = $1 == 'daylight'
1253       else
1254         dst = zone.sub!(/\s+dst\z/, '')
1255       end
1256       if Format::ZONES.include?(zone)
1257         offset = Format::ZONES[zone]
1258         offset += 3600 if dst
1259       elsif zone.sub!(/\A(?:gmt|utc?)?([-+])/, '')
1260         sign = $1
1261         if zone.include?(':')
1262           hour, min, sec, = zone.split(':')
1263         elsif zone.include?(',') || zone.include?('.')
1264           hour, fr, = zone.split(/[,.]/)
1265           min = Rational(fr.to_i, 10**fr.size) * 60
1266         else
1267           case zone.size
1268           when 3
1269             hour = zone[0,1]
1270             min = zone[1,2]
1271           else
1272             hour = zone[0,2]
1273             min = zone[2,2]
1274             sec = zone[4,2]
1275           end
1276         end
1277         offset = hour.to_i * 3600 + min.to_i * 60 + sec.to_i
1278         offset *= -1 if sign == '-'
1279       end
1280       offset
1281     end
1283   end
1285   extend  t
1286   include t
1290 class DateTime < Date
1292   def strftime(fmt='%FT%T%:z')
1293     super(fmt)
1294   end
1296   def self._strptime(str, fmt='%FT%T%z')
1297     super(str, fmt)
1298   end
1300   def iso8601_timediv(n) # :nodoc:
1301     strftime('T%T' +
1302              if n < 1
1303                ''
1304              else
1305                '.%0*d' % [n, (sec_fraction / Rational(1, 10**n)).round]
1306              end +
1307              '%:z')
1308   end
1310   private :iso8601_timediv
1312   def iso8601(n=0)
1313     super() + iso8601_timediv(n)
1314   end
1316   def rfc3339(n=0) iso8601(n) end
1318   def xmlschema(n=0) iso8601(n) end # :nodoc:
1320   def jisx0301(n=0)
1321     super() + iso8601_timediv(n)
1322   end