Source: common-sk/modules/human.js

/** @module common-sk/modules/human
 *  @description Utitities for working with human friendly I/O.
 */

const TIME_DELTAS = [
  { units: "w", delta: 7*24*60*60 },
  { units: "d", delta:   24*60*60 },
  { units: "h", delta:      60*60 },
  { units: "m", delta:         60 },
  { units: "s", delta:          1 },
];

/** @constant {number} */
export const KB = 1024;
/** @constant {number} */
export const MB = KB * 1024;
/** @constant {number} */
export const GB = MB * 1024;
/** @constant {number} */
export const TB = GB * 1024;
/** @constant {number} */
export const PB = TB * 1024;

const BYTES_DELTAS = [
  { units: " PB", delta: PB},
  { units: " TB", delta: TB},
  { units: " GB", delta: GB},
  { units: " MB", delta: MB},
  { units: " KB", delta: KB},
  { units: " B",  delta:  1},
];

/** Left pad a number with 0's.
 *
 * @param {number} num - The number to pad.
 * @param {number} size - The number of digits to pad out to.
 * @returns {string}
 */
export function pad(num, size) {
  let str = num + "";
  while (str.length < size) str = "0" + str;
  return str;
}

/**
 * Returns a human-readable format of the given duration in seconds.
 * For example, 'strDuration(123)' would return "2m 3s".
 * Negative seconds is treated the same as positive seconds.
 *
 * @param {number} seconds - The duration.
 * @returns {string}
 */
export function strDuration(seconds) {
  if (seconds < 0) {
    seconds = -seconds;
  }
  if (seconds === 0) { return '  0s'; }
  let rv = "";
  for (let i=0; i<TIME_DELTAS.length; i++) {
    if (TIME_DELTAS[i].delta <= seconds) {
      let s = Math.floor(seconds/TIME_DELTAS[i].delta)+TIME_DELTAS[i].units;
      while (s.length < 4) {
        s = ' ' + s;
      }
      rv += s;
      seconds = seconds % TIME_DELTAS[i].delta;
    }
  }
  return rv;
};

/**
 * Returns the difference between the current time and 's' as a string in a
 * human friendly format. If 's' is a number it is assumed to contain the time
 * in milliseconds otherwise it is assumed to contain a time string parsable
 * by Date.parse().
 *
 * For example, a difference of 123 seconds between 's' and the current time
 * would return "2m".
 *
 * @param {Object} milliseconds - The time in milliseconds or a time string.
 * @returns {string}
 */
export function diffDate(s) {
  let ms = (typeof(s) === "number") ? s : Date.parse(s);
  let diff = (ms - Date.now())/1000;
  if (diff < 0) {
    diff = -1.0 * diff;
  }
  return humanize(diff, TIME_DELTAS);
}

/**
 * Formats the amount of bytes in a human friendly format.
 * unit may be supplied to indicate b is not in bytes, but in something
 * like kilobytes (KB) or megabytes (MB)
 *
 * @example
 * // returns "1 KB"
 * bytes(1234)
 * @example
 * // returns "5 GB"
 * bytes(5321, MB)
 *
 * @param {number} b - The number of bytes in units 'unit'.
 * @param {number} unit - The number of bytes per unit.
 * @returns {string}
 */
export function bytes(b, unit = 1) {
  if (Number.isInteger(unit)) {
    b = b * unit;
  }
  return humanize(b, BYTES_DELTAS);
}

/** localeTime formats the provided Date object in locale time and appends the timezone to the end.
 *
 * @param {Date} date
 * @returns {string}
 */
export function localeTime(date) {
  // caching timezone could be buggy, especially if times from a wide range
  // of dates are used. The main concern would be crossing over Daylight
  // Savings time and having some times be erroneously in EST instead of
  // EDT, for example
  let str = date.toString();
  let timezone = str.substring(str.indexOf("("));
  return date.toLocaleString() + " " + timezone;
}


function humanize(n, deltas) {
  for (let i=0; i<deltas.length-1; i++) {
    // If n would round to '60s', return '1m' instead.
    let nextDeltaRounded =
      Math.round(n/deltas[i+1].delta)*deltas[i+1].delta;
    if (nextDeltaRounded/deltas[i].delta >= 1) {
      return Math.round(n/deltas[i].delta)+deltas[i].units;
    }
  }
  let i = deltas.length-1;
  return Math.round(n/deltas[i].delta)+deltas[i].units;
}