/src/kernel/bootstrap/Time.rb
Ruby | 553 lines | 408 code | 96 blank | 49 comment | 42 complexity | 610a4f5085224c94429fa20353d344a4 MD5 | raw file
- # Time in Ruby is identically Smalltalk RubyTime
- #
- # The Smalltalk RubyTime class holds a single value, @microseconds, which
- # is the number of microseconds since the epoch.
- class Time
- ######################################################################
- # BEGIN RUBINIUS ( with Gemstone modifications)
- ######################################################################
- include Comparable
- ZoneOffset = {
- 'UTC' => 0,
- # ISO 8601
- 'Z' => 0,
- # RFC 822
- 'UT' => 0, 'GMT' => 0,
- 'EST' => -5, 'EDT' => -4,
- 'CST' => -6, 'CDT' => -5,
- 'MST' => -7, 'MDT' => -6,
- 'PST' => -8, 'PDT' => -7,
- # Following definition of military zones is original one.
- # See RFC 1123 and RFC 2822 for the error in RFC 822.
- 'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5, 'F' => +6,
- 'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12,
- 'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6,
- 'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12,
- }
- MonthValue = {
- 'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
- 'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' =>10, 'NOV' =>11, 'DEC' =>12
- }
- LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- FULL_DAY_NAME = ['Sunday, Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
- FULL_MONTH_NAME = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
- RFC2822_DAY_NAME = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
- RFC2822_MONTH_NAME = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- PRE_EPOCH_DAYS = 719468
- TM_FIELDS = {
- :sec => 0,
- :min => 1,
- :hour => 2,
- :mday => 3,
- :mon => 4,
- :year => 5,
- :wday => 6,
- :yday => 7,
- :isdst => 8,
- }
- # TIMEVAL_FIELDS = { # not used in Gemstone
- # :sec => 0,
- # :usec => 1,
- #}
- #--
- # TODO: doesn't load nsec or ivars
- #++
- def self._load(data) # used by Marshal
- raise TypeError, 'marshaled time format differ' unless data.length == 8
- major, minor = data.unpack 'VV'
- if (major & (1 << 31)) == 0 then
- at major, minor
- else
- major &= ~(1 << 31)
- is_gmt = (major >> 30) & 0x1
- year = ((major >> 14) & 0xffff) + 1900
- mon = ((major >> 10) & 0xf) + 1
- mday = (major >> 5) & 0x1f
- hour = major & 0x1f
- min = (minor >> 26) & 0x3f
- sec = (minor >> 20) & 0x3f
- isdst = false
- usec = minor & 0xfffff
- time = self.gm(year, mon, mday, hour, min, sec, usec)
- time.localtime # unless is_gmt.zero? # HACK MRI ignores the is_gmt flag
- time
- end
- end
- #--
- # TODO: doesn't dump nsec or ivars
- #++
- def _dump(limit = nil) # used by marshal
- is_gmt = @_st_is_gmt
- unless is_gmt
- # Maglev deviation, always dumping in GMT format
- t = self.dup.gmtime
- return t._dump
- end
- tm = @_st_tm
- year = tm[TM_FIELDS[:year]]
- if (year & 0xffff) != year then
- raise ArgumentError, "year too big to marshal: #{year}"
- end
- major = 1 << 31 | # 1 bit
- (is_gmt ? 1 : 0) << 30 | # 1 bit
- tm[TM_FIELDS[:year]] << 14 | # 16 bits
- tm[TM_FIELDS[:mon]] << 10 | # 4 bits
- tm[TM_FIELDS[:mday]] << 5 | # 5 bits
- tm[TM_FIELDS[:hour]] # 5 bits
- minor = tm[TM_FIELDS[:min]] << 26 | # 6 bits
- tm[TM_FIELDS[:sec]] << 20 | # 6 bits
- self.usec # 20 bits # GEMSTONE changes
- [major, minor].pack 'VV'
- end
- def dup
- t = self.class.allocate # Gemstone changes
- t.__init(@_st_microseconds, @_st_is_gmt)
- end
- def self.local(first, *args)
- if args.size == 9
- second = first
- minute = args[0]
- hour = args[1]
- day = args[2]
- month = args[3]
- year = args[4]
- # args[5] is wday , not used # gemstone comments
- # args[6] is yday , not used
- isdst = args[7] ? 1 : 0
- # args[8] is tz , not used yet
- usec = 0
- else
- year = first
- month = self.__monthname_to_num(args[0])
- day = args[1] || 1
- hour = args[2] || 0
- minute = args[3] || 0
- second = args[4] || 0
- usec = args[5] || 0
- isdst = -1 # ask C mktime() to determine dst
- end
- self.__mktime(second, minute, hour, day, month, year, usec, isdst, false)
- end
- def self.mktime(first, *args)
- self.local(first, *args)
- end
- def self.__monthname_to_num(ma)
- # resolve month names to numbers
- if ma._isInteger
- month = ma
- else
- ma = Type.coerce_to_or_nil(ma, String, :to_str)
- if ma._isString
- mint = ma.to_i
- if mint._equal?(0)
- month = MonthValue[ ma.upcase] || raise(ArgumentError.new('argument out of range'))
- else
- month = mint
- end
- else
- month = ma || 1
- end
- end
- month
- end
- def self.gm(first, *args)
- if args.size == 9
- second = first
- minute = args[0]
- hour = args[1]
- day = args[2]
- month = args[3]
- year = args[4]
- usec = 0
- else
- # set argument defaults
- year = first
- month = self.__monthname_to_num(args[0])
- day = args[1] || 1
- hour = args[2] || 0
- minute = args[3] || 0
- second = args[4] || 0
- usec = args[5] || 0
- end
- self.__mktime(second, minute, hour, day, month, year, usec, 0, true)
- end
- # Gemstone , new implementations at end of file
- # def self.at ; end
- def inspect
- if @_st_is_gmt
- strftime("%a %b %d %H:%M:%S UTC %Y")
- else
- strftime("%a %b %d %H:%M:%S %z %Y")
- end
- end
- def seconds
- @_st_microseconds.__divide(1_000_000) # Gemstone
- end
- def +(other)
- microsecs = @_st_microseconds
- if other._isInteger
- microsecs += other * 1_000_000
- elsif other._kind_of?(Time)
- raise TypeError , 'Time#+ , arg may not be a Time'
- else
- # addition with rounding to nearest microsecond
- other = Type.coerce_to(other, Float, :to_f)
- if other > 0.0
- microsecs += ((other + 0.0000005) * 1_000_000.0).to_i
- elsif other <= 0.0
- unless other == 0.0
- microsecs += ((other - 0.0000005) * 1_000_000.0).to_i
- end
- end
-
- end
- t = self.class.allocate
- t.__init(microsecs, @_st_is_gmt)
- end
- def -(other)
- microsecs = @_st_microseconds
- if other._isInteger
- microsecs -= other * 1_000_000
- elsif other._kind_of?(Time)
- delta = microsecs - other.__microsecs
- return (delta.to_f).__divide(1_000_000.0)
- else
- other = Type.coerce_to(other, Float, :to_f)
- microsecs -= (other * 1_000_000.0).to_i
- end
- t = self.class.allocate
- t.__init(microsecs, @_st_is_gmt)
- end
- def succ
- self + 1
- end
- def <=>(other)
- if other._kind_of?(Time)
- @_st_microseconds <=> other.__microsecs
- else
- nil
- end
- end
- # It seems that MRI return nil if other isn't a Time object
- def ==(other)
- result = self <=> other
- result._equal?(nil) ? result : result._equal?(0)
- end
- def hash
- micro_sec = @_st_microseconds
- (micro_sec.__divide(1_000_000)) ^ (micro_sec % 1_000_000)
- end
- def eql?(other)
- (self <=> other)._equal?(0)
- end
- def asctime
- strftime("%a %b %e %H:%M:%S %Y")
- end
- def hour
- @_st_tm[2]
- end
- def min
- @_st_tm[1]
- end
- def sec
- @_st_tm[0]
- end
- def day
- @_st_tm[3]
- end
- def year
- @_st_tm[5] + 1900
- end
- def yday
- @_st_tm[7] + 1
- end
- def wday
- @_st_tm[6]
- end
- def zone
- strftime("%Z")
- end
- def mon
- @_st_tm[4] + 1
- end
- def gmt?
- @_st_is_gmt
- end
- def usec
- @_st_microseconds % 1_000_000
- end
- def to_i
- @_st_microseconds.__divide(1_000_000)
- end
- def to_f
- @_st_microseconds.__divide(1_000_000.0)
- end
- ##
- # Returns:
- # [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]
- def to_a
- [sec, min, hour, day, month, year, wday, yday, isdst, zone]
- end
- def gmt_offset
- return 0 if @_st_is_gmt
- other = dup.gmtime
- if year != other.year
- offset = year < other.year ? -1 : 1
- elsif month != other.month
- offset = month < other.month ? -1 : 1
- elsif mday != other.mday
- offset = mday < other.mday ? -1 : 1
- else
- offset = 0
- end
- offset *= 24
- offset += hour - other.hour
- offset *= 60
- offset += min - other.min
- offset *= 60
- offset += sec - other.sec
- end
- def localtime
- if @_st_is_gmt
- self.__set_tmarray(false)
- end
- self
- end
- def gmtime
- unless @_st_is_gmt
- self.__set_tmarray(true)
- end
- self
- end
- def dst?
- @_st_tm[8]._not_equal?(0)
- end
- def getlocal
- dup.localtime
- end
- def getgm
- dup.gmtime
- end
- def self.__to_int(arg)
- if arg._isString
- arg.to_i
- else
- Type.coerce_to(arg, Integer, :to_int )
- end
- end
- def self.__mktime(sec, min, hour, mday, mon, year, usec, isdst=-1, from_gmt=false)
- sec = __to_int(sec)
- min = __to_int(min)
- hour = __to_int(hour)
- mday = __to_int(mday)
- mon = __to_int(mon)
- # range checks on above args done in C in __c_mktime
- year = __to_int(year)
- usec = __to_int(usec)
- sec += usec.__divide(1000000)
- # This logic is taken from MRI, on how to deal with 2 digit dates.
- if year < 200
- if 0 <= year and year < 39
- warn "2 digit year used: #{year}"
- year += 2000
- elsif 69 <= year and year < 139
- warn "2 or 3 digit year used: #{year}"
- year += 1900
- end
- end
- year = year - 1900
- # build args to C mktime , args are in local time
- args = [ sec, min, hour, mday, mon, year,
- 0, # wday
- 0, # yday,
- isdst # negative means ask mktime() to determine Daylight/Standard
- ]
- atime_t = __c_mktime(args, from_gmt)
- t = self.allocate
- microsecs = (atime_t * 1000000) + (usec % 1000000)
- t.__init(microsecs, from_gmt)
- end
- ##
- # +sec+ and +usec+ are always given in gmt here.
- #
- # +want_gmt+ says whether the caller wants a gmtime or local time object.
- def at_gmt(sec, usec, want_gmt) # GEMSTONE
- if sec._isInteger
- usec = usec ? usec.to_i : 0
- elsif usec
- sec = Type.coerce_to(sec, Integer, :to_i)
- usec = usec.to_i
- else
- sec = Type.coerce_to(sec, Float, :to_f)
- usec = ((sec % 1) * 1_000_000).to_i
- sec = sec.to_i
- end
- usec = usec + (sec * 1000000)
- @_st_microseconds = usec
- if want_gmt
- force_gmtime
- else
- force_localtime
- end
- end
- def self.utc(first, *args) # GEMSTONE
- gm(first, *args)
- end
- alias_method :utc?, :gmt?
- alias_method :month, :mon
- alias_method :ctime, :asctime
- alias_method :mday, :day
- alias_method :to_i, :seconds
- alias_method :to_s, :inspect
- alias_method :tv_sec, :seconds
- alias_method :tv_usec, :usec
- alias_method :utc, :gmtime
- alias_method :isdst, :dst?
- alias_method :utc_offset, :gmt_offset
- alias_method :gmtoff, :gmt_offset
- alias_method :getutc, :getgm
- ######################################################################
- # END Modified RUBINIUS
- ######################################################################
- # begin Gemstone specific code
- class_primitive 'new'
- class_primitive 'now'
- class_primitive_nobridge 'allocate' , '_basicNew'
- class_primitive_nobridge '__c_mktime' , 'mktime:fromGmt:'
- # __strftime takes a format String as the arg
- primitive_nobridge '__strftime' , 'strftime:'
- # Smalltalk initialize takes care of all instvars,
- # for use cases Time.new Time.now
- primitive_nobridge 'initialize'
- # __set_tmarray fills in @is_gmt , @tm based on @microseconds
- primitive_nobridge '__set_tmarray', '_setTmArray:'
- def __microsecs
- @_st_microseconds
- end
- def __init(aMicrosecs, isGmt)
- @_st_microseconds = aMicrosecs
- __set_tmarray(isGmt)
- self
- end
- def self.at(a_time)
- res = self.allocate
- if (a_time._kind_of?(self))
- usecs = a_time.__microsecs
- elsif a_time._isInteger
- usecs = a_time * 1_000_000
- else
- a_time = Type.coerce_to(a_time, Float, :to_f)
- usecs = (a_time * 1000000.0).to_int
- end
- res.__init( usecs , false)
- end
- def self.at(secs, microsecs)
- res = self.allocate
- usecs = (secs.to_i * 1_000_000) + microsecs.to_i
- res.__init( usecs , false)
- end
- def strftime(fmt='%a %b %d %H:%M:%S %Z %Y' )
- fmt = fmt.to_str unless fmt._isString
- __strftime(fmt)
- end
- def self.times
- Process.times
- end
- end
- Time.__freeze_constants