lib/volt/utils/time_opal_patch.rb

Summary

Maintainability
F
3 days
Test Coverage
# Copied from https://github.com/opal/opal/blob/dd81fb4bfe337c0321ef1a8f7def2a3049a3fd83/opal/corelib/time.rb
# This patches time to the level in Opal 0.9.0 so that Volt can continue to run against Opal 0.8.0 and 
# implement VoltTime

require 'corelib/comparable'

class Time 
  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) {
          #{raise TypeError, "can't convert Time into an exact number"}
        }
        result = new Date(seconds.getTime());
        result.is_utc = seconds.is_utc;
        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) {
        #{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) {
        #{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) {
        #{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) {
        #{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) {
        #{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;

      if (year === undefined) {
        return new Date();
      }

      if (utc_offset !== nil) {
        #{raise ArgumentError, 'Opal does not support explicitly specifying UTC offset for Time'}
      }

      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.local(year, month = nil, day = nil, hour = nil, min = nil, sec = nil, millisecond = nil)
    %x{
      var args, result;

      if (arguments.length === 10) {
        args  = $slice.call(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)
    %x{
      var args, result;

      if (arguments.length === 10) {
        args  = $slice.call(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.is_utc = true;
      return result;
    }
  end

  class << self
    alias mktime local
    alias utc gm
  end

  def self.now
    new
  end

  def +(other)
    if Time === other
      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.is_utc = self.is_utc;
      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.is_utc = self.is_utc;
      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)
    `#{to_f} === #{other.to_f}`
  end

  def asctime
    strftime '%a %b %e %H:%M:%S %Y'
  end

  alias ctime asctime

  def day
    `self.is_utc ? self.getUTCDate() : self.getDate()`
  end

  def yday
    %x{
      // http://javascript.about.com/library/bldayyear.htm
      var onejan = new Date(self.getFullYear(), 0, 1);
      return Math.ceil((self - onejan) / 86400000);
    }
  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

  alias dst? isdst

  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

  def friday?
    `#{wday} == 5`
  end

  def hash
    `'Time:' + self.getTime()`
  end

  def hour
    `self.is_utc ? self.getUTCHours() : self.getHours()`
  end

  def inspect
    if utc?
      strftime '%Y-%m-%d %H:%M:%S UTC'
    else
      strftime '%Y-%m-%d %H:%M:%S %z'
    end
  end

  alias mday day

  def min
    `self.is_utc ? self.getUTCMinutes() : self.getMinutes()`
  end

  def mon
    `(self.is_utc ? self.getUTCMonth() : self.getMonth()) + 1`
  end

  def monday?
    `#{wday} == 1`
  end

  alias month mon

  def saturday?
    `#{wday} == 6`
  end

  def sec
    `self.is_utc ? self.getUTCSeconds() : self.getSeconds()`
  end

  def succ
    %x{
      var result = new Date(self.getTime() + 1000);
      result.is_utc = self.is_utc;
      return result;
    }
  end

  def usec
    `self.getMilliseconds() * 1000`
  end

  def zone
    %x{
      var string = self.toString(),
          result;

      if (string.indexOf('(') == -1) {
        result = string.match(/[A-Z]{3,4}/)[0];
      }
      else {
        result = string.match(/\([^)]+\)/)[0].match(/[A-Z]/g).join('');
      }

      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.is_utc = true;
      return result;
    }
  end

  alias getutc getgm

  def gmtime
    %x{
      self.is_utc = true;
      return self;
    }
  end

  alias utc gmtime

  def gmt?
    `self.is_utc === true`
  end

  def gmt_offset
    `-self.getTimezoneOffset() * 60`
  end

  def strftime(format)
    %x{
      return format.replace(/%([\-_#^0]*:{0,2})(\d+)?([EO]*)(.)/g, function(full, flags, width, _, conv) {
        var result = "",
            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':
            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.getTimezoneOffset(),
                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;

          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 sunday?
    `#{wday} == 0`
  end

  def thursday?
    `#{wday} == 4`
  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

  alias to_s inspect

  def tuesday?
    `#{wday} == 2`
  end

  alias tv_sec sec

  alias tv_usec usec

  alias utc? gmt?

  alias gmtoff gmt_offset
  alias utc_offset gmt_offset

  def wday
    `self.is_utc ? self.getUTCDay() : self.getDay()`
  end

  def wednesday?
    `#{wday} == 3`
  end

  def year
    `self.is_utc ? self.getUTCFullYear() : self.getFullYear()`
  end

  def cweek_cyear
    jan01 = Time.new(self.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 = ((self.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
end