//@include lib_Class.js
/* eslint-disable */
//@include lib_moment.js
/* eslint-enable */

/**
 * Utils for handling dates. This allows e.g. converting ELO ISO dates to moment.js date-objects.
 *
 * Uses moment.js for converting and handling date strings. Please refer to the official documentation of
 * moment.js for further information. http://momentjs.com/docs/
 *
 *     var momentDate = sol.common.DateUtils.isoToDate('20151203');
 *
 *     // add 7 days with moment.js
 *     var oneWeekAfter = momentDate.add(7, 'days');
 *
 * @eloall
 * @author PZ, ELO Digital Office GmbH
 *
 */
 sol.define("sol.common.DateUtils", {
  singleton: true,
  alternateClassName: "sol.Date",

  /**
   * @property
   * @private
   * Internal date formats used for checking and parsing
   */
  ISO_CONFIGS: {
    ISODATE: { format: "YYYYMMDD", regex: /^\d{8}$/ },
    ISODATETIME: { format: "YYYYMMDDHHmmss", regex: /^\d{14}$/ },
    ELOTSTAMP: { format: "YYYY.MM.DD.HH.mm.ss", regex: /^\d{4}(\.\d{2}){5}$/ },
    ISODATEMILLISECONDS: { format: "YYYYMMDDHHmmssSSS", regex: /^\d{17}$/ }
  },

  TIME_CONFIGS: {
    TIME: { format: "HH:mm", regex: /(\d\d?):(\d\d?)/ }
  },

  /**
   * Converts an iso date string to a date object
   * @param {String} isoDate The iso date string
   * @param {Object} config Configuration
   * @param {Boolean} config.startOfDay Return the start of the day
   * @param {Boolean} config.endOfDay Return the end of the day
   * @return {Date} A JavaScript Date object
   */
  isoToDate: function (isoDate, config) {
    var me = this,
        mom;

    if (!isoDate) {
      return;
    }

    mom = me.isoToMoment(isoDate, config);

    return mom.toDate();
  },

  /**
   * Converts an iso date string to a moment object
   * @param {String} isoDate The iso date string
   * @param {Object} config Configuration
   * @param {Boolean} config.asUtc Parse as UTC
   * @param {Boolean} config.startOfDay Return the start of the day
   * @param {Boolean} config.endOfDay Return the end of the day
   * @return {Moment} A Moment Date object
   *
   * https://momentjs.com/docs/#/parsing/
   */
  isoToMoment: function (isoDate, config) {
    var mom, cfg;

    if (!isoDate) {
      return;
    }

    config = config || {};

    for (cfg in this.ISO_CONFIGS) {
      if (this.ISO_CONFIGS.hasOwnProperty(cfg) && this.ISO_CONFIGS[cfg].regex.test(isoDate)) {
        if (config.asUtc) {
          mom = moment.utc(isoDate, this.ISO_CONFIGS[cfg].format, true);
        } else {
          mom = moment(isoDate, this.ISO_CONFIGS[cfg].format, true);
        }
        break;
      }
    }

    if (config.startOfDay) {
      mom = mom.startOf("day");
    } else if (config.endOfDay) {
      mom = mom.endOf("day");
    }

    if (!mom) {
      throw "sol.common.DateUtils.isoToDate: Wrong input format, must be an ELO iso date string: " + isoDate;
    }

    return mom;
  },

  /**
   * Converts a date object to an iso string
   * @param {Date} date The date object
   * @param {Object} params (optional)
   * @param {Boolean} [params.withoutTime=false] (optional) If set to `true`, the time will be omitted from the ISO string
   * @param {Boolean} [params.startOfDay=false] (optional) Start of day
   * @param {String|Number} params.utcOffset (optional) UTC offset
   * @param {Boolean} params.pattern Pattern
   * @return {String} The resulting iso date
   */
  dateToIso: function (date, params) {
    var me = this,
        isoDate, mo;

    params = params || {};
    mo = moment(date);

    isoDate = me.momentToIso(mo, params);

    return isoDate;
  },

  /**
   * Converts a moment object to an iso string
   * @param {Moment} mo The moment object
   * @param {Object} params (optional)
   * @param {Boolean} [params.withoutTime=false] (optional) If set to `true`, the time will be omitted from the ISO string
   * @param {Object} [params.add] (optional) Add timespan, e.g. `{ days: 1 }`, see moment.js documentation
   * @param {Boolean} [params.startOfDay=false] (optional) Start of day
   * @param {Boolean} [params.endOfDay=false] (optional) End of day
   * @param {String|Number} params.utcOffset (optional) UTC offset
   * @param {Boolean} params.pattern Pattern
   * @return {String} The resulting iso date
   */
  momentToIso: function (mo, params) {
    var me = this,
        isoDate;

    params = params || {};

    if (typeof params.utcOffset != "undefined") {
      mo.utcOffset(params.utcOffset);
    }

    if (params.add) {
      mo.add(params.add);
    }

    if (params.startOfDay) {
      mo.startOf("d");
    }

    if (params.endOfDay) {
      mo.endOf("d");
    }

    if (!params.pattern) {
      params.pattern = (params.withoutTime === true) ? me.ISO_CONFIGS.ISODATE.format : me.ISO_CONFIGS.ISODATETIME.format;
    }

    isoDate = mo.format(params.pattern);

    return isoDate;
  },

  /**
   * Returns now as iso string
   * @param {Object} params (optional)
   * @param {String} params.withMilliseconds
   * @param {String|Number} params.utcOffset
   * @return {String}
   */
  nowIso: function (params) {
    var me = this,
        now;

    params = params || {};

    if (params.withMilliseconds === true) {
      return String(moment().format(this.ISO_CONFIGS.ISODATEMILLISECONDS.format));
    }
    now = me.dateToIso(new Date(), params);
    return now;
  },

  /**
   * Adds or removes time units from a date object.
   *
   * The following example will substracts 3 days from myDate and returns a Date
   *
   *     sol.common.DateUtils.shift(myDate, -3)
   *
   * The following example will adds 7 hours to myDate and returns a String with the specified pattern:
   *
   *     sol.common.DateUtils.shift(myDate, 7, { pattern: "YYYY.MM.DD HH:mm:ss", unit: "h" })
   *
   * Supported `unit` patterns:
   *
   * - years: y
   * - half years: hy
   * - quarters: Q
   * - months: M
   * - weeks: w
   * - days: d
   * - hours: h
   * - minutes: m
   * - seconds: s
   * - milliseconds: ms
   *
   * @param {Date} date The date object which should be used as base for the shift
   * @param {Number} value The amount which should be shifted
   * @param {Object} params (optional) Additional parameters
   * @param {String} params.pattern (optional) Format string (see [moment.js](http://momentjs.com/) for formating options)
   * @param {String} [params.unit="d"] (optional) The unit which should be added/substracted
   * @return {Date|String} Returns a `String` if a params.pattern is defined and a `Date` if not.
   */
  shift: function (date, value, params) {
    var shifted, unit;

    shifted = moment(date.getTime());
    unit = (params && params.unit) ? params.unit : "d";

    if (unit == "hy") {
      unit = "M";
      value *= 6;
    }

    if (!(/^[yQMwdhms]$|^ms$|^hy$/.test(unit))) {
      throw "IllegalArgumentException: unsupported unit for shift: " + unit;
    }

    shifted.add(value, unit);

    return (params && params.pattern) ? shifted.format(params.pattern) : shifted.toDate();
  },

  /**
   * Formats a Date object.
   *
   *      sol.common.DateUtils.format(new Date(), "YYYY.MM.DD HH:mm:ss.SSS")
   *
   * @param {Date} date The date object
   * @param {String} pattern Format string (see [moment.js](http://momentjs.com/) for formating options)
   * @return {String}
   */
  format: function (date, pattern) {
    return moment(date.getTime()).format(pattern);
  },

  /**
   * Parses a date string with a given pattern.
   *
   *     var maythefourth = sol.common.DateUtils.parse("1977-05-04");
   *
   *     var maythefourth = sol.common.DateUtils.parse("04.05.1977", "DD.MM.YYYY");
   *
   *     var maythefourth = sol.common.DateUtils.parse("05/04/1977", "MM/DD/YYYY");
   *
   * If no `pattern` is defined the function tries to figure out the format by using various standards (see [moment.js/parsing](https://momentjs.com/docs/#/parsing/string/)).
   * For consistent results the use of `pattern` is encouraged.
   *
   * @param {String} dateString
   * @param {String} pattern (optional) Parse string (see [moment.js](http://momentjs.com/) for parsing options)
   * @return {Date} Returns `null` if date could not be determined
   */
  parse: function (dateString, pattern) {
    var mom, me = this;

    mom = me.createMoment(dateString, pattern);
    return (mom.isValid()) ? mom.toDate() : null;
  },

  /**
   * Create a moment object of the given dateString
   * @param {*} dateString
   * @param {String} pattern (optional) Parse string (see [moment.js](http://momentjs.com/) for parsing options)
   * @returns {Moment} Returns Moment object (Attention: the moment object could be invalid)
   */
  createMoment: function (dateString, pattern) {
    return (pattern) ? moment(dateString, pattern, true) : moment(dateString);
  },

  /**
   * Create a new fresh moment object by usage copy constructor of moment
   * @param {Moment} mom
   * @returns {Moment} Clone of the copy object, when the given moment obj was invalid it will be returned null
   */
  of: function (mom) {
    return mom && mom.isValid() ? moment(mom) : null;
  },

  /**
   * Calculates a difference between to dates
   * @param {Date} startDate Start date
   * @param {Date} endDate End date
   * @param {String} unit Unit
   * @param {Object} config Configuration
   * @param {Boolean} config.roundUp Round up
   * @return {Number}
   */
  diff: function (startDate, endDate, unit, config) {
    var number, isQuarter, isHalfYear;

    number;
    isQuarter = false;

    if (!startDate) {
      throw "Start date is empty";
    }

    if (!endDate) {
      throw "End date is empty";
    }

    if (!unit) {
      throw "Unit is empty";
    }

    if (unit == "O") {
      return 1;
    }

    if (unit == "Q") {
      isQuarter = true;
      unit = "M";
    }

    if (unit == "hy") {
      isHalfYear = true;
      unit = "M";
    }

    config = config || {};

    number = moment(endDate.getTime()).diff(startDate.getTime(), unit, true);

    number = isQuarter ? (number / 3) : number;
    number = isHalfYear ? (number / 6) : number;

    if (config.roundUp) {
      number = Math.ceil(number);
    }
    return number;
  },

  /**
   * Checks whether a date is between a start date and an end end date
   * @param {Date} [startDate=now] Start date
   * @param {Date} [endDate=now] End date
   * @param {Date} [checkDate=now] Check date
   * @return {Boolean}
   */
  isBetween: function (startDate, endDate, checkDate) {
    var checkMoment, startMoment, endMoment;

    startMoment = startDate ? moment(startDate.getTime()) : moment();
    endMoment = endDate ? moment(endDate.getTime()) : moment();
    checkMoment = checkDate ? moment(checkDate.getTime()) : moment();

    return checkMoment.isBetween(startMoment, endMoment);
  },

  /**
   * Prepare duration
   * @param {Number} number Duration
   * @param {String} unit Duration unit
   * @return {Object} Duration
   */
  prepareDuration: function (number, unit) {
    var duration, splitParts;

    if ((typeof number == "undefined") || (number == "")) {
      return;
    }

    unit += "";

    splitParts = unit.split(" ");
    unit = splitParts[0];

    unit = unit || "d";

    switch (unit) {
      case "y":
        duration = { years: number };
        break;

      case "hy":
        duration = { months: number * 6 };
        break;

      case "Q":
        duration = { months: number * 3 };
        break;

      case "M":
        duration = { months: number };
        break;

      case "w":
        duration = { weeks: number };
        break;

      case "d":
        duration = { days: number };
        break;

      case "h":
        duration = { hours: number };
        break;

      case "m":
        duration = { minutes: number };
        break;

      case "seconds":
        duration = { seconds: number };
        break;

      default:
        throw "Invalid duration unit: " + unit;
    }

    return duration;
  },

  /**
   * Returns the `end of` time of a specified period
   * @param {Moment} moment Moment
   * @param {String} unit Unit
   * @return {Moment} Moment
   */
  endOf: function (moment, unit) {
    var month;

    if (!unit) {
      return moment;
    }

    if (unit == "hy") {
      month = moment.month();
      while ((month != 5) && (month != 11)) {
        moment = moment.add({ month: 1 });
        month = moment.month();
      }
      moment = moment.endOf("M");
      return moment;
    }

    moment.endOf(unit);

    return moment;
  },

  /**
   * Transforms an ISO date
   * @param {String} isoDate ISO date
   * @param {Object} config Configuration
   * @param {Boolean} config.asUtc Parse as UTC
   * @param {String|Number} config.utcOffset UTC offset
   */
  transformIsoDate: function (isoDate, config) {
    var mo;
    if (!isoDate) {
      return "";
    }
    mo = sol.common.DateUtils.isoToMoment(isoDate, config);
    isoDate = sol.common.DateUtils.momentToIso(mo, config);

    return isoDate;
  },

  /**
   * Checks whether the given moment represents the last day of the month
   * @param {Moment} mom Moment
   * @param {Object} params Parameters
   * @param {Number} [config.toleranceDays==0] Tolerance days
   */
  isLastDayOfMonth: function (mom, params) {
    if (!mom) {
      return false;
    }

    params = params || {};
    params.toleranceDays = params.toleranceDays || 0;

    if ((mom.get("date") + params.toleranceDays) >= mom.daysInMonth()) {
      return true;
    }

    return false;
  },

  /**
   * Checks whether time is potentially a time string.
   * This means the time value has only digits except a colon in the middle
   * of the string. This corresponds to the Time format HH:mm
   * @param {*} time value to check
   * @returns true when time (without colon) has 4 characters at least and
   *   the whole timeString (without colon) is convertable to number
   */
  couldBeTime: function (time) {
    var val = time.replace(":", "");
    return !!(val.length && (val.length < 5) && isNaN(+val) === false);
  },

  /**
   * Checks whether the hour and minutes are in range.
   * This function is used the 24h by default.
   * @param {string|number} hour must be in range (0 <= hour < 25)
   * @param {string|number} minute must be in range (0 <= minute < 60)
   * @returns true then the passed values are in range
   */
  isValidTime: function (hour, minute) {
    return ((hour = +(hour)) >= 0 && hour < 25) && ((minute = +(minute)) >= 0 && minute < 60);
  },

  /**
   * Determines from the transferred time and completes missing temporal information
   *
   * For example, the function calculates from 01 -> 01:00
   * Times that are specified as 24:00 are converted to 00:00.
   *
   * If the value passed does not correspond to a potential time,
   * then an empty string is returned.
   *
   * Output format is HH:mm
   *
   * @param {string} time
   * @returns an expanded time valid time string (format: HH:mm)
   */
  fillTime: function (time) {
    var me = this, hours, minutes, match, padded,
      regex = me.TIME_CONFIGS.TIME.regex;

    if (!time || time.trim().length === 0) {
      return time;
    }

    function pad2(s) {
      return s.length === 1 ? "0" + s : s;
    }

    function pad4(s) {
      var len = s.length;
      // this function is sued when currente time string length is 3
      function decide3() {
        var h = s.substr(0, 2), m = s.substr(2, 1);
        return me.isValidTime(h, m)
          ? h + ("0" + m)
          : ("0" + h) + m;
      }
      // this function is used when current time string length is 2
      function decide2() {
        return (me.isValidTime(s, 0) && s + "00")
              || (me.isValidTime(0, s) && "00" + s)
              || ("0" + s[0] + "0" + s[1]);
      };

      return (len === 4 && s)
        || (len === 3 && decide3(s))
        || (len === 2 && decide2(s))
        || ("0" + s + "00");
    }

    function sanitizeHour(hour) {
      return hour === "24" ? "00" : hour;
    }

    if (time.indexOf(":") > 0) {
      if (match = time.match(regex)) {
        hours = pad2(match[1]);
        minutes = pad2(match[2]);
      } else {
        return "";
      }
    } else {
      // time is without colon
      padded = pad4(time);
      hours = padded.substr(0, 2);
      minutes = padded.substr(2, 2);
    }
    // when we detect hour=24 we have to convert it to zero
    hours = sanitizeHour(hours);
    if (me.isValidTime(hours, minutes)) {
      return hours + ":" + minutes;
    } else {
      return "";
    }
  }

});

//# sourceURL=lib_sol.common.DateUtils.js