index.js
  1  'use strict'
  2  
  3  var extend = require('xtend/mutable')
  4  
  5  module.exports = PostgresInterval
  6  
  7  function PostgresInterval (raw) {
  8    if (!(this instanceof PostgresInterval)) {
  9      return new PostgresInterval(raw)
 10    }
 11    extend(this, parse(raw))
 12  }
 13  var properties = ['seconds', 'minutes', 'hours', 'days', 'months', 'years']
 14  PostgresInterval.prototype.toPostgres = function () {
 15    var filtered = properties.filter(this.hasOwnProperty, this)
 16  
 17    // In addition to `properties`, we need to account for fractions of seconds.
 18    if (this.milliseconds && filtered.indexOf('seconds') < 0) {
 19      filtered.push('seconds')
 20    }
 21  
 22    if (filtered.length === 0) return '0'
 23    return filtered
 24      .map(function (property) {
 25        var value = this[property] || 0
 26  
 27        // Account for fractional part of seconds,
 28        // remove trailing zeroes.
 29        if (property === 'seconds' && this.milliseconds) {
 30          value = (value + this.milliseconds / 1000).toFixed(6).replace(/\.?0+$/, '')
 31        }
 32  
 33        return value + ' ' + property
 34      }, this)
 35      .join(' ')
 36  }
 37  
 38  var propertiesISOEquivalent = {
 39    years: 'Y',
 40    months: 'M',
 41    days: 'D',
 42    hours: 'H',
 43    minutes: 'M',
 44    seconds: 'S'
 45  }
 46  var dateProperties = ['years', 'months', 'days']
 47  var timeProperties = ['hours', 'minutes', 'seconds']
 48  // according to ISO 8601
 49  PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = function () {
 50    var datePart = dateProperties
 51      .map(buildProperty, this)
 52      .join('')
 53  
 54    var timePart = timeProperties
 55      .map(buildProperty, this)
 56      .join('')
 57  
 58    return 'P' + datePart + 'T' + timePart
 59  
 60    function buildProperty (property) {
 61      var value = this[property] || 0
 62  
 63      // Account for fractional part of seconds,
 64      // remove trailing zeroes.
 65      if (property === 'seconds' && this.milliseconds) {
 66        value = (value + this.milliseconds / 1000).toFixed(6).replace(/0+$/, '')
 67      }
 68  
 69      return value + propertiesISOEquivalent[property]
 70    }
 71  }
 72  
 73  var NUMBER = '([+-]?\\d+)'
 74  var YEAR = NUMBER + '\\s+years?'
 75  var MONTH = NUMBER + '\\s+mons?'
 76  var DAY = NUMBER + '\\s+days?'
 77  var TIME = '([+-])?([\\d]*):(\\d\\d):(\\d\\d)\\.?(\\d{1,6})?'
 78  var INTERVAL = new RegExp([YEAR, MONTH, DAY, TIME].map(function (regexString) {
 79    return '(' + regexString + ')?'
 80  })
 81    .join('\\s*'))
 82  
 83  // Positions of values in regex match
 84  var positions = {
 85    years: 2,
 86    months: 4,
 87    days: 6,
 88    hours: 9,
 89    minutes: 10,
 90    seconds: 11,
 91    milliseconds: 12
 92  }
 93  // We can use negative time
 94  var negatives = ['hours', 'minutes', 'seconds', 'milliseconds']
 95  
 96  function parseMilliseconds (fraction) {
 97    // add omitted zeroes
 98    var microseconds = fraction + '000000'.slice(fraction.length)
 99    return parseInt(microseconds, 10) / 1000
100  }
101  
102  function parse (interval) {
103    if (!interval) return {}
104    var matches = INTERVAL.exec(interval)
105    var isNegative = matches[8] === '-'
106    return Object.keys(positions)
107      .reduce(function (parsed, property) {
108        var position = positions[property]
109        var value = matches[position]
110        // no empty string
111        if (!value) return parsed
112        // milliseconds are actually microseconds (up to 6 digits)
113        // with omitted trailing zeroes.
114        value = property === 'milliseconds'
115          ? parseMilliseconds(value)
116          : parseInt(value, 10)
117        // no zeros
118        if (!value) return parsed
119        if (isNegative && ~negatives.indexOf(property)) {
120          value *= -1
121        }
122        parsed[property] = value
123        return parsed
124      }, {})
125  }