'use strict';
var Int64 = require('node-int64');
//
// Timestamp
//
/**
* Constructor accepts any of the following argument types:
* <p/>
* <table>
* <tr>
* <td>new Timestamp(buffer[, offset=0]) </td>
* <td>Buffer, with byte offset, holding 64-bit, big-endian encoded timestamp</td>
* </tr>
* <tr>
* <td>new Timestamp(Uint8Array[, offset=0]) </td>
* <td>Uint8Array, with byte offset, holding 64-bit, big-endian encoded timestamp</td>
* </tr>
* <tr>
* <td>new Timestamp(seconds, nanoseconds) </td>
* <td>Seconds (and nanoseconds fraction) since the Epoch</td>
* </tr>
* <tr><td>new Timestamp(Date) </td><td>Date object representing timestamp</td></tr>
* <tr>
* <td>new Timestamp(Int64) </td>
* <td>Int64 object holding 64-bit, big-endian encoded timestamp</td>
* </tr>
* </table>
* @class
* @classdesc High resolution timestamp (64-bit number of nanoseconds since the Epoch).
*/
var Timestamp = module.exports = function(param1, param2) {
/**
* Holds 8 byte timestamp in big-endian order.
* @name Timestamp#buffer
* @type Buffer
*/
/**
* Offset within {@link Timestamp#buffer buffer} of beginning of timestamp bytes.
* @name Timestamp#offset
* @type Number
*/
/**
* Seconds since the Epoch.
* @name Timestamp#seconds
* @type Number
*/
/**
* Nanosecond portion (sub-second) of nanoseconds since Epoch. Used in
* combination with {@link Timestamp#seconds seconds} to obtain full
* resolution timestamp.
* @name Timestamp#nanoseconds
* @type Number
*/
if (param1 instanceof Buffer) {
this.buffer = param1;
this.offset = param2 || 0;
}
else if (Object.prototype.toString.call(param1) == '[object Uint8Array]') {
// Under Browserify, Buffers can extend Uint8Arrays rather than an
// instance of Buffer. We could assume the passed in Uint8Array is actually
// a buffer but that won't handle the case where a raw Uint8Array is passed
// in. We construct a new Buffer just in case.
this.buffer = new Buffer(param1);
this.offset = param2 || 0;
}
else if (typeof param1 === 'number' &&
(typeof param2 === 'number' || param2 === undefined)) {
this.buffer = new Buffer(8);
this.offset = 0;
this.setSeconds(param1, param2);
}
else if (param1 instanceof Date) {
this.buffer = new Buffer(8);
this.offset = 0;
var millis = param1.getTime();
this.setSeconds(Math.floor(millis/1000), (millis%1000)*1000000);
}
else if (typeof param1 === 'object') {
this.buffer = param1.buffer;
this.offset = param1.offset;
}
else
throw new TypeError('Unsupported types Timestamp(' + typeof param1 +
', ' + typeof param2 + ')');
};
Timestamp.prototype = Object.create(Int64.prototype);
Timestamp.prototype.constructor = Timestamp;
/**
* Sets timestamp from seconds and nanoseconds (fraction of one second). Sets
* {@link Timestamp#seconds seconds} and
* {@link Timestamp#nanoseconds nanoseconds} members and stores the combined
* nanoseconds since the Epoch number as a 64-bit integer in big-endian order
* starting at {@link Timestamp#offset offset} within
* {@link Timestamp#buffer buffer}, creating the buffer if necessary.
*
* @param seconds Seconds since Epoch.
* @param nanoseconds Nanosecond portion (sub-second) of nanoseconds since Epoch.
*/
Timestamp.prototype.setSeconds = function(seconds, nanoseconds) {
var negate = false;
nanoseconds = nanoseconds || 0;
if (seconds < 0 || nanoseconds < 0) {
negate = true;
if (seconds && seconds < 0)
seconds = Math.abs(seconds);
if (nanoseconds && nanoseconds < 0)
nanoseconds = Math.abs(nanoseconds);
}
var lo = 0;
var hi = 0;
var multiplier = 1000000000 % 0x10000;
var tmp = nanoseconds;
tmp += multiplier * seconds;
lo = tmp % 0x100000000;
hi = tmp / 0x100000000;
hi = Math.floor(hi);
multiplier = 1000000000 >>> 16;
tmp = multiplier * seconds;
lo += (tmp % 0x10000) * 0x10000;
hi += tmp / 0x10000;
hi = Math.floor(hi);
if (lo > 0xFFFFFFFF) {
tmp = lo / 0x100000000;
tmp = Math.floor(tmp);
hi += tmp;
lo %= 0x100000000;
}
// Store Big-endian
this.buffer[this.offset+7] = lo & 0xff;
lo >>= 8;
this.buffer[this.offset+6] = lo & 0xff;
lo >>= 8;
this.buffer[this.offset+5] = lo & 0xff;
lo >>= 8;
this.buffer[this.offset+4] = lo & 0xff;
this.buffer[this.offset+3] = hi & 0xff;
hi >>= 8;
this.buffer[this.offset+2] = hi & 0xff;
hi >>= 8;
this.buffer[this.offset+1] = hi & 0xff;
hi >>= 8;
this.buffer[this.offset] = hi & 0xff;
this.seconds = seconds;
this.nanoseconds = nanoseconds;
if (negate) {
this._2scomp();
if (seconds)
this.seconds = -seconds;
if (nanoseconds)
this.nanoseconds = -nanoseconds;
}
};
/**
* Decodes timestamp from {@link Timestamp#buffer buffer} starting at
* {@link Timestamp#offset offset} and populates
* {@link Timestamp#seconds seconds} and
* {@link Timestamp#nanoseconds nanoseconds} members.
*/
Timestamp.prototype.bufferToSeconds = function() {
var negate = false;
var hi = this.buffer.readUInt32BE(this.offset);
var lo = this.buffer.readUInt32BE(this.offset+4);
if (hi & 0x80000000) {
if (hi === 0x80000000 && lo === 0) {
this.seconds = -9223372036;
this.nanoseconds = -854775808;
return;
}
negate = true;
hi = (~hi & 0xFFFFFFFF) >>> 0;
lo = ~lo >>> 0;
if (lo === 0xFFFFFFFF)
hi += 1;
else
lo += 1;
}
var r = (lo >>> 16) + (hi * 0x10000);
var ans = Math.floor(r / 1000000000);
r = ((r % 1000000000) * 0x10000) + lo % 0x10000;
ans = (ans * 0x10000) + Math.floor(r / 1000000000);
this.seconds = negate ? -ans : ans;
this.nanoseconds = negate ? (r % 1000000000) * -1 : r % 1000000000;
};
/**
* Return string representation of timestamp. The <code>radix</code> value of
* 10 is handled specially and returns the full resolution timstamp in digits.
* A <code>radix</code> value of 16 is handled with a call to
* <code>this.toOctetString()</code>. Otherwise, the result of
* <code>valueOf().toString(radix)</code> is returned.
*
* @param {Number} [radix=10] Just like <code>Number.toString()</code> radix
*/
Timestamp.prototype.toString = function(radix) {
if (!radix)
radix = 10;
function zeroPad (number) {
var strNumber = ''+number;
return '000000000'.slice(0, 9-strNumber.length) + strNumber;
}
if (radix == 10) {
if (this.seconds === undefined)
this.bufferToSeconds();
return ''+this.seconds+zeroPad(Math.abs(this.nanoseconds));
}
else if (radix == 16)
return this.toOctetString();
return this.valueOf().toString(radix);
};
/**
* Copy 8 bytes of int64 in little-endian order into <code>targetBuffer</code>
* starting at <code>targetOffset</code>.
*
* @param {Buffer} targetBuffer Buffer to copy into.
* @param {number} [targetOffset=0] Offset into target buffer.
*/
Timestamp.prototype.copyLE = function (targetBuffer, targetOffset) {
targetOffset = targetOffset || 0;
for (var i=0; i<8; i++)
targetBuffer[targetOffset+i] = this.buffer[this.offset+(7-i)];
};
/**
* Compares this object with other object. Performs a bytewise comparison of
* the underlying buffers.
* @param {Int64} other Other Int64 derived object with which to compare
* @return {Number} Number less than 0, equal to 0, or greater than 0, depending
* of whether this object is less than, equal to, or greater than other.
*/
Timestamp.prototype.compare = function (other) {
if (!other)
throw new TypeError('Invalid comparison object (' + typeof Int64 + ')');
var thisSlice = (this.offset === 0) ? this.buffer : this.buffer.slice(this.offset, this.offset+8);
var otherSlice = (other.offset === 0) ? other.buffer : other.buffer.slice(other.offset, other.offset+8);
return thisSlice.buffer.compare(otherSlice.buffer);
};
/**
* Equality test of this object with other object. Performs a bytewise comparison of
* the underlying buffers.
* @param {Int64} other Other Int64 derived object with which to compare
* @return {Boolean} <i>true</i> if equal, <i>false</i> otherwise
*/
Timestamp.prototype.equals = function (other) {
if (!other)
throw new TypeError('Invalid comparison object (' + typeof Int64 + ')');
var thisSlice = (this.offset === 0) ? this.buffer : this.buffer.slice(this.offset, this.offset+8);
var otherSlice = (other.offset === 0) ? other.buffer : other.buffer.slice(other.offset, other.offset+8);
return thisSlice.buffer.equals(otherSlice.buffer);
};
/**
* Returns Date object representing this timestamp rounded to milliseconds.
* @return {Date} Newly allocated Date object representing this timestamp
* rounded to milliseconds.
*/
Timestamp.prototype.toDate = function() {
if (this.seconds == undefined)
this.bufferToSeconds();
return new Date((this.seconds*1000)+Math.floor(this.nanoseconds/1000000));
};
/**
* Returns number of seconds since the Epoch ({@link Timestamp#seconds seconds}).
* If {@link Timestamp#seconds seconds} is undefined,
* {@link Timestamp#bufferToSeconds bufferToSeconds()} is first called to
* populated it.
* @return {Number} Number of seconds since the Epoch.
*/
Timestamp.prototype.getSeconds = function() {
if (this.seconds == undefined)
this.bufferToSeconds();
return this.seconds;
};
/**
* Returns nanosecond portion (sub-second) of timestamp.
* If {@link Timestamp#nanoseconds nanoseconds} is undefined,
* {@link Timestamp#bufferToSeconds bufferToSeconds()} is first called to
* populated it.
* @return {Number} Nanosecond portion (sub-second) of timestamp.
*/
Timestamp.prototype.getNanoseconds = function() {
if (this.seconds == undefined)
this.bufferToSeconds();
return this.nanoseconds;
};
/**
* Minimum timestamp constant (INT64_MIN).
* @type {Timestamp}
* @constant
* @default
*/
Timestamp.TIMESTAMP_MIN = new Timestamp(-9223372036, -854775808);
/**
* Maximum timestamp constant (INT64_MAX).
* @type {Timestamp}
* @constant
* @default
*/
Timestamp.TIMESTAMP_MAX = new Timestamp(9223372036, 854775807);
/**
* NULL timestamp constant (INT64_MIN+1).
* @type {Timestamp}
* @constant
* @default
*/
Timestamp.TIMESTAMP_NULL = new Timestamp(-9223372036, -854775807);
/**
* Auto-assign timestamp constant (INT64_MIN+2).
* @type {Timestamp}
* @constant
* @default
*/
Timestamp.TIMESTAMP_AUTO = new Timestamp(-9223372036, -854775806);
Timestamp.AUTO_ASSIGN = Timestamp.TIMESTAMP_AUTO;