Source: lib/hypertable/Timestamp.js

'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])&nbsp;</td>
 *     <td>Buffer, with byte offset, holding 64-bit, big-endian encoded timestamp</td>
 *   </tr>
 *   <tr>
 *     <td>new Timestamp(Uint8Array[, offset=0])&nbsp;</td>
 *     <td>Uint8Array, with byte offset, holding 64-bit, big-endian encoded timestamp</td>
 *  </tr>
 *  <tr>
 *     <td>new Timestamp(seconds, nanoseconds)&nbsp;</td>
 *     <td>Seconds (and nanoseconds fraction) since the Epoch</td>
 *  </tr>
 *  <tr><td>new Timestamp(Date)&nbsp;</td><td>Date object representing timestamp</td></tr>
 *  <tr>
 *    <td>new Timestamp(Int64)&nbsp;</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;