Temporary tag for this failure. Updated CI spec coming.
[rbx.git] / kernel / core / time.rb
blob517de09de8b1ef5452bf58ac9c65126cac665b0f
1 # depends on: comparable.rb class.rb module.rb
3 #--
4 # _load
5 # parse
6 # --------------------
7 # _dump
8 # marshal_dump
9 # marshal_load
10 #++
12 class Time
13   include Comparable
15   ZoneOffset =      { 'UTC' => 0, 'Z' => 0,  'UT' => 0, 'GMT' => 0,
16                       'EST' => -5, 'EDT' => -4, 'CST' => -6, 'CDT' => -5,
17                       'CET' => 1, 'CEST' => 2,
18                       'MST' => -7, 'MDT' => -6, 'PST' => -8, 'PDT' => -7,
19                       'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5,
20                       'F' => +6, 'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10,
21                       'L' => +11, 'M' => +12, 'N' => -1, 'O' => -2, 'P' => -3,
22                       'Q' => -4, 'R' => -5, 'S' => -6, 'T' => -7, 'U' => -8,
23                       'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12, }
25   MonthValue = {
26     'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
27     'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' =>10, 'NOV' =>11, 'DEC' =>12
28   }
30   LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
32   CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
34   RFC2822_DAY_NAME = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
36   RFC2822_MONTH_NAME = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
38   PRE_EPOCH_DAYS = 719468
40   TM_FIELDS = {
41     :sec => 0,
42     :min => 1,
43     :hour => 2,
44     :mday => 3,
45     :mon => 4,
46     :year => 5,
47     :wday => 6,
48     :yday => 7,
49     :isdst => 8,
50   }
52   TIMEVAL_FIELDS = {
53     :sec => 0,
54     :usec => 1,
55   }
57   def initialize
58     @timeval = Time.gettimeofday
60     # this flag specifies whether this Time instance represents
61     # local time or GMT. it is independent of the actual time zone.
62     @is_gmt = false
64     @tm = time_switch(@timeval.first, false)
65   end
67   #--
68   # TODO: doesn't load nsec or ivars
69   #++
71   def self._load(data)
72     raise TypeError, 'marshaled time format differ' unless data.length == 8
74     major, minor = data.unpack 'VV'
76     if (major & (1 << 31)) == 0 then
77       at major, minor
78     else
79       major &= ~(1 << 31)
81       is_gmt =  (major >> 30) & 0x1
82       year   =  (major >> 14) & 0xffff
83       mon    = ((major >> 10) & 0xf) + 1
84       mday   =  (major >>  5) & 0x1f
85       hour   =  major         & 0x1f
87       min   =  (minor >> 26) & 0x3f
88       sec   =  (minor >> 20) & 0x3f
89       isdst = false
91       usec = minor & 0xfffff
93       time = gm year, mon, mday, hour, min, sec, usec
94       time.localtime # unless is_gmt.zero? # HACK MRI ignores the is_gmt flag
95       time
96     end
97   end
99   #--
100   # TODO: doesn't dump nsec or ivars
101   #++
103   def _dump(limit = nil)
104     tm = time_switch @timeval.first, true
105     year = tm[TM_FIELDS[:year]]
107     if (year & 0xffff) != year then
108       raise ArgumentError, "year too big to marshal: #{year}"
109     end
111     gmt = @is_gmt ? 1 : 0
113     major = 1                    << 31 | # 1 bit
114             gmt                  << 30 | # 1 bit
115             tm[TM_FIELDS[:year]] << 14 | # 16 bits
116             tm[TM_FIELDS[:mon]]  << 10 | # 4 bits
117             tm[TM_FIELDS[:mday]] <<  5 | # 5 bits
118             tm[TM_FIELDS[:hour]]         # 5 bits
119     minor = tm[TM_FIELDS[:min]]  << 26 | # 6 bits
120             tm[TM_FIELDS[:sec]]  << 20 | # 6 bits
121             @timeval[TIMEVAL_FIELDS[:usec]] # 20 bits
123     [major, minor].pack 'VV'
124   end
126   def dup
127     t = Time.allocate
128     t.instance_variable_set(:@timeval, @timeval)
129     t.instance_variable_set(:@tm, @tm)
130     t.instance_variable_set(:@is_gmt, @is_gmt)
131     t
132   end
134   def self.local(first, *args)
135     if args.size == 9
136       second = first
137       minute = args[0]
138       hour = args[1]
139       day = args[2]
140       month = args[3]
141       year = args[4]
142       usec = 0
143       isdst = args[7] ? 1 : 0
144     else
145       # resolve month names to numbers
146       if args[0] && (args[0].kind_of?(String) || args[0].respond_to?(:to_str))
147         args[0] = args[0].to_str if args[0].respond_to?(:to_str)
148         month = MonthValue[args[0].upcase] || args[0].to_i || raise(ArgumentError.new('argument out of range'))
149       end
151       year = first
152       month ||= args[0] || 1
153       day = args[1] || 1
154       hour = args[2] || 0
155       minute = args[3] || 0
156       second = args[4] || 0
157       usec = args[5] || 0
158       isdst = -1
159     end
161     t = Time.allocate
162     t.mktime(second, minute, hour, day, month, year, usec, isdst, false)
163     t.force_localtime
164   end
166   def self.gm(first, *args)
167     if args.size == 9
168       second = first
169       minute = args[0]
170       hour = args[1]
171       day = args[2]
172       month = args[3]
173       year = args[4]
174       usec = 0
175     else
176       # resolve month names to numbers
177       if args[0] && args[0].respond_to?(:to_str) && (args[0] = args[0].to_str).to_i == 0
178         month = MonthValue[args[0].upcase] || raise(ArgumentError.new('argument out of range'))
179       end
181       # set argument defaults
182       year = first
183       month ||= args[0] || 1
184       day = args[1] || 1
185       hour = args[2] || 0
186       minute = args[3] || 0
187       second = args[4] || 0
188       usec = args[5] || 0
189     end
191     t = Time.allocate
192     t.mktime(second, minute, hour, day, month, year, usec, -1, true)
193     t.force_gmtime
194   end
196   def self.at(secs_or_time, msecs = nil)
197     if secs_or_time.kind_of? Time
198       return secs_or_time.dup
199     end
200       
201     Time.allocate.at_gmt(secs_or_time, msecs, false)
202   end
204   def strftime(format)
205     __strftime(@tm, format.to_str)
206   end
208   def inspect
209     if @is_gmt
210       strftime("%a %b %d %H:%M:%S UTC %Y")
211     else
212       strftime("%a %b %d %H:%M:%S %z %Y")
213     end
214   end
216   def seconds
217     @timeval.first
218   end
220   def +(other)
221     raise TypeError, 'time + time?' if other.kind_of?(Time)
222     other      = FloatValue(other) unless other.is_a?(Integer)
223     other_usec = 0
224     if other.kind_of?(Float) 
225       other_usec = (other % 1) * 1000000
226       other      = other.to_i
227     end
228     dup.at_gmt(seconds + other, usec + other_usec, @is_gmt)
229   end
231   def -(other)
232     if other.kind_of? Time
233       seconds - other.seconds + (usec - other.usec) * 0.000001
234     else
235       other      = FloatValue(other) unless other.is_a?(Integer)
236       other_usec = 0
237       if other.kind_of?(Float) 
238         other_usec = (1 - other % 1) * 1000000
239         other      = other.ceil.to_i
240       end
241       dup.at_gmt(seconds - other, usec + other_usec, @is_gmt)
242     end
243   end
245   def succ
246     self + 1
247   end
249   def <=>(other)
250     if other.kind_of? Time
251       [self.seconds, self.usec] <=> [other.seconds, other.usec]
252     else
253       nil
254     end
255   end
257   def eql?(other)
258     (self <=> other) == 0
259   end
261   def asctime
262     strftime("%a %b %e %H:%M:%S %Y")
263   end
265   def hour
266     @tm[2]
267   end
269   def min
270     @tm[1]
271   end
273   def sec
274     @tm[0]
275   end
277   def day
278     @tm[3]
279   end
281   def year
282     @tm[5] + 1900
283   end
285   def yday
286     @tm[7] + 1
287   end
289   def wday
290     @tm[6]
291   end
293   def zone
294     strftime("%Z")
295   end
297   def mon
298     @tm[4] + 1
299   end
301   def gmt?
302     @is_gmt
303   end
305   def usec
306     @timeval.last
307   end
309   def to_i
310     self.seconds
311   end
313   def to_f
314     seconds + (usec / 1000000.0)
315   end
317   ##
318   # Returns:
319   #   [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]
321   def to_a
322     [sec, min, hour, day, month, year, wday, yday, isdst, zone]
323   end
325   def gmt_offset
326     return 0 if @is_gmt
328     other = dup.gmtime
330     if year != other.year
331       offset = year < other.year ? -1 : 1
332     elsif month != other.month
333       offset = month < other.month ? -1 : 1
334     elsif mday != other.mday
335       offset = mday < other.mday ? -1 : 1
336     else
337       offset = 0
338     end
340     offset *= 24
341     offset += hour - other.hour
343     offset *= 60
344     offset += min - other.min
346     offset *= 60
347     offset += sec - other.sec
348   end
350   def localtime
351     force_localtime if @is_gmt
353     self
354   end
356   def gmtime
357     force_gmtime unless @is_gmt
359     self
360   end
362   def dst?
363     !@tm[8].zero?
364   end
366   def getlocal
367     dup.localtime
368   end
370   def getgm
371     dup.gmtime
372   end
374   def hash
375     seconds ^ usec
376   end
378   def force_localtime
379     @tm = time_switch(@timeval.first, false)
380     @is_gmt = false
382     self
383   end
385   def force_gmtime
386     @tm = time_switch(@timeval.first, true)
387     @is_gmt = true
389     self
390   end
392   def mktime(sec, min, hour, mday, mon, year, usec, isdst, from_gmt)
393     sec  = sec.to_i
394     min  = min.to_i
395     hour = hour.to_i
396     mday = mday.to_i
397     mon  = mon.to_i
398     year = year.to_i
399     usec = usec.to_i
401     # This logic is taken from MRI, on how to deal with 2 digit dates.
402     if year < 200
403       if 0 <= year and year < 39
404         warn "2 digit year used: #{year}"
405         year += 2000
406       elsif 69 <= year and year < 139
407         warn "2 or 3 digit year used: #{year}"
408         year += 1900
409       end
410     end
412     @timeval = time_mktime(sec, min, hour, mday, mon, year, usec, isdst, from_gmt)
413     raise ArgumentError, "time out of range" if @timeval.first == -1
415     self
416   end
418   ##
419   # +sec+ and +usec+ are always given in gmt here.
420   #
421   # +want_gmt+ says whether the caller wants a gmtime or local time object.
423   def at_gmt(sec, usec, want_gmt)
424     if sec.kind_of?(Integer) || usec
425       sec  = Type.coerce_to sec, Integer, :to_i
426       usec = usec ? usec.to_i : 0
427     else
428       sec  = Type.coerce_to sec, Float, :to_f
429       usec = ((sec % 1) * 1000000).to_i
430       sec  = sec.to_i
431     end
432     
433     sec  = sec + (usec / 1000000)
434     usec = usec % 1000000
435     
436     @timeval = [sec, usec]
438     if want_gmt
439       force_gmtime
440     else
441       force_localtime
442     end
443   end
445   def self.month_days(y, m)
446     if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
447       LeapYearMonthDays[m-1]
448     else
449       CommonYearMonthDays[m-1]
450     end
451   end
453   def self.apply_offset(year, mon, day, hour, min, sec, off)
454     if off < 0
455       off = -off
456       off, o = off.divmod(60)
457       if o != 0 then sec += o; o, sec = sec.divmod(60); off += o end
458       off, o = off.divmod(60)
459       if o != 0 then min += o; o, min = min.divmod(60); off += o end
460       off, o = off.divmod(24)
461       if o != 0 then hour += o; o, hour = hour.divmod(24); off += o end
462       if off != 0
463         day += off
464         if month_days(year, mon) < day
465           mon += 1
466           if 12 < mon
467             mon = 1
468             year += 1
469           end
470           day = 1
471         end
472       end
473     elsif 0 < off
474       off, o = off.divmod(60)
475       if o != 0 then sec -= o; o, sec = sec.divmod(60); off -= o end
476       off, o = off.divmod(60)
477       if o != 0 then min -= o; o, min = min.divmod(60); off -= o end
478       off, o = off.divmod(24)
479       if o != 0 then hour -= o; o, hour = hour.divmod(24); off -= o end
480       if off != 0 then
481         day -= off
482         if day < 1
483           mon -= 1
484           if mon < 1
485             year -= 1
486             mon = 12
487           end
488           day = month_days(year, mon)
489         end
490       end
491     end
492     return year, mon, day, hour, min, sec
493   end
495   public
497   class << self
498     alias_method :now,    :new
499     alias_method :mktime, :local
500     alias_method :utc,    :gm
501   end
503   alias_method :utc?,       :gmt?
504   alias_method :month,      :mon
505   alias_method :ctime,      :asctime
506   alias_method :mday,       :day
507   alias_method :to_i,       :seconds
508   alias_method :to_s,       :inspect
509   alias_method :tv_sec,     :seconds
510   alias_method :tv_usec,    :usec
511   alias_method :utc,        :gmtime
512   alias_method :isdst,      :dst?
513   alias_method :utc_offset, :gmt_offset
514   alias_method :gmtoff,     :gmt_offset
515   alias_method :getutc,     :getgm