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 }