Home Reference Source Repository

es6/utils/Time.js

/** 
 * A collection of Time utils to make working with Time a little easier in Javascript
 * Have care with delays larger than 2147483647 milliseconds (approximately 25 days) or less than 1.
 * 
 * @namespace Time
 */

/**
 * different millisecond time intervals
 *
 * @type       {Object}
 * @property   {Integer} second        number of milliseconds in a second
 * @property   {Integer} minutes       number of milliseconds in a minute
 * @property   {Integer} hour          number of milliseconds in a hour
 * @property   {Integer} day           number of milliseconds in a day
 */

export const EPOCS   = {}
EPOCS.second  = 1000
EPOCS.minute  = 60 * EPOCS.second
EPOCS.hour    = EPOCS.minute * 60
EPOCS.day     = EPOCS.hour   * 24

/**
 * returns a humanized time representation of the difference between two millisecond dates
 *
 * @function humanize
 * @memberof Time
 * @param   {Integer}               start   the start date in number of milliseconds since 1 January, 1970 UTC
 * @param   {Integer} [Date.now()]  end     the end date in number of milliseconds
 * @return  {Object}
 */

export function humanize ( start, end=Date.now() ) {
  let diff  =  end - start
  
  const days = Math.floor( diff / EPOCS.day )
  
  diff = diff % EPOCS.day

  const hours = Math.floor( diff / EPOCS.hour )
  
  diff = diff % EPOCS.hour
  
  const minutes = Math.floor( diff / EPOCS.minute )
  
  diff = diff % EPOCS.minute
  
  const seconds = Math.floor(diff / EPOCS.second ) || 0

  return {
      days    : days
    , hours   : hours
    , minutes : minutes
    , seconds : seconds
  }
}

/**
 * returns how many seconds have elapsed since `n` compared to `Date.now()`
 * 
 * @function secondsAgo
 * @memberof Time
 * @param   {Integer} n    the start date in milliseconds
 * @return  {Integer}      the number of seconds that have elapsed
 */

export function secondsAgo (n) {
  return Date.now() - EPOCS.second * n
}

/**
 * returns `n` number of seconds converted to milliseconds
 * 
 * @function seconds
 * @memberof Time
 * @param  {Integer} n     the number of seconds
 * @return {Integer}
 * 
 * @example
 * 
 * seconds(2) // 2000 
 */
export function seconds (n) {
  return EPOCS.second * n
}

/**
 * returns `n` number of minutes converted to milliseconds
 * 
 * @function minutes
 * @memberof Time
 * @param  {Integer} n     the number of minutes
 * @return {Integer}
 * 
 * @example
 * 
 * minutes(1) // 1000 * 60
 */
export function minutes (n) {
  return EPOCS.minute * n
}

/**
 * returns `n` number of hours converted to milliseconds
 * 
 * @function hours
 * @memberof Time
 * @param  {Integer} n     the number of hours
 * @return {Integer}
 * 
 * @example
 * 
 * hours(1) // 1000 * 60 * 60
 */
export function hours (n) {
  return EPOCS.hour * n
}

/**
 * returns `n` number of days converted to milliseconds. 
 * 
 * @function days
 * @memberof Time
 * @param  {Integer} n     the number of days
 * @return {Integer}
 * 
 * @example
 * 
 * days(1) // 1000 * 60 * 60 * 24
 */

export function days (n) {
  return EPOCS.day * n
}

/**
 * compares a Date Object to a millisecond representation
 * 
 * @function olderThan
 * @memberof Time
 * @param    {Date}     date        a Date object
 * @param    {Integer}  milliDate   a millisecond representation of a Date
 * 
 * @returns  {Boolean}  whether date is older than milliDate
 */
export function olderThan (date, milliDate) {
  return (new Date(date)).getTime() <  milliDate
}

/**
 * used for making sure a code block runs within a certain amount of time
 * 
 * @class Timer
 *
 */
export class Timer {
  
  /**
   * creates a new Timer
   *
   * @method     create
   * @param      {Object}   config                                                      The config object
   * @param      {String}   config.name           [__filename where Timer is created]   The name of the Timer (for errors)
   * @param      {Integer}  config.max_run_time   [1 minute]                            The max run time in milliseconds
   * @param      {Function} config.after          [Timer.prototype.error]               The function to run after a Timer ends
   */
  static create (config) {
    config.name = config.name || (new Error()).stack.split("\n")[2].split(" at ")[1]
    return new Timer(config)
  }

  /**
   * constructor
   *
   * @method     constructor
   * @param      {Object}  config  The config
   */
  constructor (config) {
    this.name    = config.name
    this.onEnd   = config.after        || this.error.bind(this, `${this.name} expired without an after effect`)
    this.runtime = config.max_run_time || minutes(1)
    this.touches = 0
    return this.begin()
  }

  /**
   * Throws a Timer Error
   *
   * @method     error
   * @param      {<type>}  msg     The error message
   */
  error (msg) {
    throw new Error(msg)
  }

  /**
   * Restarts a Timer's clock
   *
   * @method     touch
   * @return     this
   */
  touch () {
    this.touches++
    this.cancel()
    return this.begin()
  }

  /**
   * convenience method to begin a Timer
   *
   * @method     begin
   * @return     this 
   */
  begin () {
    const timer = this
    // prevent creating unreachable Timeouts
    if (timer.$ && timer.$._onTimeout) {
      return timer
    }
    timer.$     = setTimeout( function interrupt () { timer.onEnd(timer) }, timer.runtime)
    return timer
  }
  
  /**
   * cancels a Timer so it will not throw
   *
   * @method     cancel
   */
  cancel () {
    clearTimeout(this.$)
    return this
  }
}