1require 'date'
2
3# = time.rb
4#
5# When 'time' is required, Time is extended with additional methods for parsing
6# and converting Times.
7#
8# == Features
9#
10# This library extends the Time class with the following conversions between
11# date strings and Time objects:
12#
13# * date-time defined by {RFC 2822}[http://www.ietf.org/rfc/rfc2822.txt]
14# * HTTP-date defined by {RFC 2616}[http://www.ietf.org/rfc/rfc2616.txt]
15# * dateTime defined by XML Schema Part 2: Datatypes ({ISO
16#   8601}[http://www.iso.org/iso/date_and_time_format])
17# * various formats handled by Date._parse
18# * custom formats handled by Date._strptime
19#
20# == Examples
21#
22# All examples assume you have loaded Time with:
23#
24#   require 'time'
25#
26# All of these examples were done using the EST timezone which is GMT-5.
27#
28# === Converting to a String
29#
30#   t = Time.now
31#   t.iso8601  # => "2011-10-05T22:26:12-04:00"
32#   t.rfc2822  # => "Wed, 05 Oct 2011 22:26:12 -0400"
33#   t.httpdate # => "Thu, 06 Oct 2011 02:26:12 GMT"
34#
35# === Time.parse
36#
37# #parse takes a string representation of a Time and attempts to parse it
38# using a heuristic.
39#
40#   Date.parse("2010-10-31") #=> 2010-10-31 00:00:00 -0500
41#
42# Any missing pieces of the date are inferred based on the current date.
43#
44#   # assuming the current date is "2011-10-31"
45#   Time.parse("12:00") #=> 2011-10-31 12:00:00 -0500
46#
47# We can change the date used to infer our missing elements by passing a second
48# object that responds to #mon, #day and #year, such as Date, Time or DateTime.
49# We can also use our own object.
50#
51#   class MyDate
52#     attr_reader :mon, :day, :year
53#
54#     def initialize(mon, day, year)
55#       @mon, @day, @year = mon, day, year
56#     end
57#   end
58#
59#   d  = Date.parse("2010-10-28")
60#   t  = Time.parse("2010-10-29")
61#   dt = DateTime.parse("2010-10-30")
62#   md = MyDate.new(10,31,2010)
63#
64#   Time.parse("12:00", d)  #=> 2010-10-28 12:00:00 -0500
65#   Time.parse("12:00", t)  #=> 2010-10-29 12:00:00 -0500
66#   Time.parse("12:00", dt) #=> 2010-10-30 12:00:00 -0500
67#   Time.parse("12:00", md) #=> 2010-10-31 12:00:00 -0500
68#
69# #parse also accepts an optional block. You can use this block to specify how
70# to handle the year component of the date. This is specifically designed for
71# handling two digit years. For example, if you wanted to treat all two digit
72# years prior to 70 as the year 2000+ you could write this:
73#
74#   Time.parse("01-10-31") {|year| year + (year < 70 ? 2000 : 1900)}
75#   #=> 2001-10-31 00:00:00 -0500
76#   Time.parse("70-10-31") {|year| year + (year < 70 ? 2000 : 1900)}
77#   #=> 1970-10-31 00:00:00 -0500
78#
79# === Time.strptime
80#
81# #strptime works similar to +parse+ except that instead of using a heuristic
82# to detect the format of the input string, you provide a second argument that
83# is describes the format of the string. For example:
84#
85#   Time.strptime("2000-10-31", "%Y-%m-%d") #=> 2000-10-31 00:00:00 -0500
86
87class Time
88  class << Time
89
90    #
91    # A hash of timezones mapped to hour differences from UTC. The
92    # set of time zones corresponds to the ones specified by RFC 2822
93    # and ISO 8601.
94    #
95    ZoneOffset = { # :nodoc:
96      'UTC' => 0,
97      # ISO 8601
98      'Z' => 0,
99      # RFC 822
100      'UT' => 0, 'GMT' => 0,
101      'EST' => -5, 'EDT' => -4,
102      'CST' => -6, 'CDT' => -5,
103      'MST' => -7, 'MDT' => -6,
104      'PST' => -8, 'PDT' => -7,
105      # Following definition of military zones is original one.
106      # See RFC 1123 and RFC 2822 for the error in RFC 822.
107      'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4,  'E' => +5,  'F' => +6,
108      'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12,
109      'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4,  'R' => -5,  'S' => -6,
110      'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12,
111    }
112
113    #
114    # Return the number of seconds the specified time zone differs
115    # from UTC.
116    #
117    # Numeric time zones that include minutes, such as
118    # <code>-10:00</code> or <code>+1330</code> will work, as will
119    # simpler hour-only time zones like <code>-10</code> or
120    # <code>+13</code>.
121    #
122    # Textual time zones listed in ZoneOffset are also supported.
123    #
124    # If the time zone does not match any of the above, +zone_offset+
125    # will check if the local time zone (both with and without
126    # potential Daylight Saving \Time changes being in effect) matches
127    # +zone+. Specifying a value for +year+ will change the year used
128    # to find the local time zone.
129    #
130    # If +zone_offset+ is unable to determine the offset, nil will be
131    # returned.
132    def zone_offset(zone, year=self.now.year)
133      off = nil
134      zone = zone.upcase
135      if /\A([+-])(\d\d):?(\d\d)\z/ =~ zone
136        off = ($1 == '-' ? -1 : 1) * ($2.to_i * 60 + $3.to_i) * 60
137      elsif /\A[+-]\d\d\z/ =~ zone
138        off = zone.to_i * 3600
139      elsif ZoneOffset.include?(zone)
140        off = ZoneOffset[zone] * 3600
141      elsif ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false)
142        off = t.utc_offset
143      elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false)
144        off = t.utc_offset
145      end
146      off
147    end
148
149    def zone_utc?(zone)
150      # * +0000
151      #   In RFC 2822, +0000 indicate a time zone at Universal Time.
152      #   Europe/Lisbon is "a time zone at Universal Time" in Winter.
153      #   Atlantic/Reykjavik is "a time zone at Universal Time".
154      #   Africa/Dakar is "a time zone at Universal Time".
155      #   So +0000 is a local time such as Europe/London, etc.
156      # * GMT
157      #   GMT is used as a time zone abbreviation in Europe/London,
158      #   Africa/Dakar, etc.
159      #   So it is a local time.
160      #
161      # * -0000, -00:00
162      #   In RFC 2822, -0000 the date-time contains no information about the
163      #   local time zone.
164      #   In RFC 3339, -00:00 is used for the time in UTC is known,
165      #   but the offset to local time is unknown.
166      #   They are not appropriate for specific time zone such as
167      #   Europe/London because time zone neutral,
168      #   So -00:00 and -0000 are treated as UTC.
169      if /\A(?:-00:00|-0000|-00|UTC|Z|UT)\z/i =~ zone
170        true
171      else
172        false
173      end
174    end
175    private :zone_utc?
176
177    LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
178    CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
179    def month_days(y, m)
180      if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
181        LeapYearMonthDays[m-1]
182      else
183        CommonYearMonthDays[m-1]
184      end
185    end
186    private :month_days
187
188    def apply_offset(year, mon, day, hour, min, sec, off)
189      if off < 0
190        off = -off
191        off, o = off.divmod(60)
192        if o != 0 then sec += o; o, sec = sec.divmod(60); off += o end
193        off, o = off.divmod(60)
194        if o != 0 then min += o; o, min = min.divmod(60); off += o end
195        off, o = off.divmod(24)
196        if o != 0 then hour += o; o, hour = hour.divmod(24); off += o end
197        if off != 0
198          day += off
199          if month_days(year, mon) < day
200            mon += 1
201            if 12 < mon
202              mon = 1
203              year += 1
204            end
205            day = 1
206          end
207        end
208      elsif 0 < off
209        off, o = off.divmod(60)
210        if o != 0 then sec -= o; o, sec = sec.divmod(60); off -= o end
211        off, o = off.divmod(60)
212        if o != 0 then min -= o; o, min = min.divmod(60); off -= o end
213        off, o = off.divmod(24)
214        if o != 0 then hour -= o; o, hour = hour.divmod(24); off -= o end
215        if off != 0 then
216          day -= off
217          if day < 1
218            mon -= 1
219            if mon < 1
220              year -= 1
221              mon = 12
222            end
223            day = month_days(year, mon)
224          end
225        end
226      end
227      return year, mon, day, hour, min, sec
228    end
229    private :apply_offset
230
231    def make_time(year, mon, day, hour, min, sec, sec_fraction, zone, now)
232      usec = nil
233      usec = sec_fraction * 1000000 if sec_fraction
234      if now
235        begin
236          break if year; year = now.year
237          break if mon; mon = now.mon
238          break if day; day = now.day
239          break if hour; hour = now.hour
240          break if min; min = now.min
241          break if sec; sec = now.sec
242          break if sec_fraction; usec = now.tv_usec
243        end until true
244      end
245
246      year ||= 1970
247      mon ||= 1
248      day ||= 1
249      hour ||= 0
250      min ||= 0
251      sec ||= 0
252      usec ||= 0
253
254      off = nil
255      off = zone_offset(zone, year) if zone
256
257      if off
258        year, mon, day, hour, min, sec =
259          apply_offset(year, mon, day, hour, min, sec, off)
260        t = self.utc(year, mon, day, hour, min, sec, usec)
261        t.localtime if !zone_utc?(zone)
262        t
263      else
264        self.local(year, mon, day, hour, min, sec, usec)
265      end
266    end
267    private :make_time
268
269    #
270    # Parses +date+ using Date._parse and converts it to a Time object.
271    #
272    # If a block is given, the year described in +date+ is converted by the
273    # block.  For example:
274    #
275    #     Time.parse(...) {|y| 0 <= y && y < 100 ? (y >= 69 ? y + 1900 : y + 2000) : y}
276    #
277    # If the upper components of the given time are broken or missing, they are
278    # supplied with those of +now+.  For the lower components, the minimum
279    # values (1 or 0) are assumed if broken or missing.  For example:
280    #
281    #     # Suppose it is "Thu Nov 29 14:33:20 GMT 2001" now and
282    #     # your time zone is GMT:
283    #     now = Time.parse("Thu Nov 29 14:33:20 GMT 2001")
284    #     Time.parse("16:30", now)     #=> 2001-11-29 16:30:00 +0900
285    #     Time.parse("7/23", now)      #=> 2001-07-23 00:00:00 +0900
286    #     Time.parse("Aug 31", now)    #=> 2001-08-31 00:00:00 +0900
287    #     Time.parse("Aug 2000", now)  #=> 2000-08-01 00:00:00 +0900
288    #
289    # Since there are numerous conflicts among locally defined time zone
290    # abbreviations all over the world, this method is not intended to
291    # understand all of them.  For example, the abbreviation "CST" is
292    # used variously as:
293    #
294    #     -06:00 in America/Chicago,
295    #     -05:00 in America/Havana,
296    #     +08:00 in Asia/Harbin,
297    #     +09:30 in Australia/Darwin,
298    #     +10:30 in Australia/Adelaide,
299    #     etc.
300    #
301    # Based on this fact, this method only understands the time zone
302    # abbreviations described in RFC 822 and the system time zone, in the
303    # order named. (i.e. a definition in RFC 822 overrides the system
304    # time zone definition.)  The system time zone is taken from
305    # <tt>Time.local(year, 1, 1).zone</tt> and
306    # <tt>Time.local(year, 7, 1).zone</tt>.
307    # If the extracted time zone abbreviation does not match any of them,
308    # it is ignored and the given time is regarded as a local time.
309    #
310    # ArgumentError is raised if Date._parse cannot extract information from
311    # +date+ or if the Time class cannot represent specified date.
312    #
313    # This method can be used as a fail-safe for other parsing methods as:
314    #
315    #   Time.rfc2822(date) rescue Time.parse(date)
316    #   Time.httpdate(date) rescue Time.parse(date)
317    #   Time.xmlschema(date) rescue Time.parse(date)
318    #
319    # A failure of Time.parse should be checked, though.
320    #
321    # You must require 'time' to use this method.
322    #
323    def parse(date, now=self.now)
324      comp = !block_given?
325      d = Date._parse(date, comp)
326      if !d[:year] && !d[:mon] && !d[:mday] && !d[:hour] && !d[:min] && !d[:sec] && !d[:sec_fraction]
327        raise ArgumentError, "no time information in #{date.inspect}"
328      end
329      year = d[:year]
330      year = yield(year) if year && !comp
331      make_time(year, d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
332    end
333
334    #
335    # Parses +date+ using Date._strptime and converts it to a Time object.
336    #
337    # If a block is given, the year described in +date+ is converted by the
338    # block.  For example:
339    #
340    #   Time.strptime(...) {|y| y < 100 ? (y >= 69 ? y + 1900 : y + 2000) : y}
341    #
342    # Below is a list of the formating options:
343    #
344    # %a :: The abbreviated weekday name ("Sun")
345    # %A :: The  full  weekday  name ("Sunday")
346    # %b :: The abbreviated month name ("Jan")
347    # %B :: The  full  month  name ("January")
348    # %c :: The preferred local date and time representation
349    # %C :: Century (20 in 2009)
350    # %d :: Day of the month (01..31)
351    # %D :: Date (%m/%d/%y)
352    # %e :: Day of the month, blank-padded ( 1..31)
353    # %F :: Equivalent to %Y-%m-%d (the ISO 8601 date format)
354    # %h :: Equivalent to %b
355    # %H :: Hour of the day, 24-hour clock (00..23)
356    # %I :: Hour of the day, 12-hour clock (01..12)
357    # %j :: Day of the year (001..366)
358    # %k :: hour, 24-hour clock, blank-padded ( 0..23)
359    # %l :: hour, 12-hour clock, blank-padded ( 0..12)
360    # %L :: Millisecond of the second (000..999)
361    # %m :: Month of the year (01..12)
362    # %M :: Minute of the hour (00..59)
363    # %n :: Newline (\n)
364    # %N :: Fractional seconds digits, default is 9 digits (nanosecond)
365    #       %3N :: millisecond (3 digits)
366    #       %6N :: microsecond (6 digits)
367    #       %9N :: nanosecond (9 digits)
368    # %p :: Meridian indicator ("AM" or "PM")
369    # %P :: Meridian indicator ("am" or "pm")
370    # %r :: time, 12-hour (same as %I:%M:%S %p)
371    # %R :: time, 24-hour (%H:%M)
372    # %s :: Number of seconds since 1970-01-01 00:00:00 UTC.
373    # %S :: Second of the minute (00..60)
374    # %t :: Tab character (\t)
375    # %T :: time, 24-hour (%H:%M:%S)
376    # %u :: Day of the week as a decimal, Monday being 1. (1..7)
377    # %U :: Week number of the current year, starting with the first Sunday as
378    #       the first day of the first week (00..53)
379    # %v :: VMS date (%e-%b-%Y)
380    # %V :: Week number of year according to ISO 8601 (01..53)
381    # %W :: Week  number  of the current year, starting with the first Monday
382    #       as the first day of the first week (00..53)
383    # %w :: Day of the week (Sunday is 0, 0..6)
384    # %x :: Preferred representation for the date alone, no time
385    # %X :: Preferred representation for the time alone, no date
386    # %y :: Year without a century (00..99)
387    # %Y :: Year with century
388    # %z :: Time zone as  hour offset from UTC (e.g. +0900)
389    # %Z :: Time zone name
390    # %% :: Literal "%" character
391
392    def strptime(date, format, now=self.now)
393      d = Date._strptime(date, format)
394      raise ArgumentError, "invalid strptime format - `#{format}'" unless d
395      if seconds = d[:seconds]
396        if offset = d[:offset]
397          Time.at(seconds).localtime(offset)
398        else
399          Time.at(seconds)
400        end
401      else
402        year = d[:year]
403        year = yield(year) if year && block_given?
404        make_time(year, d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
405      end
406    end
407
408    MonthValue = {
409      'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
410      'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' =>10, 'NOV' =>11, 'DEC' =>12
411    }
412
413    #
414    # Parses +date+ as date-time defined by RFC 2822 and converts it to a Time
415    # object.  The format is identical to the date format defined by RFC 822 and
416    # updated by RFC 1123.
417    #
418    # ArgumentError is raised if +date+ is not compliant with RFC 2822
419    # or if the Time class cannot represent specified date.
420    #
421    # See #rfc2822 for more information on this format.
422    #
423    # You must require 'time' to use this method.
424    #
425    def rfc2822(date)
426      if /\A\s*
427          (?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*,\s*)?
428          (\d{1,2})\s+
429          (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+
430          (\d{2,})\s+
431          (\d{2})\s*
432          :\s*(\d{2})\s*
433          (?::\s*(\d{2}))?\s+
434          ([+-]\d{4}|
435           UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Z])/ix =~ date
436        # Since RFC 2822 permit comments, the regexp has no right anchor.
437        day = $1.to_i
438        mon = MonthValue[$2.upcase]
439        year = $3.to_i
440        hour = $4.to_i
441        min = $5.to_i
442        sec = $6 ? $6.to_i : 0
443        zone = $7
444
445        # following year completion is compliant with RFC 2822.
446        year = if year < 50
447                 2000 + year
448               elsif year < 1000
449                 1900 + year
450               else
451                 year
452               end
453
454        year, mon, day, hour, min, sec =
455          apply_offset(year, mon, day, hour, min, sec, zone_offset(zone))
456        t = self.utc(year, mon, day, hour, min, sec)
457        t.localtime if !zone_utc?(zone)
458        t
459      else
460        raise ArgumentError.new("not RFC 2822 compliant date: #{date.inspect}")
461      end
462    end
463    alias rfc822 rfc2822
464
465    #
466    # Parses +date+ as an HTTP-date defined by RFC 2616 and converts it to a
467    # Time object.
468    #
469    # ArgumentError is raised if +date+ is not compliant with RFC 2616 or if
470    # the Time class cannot represent specified date.
471    #
472    # See #httpdate for more information on this format.
473    #
474    # You must require 'time' to use this method.
475    #
476    def httpdate(date)
477      if /\A\s*
478          (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\x20
479          (\d{2})\x20
480          (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
481          (\d{4})\x20
482          (\d{2}):(\d{2}):(\d{2})\x20
483          GMT
484          \s*\z/ix =~ date
485        self.rfc2822(date)
486      elsif /\A\s*
487             (?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\x20
488             (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d)\x20
489             (\d\d):(\d\d):(\d\d)\x20
490             GMT
491             \s*\z/ix =~ date
492        year = $3.to_i
493        if year < 50
494          year += 2000
495        else
496          year += 1900
497        end
498        self.utc(year, $2, $1.to_i, $4.to_i, $5.to_i, $6.to_i)
499      elsif /\A\s*
500             (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\x20
501             (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
502             (\d\d|\x20\d)\x20
503             (\d\d):(\d\d):(\d\d)\x20
504             (\d{4})
505             \s*\z/ix =~ date
506        self.utc($6.to_i, MonthValue[$1.upcase], $2.to_i,
507                 $3.to_i, $4.to_i, $5.to_i)
508      else
509        raise ArgumentError.new("not RFC 2616 compliant date: #{date.inspect}")
510      end
511    end
512
513    #
514    # Parses +date+ as a dateTime defined by the XML Schema and converts it to
515    # a Time object.  The format is a restricted version of the format defined
516    # by ISO 8601.
517    #
518    # ArgumentError is raised if +date+ is not compliant with the format or if
519    # the Time class cannot represent specified date.
520    #
521    # See #xmlschema for more information on this format.
522    #
523    # You must require 'time' to use this method.
524    #
525    def xmlschema(date)
526      if /\A\s*
527          (-?\d+)-(\d\d)-(\d\d)
528          T
529          (\d\d):(\d\d):(\d\d)
530          (\.\d+)?
531          (Z|[+-]\d\d:\d\d)?
532          \s*\z/ix =~ date
533        year = $1.to_i
534        mon = $2.to_i
535        day = $3.to_i
536        hour = $4.to_i
537        min = $5.to_i
538        sec = $6.to_i
539        usec = 0
540        if $7
541          usec = Rational($7) * 1000000
542        end
543        if $8
544          zone = $8
545          year, mon, day, hour, min, sec =
546            apply_offset(year, mon, day, hour, min, sec, zone_offset(zone))
547          self.utc(year, mon, day, hour, min, sec, usec)
548        else
549          self.local(year, mon, day, hour, min, sec, usec)
550        end
551      else
552        raise ArgumentError.new("invalid date: #{date.inspect}")
553      end
554    end
555    alias iso8601 xmlschema
556  end # class << self
557
558  #
559  # Returns a string which represents the time as date-time defined by RFC 2822:
560  #
561  #   day-of-week, DD month-name CCYY hh:mm:ss zone
562  #
563  # where zone is [+-]hhmm.
564  #
565  # If +self+ is a UTC time, -0000 is used as zone.
566  #
567  # You must require 'time' to use this method.
568  #
569  def rfc2822
570    sprintf('%s, %02d %s %0*d %02d:%02d:%02d ',
571      RFC2822_DAY_NAME[wday],
572      day, RFC2822_MONTH_NAME[mon-1], year < 0 ? 5 : 4, year,
573      hour, min, sec) +
574    if utc?
575      '-0000'
576    else
577      off = utc_offset
578      sign = off < 0 ? '-' : '+'
579      sprintf('%s%02d%02d', sign, *(off.abs / 60).divmod(60))
580    end
581  end
582  alias rfc822 rfc2822
583
584  RFC2822_DAY_NAME = [
585    'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
586  ]
587  RFC2822_MONTH_NAME = [
588    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
589    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
590  ]
591
592  #
593  # Returns a string which represents the time as RFC 1123 date of HTTP-date
594  # defined by RFC 2616:
595  #
596  #   day-of-week, DD month-name CCYY hh:mm:ss GMT
597  #
598  # Note that the result is always UTC (GMT).
599  #
600  # You must require 'time' to use this method.
601  #
602  def httpdate
603    t = dup.utc
604    sprintf('%s, %02d %s %0*d %02d:%02d:%02d GMT',
605      RFC2822_DAY_NAME[t.wday],
606      t.day, RFC2822_MONTH_NAME[t.mon-1], t.year < 0 ? 5 : 4, t.year,
607      t.hour, t.min, t.sec)
608  end
609
610  #
611  # Returns a string which represents the time as a dateTime defined by XML
612  # Schema:
613  #
614  #   CCYY-MM-DDThh:mm:ssTZD
615  #   CCYY-MM-DDThh:mm:ss.sssTZD
616  #
617  # where TZD is Z or [+-]hh:mm.
618  #
619  # If self is a UTC time, Z is used as TZD.  [+-]hh:mm is used otherwise.
620  #
621  # +fractional_digits+ specifies a number of digits to use for fractional
622  # seconds.  Its default value is 0.
623  #
624  # You must require 'time' to use this method.
625  #
626  def xmlschema(fraction_digits=0)
627    fraction_digits = fraction_digits.to_i
628    s = strftime("%FT%T")
629    if fraction_digits > 0
630      s << strftime(".%#{fraction_digits}N")
631    end
632    s << (utc? ? 'Z' : strftime("%:z"))
633  end
634  alias iso8601 xmlschema
635end
636
637