# helpers: slice, deny_frozen_access # backtick_javascript: true require 'corelib/comparable' class ::Time < `Date` include ::Comparable %x{ var days_of_week = #{%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday]}, short_days = #{%w[Sun Mon Tue Wed Thu Fri Sat]}, short_months = #{%w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]}, long_months = #{%w[January February March April May June July August September October November December]}; } def self.at(seconds, frac = undefined) %x{ var result; if (#{::Time === seconds}) { if (frac !== undefined) { #{::Kernel.raise ::TypeError, "can't convert Time into an exact number"} } result = new Date(seconds.getTime()); result.timezone = seconds.timezone; return result; } if (!seconds.$$is_number) { seconds = #{::Opal.coerce_to!(seconds, ::Integer, :to_int)}; } if (frac === undefined) { return new Date(seconds * 1000); } if (!frac.$$is_number) { frac = #{::Opal.coerce_to!(frac, ::Integer, :to_int)}; } return new Date(seconds * 1000 + (frac / 1000)); } end %x{ function time_params(year, month, day, hour, min, sec) { if (year.$$is_string) { year = parseInt(year, 10); } else { year = #{::Opal.coerce_to!(`year`, ::Integer, :to_int)}; } if (month === nil) { month = 1; } else if (!month.$$is_number) { if (#{`month`.respond_to?(:to_str)}) { month = #{`month`.to_str}; switch (month.toLowerCase()) { case 'jan': month = 1; break; case 'feb': month = 2; break; case 'mar': month = 3; break; case 'apr': month = 4; break; case 'may': month = 5; break; case 'jun': month = 6; break; case 'jul': month = 7; break; case 'aug': month = 8; break; case 'sep': month = 9; break; case 'oct': month = 10; break; case 'nov': month = 11; break; case 'dec': month = 12; break; default: month = #{`month`.to_i}; } } else { month = #{::Opal.coerce_to!(`month`, ::Integer, :to_int)}; } } if (month < 1 || month > 12) { #{::Kernel.raise ::ArgumentError, "month out of range: #{`month`}"} } month = month - 1; if (day === nil) { day = 1; } else if (day.$$is_string) { day = parseInt(day, 10); } else { day = #{::Opal.coerce_to!(`day`, ::Integer, :to_int)}; } if (day < 1 || day > 31) { #{::Kernel.raise ::ArgumentError, "day out of range: #{`day`}"} } if (hour === nil) { hour = 0; } else if (hour.$$is_string) { hour = parseInt(hour, 10); } else { hour = #{::Opal.coerce_to!(`hour`, ::Integer, :to_int)}; } if (hour < 0 || hour > 24) { #{::Kernel.raise ::ArgumentError, "hour out of range: #{`hour`}"} } if (min === nil) { min = 0; } else if (min.$$is_string) { min = parseInt(min, 10); } else { min = #{::Opal.coerce_to!(`min`, ::Integer, :to_int)}; } if (min < 0 || min > 59) { #{::Kernel.raise ::ArgumentError, "min out of range: #{`min`}"} } if (sec === nil) { sec = 0; } else if (!sec.$$is_number) { if (sec.$$is_string) { sec = parseInt(sec, 10); } else { sec = #{::Opal.coerce_to!(`sec`, ::Integer, :to_int)}; } } if (sec < 0 || sec > 60) { #{::Kernel.raise ::ArgumentError, "sec out of range: #{`sec`}"} } return [year, month, day, hour, min, sec]; } } def self.new(year = undefined, month = nil, day = nil, hour = nil, min = nil, sec = nil, utc_offset = nil) %x{ var args, result, timezone, utc_date; if (year === undefined) { return new Date(); } args = time_params(year, month, day, hour, min, sec); year = args[0]; month = args[1]; day = args[2]; hour = args[3]; min = args[4]; sec = args[5]; if (utc_offset === nil) { result = new Date(year, month, day, hour, min, 0, sec * 1000); if (year < 100) { result.setFullYear(year); } return result; } timezone = #{_parse_offset(utc_offset)}; utc_date = new Date(Date.UTC(year, month, day, hour, min, 0, sec * 1000)); if (year < 100) { utc_date.setUTCFullYear(year); } result = new Date(utc_date.getTime() - timezone * 3600000); result.timezone = timezone; return result; } end # @private def self._parse_offset(utc_offset) %x{ var timezone; if (utc_offset.$$is_string) { if (utc_offset == 'UTC') { timezone = 0; } else if(/^[+-]\d\d:[0-5]\d$/.test(utc_offset)) { var sign, hours, minutes; sign = utc_offset[0]; hours = +(utc_offset[1] + utc_offset[2]); minutes = +(utc_offset[4] + utc_offset[5]); timezone = (sign == '-' ? -1 : 1) * (hours + minutes / 60); } else { // Unsupported: "A".."I","K".."Z" #{::Kernel.raise ::ArgumentError, %'"+HH:MM", "-HH:MM", "UTC" expected for utc_offset: #{utc_offset}'} } } else if (utc_offset.$$is_number) { timezone = utc_offset / 3600; } else { #{::Kernel.raise ::ArgumentError, "Opal doesn't support other types for a timezone argument than Integer and String"} } return timezone; } end def self.local(year, month = nil, day = nil, hour = nil, min = nil, sec = nil, millisecond = nil, _dummy1 = nil, _dummy2 = nil, _dummy3 = nil) # The _dummy args are there only because the MRI version accepts up to 10 arguments %x{ var args, result; if (arguments.length === 10) { args = $slice(arguments); year = args[5]; month = args[4]; day = args[3]; hour = args[2]; min = args[1]; sec = args[0]; } args = time_params(year, month, day, hour, min, sec); year = args[0]; month = args[1]; day = args[2]; hour = args[3]; min = args[4]; sec = args[5]; result = new Date(year, month, day, hour, min, 0, sec * 1000); if (year < 100) { result.setFullYear(year); } return result; } end def self.gm(year, month = nil, day = nil, hour = nil, min = nil, sec = nil, millisecond = nil, _dummy1 = nil, _dummy2 = nil, _dummy3 = nil) # The _dummy args are there only because the MRI version accepts up to 10 arguments %x{ var args, result; if (arguments.length === 10) { args = $slice(arguments); year = args[5]; month = args[4]; day = args[3]; hour = args[2]; min = args[1]; sec = args[0]; } args = time_params(year, month, day, hour, min, sec); year = args[0]; month = args[1]; day = args[2]; hour = args[3]; min = args[4]; sec = args[5]; result = new Date(Date.UTC(year, month, day, hour, min, 0, sec * 1000)); if (year < 100) { result.setUTCFullYear(year); } result.timezone = 0; return result; } end def self.now new end def +(other) if ::Time === other ::Kernel.raise ::TypeError, 'time + time?' end %x{ if (!other.$$is_number) { other = #{::Opal.coerce_to!(other, ::Integer, :to_int)}; } var result = new Date(self.getTime() + (other * 1000)); result.timezone = self.timezone; return result; } end def -(other) if ::Time === other return `(self.getTime() - other.getTime()) / 1000` end %x{ if (!other.$$is_number) { other = #{::Opal.coerce_to!(other, ::Integer, :to_int)}; } var result = new Date(self.getTime() - (other * 1000)); result.timezone = self.timezone; return result; } end def <=>(other) if ::Time === other to_f <=> other.to_f else r = other <=> self if r.nil? nil elsif r > 0 -1 elsif r < 0 1 else 0 end end end def ==(other) ::Time === other && `#{to_f} === #{other.to_f}` end def asctime strftime '%a %b %e %H:%M:%S %Y' end [ [:year, 'getFullYear', 'getUTCFullYear'], [:mon, 'getMonth', 'getUTCMonth', 1], [:wday, 'getDay', 'getUTCDay'], [:day, 'getDate', 'getUTCDate'], [:hour, 'getHours', 'getUTCHours'], [:min, 'getMinutes', 'getUTCMinutes'], [:sec, 'getSeconds', 'getUTCSeconds'], ].each do |method, getter, utcgetter, difference = 0| define_method method do %x{ return difference + ((self.timezone != null) ? (new Date(self.getTime() + self.timezone * 3600000))[utcgetter]() : self[getter]()) } end end def yday # http://javascript.about.com/library/bldayyear.htm # also see moment.js implementation: http://git.io/vCKNE start_of_year = Time.new(year).to_i start_of_day = Time.new(year, month, day).to_i one_day = 86_400 ((start_of_day - start_of_year) / one_day).round + 1 end def isdst %x{ var jan = new Date(self.getFullYear(), 0, 1), jul = new Date(self.getFullYear(), 6, 1); return self.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); } end def dup copy = `new Date(self.getTime())` copy.copy_instance_variables(self) copy.initialize_dup(self) copy end def eql?(other) other.is_a?(::Time) && (self <=> other).zero? end [ [:sunday?, 0], [:monday?, 1], [:tuesday?, 2], [:wednesday?, 3], [:thursday?, 4], [:friday?, 5], [:saturday?, 6] ].each do |method, weekday| define_method method do `#{wday} === weekday` end end def hash `'Time:' + self.getTime()` end def inspect if utc? strftime '%Y-%m-%d %H:%M:%S UTC' else strftime '%Y-%m-%d %H:%M:%S %z' end end def succ %x{ var result = new Date(self.getTime() + 1000); result.timezone = self.timezone; return result; } end def usec `self.getMilliseconds() * 1000` end def zone %x{ if (self.timezone === 0) return "UTC"; else if (self.timezone != null) return nil; var string = self.toString(), result; if (string.indexOf('(') == -1) { result = string.match(/[A-Z]{3,4}/)[0]; } else { result = string.match(/\((.+)\)(?:\s|$)/)[1] } if (result == "GMT" && /(GMT\W*\d{4})/.test(string)) { return RegExp.$1; } else { return result; } } end def getgm %x{ var result = new Date(self.getTime()); result.timezone = 0; return result; } end def gmtime %x{ if (self.timezone !== 0) { $deny_frozen_access(self); self.timezone = 0; } return self; } end def gmt? `self.timezone === 0` end def gmt_offset `(self.timezone != null) ? self.timezone * 60 : -self.getTimezoneOffset() * 60` end def strftime(format) %x{ return format.replace(/%([\-_#^0]*:{0,2})(\d+)?([EO]*)(.)/g, function(full, flags, width, _, conv) { var result = "", jd, c, s, zero = flags.indexOf('0') !== -1, pad = flags.indexOf('-') === -1, blank = flags.indexOf('_') !== -1, upcase = flags.indexOf('^') !== -1, invert = flags.indexOf('#') !== -1, colons = (flags.match(':') || []).length; width = parseInt(width, 10); if (zero && blank) { if (flags.indexOf('0') < flags.indexOf('_')) { zero = false; } else { blank = false; } } switch (conv) { case 'Y': result += #{year}; break; case 'C': zero = !blank; result += Math.round(#{year} / 100); break; case 'y': zero = !blank; result += (#{year} % 100); break; case 'm': zero = !blank; result += #{mon}; break; case 'B': result += long_months[#{mon} - 1]; break; case 'b': case 'h': blank = !zero; result += short_months[#{mon} - 1]; break; case 'd': zero = !blank result += #{day}; break; case 'e': blank = !zero result += #{day}; break; case 'j': zero = !blank; width = isNaN(width) ? 3 : width; result += #{yday}; break; case 'H': zero = !blank; result += #{hour}; break; case 'k': blank = !zero; result += #{hour}; break; case 'I': zero = !blank; result += (#{hour} % 12 || 12); break; case 'l': blank = !zero; result += (#{hour} % 12 || 12); break; case 'P': result += (#{hour} >= 12 ? "pm" : "am"); break; case 'p': result += (#{hour} >= 12 ? "PM" : "AM"); break; case 'M': zero = !blank; result += #{min}; break; case 'S': zero = !blank; result += #{sec} break; case 'L': zero = !blank; width = isNaN(width) ? 3 : width; result += self.getMilliseconds(); break; case 'N': width = isNaN(width) ? 9 : width; result += #{`self.getMilliseconds().toString()`.rjust(3, '0')}; result = #{`result`.ljust(`width`, '0')}; break; case 'z': var offset = (self.timezone == null) ? self.getTimezoneOffset() : (-self.timezone * 60), hours = Math.floor(Math.abs(offset) / 60), minutes = Math.abs(offset) % 60; result += offset < 0 ? "+" : "-"; result += hours < 10 ? "0" : ""; result += hours; if (colons > 0) { result += ":"; } result += minutes < 10 ? "0" : ""; result += minutes; if (colons > 1) { result += ":00"; } break; case 'Z': result += #{zone}; break; case 'A': result += days_of_week[#{wday}]; break; case 'a': result += short_days[#{wday}]; break; case 'u': result += (#{wday} + 1); break; case 'w': result += #{wday}; break; case 'V': result += #{cweek_cyear[0].to_s.rjust(2, '0')}; break; case 'G': result += #{cweek_cyear[1]}; break; case 'g': result += #{cweek_cyear[1][-2..-1]}; break; case 's': result += #{to_i}; break; case 'n': result += "\n"; break; case 't': result += "\t"; break; case '%': result += "%"; break; case 'c': result += #{strftime('%a %b %e %T %Y')}; break; case 'D': case 'x': result += #{strftime('%m/%d/%y')}; break; case 'F': result += #{strftime('%Y-%m-%d')}; break; case 'v': result += #{strftime('%e-%^b-%4Y')}; break; case 'r': result += #{strftime('%I:%M:%S %p')}; break; case 'R': result += #{strftime('%H:%M')}; break; case 'T': case 'X': result += #{strftime('%H:%M:%S')}; break; // Non-standard: JIS X 0301 date format case 'J': jd = #{to_date.jd}; if (jd < 2405160) { result += #{strftime('%Y-%m-%d')}; break; } else if (jd < 2419614) c = 'M', s = 1867; else if (jd < 2424875) c = 'T', s = 1911; else if (jd < 2447535) c = 'S', s = 1925; else if (jd < 2458605) c = 'H', s = 1988; else c = 'R', s = 2018; result += #{format '%c%02d', `c`, year - `s`}; result += #{strftime('-%m-%d')}; break; default: return full; } if (upcase) { result = result.toUpperCase(); } if (invert) { result = result.replace(/[A-Z]/, function(c) { c.toLowerCase() }). replace(/[a-z]/, function(c) { c.toUpperCase() }); } if (pad && (zero || blank)) { result = #{`result`.rjust(`isNaN(width) ? 2 : width`, `blank ? " " : "0"`)}; } return result; }); } end def to_a [sec, min, hour, day, month, year, wday, yday, isdst, zone] end def to_f `self.getTime() / 1000` end def to_i `parseInt(self.getTime() / 1000, 10)` end def cweek_cyear jan01 = ::Time.new(year, 1, 1) jan01_wday = jan01.wday first_monday = 0 year = self.year if jan01_wday <= 4 && jan01_wday != 0 # Jan 01 is in the first week of the year offset = jan01_wday - 1 else # Jan 01 is in the last week of the previous year offset = jan01_wday - 7 - 1 offset = -1 if offset == -8 # Adjust if Jan 01 is a Sunday end week = ((yday + offset) / 7.00).ceil if week <= 0 # Get the last week of the previous year return ::Time.new(self.year - 1, 12, 31).cweek_cyear elsif week == 53 # Find out whether this is actually week 53 or already week 01 of the following year dec31 = ::Time.new(self.year, 12, 31) dec31_wday = dec31.wday if dec31_wday <= 3 && dec31_wday != 0 week = 1 year += 1 end end [week, year] end class << self alias mktime local alias utc gm end alias ctime asctime alias dst? isdst alias getutc getgm alias gmtoff gmt_offset alias mday day alias month mon alias to_s inspect alias tv_sec to_i alias tv_usec usec alias utc gmtime alias utc? gmt? alias utc_offset gmt_offset end