UNPKG

snowflake-sdk

Version:
238 lines (206 loc) 6.56 kB
/* * Copyright (c) 2015-2024 Snowflake Computing Inc. All rights reserved. */ const Moment = require('moment-timezone'); const Util = require('../../util'); /** * An array of tag mappings to convert a sql format to a moment.js format. If * the 2nd element is empty, special code is needed. */ const CONST_TAGS = [ // proper mappings ['YYYY', 'YYYY'], ['YY', 'YY'], ['MM', 'MM'], ['MON', 'MMM'], ['DD', 'DD'], ['DY', 'ddd'], ['HH24', 'HH'], ['HH12', 'hh'], ['HH', 'HH'], ['AM', 'A'], ['PM', 'A'], ['MI', 'mm'], ['SS', 'ss'], ['TZH:TZM', 'Z'], ['TZHTZM', 'ZZ'], // special code needed ['TZH', ''], ['TZM', ''], ['FF', ''] ]; /** * 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 = CONST_TAGS; // 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 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;