baja/obj/TimeZoneDatabase.js

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

/**
 * Defines {@link baja.TimeZoneDatabase}.
 * @module baja/obj/TimeZoneDatabase
 */
define([ 'bajaScript/sys',
        'bajaScript/baja/comm/Callback',
        'bajaScript/baja/obj/dateTimeUtil',
        'bajaScript/baja/obj/TimeZone',
        'bajaScript/baja/sys/structures/OrderedMap' ], function (
         baja,
         Callback,
         dateTimeUtil,
         TimeZone,
         OrderedMap) {

  'use strict';

  var callbackify = baja.callbackify,

      STORAGE_KEY = 'bsTimeZoneDatabase',

      retrievePromise;

  /**
   * Clear the saved time zone database JSON from local storage.
   *
   * @inner
   */
  function clearJsonFromStorage() {
    try {
      return baja.storage.removeItem(STORAGE_KEY);
    } catch (ignore) {
      //what to do?
    }
  }

  /**
   * Retrieve the saved time zone database JSON from local storage.
   *
   * @inner
   * @returns {Object} retrieved and parsed object, or null if not found
   */
  function getJsonFromStorage() {
    try {
      var str = baja.storage.getItem(STORAGE_KEY);
      return str && JSON.parse(str);
    } catch (ignore) {
      clearJsonFromStorage();
    }
  }

  /**
   * Persist the time zone database JSON retrieved from the station into local
   * storage.
   *
   * @param {Object} json
   */
  function saveJsonToStorage(json) {
    try {
      return baja.storage.setItem(STORAGE_KEY, JSON.stringify(json));
    } catch (ignore) {
      //what to do?
    }
  }

  /**
   * Retrieve the time zone database from the station.
   *
   * It will make a network call to the `TimeZoneChannel` on the BOX service.
   * The time zone database will be retrieved and stored in local storage.
   *
   * @inner
   * @param {baja.comm.Batch} [batch] optional batch to use to retrieve the
   * unit database
   * @returns {Promise}
   */
  function retrieveJson(batch) {
    var cb = new Callback(baja.ok, baja.fail, batch),
        fromStorage = getJsonFromStorage(),
        lastKnownBuildTime = (fromStorage && fromStorage.buildTime) || 0;

    cb.addOk(function (ok, fail, resp) {
      baja.runAsync(function () {
        var json;

        if (fromStorage && !resp.db) {
          //we had the database saved locally, and it hasn't been updated since
          //we last retrieved it. use the existing db.
          json = fromStorage;
        } else {
          //new unit database from the station.
          json = resp;
          saveJsonToStorage(json);
        }

        ok(json);
      });
    });

    cb.addReq('timeZone', 'getTimeZoneDatabase', {
      ifModifiedSince: lastKnownBuildTime
    });

    cb.autoCommit();

    return cb.promise();
  }

  /**
   * JSON structure:
   *
   *     {
   *       "buildTime": {long} db last build time,
   *       "db":
   *       {
   *         "timeZoneId":
   *         {
   *           "v": {string} string-encoded time zone,
   *           "dn": {string} display name,
   *           "ddn": {string} dst display name,
   *           "sdn": {string} short display name,
   *           "dsdn": {string} short dst display name
   *         }
   *       }
   *     }
   *
   * @inner
   * @param db
   * @returns {Array}
   */
  function toOrderedMap(db) {
    var map = new OrderedMap(),
        zone,
        timeZoneId,
        obj;

    for (timeZoneId in db) {
      if (db.hasOwnProperty(timeZoneId)) {
        obj = db[timeZoneId];
        zone = TimeZone.DEFAULT.decodeFromString(obj.v);
        map.put(timeZoneId, TimeZone.make(
          zone.getId(),
          zone.getUtcOffset(),
          zone.getDaylightAdjustment(),
          zone.$getDaylightStartRule(),
          zone.$getDaylightEndRule(),
          {
            displayName: obj.dn,
            dstDisplayName: obj.ddn,
            shortDisplayName: obj.sdn,
            shortDstDisplayName: obj.dsdn
          }
        ));
      }
    }

    return map;
  }

  /**
   * Queries the time zone database from the station.
   *
   * There is no reason to call this constructor directly; rather use the
   * static accessor functions.
   *
   * @class
   * @alias baja.TimeZoneDatabase
   */
  var TimeZoneDatabase = function TimeZoneDatabase(json) {
    this.$map = toOrderedMap(json.db);
  };


  /**
   * Asynchronously retrieve the time zone database from the station. The
   * network call to the station will only happen once: the same database
   * instance will be resolved no matter how many times this function is called.
   *
   * @param {Object} callbacks
   * @param {Function} callbacks.ok ok callback, will receive a
   * `TimeZoneDatabase` instance populated with data retrieved from the station
   * @param {Function} callbacks.fail fail callback
   * @param {baja.comm.Batch} [callbacks.batch] batch to use for the network
   * request
   * @returns {Promise}
   */
  TimeZoneDatabase.get = function (callbacks) {
    callbacks = callbackify(callbacks);

    if (!retrievePromise) {
      retrievePromise = retrieveJson(callbacks.batch)
        .then(function (json) {
          return new TimeZoneDatabase(json);
        });
    }

    retrievePromise.then(callbacks.ok, callbacks.fail);
    return retrievePromise;
  };

  /**
   * Return all time zone IDs the station knows about.
   *
   * @returns {Array.<String>}
   */
  TimeZoneDatabase.prototype.getAllSupportedZoneIds = function () {
    return this.$map.getKeys();
  };

  /**
   * Return the `TimeZone` instance corresponding to the given ID.
   * @param {String} id
   * @returns {baja.TimeZone} the time zone, or `null` if not found
   */
  TimeZoneDatabase.prototype.getTimeZone = function (id) {
    return this.$map.get(id) || null;
  };

  /**
   * Return all `TimeZone` instances the station knows about.
   *
   * @returns {Array.<baja.TimeZone>}
   */
  TimeZoneDatabase.prototype.getTimeZones = function () {
    var map = this.$map,
        keys = map.getKeys(),
        zones = [];
    for (var i = 0, len = keys.length; i < len; i++) {
      zones.push(map.get(keys[i]));
    }
    return zones;
  };

  /**
   * Return true if the station knows about the given time zone ID.
   *
   * @param {string} id
   * @returns {boolean}
   */
  TimeZoneDatabase.prototype.isZoneIdSupported = function (id) {
    return !!this.$map.get(id);
  };

  /**
   * Return the location based TimeZone value.
   * @returns {baja.TimeZone}
   * @since Niagara 4.14
   */
  TimeZoneDatabase.prototype.getLocalTimeZone = function () {
    return this.$map.get(dateTimeUtil.getCurrentTimeZoneId()) || TimeZone.DEFAULT;
  };

  return TimeZoneDatabase;
});