baja/obj/TimeZone.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Gareth Johnson
 */

/**
 * Defines {@link baja.TimeZone}.
 * @module baja/obj/TimeZone
 */
define([ 'bajaScript/sys',
        'bajaScript/baja/obj/RelTime',
        'bajaScript/baja/obj/Simple',
        'bajaScript/baja/obj/objUtil',
        'bajaScript/baja/obj/dateTimeUtil' ], function (
         baja,
         RelTime,
         Simple,
         objUtil,
         dateTimeUtil) {

  'use strict';

  var cacheDecode = objUtil.cacheDecode,
      cacheEncode = objUtil.cacheEncode,

      isDstActive = dateTimeUtil.isDstActive;

  function hourAndMinute(relTime) {
    var hour = relTime.getHoursPart(),
        minute = relTime.getMinutesPart();
    return (hour >= 0 ? '+' : '') + hour + (minute > 0 ? ':' + minute : '');
  }

  /**
   * Represents a `baja:TimeZone` in BajaScript.
   *
   * @class
   * @alias baja.TimeZone
   * @extends baja.Simple
   */
  var TimeZone = function TimeZone() {
    Simple.apply(this, arguments);
  };
  TimeZone.prototype = Object.create(Simple.prototype);
  TimeZone.prototype.constructor = TimeZone;

  /**
   * Make a new `baja:TimeZone` instance.
   *
   * There is not usually a reason, nor a good way, to create time zones
   * directly in BajaScript: you will most likely want to use the time zones
   * returned from the station, using the `TimeZoneDatabase`.
   *
   * @param {string} id
   * @param {number} [utcOffset=0] UTC offset in milliseconds
   * @param {number} [dstOffset=0] DST offset in milliseconds
   * @param {string} [startRule=null] daylight savings start rule, encoded as a
   * string. This can be specific to the JVM. `null` is an acceptable value.
   * @param {string} [endRule=null] daylight savings end rule - same caveats as
   * startRule
   * @param {Object} [params]
   * @param {string} [params.displayName] long display name, when DST is
   * inactive
   * @param {string} [params.dstDisplayName] long display name, when DST is
   * active
   * @param {string} [params.shortDisplayName] short display name, when DST is
   * inactive
   * @param {string} [params.shortDstDisplayName] short display name, when DST
   * is active
   */
  TimeZone.make = function (id, utcOffset, dstOffset, startRule, endRule, params) {
    if (typeof id !== 'string') {
      throw new Error('time zone id required');
    }

    params = params || {};

    var z = new TimeZone();
    z.$id = id;
    z.$utcOffset = utcOffset || 0;
    z.$dstOffset = dstOffset || 0;
    z.$startRule = startRule || null;
    z.$endRule = endRule || null;
    z.$dn = params.displayName;
    z.$ddn = params.dstDisplayName;
    z.$sdn = params.shortDisplayName;
    z.$dsdn = params.shortDstDisplayName;
    return z;
  };

  /**
   * Return true if daylight savings time is active for the given date.
   * Note: If no timezone is provided, this will not use timezone rules, rather
   * a simplistic method of comparing offsets in January and June for the local
   * browser.
   *
   * If a timezone is provided, daylights savings will be determined at the
   * given date in that timezone.
   *
   * Please note: javascript dates will always have a local timezone
   * offset. The underlying milliseconds from epoch value will be used as the
   * source of truth for the time. This point in time will then be used to
   * determine whether daylight savings is active at that underlying millisecond
   * value in the given timezone (if provided).
   *
   * @param {Date} [d] the date to check. If omitted, the current date will be
   * checked.
   * @param {baja.TimeZone} [timeZone] the timezone to use.
   * @returns {boolean} true if daylight savings time is active
   */
  TimeZone.isDstActive = function (d, timeZone) {
    return isDstActive(d, timeZone);
  };

  /**
   * @see .make
   */
  TimeZone.prototype.make = function () {
    return TimeZone.make.apply(TimeZone, arguments);
  };

  /**
   * Return the string encoding of the daylight start rule. This can be JVM
   * specific and usually not of use in a BajaScript application.
   *
   * @private
   * @returns {string|null}
   */
  TimeZone.prototype.$getDaylightStartRule = function () {
    return this.$startRule;
  };

  /**
   * Return the string encoding of the daylight end rule. This can be JVM
   * specific and usually not of use in a BajaScript application.
   *
   * @private
   * @returns {string|null}
   */
  TimeZone.prototype.$getDaylightEndRule = function () {
    return this.$endRule;
  };

  /**
   * Decode a `baja:TimeZone` instance from the given string.
   *
   * @function
   * @param {string} str
   * @returns {baja.TimeZone}
   */
  TimeZone.prototype.decodeFromString = cacheDecode(function (str) {
    var fields = str.split(';'),
        id = fields[0];

    if (fields.length === 1) {
      return TimeZone.make(id);
    }

    var utc = parseInt(fields[1], 10),
        dst = parseInt(fields[2], 10),
        startRule = fields[3],
        endRule = fields[4];

    if (isNaN(utc) || isNaN(dst)) {
      throw new Error('invalid time zone string ' + str);
    }

    return TimeZone.make(id, utc, dst, startRule, endRule);
  });

  /**
   * Encode this `baja:TimeZone` instance to a string.
   *
   * @function
   * @returns {string}
   */
  TimeZone.prototype.encodeToString = cacheEncode(function () {
    return [
      this.getId(),
      this.getUtcOffset(),
      this.getDaylightAdjustment(),
      String(this.$getDaylightStartRule()),
      String(this.$getDaylightEndRule())
    ].join(';');
  });

  TimeZone.prototype.getDataTypeSymbol = function () {
    return 'z';
  };

  /**
   * Return the DST adjustment, in milliseconds.
   *
   * @returns {number}
   */
  TimeZone.prototype.getDaylightAdjustment = function () {
    return this.$dstOffset;
  };

  /**
   * Get the display name for this time zone.
   *
   * @param {boolean} [dst] whether DST should be considered active when getting
   * the display name. If omitted, the method will consider whether DST is
   * active at the present moment.
   * @returns {string}
   */
  TimeZone.prototype.getDisplayName = function (dst) {
    if (arguments.length === 0) {
      dst = TimeZone.isDstActive();
    }
    return dst ? this.$ddn : this.$dn;
  };

  /**
   * Get the time zone ID.
   *
   * @returns {string}
   */
  TimeZone.prototype.getId = function () {
    return this.$id;
  };

  /**
   * Get the short display name for this time zone.
   *
   * @param {boolean} [dst] whether DST should be considered active when getting
   * the short display name. If omitted, the method will consider whether DST is
   * active at the present moment.
   * @returns {string}
   */
  TimeZone.prototype.getShortDisplayName = function (dst) {
    if (arguments.length === 0) {
      dst = TimeZone.isDstActive();
    }
    return dst ? this.$dsdn : this.$sdn;
  };

  /**
   * Get the UTC offset in milliseconds.
   *
   * @returns {number}
   */
  TimeZone.prototype.getUtcOffset = function () {
    return this.$utcOffset;
  };

  TimeZone.prototype.toString = function () {
    var utcOffset = this.getUtcOffset(),
        utcRelTime = RelTime.make(utcOffset),
        daylight = this.getDaylightAdjustment(),
        str = this.getId() + ' (' + hourAndMinute(utcRelTime);

    if (daylight !== 0) {
      str += '/' + hourAndMinute(RelTime.make(utcOffset + daylight));
    }

    return str + ')';
  };

  /** @type baja.TimeZone */
  TimeZone.UTC = TimeZone.make("UTC");
  /** @type baja.TimeZone */
  TimeZone.GMT = TimeZone.make("GMT");
  /** @type baja.TimeZone */
  TimeZone.NULL = TimeZone.make("NULL");
  /** @type baja.TimeZone */
  TimeZone.DEFAULT = TimeZone.UTC;

  return TimeZone;
});