main.js (5219B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2021 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 24 var isInteger = require( '@stdlib/assert/is-integer' ); 25 var isString = require( '@stdlib/assert/is-string' ).isPrimitive; 26 var isObject = require( '@stdlib/assert/is-object' ); 27 var floor = require( '@stdlib/math/base/special/floor' ); 28 var round = require( '@stdlib/math/base/special/round' ); 29 var ceil = require( '@stdlib/math/base/special/ceil' ); 30 31 32 // VARIABLES // 33 34 var timestamp = /^\d{10}$|^\d{13}$/; 35 var rounders = [ 'floor', 'ceil', 'round' ]; 36 37 38 // FUNCTIONS // 39 40 /** 41 * Validates a date parameter. 42 * 43 * @private 44 * @param {*} value - value to be validated 45 * @param {string} name - name to be used in error messages 46 * @throws {TypeError} value must either be a date string, Date object, Unix timestamp, or JavaScript timestamp 47 * @throws {Error} numeric date must be either a Unix or Javascript timestamp 48 * @returns {Date} validated date 49 */ 50 function validDate( value, name ) { 51 var type; 52 53 type = typeof value; 54 if ( type === 'string' ) { 55 value = Date.parse( value ); 56 if ( value !== value ) { 57 throw new Error( 'invalid argument. Unable to parse ' + name.toLowerCase() + ' date.' ); 58 } 59 value = new Date( value ); 60 } 61 if ( type === 'number' ) { 62 if ( !timestamp.test( value ) ) { 63 throw new Error( 'invalid argument. Numeric ' + name.toLowerCase() + ' date must be either a Unix or Javascript timestamp.' ); 64 } 65 if ( value.toString().length === 10 ) { 66 value *= 1000; // sec to ms 67 } 68 value = new Date( value ); 69 } 70 if ( !(value instanceof Date) ) { 71 throw new TypeError( 'invalid argument. ' + name + ' date must either be a date string, Date object, Unix timestamp, or JavaScript timestamp.' ); 72 } 73 return value; 74 } 75 76 77 // MAIN // 78 79 /** 80 * Generates an array of linearly spaced dates. 81 * 82 * @param {(Date|number|string)} start - start time as either a `Date` object, Unix timestamp, JavaScript timestamp, or date string 83 * @param {(Date|number|string)} stop - stop time as either a `Date` object, Unix timestamp, JavaScript timestamp, or date string 84 * @param {number} [length] - output array length (default: 100) 85 * @param {Object} [options] - function options 86 * @param {string} [options.round] - specifies how sub-millisecond times should be rounded: [ 'floor', 'ceil', 'round' ] (default: 'floor' ) 87 * @throws {TypeError} length argument must a positive integer 88 * @throws {Error} must provide valid options 89 * @returns {Array} array of dates 90 * 91 * @example 92 * var stop = '2014-12-02T07:00:54.973Z'; 93 * var start = new Date( stop ) - 60000; 94 * 95 * var arr = datespace( start, stop, 6 ); 96 * // returns [...] 97 * 98 * @example 99 * // Equivalent of Math.ceil(): 100 * var arr = datespace( 1417503655000, 1417503655001, 3, { 'round': 'ceil' } ); 101 * // returns [...] 102 * 103 * // Equivalent of Math.round(): 104 * arr = datespace( 1417503655000, 1417503655001, 3, { 'round': 'round' } ); 105 * // returns [...] 106 */ 107 function datespace( start, stop, length, options ) { 108 var opts; 109 var len; 110 var flg; 111 var arr; 112 var end; 113 var fcn; 114 var tmp; 115 var d; 116 var i; 117 118 len = 100; 119 flg = true; 120 opts = { 121 'round': 'floor' 122 }; 123 start = validDate( start, 'Start' ); 124 stop = validDate( stop, 'Stop' ); 125 if ( arguments.length > 2 ) { 126 if ( arguments.length === 3 ) { 127 if ( isObject( length ) ) { 128 opts = length; 129 } else { 130 len = length; 131 132 // Turn off checking the options object... 133 flg = false; 134 } 135 } else { 136 opts = options; 137 len = length; 138 } 139 if ( len === 0 ) { 140 return []; 141 } 142 if ( !isInteger( len ) || len < 0 ) { 143 throw new TypeError( 'invalid argument. Length must a positive integer.' ); 144 } 145 if ( flg ) { 146 if ( !isObject( opts ) ) { 147 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 148 } 149 if ( hasOwnProp( opts, 'round' ) ) { 150 if ( !isString( opts.round ) ) { 151 throw new TypeError( 'invalid option. `round` option must be a string.' ); 152 } 153 if ( rounders.indexOf( opts.round ) === -1 ) { 154 throw new Error( 'invalid input option. `round` option must be one of [' + rounders.join( ',' ) + '].' ); 155 } 156 } 157 } 158 } 159 switch ( opts.round ) { 160 case 'round': 161 fcn = round; 162 break; 163 case 'ceil': 164 fcn = ceil; 165 break; 166 default: 167 case 'floor': 168 fcn = floor; 169 break; 170 } 171 172 // Calculate the increment... 173 end = len - 1; 174 d = ( stop.getTime() - start.getTime() ) / end; 175 176 // Build the output array... 177 arr = new Array( len ); 178 tmp = start; 179 arr[ 0 ] = tmp; 180 tmp = tmp.getTime(); 181 for ( i = 1; i < end; i++ ) { 182 tmp += d; 183 arr[ i ] = new Date( fcn( tmp ) ); 184 } 185 arr[ end ] = stop; 186 return arr; 187 } 188 189 190 // EXPORTS // 191 192 module.exports = datespace;