snowflake-sdk
Version:
Node.js driver for Snowflake
194 lines • 7.1 kB
JavaScript
"use strict";
const Moment = require('moment-timezone');
const Util = require('../../util');
const datetimeFormatConverter = require('./datetime_format_converter');
/**
* Creates a new SfTimestamp instance.
*
* @param {Number} epochSeconds the epoch time in seconds.
* @param {Number} nanoSeconds the number of nano seconds (incremental, not
* epoch).
* @param {Number} scale the precision for the fractional part of the timestamp.
* @param {String | Number} [timezone] the timezone name as a string
* (e.g. 'America/New_York') or the timezone offset in minutes (e.g. -240).
* @param {String} [format] the SQL format to use to format the timestamp.
* @constructor
*/
function SfTimestamp(epochSeconds, nanoSeconds, scale, timezone, format) {
// pick reasonable defaults for the inputs if needed
epochSeconds = Util.isNumber(epochSeconds) ? epochSeconds : 0;
nanoSeconds = Util.isNumber(nanoSeconds) ? nanoSeconds : 0;
scale = Util.isNumber(scale) ? scale : 0;
format = Util.isString(format) ? format : '';
// save any information we'll need later
this.epochSeconds = epochSeconds;
this.nanoSeconds = nanoSeconds;
this.scale = scale;
this.timezone = timezone;
this.format = format;
// Milliseconds are truncated to avoid rounding issues, and the decimal part is not useful since Moment only supports milliseconds precision
const milliseconds = Math.trunc(nanoSeconds / 1000000);
let moment = Moment(epochSeconds * 1000 + milliseconds);
// set the moment's timezone
if (Util.isString(timezone)) {
moment = moment.tz(timezone);
}
else if (Util.isNumber(timezone)) {
moment = moment.utcOffset(timezone);
}
// save the moment
this.moment = moment;
}
/**
* Returns a string representing the specified SfTimestamp instance.
*
* @returns {String}
*/
SfTimestamp.prototype.toString = function () {
// use cached value if possible
if (this._valueAsString) {
return this._valueAsString;
}
const moment = this.moment;
const nanoSeconds = this.nanoSeconds;
let scale = this.scale;
const formatSql = this.format;
// get an upper-case version of the input sql format
const formatSqlUpper = formatSql.toUpperCase();
const tags = datetimeFormatConverter.formatTagsMap();
// iterate over the format string
const length = formatSql.length;
let formatMoment = '';
for (let pos = 0; pos < length;) {
let tag = null;
let out = null;
// at each position, check if there's a tag at that position; if so, use
// 'out' as the replacement
for (let index = 0; index < tags.length; index++) {
if (formatSqlUpper.substr(pos).indexOf(tags[index][0]) === 0) {
tag = tags[index][0];
out = tags[index][1];
break;
}
}
// if we didn't find a match, just insert the character after escaping it
// (by wrapping it in square brackets)
if (out === null) {
formatMoment += '[' + formatSql[pos] + ']';
pos++;
}
else {
// we found one of our special tags
if (out === '') {
if (tag === 'TZH') {
// format the moment to get the timezone string and extract the
// hours; for example, '-0700' will be converted to '-07'
out = moment.format('ZZ').substr(0, 3);
}
else if (tag === 'TZM') {
// format the moment to get the timezone string and extract the
// minutes; for example, '-0700' will be converted to '00
out = moment.format('ZZ').substr(3);
}
else if (tag === 'FF') {
// if 'FF' is followed by a digit, use the digit as the scale
let digit = null;
if (pos + tag.length < length) {
const matches = formatSql[pos + tag.length].match(/[0-9]/);
if (matches) {
digit = matches[0];
}
}
if (digit !== null) {
scale = digit;
pos++; // skip the digit as well
}
// if we need to include fractional seconds
if (scale > 0) {
// divide the nanoSeconds to get the requested number of
// meaningful digits
const scaled = Math.floor(nanoSeconds / Math.pow(10, 9 - scale));
// pad with the appropriate number of leading zeros
out = (new Array(9).join('0') + scaled).substr(-scale);
}
}
}
// append the 'out' text to the moment format and update the position
formatMoment += out;
pos += tag.length;
}
}
// format the moment and cache the result
const timezone = this.timezone.name || this.timezone;
if (timezone) {
if (typeof timezone === 'number') {
this._valueAsString = moment.utcOffset(timezone).format(formatMoment);
}
else {
this._valueAsString = moment.tz(timezone).format(formatMoment);
}
}
else {
this._valueAsString = moment.format(formatMoment);
}
return this._valueAsString;
};
/**
* Converts this SfTimestamp to an SfDate that's just a normal JavaScript Date
* with some additional methods like getEpochSeconds(), getNanoSeconds(),
* getTimezone(), etc.
*
* @returns {Date}
*/
SfTimestamp.prototype.toSfDate = function () {
// create a Date from the moment
const date = this.moment.toDate();
const self = this;
date.getEpochSeconds = function () {
return self.epochSeconds;
};
date.getNanoSeconds = function () {
return self.nanoSeconds;
};
date.getScale = function () {
return self.scale;
};
date.getTimezone = function () {
return self.timezone;
};
date.getFormat = function () {
return self.format;
};
date.toJSON = function () {
return self.toString();
};
return date;
};
/**
* Converts this SfTimestamp to an SfTime, which is just a JavaScript Object
* with some methods: getMidnightSeconds(), getNanoSeconds(), etc.
*
* @returns {Object}
*/
SfTimestamp.prototype.toSfTime = function () {
const self = this;
return {
getMidnightSeconds: function () {
return self.epochSeconds;
},
getNanoSeconds: function () {
return self.nanoSeconds;
},
getScale: function () {
return self.scale;
},
getFormat: function () {
return self.format;
},
toJSON: function () {
return self.toString();
},
};
};
module.exports = SfTimestamp;
//# sourceMappingURL=sf_timestamp.js.map