lib/time.rb


DEFINITIONS

This source file includes following functions.


   1  # $Id$
   2  
   3  require 'parsedate'
   4  
   5  =begin
   6  = time
   7  
   8  This library extends Time class:
   9  * conversion between date string and time object.
  10    * date-time defined by RFC 2822
  11    * HTTP-date defined by RFC 2616
  12    * dateTime defined by XML Schema Part 2: Datatypes (ISO 8601)
  13    * various format handled by ParseDate (string to time only)
  14  
  15  == Design Issue
  16  
  17  * specialized interface
  18  
  19    This library provides methods dedicated to special puposes:
  20    RFC 2822, RFC 2616 and XML Schema.
  21    They makes usual life easier.
  22  
  23  * doesn't depend on strftime
  24  
  25    This library doesn't use strftime.
  26    Especially Time#rfc2822 doesn't depend on strftime because:
  27  
  28    * %a and %b are locale sensitive
  29  
  30      Since they are locale sensitive, they may be replaced to
  31      invalid weekday/month name in some locales.
  32      Since ruby-1.6 doesn't invoke setlocale by default,
  33      the problem doesn't arise until some external library invokes setlocale.
  34      Ruby/GTK is the example of such library.
  35  
  36    * %z is not portable
  37  
  38      %z is required to generate zone in date-time of RFC 2822
  39      but it is not portable.
  40  =end
  41  
  42  class Time
  43    class << Time
  44  
  45      ZoneOffset = {
  46        'UTC' => 0,
  47        # ISO 8601
  48        'Z' => 0,
  49        # RFC 822
  50        'UT' => 0, 'GMT' => 0,
  51        'EST' => -5, 'EDT' => -4,
  52        'CST' => -6, 'CDT' => -5,
  53        'MST' => -7, 'MDT' => -6,
  54        'PST' => -8, 'PDT' => -7,
  55        # Following definition of military zones is original one.
  56        # See RFC 1123 and RFC 2822 for the error of RFC 822.
  57        'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4,  'E' => +5,  'F' => +6, 
  58        'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12,
  59        'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4,  'R' => -5,  'S' => -6, 
  60        'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12,
  61      }
  62      def zone_offset(zone, year=Time.now.year)
  63        off = nil
  64        zone = zone.upcase
  65        if /\A([-+])(\d\d):?(\d\d)\z/ =~ zone
  66          off = ($1 == '-' ? -1 : 1) * ($2.to_i * 60 + $3.to_i) * 60
  67        elsif /\A[-+]\d\d\z/ =~ zone
  68          off = zone.to_i * 3600
  69        elsif ZoneOffset.include?(zone)
  70          off = ZoneOffset[zone] * 3600
  71        elsif ((t = Time.local(year, 1, 1)).zone.upcase == zone rescue false)
  72          off = t.utc_offset
  73        elsif ((t = Time.local(year, 7, 1)).zone.upcase == zone rescue false)
  74          off = t.utc_offset
  75        end
  76        off
  77      end
  78  
  79  =begin
  80  == class methods
  81  
  82  --- Time.parse(date, now=Time.now)
  83  --- Time.parse(date, now=Time.now) {|year| year}
  84      parses ((|date|)) using ParseDate.parsedate and converts it to a
  85      Time object.
  86  
  87      If a block is given, the year described in ((|date|)) is converted
  88      by the block.  For example:
  89  
  90          Time.parse(...) {|y| y < 100 ? (y >= 69 ? y + 1900 : y + 2000) : y}
  91  
  92      If the upper components of the given time are broken or missing,
  93      they are supplied with those of ((|now|)).  For the lower
  94      components, the minimum values (1 or 0) are assumed if broken or
  95      missing.  For example:
  96  
  97          # Suppose it is "Thu Nov 29 14:33:20 GMT 2001" now and
  98          # your timezone is GMT:
  99          Time.parse("16:30")     #=> Thu Nov 29 16:30:00 GMT 2001
 100          Time.parse("7/23")      #=> Mon Jul 23 00:00:00 GMT 2001
 101          Time.parse("2002/1")    #=> Tue Jan 01 00:00:00 GMT 2002
 102  
 103      Since there are numerous conflicts among locally defined timezone
 104      abbreviations all over the world, this method is not made to
 105      understand all of them.  For example, the abbreviation "CST" is
 106      used variously as:
 107  
 108          -06:00 in America/Chicago,
 109          -05:00 in America/Havana,
 110          +08:00 in Asia/Harbin,
 111          +09:30 in Australia/Darwin,
 112          +10:30 in Australia/Adelaide,
 113          etc.
 114  
 115      Based on the fact, this method only understands the timezone
 116      abbreviations described in RFC 822 and the system timezone, in the
 117      order named. (i.e. a definition in RFC 822 overrides the system
 118      timezone definition)  The system timezone is taken from
 119      (({Time.local(year, 1, 1).zone})) and
 120      (({Time.local(year, 7, 1).zone})).
 121      If the extracted timezone abbreviation does not match any of them,
 122      it is ignored and the given time is regarded as a local time.
 123  
 124      ArgumentError is raised if ParseDate cannot extract
 125      information from ((|date|))
 126      or Time class cannot represent specified date.
 127  
 128      This method can be used as fail-safe for other parsing methods as:
 129  
 130        Time.rfc2822(date) rescue Time.parse(date)
 131        Time.httpdate(date) rescue Time.parse(date)
 132        Time.xmlschema(date) rescue Time.parse(date)
 133  
 134      A failure for Time.parse should be checked, though.
 135  =end
 136      def parse(date, now=Time.now)
 137        year, mon, day, hour, min, sec, zone, _ = ParseDate.parsedate(date)
 138        year = yield year if year && block_given?
 139  
 140        if now
 141          begin
 142            break if year; year = now.year
 143            break if mon; mon = now.mon
 144            break if day; day = now.day
 145            break if hour; hour = now.hour
 146            break if min; min = now.min
 147            break if sec; sec = now.sec
 148          end until true
 149        end
 150  
 151        year ||= 1970
 152        mon ||= 1
 153        day ||= 1
 154        hour ||= 0
 155        min ||= 0
 156        sec ||= 0
 157  
 158        off = nil
 159        off = zone_offset(zone, year) if zone
 160  
 161        if off
 162          t = Time.utc(year, mon, day, hour, min, sec) - off
 163          t.localtime if off != 0
 164          t
 165        else
 166          Time.local(year, mon, day, hour, min, sec)
 167        end
 168      end
 169  
 170      MonthValue = {
 171        'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
 172        'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' =>10, 'NOV' =>11, 'DEC' =>12
 173      }
 174  
 175  =begin
 176  --- Time.rfc2822(date)
 177  --- Time.rfc822(date)
 178      parses ((|date|)) as date-time defined by RFC 2822 and converts it to a
 179      Time object.
 180      The format is identical to the date format defined by RFC 822 and
 181      updated by RFC 1123.
 182  
 183      ArgumentError is raised if ((|date|)) is not compliant with RFC 2822
 184      or Time class cannot represent specified date.
 185  =end
 186      def rfc2822(date)
 187        if /\A\s*
 188            (?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*,\s*)?
 189            (\d{1,2})\s+
 190            (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+
 191            (\d{2,})\s+
 192            (\d{2})\s*
 193            :\s*(\d{2})\s*
 194            (?::\s*(\d{2}))?\s+
 195            ([+\-]\d{4}|
 196             UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Z])/ix =~ date
 197          # Since RFC 2822 permit comments, the regexp has no right anchor.
 198          day = $1.to_i
 199          mon = MonthValue[$2.upcase]
 200          year = $3.to_i
 201          hour = $4.to_i
 202          min = $5.to_i
 203          sec = $6 ? $6.to_i : 0
 204          zone = $7
 205  
 206          # following year completion is compliant with RFC 2822.
 207          year = if year < 50
 208                   2000 + year
 209                 elsif year < 1000
 210                   1900 + year
 211                 else
 212                   year
 213                 end
 214  
 215          t = Time.utc(year, mon, day, hour, min, sec)
 216          offset = zone_offset(zone)
 217          t = (t - offset).localtime if offset != 0 || zone == '+0000'
 218          t
 219        else
 220          raise ArgumentError.new("not RFC 2822 compliant date: #{date.inspect}")
 221        end
 222      end
 223      alias rfc822 rfc2822
 224  
 225  =begin
 226  --- Time.httpdate(date)
 227      parses ((|date|)) as HTTP-date defined by RFC 2616 and converts it to a
 228      Time object.
 229  
 230      ArgumentError is raised if ((|date|)) is not compliant with RFC 2616
 231      or Time class cannot represent specified date.
 232  =end
 233      def httpdate(date)
 234        if /\A\s*
 235            (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\x20
 236            (\d{2})\x20
 237            (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
 238            (\d{4})\x20
 239            (\d{2}):(\d{2}):(\d{2})\x20
 240            GMT
 241            \s*\z/ix =~ date
 242          Time.rfc2822(date)
 243        elsif /\A\s*
 244               (?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\x20
 245               (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d)\x20
 246               (\d\d):(\d\d):(\d\d)\x20
 247               GMT
 248               \s*\z/ix =~ date
 249          Time.parse(date)
 250        elsif /\A\s*
 251               (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\x20
 252               (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
 253               (\d\d|\x20\d)\x20
 254               (\d\d):(\d\d):(\d\d)\x20
 255               (\d{4})
 256               \s*\z/ix =~ date
 257          Time.utc($6.to_i, MonthValue[$1.upcase], $2.to_i,
 258                   $3.to_i, $4.to_i, $5.to_i)
 259        else
 260          raise ArgumentError.new("not RFC 2616 compliant date: #{date.inspect}")
 261        end
 262      end
 263  
 264  =begin
 265  --- Time.xmlschema(date)
 266  --- Time.iso8601(date)
 267      parses ((|date|)) as dateTime defined by XML Schema and
 268      converts it to a Time object.
 269      The format is restricted version of the format defined by ISO 8601.
 270  
 271      ArgumentError is raised if ((|date|)) is not compliant with the format
 272      or Time class cannot represent specified date.
 273  =end
 274      def xmlschema(date)
 275        if /\A\s*
 276            (-?\d+)-(\d\d)-(\d\d)
 277            T
 278            (\d\d):(\d\d):(\d\d)
 279            (\.\d*)?
 280            (Z|[+\-]\d\d:\d\d)?
 281            \s*\z/ix =~ date
 282          datetime = [$1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i] 
 283          datetime << $7.to_f * 1000000 if $7
 284          if $8
 285            Time.utc(*datetime) - zone_offset($8)
 286          else
 287            Time.local(*datetime)
 288          end
 289        else
 290          raise ArgumentError.new("invalid date: #{date.inspect}")
 291        end
 292      end
 293      alias iso8601 xmlschema
 294    end
 295  
 296  =begin
 297  == methods
 298  =end
 299  
 300  =begin
 301  --- Time#rfc2822
 302  --- Time#rfc822
 303      returns a string which represents the time as date-time defined by RFC 2822:
 304  
 305        day-of-week, DD month-name CCYY hh:mm:ss zone
 306  
 307      where zone is [+-]hhmm.
 308  
 309      If self is a UTC time, -0000 is used as zone.
 310  =end
 311  
 312    RFC2822_DAY_NAME = [
 313      'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
 314    ]
 315    RFC2822_MONTH_NAME = [
 316      'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
 317      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
 318    ]
 319    def rfc2822
 320      sprintf('%s, %02d %s %d %02d:%02d:%02d ',
 321        RFC2822_DAY_NAME[wday],
 322        day, RFC2822_MONTH_NAME[mon-1], year,
 323        hour, min, sec) +
 324      if utc?
 325        '-0000'
 326      else
 327        off = utc_offset
 328        sign = off < 0 ? '-' : '+'
 329        sprintf('%s%02d%02d', sign, *(off.abs / 60).divmod(60))
 330      end
 331    end
 332    alias rfc822 rfc2822
 333  
 334  =begin
 335  --- Time#httpdate
 336      returns a string which represents the time as rfc1123-date of HTTP-date
 337      defined by RFC 2616: 
 338  
 339        day-of-week, DD month-name CCYY hh:mm:ss GMT
 340  
 341      Note that the result is always UTC (GMT).
 342  =end
 343    def httpdate
 344      t = dup.utc
 345      sprintf('%s, %02d %s %d %02d:%02d:%02d GMT',
 346        RFC2822_DAY_NAME[t.wday],
 347        t.day, RFC2822_MONTH_NAME[t.mon-1], t.year,
 348        t.hour, t.min, t.sec)
 349    end
 350  
 351  =begin
 352  --- Time#xmlschema([fractional_seconds])
 353  --- Time#iso8601([fractional_seconds])
 354      returns a string which represents the time as dateTime
 355      defined by XML Schema:
 356  
 357        CCYY-MM-DDThh:mm:ssTZD
 358        CCYY-MM-DDThh:mm:ss.sssTZD
 359  
 360      where TZD is Z or [+-]hh:mm.
 361  
 362      If self is a UTC time, Z is used as TZD.
 363      [+-]hh:mm is used otherwise.
 364  
 365      ((|fractional_seconds|)) specify a number of digits of
 366      fractional seconds.
 367      The default value of ((|fractional_seconds|)) is 0.
 368  =end
 369    def xmlschema(fraction_digits=0)
 370      sprintf('%d-%02d-%02dT%02d:%02d:%02d',
 371        year, mon, day, hour, min, sec) +
 372      if fraction_digits == 0
 373        ''
 374      elsif fraction_digits <= 6
 375        '.' + sprintf('%06d', usec)[0, fraction_digits]
 376      else
 377        '.' + sprintf('%06d', usec) + '0' * (fraction_digits - 6)
 378      end +
 379      if utc?
 380        'Z'
 381      else
 382        off = utc_offset
 383        sign = off < 0 ? '-' : '+'
 384        sprintf('%s%02d:%02d', sign, *(off.abs / 60).divmod(60))
 385      end
 386    end
 387    alias iso8601 xmlschema
 388  end
 389  
 390  if __FILE__ == $0
 391    require 'runit/testcase'
 392    require 'runit/cui/testrunner'
 393  
 394    class TimeExtentionTest < RUNIT::TestCase
 395      def test_rfc822
 396        assert_equal(Time.utc(1976, 8, 26, 14, 30) + 4 * 3600,
 397                     Time.rfc2822("26 Aug 76 14:30 EDT"))
 398        assert_equal(Time.utc(1976, 8, 27, 9, 32) + 7 * 3600,
 399                     Time.rfc2822("27 Aug 76 09:32 PDT"))
 400      end
 401  
 402      def test_rfc2822
 403        assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600,
 404                     Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600"))
 405        assert_equal(Time.utc(2003, 7, 1, 10, 52, 37) - 2 * 3600,
 406                     Time.rfc2822("Tue, 1 Jul 2003 10:52:37 +0200"))
 407        assert_equal(Time.utc(1969, 2, 13, 23, 32, 54) + 3 * 3600 + 30 * 60,
 408                     Time.rfc2822("Thu, 13 Feb 1969 23:32:54 -0330"))
 409        assert_equal(Time.utc(1997, 11, 21, 10, 1, 10) + 6 * 3600,
 410                     Time.rfc2822("Fri, 21 Nov 1997 10:01:10 -0600"))
 411        assert_equal(Time.utc(1997, 11, 21, 11, 0, 0) + 6 * 3600,
 412                     Time.rfc2822("Fri, 21 Nov 1997 11:00:00 -0600"))
 413        assert_equal(Time.utc(1997, 11, 24, 14, 22, 1) + 8 * 3600,
 414                     Time.rfc2822("Mon, 24 Nov 1997 14:22:01 -0800"))
 415        assert_equal(Time.utc(1969, 2, 13, 23, 32, 0) + 3 * 3600 + 30 * 60,
 416                     Time.rfc2822(" Thu,
 417        13
 418          Feb
 419            1969
 420        23:32
 421                 -0330 (Newfoundland Time)"))
 422        assert_equal(Time.utc(1997, 11, 21, 9, 55, 6),
 423                     Time.rfc2822("21 Nov 97 09:55:06 GMT"))
 424        assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600,
 425                     Time.rfc2822("Fri, 21 Nov 1997 09 :   55  :  06 -0600"))
 426        assert_exception(ArgumentError) {
 427          # inner comment is not supported.
 428          Time.rfc2822("Fri, 21 Nov 1997 09(comment):   55  :  06 -0600")
 429        }
 430      end
 431  
 432      def test_rfc2616
 433        t = Time.utc(1994, 11, 6, 8, 49, 37)
 434        assert_equal(t, Time.httpdate("Sun, 06 Nov 1994 08:49:37 GMT"))
 435        assert_equal(t, Time.httpdate("Sunday, 06-Nov-94 08:49:37 GMT"))
 436        assert_equal(t, Time.httpdate("Sun Nov  6 08:49:37 1994"))
 437        assert_equal(Time.utc(1995, 11, 15, 6, 25, 24),
 438                     Time.httpdate("Wed, 15 Nov 1995 06:25:24 GMT"))
 439        assert_equal(Time.utc(1995, 11, 15, 4, 58, 8),
 440                     Time.httpdate("Wed, 15 Nov 1995 04:58:08 GMT"))
 441        assert_equal(Time.utc(1994, 11, 15, 8, 12, 31),
 442                     Time.httpdate("Tue, 15 Nov 1994 08:12:31 GMT"))
 443        assert_equal(Time.utc(1994, 12, 1, 16, 0, 0),
 444                     Time.httpdate("Thu, 01 Dec 1994 16:00:00 GMT"))
 445        assert_equal(Time.utc(1994, 10, 29, 19, 43, 31),
 446                     Time.httpdate("Sat, 29 Oct 1994 19:43:31 GMT"))
 447        assert_equal(Time.utc(1994, 11, 15, 12, 45, 26),
 448                     Time.httpdate("Tue, 15 Nov 1994 12:45:26 GMT"))
 449        assert_equal(Time.utc(1999, 12, 31, 23, 59, 59),
 450                     Time.httpdate("Fri, 31 Dec 1999 23:59:59 GMT"))
 451      end
 452  
 453      def test_rfc3339
 454        t = Time.utc(1985, 4, 12, 23, 20, 50, 520000)
 455        s = "1985-04-12T23:20:50.52Z"
 456        assert_equal(t, Time.iso8601(s))
 457        assert_equal(s, t.iso8601(2))
 458  
 459        t = Time.utc(1996, 12, 20, 0, 39, 57)
 460        s = "1996-12-19T16:39:57-08:00"
 461        assert_equal(t, Time.iso8601(s))
 462        # There is no way to generate time string with arbitrary timezone.
 463        s = "1996-12-20T00:39:57Z"
 464        assert_equal(t, Time.iso8601(s))
 465        assert_equal(s, t.iso8601)
 466  
 467        t = Time.utc(1990, 12, 31, 23, 59, 60)
 468        s = "1990-12-31T23:59:60Z"
 469        assert_equal(t, Time.iso8601(s))
 470        # leap second is representable only if timezone file has it.
 471        s = "1990-12-31T15:59:60-08:00"
 472        assert_equal(t, Time.iso8601(s))
 473  
 474        t = Time.utc(1937, 1, 1, 11, 40, 27, 870000)
 475        s = "1937-01-01T12:00:27.87+00:20"
 476        assert_equal(t, Time.iso8601(s))
 477      end
 478  
 479      # http://www.w3.org/TR/xmlschema-2/
 480      def test_xmlschema
 481        assert_equal(Time.utc(1999, 5, 31, 13, 20, 0) + 5 * 3600,
 482                     Time.xmlschema("1999-05-31T13:20:00-05:00"))
 483        assert_equal(Time.local(2000, 1, 20, 12, 0, 0),
 484                     Time.xmlschema("2000-01-20T12:00:00"))
 485        assert_equal(Time.utc(2000, 1, 20, 12, 0, 0),
 486                     Time.xmlschema("2000-01-20T12:00:00Z"))
 487        assert_equal(Time.utc(2000, 1, 20, 12, 0, 0) - 12 * 3600,
 488                     Time.xmlschema("2000-01-20T12:00:00+12:00"))
 489        assert_equal(Time.utc(2000, 1, 20, 12, 0, 0) + 13 * 3600,
 490                     Time.xmlschema("2000-01-20T12:00:00-13:00"))
 491        assert_equal(Time.utc(2000, 3, 4, 23, 0, 0) - 3 * 3600,
 492                     Time.xmlschema("2000-03-04T23:00:00+03:00"))
 493        assert_equal(Time.utc(2000, 3, 4, 20, 0, 0),
 494                     Time.xmlschema("2000-03-04T20:00:00Z"))
 495        assert_equal(Time.local(2000, 1, 15, 0, 0, 0),
 496                     Time.xmlschema("2000-01-15T00:00:00"))
 497        assert_equal(Time.local(2000, 2, 15, 0, 0, 0),
 498                     Time.xmlschema("2000-02-15T00:00:00"))
 499        assert_equal(Time.local(2000, 1, 15, 12, 0, 0),
 500                     Time.xmlschema("2000-01-15T12:00:00"))
 501        assert_equal(Time.utc(2000, 1, 16, 12, 0, 0),
 502                     Time.xmlschema("2000-01-16T12:00:00Z"))
 503        assert_equal(Time.local(2000, 1, 1, 12, 0, 0),
 504                     Time.xmlschema("2000-01-01T12:00:00"))
 505        assert_equal(Time.utc(1999, 12, 31, 23, 0, 0),
 506                     Time.xmlschema("1999-12-31T23:00:00Z"))
 507        assert_equal(Time.local(2000, 1, 16, 12, 0, 0),
 508                     Time.xmlschema("2000-01-16T12:00:00"))
 509        assert_equal(Time.local(2000, 1, 16, 0, 0, 0),
 510                     Time.xmlschema("2000-01-16T00:00:00"))
 511        assert_equal(Time.utc(2000, 1, 12, 12, 13, 14),
 512                     Time.xmlschema("2000-01-12T12:13:14Z"))
 513        assert_equal(Time.utc(2001, 4, 17, 19, 23, 17, 300000),
 514                     Time.xmlschema("2001-04-17T19:23:17.3Z"))
 515      end
 516  
 517      def test_encode_xmlschema
 518        t = Time.utc(2001, 4, 17, 19, 23, 17, 300000)
 519        assert_equal("2001-04-17T19:23:17Z", t.xmlschema)
 520        assert_equal("2001-04-17T19:23:17.3Z", t.xmlschema(1))
 521        assert_equal("2001-04-17T19:23:17.300000Z", t.xmlschema(6))
 522        assert_equal("2001-04-17T19:23:17.3000000Z", t.xmlschema(7))
 523  
 524        t = Time.utc(2001, 4, 17, 19, 23, 17, 123456)
 525        assert_equal("2001-04-17T19:23:17.1234560Z", t.xmlschema(7))
 526        assert_equal("2001-04-17T19:23:17.123456Z", t.xmlschema(6))
 527        assert_equal("2001-04-17T19:23:17.12345Z", t.xmlschema(5))
 528        assert_equal("2001-04-17T19:23:17.1Z", t.xmlschema(1))
 529  
 530        t = Time.utc(1960, 12, 31, 23, 0, 0, 123456)
 531        assert_equal("1960-12-31T23:00:00.123456Z", t.xmlschema(6))
 532      end
 533  
 534      def test_completion
 535        now = Time.local(2001,11,29,21,26,35)
 536        assert_equal(Time.local( 2001,11,29,21,12),
 537                     Time.parse("2001/11/29 21:12", now))
 538        assert_equal(Time.local( 2001,11,29),
 539                     Time.parse("2001/11/29", now))
 540        assert_equal(Time.local( 2001,11),
 541                     Time.parse("2001/11", now))
 542        assert_equal(Time.local( 2001,11,29),
 543                     Time.parse(     "11/29", now))
 544        #assert_equal(Time.local(2001,11,1), Time.parse("Nov", now))
 545        assert_equal(Time.local( 2001,11,29,10,22),
 546                     Time.parse(           "10:22", now))
 547      end
 548  
 549      def test_invalid
 550        # They were actually used in some web sites.
 551        assert_exception(ArgumentError) { Time.httpdate("1 Dec 2001 10:23:57 GMT") }
 552        assert_exception(ArgumentError) { Time.httpdate("Sat, 1 Dec 2001 10:25:42 GMT") }
 553        assert_exception(ArgumentError) { Time.httpdate("Sat,  1-Dec-2001 10:53:55 GMT") }
 554        assert_exception(ArgumentError) { Time.httpdate("Saturday, 01-Dec-2001 10:15:34 GMT") }
 555        assert_exception(ArgumentError) { Time.httpdate("Saturday, 01-Dec-101 11:10:07 GMT") }
 556        assert_exception(ArgumentError) { Time.httpdate("Fri, 30 Nov 2001 21:30:00 JST") }
 557  
 558        # They were actually used in some mails.
 559        assert_exception(ArgumentError) { Time.rfc2822("01-5-20") }
 560        assert_exception(ArgumentError) { Time.rfc2822("7/21/00") }
 561        assert_exception(ArgumentError) { Time.rfc2822("2001-8-28") }
 562        assert_exception(ArgumentError) { Time.rfc2822("00-5-6 1:13:06") }
 563        assert_exception(ArgumentError) { Time.rfc2822("2001-9-27 9:36:49") }
 564        assert_exception(ArgumentError) { Time.rfc2822("2000-12-13 11:01:11") }
 565        assert_exception(ArgumentError) { Time.rfc2822("2001/10/17 04:29:55") }
 566        assert_exception(ArgumentError) { Time.rfc2822("9/4/2001 9:23:19 PM") }
 567        assert_exception(ArgumentError) { Time.rfc2822("01 Nov 2001 09:04:31") }
 568        assert_exception(ArgumentError) { Time.rfc2822("13 Feb 2001 16:4 GMT") }
 569        assert_exception(ArgumentError) { Time.rfc2822("01 Oct 00 5:41:19 PM") }
 570        assert_exception(ArgumentError) { Time.rfc2822("2 Jul 00 00:51:37 JST") }
 571        assert_exception(ArgumentError) { Time.rfc2822("01 11 2001 06:55:57 -0500") }
 572        assert_exception(ArgumentError) { Time.rfc2822("18 \343\366\356\341\370 2000") }
 573        assert_exception(ArgumentError) { Time.rfc2822("Fri, Oct 2001  18:53:32") }
 574        assert_exception(ArgumentError) { Time.rfc2822("Fri, 2 Nov 2001 03:47:54") }
 575        assert_exception(ArgumentError) { Time.rfc2822("Fri, 27 Jul 2001 11.14.14 +0200") }
 576        assert_exception(ArgumentError) { Time.rfc2822("Thu, 2 Nov 2000 04:13:53 -600") }
 577        assert_exception(ArgumentError) { Time.rfc2822("Wed, 5 Apr 2000 22:57:09 JST") }
 578        assert_exception(ArgumentError) { Time.rfc2822("Mon, 11 Sep 2000 19:47:33 00000") }
 579        assert_exception(ArgumentError) { Time.rfc2822("Fri, 28 Apr 2000 20:40:47 +-900") }
 580        assert_exception(ArgumentError) { Time.rfc2822("Fri, 19 Jan 2001 8:15:36 AM -0500") }
 581        assert_exception(ArgumentError) { Time.rfc2822("Thursday, Sep 27 2001 7:42:35 AM EST") }
 582        assert_exception(ArgumentError) { Time.rfc2822("3/11/2001 1:31:57 PM Pacific Daylight Time") }
 583        assert_exception(ArgumentError) { Time.rfc2822("Mi, 28 Mrz 2001 11:51:36") }
 584        assert_exception(ArgumentError) { Time.rfc2822("P, 30 sept 2001 23:03:14") }
 585        assert_exception(ArgumentError) { Time.rfc2822("fr, 11 aug 2000 18:39:22") }
 586        assert_exception(ArgumentError) { Time.rfc2822("Fr, 21 Sep 2001 17:44:03 -1000") }
 587        assert_exception(ArgumentError) { Time.rfc2822("Mo, 18 Jun 2001 19:21:40 -1000") }
 588        assert_exception(ArgumentError) { Time.rfc2822("l\366, 12 aug 2000 18:53:20") }
 589        assert_exception(ArgumentError) { Time.rfc2822("l\366, 26 maj 2001 00:15:58") }
 590        assert_exception(ArgumentError) { Time.rfc2822("Dom, 30 Sep 2001 17:36:30") }
 591        assert_exception(ArgumentError) { Time.rfc2822("%&, 31 %2/ 2000 15:44:47 -0500") }
 592        assert_exception(ArgumentError) { Time.rfc2822("dom, 26 ago 2001 03:57:07 -0300") }
 593        assert_exception(ArgumentError) { Time.rfc2822("ter, 04 set 2001 16:27:58 -0300") }
 594        assert_exception(ArgumentError) { Time.rfc2822("Wen, 3 oct 2001 23:17:49 -0400") }
 595        assert_exception(ArgumentError) { Time.rfc2822("Wen, 3 oct 2001 23:17:49 -0400") }
 596        assert_exception(ArgumentError) { Time.rfc2822("ele, 11 h: 2000 12:42:15 -0500") }
 597        assert_exception(ArgumentError) { Time.rfc2822("Tue, 14 Aug 2001 3:55:3 +0200") }
 598        assert_exception(ArgumentError) { Time.rfc2822("Fri, 25 Aug 2000 9:3:48 +0800") }
 599        assert_exception(ArgumentError) { Time.rfc2822("Fri, 1 Dec 2000 0:57:50 EST") }
 600        assert_exception(ArgumentError) { Time.rfc2822("Mon, 7 May 2001 9:39:51 +0200") }
 601        assert_exception(ArgumentError) { Time.rfc2822("Wed, 1 Aug 2001 16:9:15 +0200") }
 602        assert_exception(ArgumentError) { Time.rfc2822("Wed, 23 Aug 2000 9:17:36 +0800") }
 603        assert_exception(ArgumentError) { Time.rfc2822("Fri, 11 Aug 2000 10:4:42 +0800") }
 604        assert_exception(ArgumentError) { Time.rfc2822("Sat, 15 Sep 2001 13:22:2 +0300") }
 605        assert_exception(ArgumentError) { Time.rfc2822("Wed,16 \276\305\324\302 2001 20:06:25 +0800") }
 606        assert_exception(ArgumentError) { Time.rfc2822("Wed,7 \312\256\322\273\324\302 2001 23:47:22 +0800") }
 607        assert_exception(ArgumentError) { Time.rfc2822("=?iso-8859-1?Q?(=C5=DA),?= 10   2 2001 23:32:26 +0900 (JST)") }
 608        assert_exception(ArgumentError) { Time.rfc2822("\307\341\314\343\332\311, 30 \344\346\335\343\310\321 2001 10:01:06") }
 609        assert_exception(ArgumentError) { Time.rfc2822("=?iso-8859-1?Q?(=BF=E5),?= 12  =?iso-8859-1?Q?9=B7=EE?= 2001 14:52:41\n+0900 (JST)") }
 610      end
 611    end
 612  
 613    RUNIT::CUI::TestRunner.run(TimeExtentionTest.suite)
 614  end