com.jkoolcloud.tnt4j.core.UsecTimestamp.java Source code

Java tutorial

Introduction

Here is the source code for com.jkoolcloud.tnt4j.core.UsecTimestamp.java

Source

/*
 * Copyright 2014-2015 JKOOL, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jkoolcloud.tnt4j.core;

import java.io.Serializable;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang3.StringUtils;

import com.jkoolcloud.tnt4j.utils.TimeService;
import com.jkoolcloud.tnt4j.utils.Useconds;
import com.jkoolcloud.tnt4j.utils.Utils;

/**
 * <p>Represents a timestamp that has microsecond accuracy. This timestamp also
 * implements Lamport clock synchronization algorithm see {@link UsecTimestamp#getLamportClock()}
 * and {@link UsecTimestamp#UsecTimestamp(long, long, long)}.</p>
 *
 * <p>Stores timestamp as <i>mmmmmmmmmm.uuu</i>, where <i>mmmmmmmmmm</i> is the
 * timestamp in milliseconds, and <i>uuu</i> is the fractional microseconds.</p>
 *
 * @version $Revision: 6 $
 */
public class UsecTimestamp extends Number implements Comparable<UsecTimestamp>, Cloneable, Serializable {
    private static final long serialVersionUID = 3658590467907047916L;

    protected static AtomicLong LamportCounter = new AtomicLong(System.currentTimeMillis());

    private long msecs;
    private long usecs;
    private long currentLamportClock = LamportCounter.incrementAndGet();

    /**
     * Creates UsecTimestamp based on current time with microsecond precision/accuracy
     *
     * @see com.jkoolcloud.tnt4j.utils.Utils#currentTimeUsec
     */
    public UsecTimestamp() {
        this(Useconds.CURRENT.get());
    }

    /**
     * Creates UsecTimestamp based on specified microsecond timestamp.
     *
     * @param usecTime timestamp, in microsecond
     * @throws IllegalArgumentException if usecTime is negative
     */
    public UsecTimestamp(long usecTime) {
        setTimeUsec(usecTime);
    }

    /**
     * Creates UsecTimestamp based on specified microsecond timestamp.
     *
     * @param usecTime timestamp, in microsecond
     * @throws IllegalArgumentException if usecTime is negative
     */
    public UsecTimestamp(Number usecTime) {
        setTimeUsec(usecTime.longValue());
    }

    /**
     * Creates UsecTimestamp based on specified millisecond timestamp
     * and fractional microsecond.
     *
     * @param msecs timestamp, in milliseconds
     * @param usecs fraction microseconds
     * @throws IllegalArgumentException if any arguments are negative,
     *  or if usecs is greater than 999
     */
    public UsecTimestamp(long msecs, long usecs) {
        setTimestampValues(msecs, usecs, 0);
    }

    /**
     * Creates UsecTimestamp based on specified millisecond timestamp
     * and fractional microsecond. This timestamp Lamport clock
     * will be computed based on the specified sender's Lamport clock
     * value.
     *
     * @param msecs timestamp, in milliseconds
     * @param usecs fraction microseconds
     * @param recvdLamportClock Lamport clock of the received event (0 do not compute Lamport clock)
     * @throws IllegalArgumentException if any arguments are negative,
     *  or if usecs is greater than 999
     */
    public UsecTimestamp(long msecs, long usecs, long recvdLamportClock) {
        setTimestampValues(msecs, usecs, recvdLamportClock);
    }

    /**
     * Creates UsecTimestamp based on specified Timestamp, providing time in
     * seconds resolution, and fractional microsecond.
     *
     * @param timestamp database timestamp, seconds resolution
     * @param usecs fraction microseconds
     * @throws NullPointerException if timestamp is <code>null</code>
     * @throws IllegalArgumentException if usecs is greater than 999999
     */
    public UsecTimestamp(Timestamp timestamp, long usecs) {
        initFromTimestamp(timestamp, usecs);
    }

    /**
     * <p>Creates UsecTimestamp from string representation of timestamp in the
     * specified format.</p>
     * <p>This is based on {@link SimpleDateFormat}, but extends its support to
     * recognize microsecond fractional seconds.  If number of fractional second
     * characters is greater than 3, then it's assumed to be microseconds.
     * Otherwise, it's assumed to be milliseconds (as this is the behavior of
     * {@link SimpleDateFormat}.
     *
     * @param timeStampStr timestamp string
     * @param formatStr format specification for timestamp string
     * @throws NullPointerException if timeStampStr is {@code null}
     * @throws IllegalArgumentException if timeStampStr is not in the correct format
     * @throws ParseException if failed to parse string based on specified format
     */
    public UsecTimestamp(String timeStampStr, String formatStr) throws ParseException {
        this(timeStampStr, formatStr, TimeZone.getDefault());
    }

    /**
     * <p>Creates UsecTimestamp from string representation of timestamp in the
     * specified format.</p>
     * <p>This is based on {@link SimpleDateFormat}, but extends its support to
     * recognize microsecond fractional seconds.  If number of fractional second
     * characters is greater than 3, then it's assumed to be microseconds.
     * Otherwise, it's assumed to be milliseconds (as this is the behavior of
     * {@link SimpleDateFormat}.
     *
     * @param timeStampStr timestamp string
     * @param formatStr format specification for timestamp string
     * @param timeZoneId time zone that timeStampStr represents. This is only needed when formatStr does not include
     *                   time zone specification and timeStampStr does not represent a string in local time zone.
     * @throws NullPointerException if timeStampStr is {@code null}
     * @throws IllegalArgumentException if timeStampStr is not in the correct format
     * @throws ParseException if failed to parse string based on specified format
     * @see java.util.TimeZone
     */
    public UsecTimestamp(String timeStampStr, String formatStr, String timeZoneId) throws ParseException {
        this(timeStampStr, formatStr, (StringUtils.isEmpty(timeZoneId) ? null : TimeZone.getTimeZone(timeZoneId)));
    }

    /**
     * <p>Creates UsecTimestamp from string representation of timestamp in the
     * specified format.</p>
     * <p>This is based on {@link SimpleDateFormat}, but extends its support to
     * recognize microsecond fractional seconds.  If number of fractional second
     * characters is greater than 3, then it's assumed to be microseconds.
     * Otherwise, it's assumed to be milliseconds (as this is the behavior of
     * {@link SimpleDateFormat}.
     *
     * @param timeStampStr timestamp string
     * @param formatStr format specification for timestamp string
     * @param timeZoneId time zone that timeStampStr represents. This is only needed when formatStr does not include
     *                   time zone specification and timeStampStr does not represent a string in local time zone.
     * @param locale locale for date format to use.
     * @throws NullPointerException if timeStampStr is {@code null}
     * @throws IllegalArgumentException if timeStampStr is not in the correct format
     * @throws ParseException if failed to parse string based on specified format
     * @see java.util.TimeZone
     */
    public UsecTimestamp(String timeStampStr, String formatStr, String timeZoneId, String locale)
            throws ParseException {
        this(timeStampStr, formatStr, (StringUtils.isEmpty(timeZoneId) ? null : TimeZone.getTimeZone(timeZoneId)),
                locale);
    }

    /**
     * <p>Creates UsecTimestamp from string representation of timestamp in the
     * specified format.</p>
     * <p>This is based on {@link SimpleDateFormat}, but extends its support to
     * recognize microsecond fractional seconds. If number of fractional second
     * characters is greater than 3, then it's assumed to be microseconds.
     * Otherwise, it's assumed to be milliseconds (as this is the behavior of
     * {@link SimpleDateFormat}.
     *
     * @param timeStampStr timestamp string
     * @param formatStr format specification for timestamp string
     * @param timeZone time zone that timeStampStr represents. This is only needed when formatStr does not include
     *                   time zone specification and timeStampStr does not represent a string in local time zone.
     * @throws NullPointerException if timeStampStr is {@code null}
     * @throws IllegalArgumentException if timeStampStr is not in the correct format
     * @throws ParseException if failed to parse string based on specified format
     * @see java.util.TimeZone
     */
    public UsecTimestamp(String timeStampStr, String formatStr, TimeZone timeZone) throws ParseException {
        this(timeStampStr, formatStr, timeZone, null);
    }

    /**
     * <p>Creates UsecTimestamp from string representation of timestamp in the
     * specified format.</p>
     * <p>This is based on {@link SimpleDateFormat}, but extends its support to
     * recognize microsecond fractional seconds. If number of fractional second
     * characters is greater than 3, then it's assumed to be microseconds.
     * Otherwise, it's assumed to be milliseconds (as this is the behavior of
     * {@link SimpleDateFormat}.
     *
     * @param timeStampStr timestamp string
     * @param formatStr format specification for timestamp string
     * @param timeZone time zone that timeStampStr represents. This is only needed when formatStr does not include
     *                   time zone specification and timeStampStr does not represent a string in local time zone.
     * @param locale locale for date format to use.
     * @throws NullPointerException if timeStampStr is {@code null}
     * @throws IllegalArgumentException if timeStampStr is not in the correct format
     * @throws ParseException if failed to parse string based on specified format
     * @see java.util.TimeZone
     */
    public UsecTimestamp(String timeStampStr, String formatStr, TimeZone timeZone, String locale)
            throws ParseException {
        if (timeStampStr == null)
            throw new NullPointerException("timeStampStr must be non-null");

        int usecs = 0;

        SimpleDateFormat dateFormat;

        if (StringUtils.isEmpty(formatStr)) {
            dateFormat = new SimpleDateFormat();
        } else {
            // Java date formatter cannot deal with usecs, so we need to extract those ourselves
            int fmtPos = formatStr.indexOf('S');
            if (fmtPos > 0) {
                int endFmtPos = formatStr.lastIndexOf('S');
                int fmtFracSecLen = endFmtPos - fmtPos + 1;

                if (fmtFracSecLen > 6)
                    throw new ParseException(
                            "Date format containing more than 6 significant digits for fractional seconds is not supported",
                            0);

                StringBuilder sb = new StringBuilder();
                int usecPos = timeStampStr.lastIndexOf('.') + 1;
                int usecEndPos;
                if (usecPos > 2) {
                    for (usecEndPos = usecPos; usecEndPos < timeStampStr.length(); usecEndPos++) {
                        if (!StringUtils.containsAny("0123456789", timeStampStr.charAt(usecEndPos)))
                            break;
                    }

                    if (fmtFracSecLen > 3) {
                        // format specification represents more than milliseconds, assume microseconds
                        String usecStr = String.format("%s", timeStampStr.substring(usecPos, usecEndPos));
                        if (usecStr.length() < fmtFracSecLen)
                            usecStr = StringUtils.rightPad(usecStr, fmtFracSecLen, '0');
                        else if (usecStr.length() > fmtFracSecLen)
                            usecStr = usecStr.substring(0, fmtFracSecLen);
                        usecs = Integer.parseInt(usecStr);

                        // trim off fractional part < microseconds from both timestamp and format strings
                        sb.append(timeStampStr);
                        sb.delete(usecPos - 1, usecEndPos);
                        timeStampStr = sb.toString();

                        sb.setLength(0);
                        sb.append(formatStr);
                        sb.delete(fmtPos - 1, endFmtPos + 1);
                        formatStr = sb.toString();
                    } else if ((usecEndPos - usecPos) < 3) {
                        // pad msec value in date string with 0's so that it is 3 digits long
                        sb.append(timeStampStr);
                        while ((usecEndPos - usecPos) < 3) {
                            sb.insert(usecEndPos, '0');
                            usecEndPos++;
                        }
                        timeStampStr = sb.toString();
                    }
                }
            }

            dateFormat = StringUtils.isEmpty(locale) ? new SimpleDateFormat(formatStr)
                    : new SimpleDateFormat(formatStr, Utils.getLocale(locale));
        }

        dateFormat.setLenient(true);
        if (timeZone != null)
            dateFormat.setTimeZone(timeZone);

        Date date = dateFormat.parse(timeStampStr);

        setTimestampValues(date.getTime(), 0, 0);
        add(0, usecs);
    }

    /**
     * Creates UsecTimestamp based on specified UsecTimestamp.
     *
     * @param other timestamp to copy
     * @throws NullPointerException if timestamp is <code>null</code>
     */
    public UsecTimestamp(UsecTimestamp other) {
        this(other.msecs, other.usecs, other.getLamportClock());
    }

    /**
     * Creates UsecTimestamp based on specified UsecTimestamp.
     *
     * @param date timestamp to copy
     * @throws NullPointerException if date is <code>null</code>
     */
    public UsecTimestamp(Date date) {
        setTimestampValues(date.getTime(), 0, 0);
    }

    /**
     * Returns Lamport clock value of this time stamp
     * (based on Lamport Clock algorithm)
     *
     * @return Lamport clock value
     */
    public long getLamportClock() {
        return currentLamportClock;
    }

    /**
     * Returns UsecTimestamp based on current time with microsecond precision/accuracy
     *
     * @return UsecTimestamp for current time
     * @see com.jkoolcloud.tnt4j.utils.Utils#currentTimeUsec
     */
    public static UsecTimestamp now() {
        return new UsecTimestamp();
    }

    /**
     * Sets UsecTimestamp based on specified microsecond timestamp.
     *
     * @param usecTime timestamp, in microsecond
     * @return current UsecTimestamp instance
     * @throws IllegalArgumentException if usecTime is negative
     */
    public UsecTimestamp setTimeUsec(long usecTime) {
        if (usecTime < 0)
            throw new IllegalArgumentException("usecTime must be non-negative");

        this.msecs = usecTime / 1000L;
        this.usecs = (int) (usecTime - (this.msecs * 1000));
        return this;
    }

    /**
     * @see #UsecTimestamp(Timestamp, long)
     */
    private void initFromTimestamp(Timestamp timestamp, long usecs) {
        if (timestamp == null)
            throw new NullPointerException("timestamp must be non-null");
        if (usecs < 0 || usecs > 999999)
            throw new IllegalArgumentException("usecs must be in the range [0,999999], inclusive");

        this.msecs = timestamp.getTime();
        if (usecs > 999) {
            // extract milliseconds portion from usecs and add to msecs
            long msecs = usecs / 1000;
            this.msecs += msecs;
            usecs -= msecs * 1000;
        }
        this.usecs = usecs;
    }

    /**
     * Creates UsecTimestamp based on specified millisecond timestamp
     * and fractional microsecond.
     *
     * @param msecs timestamp, in milliseconds
     * @param usecs fraction microseconds
     * @param recvdLamportClock received Lamport clock
     * @throws IllegalArgumentException if any arguments are negative,
     *  or if usecs is greater than 999
     */
    protected void setTimestampValues(long msecs, long usecs, long recvdLamportClock) {
        if (msecs < 0)
            throw new IllegalArgumentException("msecs must be non-negative");
        if (usecs < 0 || usecs > 999)
            throw new IllegalArgumentException("usecs must be in the range [0,999], inclusive");

        this.msecs = msecs;
        this.usecs = usecs;
        assignLamportClock(recvdLamportClock);
    }

    /**
     * Assign local Lamport clock based on the value of the received
     * Lamport clock.
     *
     * @param recvdLamportClock received Lamport clock
     */
    public void assignLamportClock(long recvdLamportClock) {
        while (recvdLamportClock > currentLamportClock) {
            if (LamportCounter.compareAndSet(currentLamportClock, recvdLamportClock + 1)) {
                currentLamportClock = recvdLamportClock + 1;
                break;
            } else {
                currentLamportClock = LamportCounter.incrementAndGet();
            }
        }
    }

    /**
     * Gets current time stamp value to seconds resolution.
     *
     * @return timestamp, in seconds.
     */
    public long getTimeSec() {
        return msecs / 1000;
    }

    /**
     * Gets current time stamp value to milliseconds resolution.
     *
     * @return timestamp, in milliseconds.
     */
    public long getTimeMillis() {
        return msecs;
    }

    /**
     * Gets current time stamp value to microseconds resolution.
     *
     * @return timestamp, in microseconds.
     */
    public long getTimeUsec() {
        return msecs * 1000 + usecs;
    }

    /**
     * {@inheritDoc}
     *
     * <p>Returns {@link #getTimeUsec()} as an int, possibly truncated.</p>
     */
    @Override
    public int intValue() {
        return (int) getTimeUsec();
    }

    /**
     * {@inheritDoc}
     *
     * <p>Returns {@link #getTimeUsec()}.</p>
     */
    @Override
    public long longValue() {
        return getTimeUsec();
    }

    /**
     * {@inheritDoc}
     *
     * <p>Returns {@link #getTimeUsec()} as a float, possibly truncated.</p>
     */
    @Override
    public float floatValue() {
        return getTimeUsec();
    }

    /**
     * {@inheritDoc}
     *
     * <p>Returns {@link #getTimeUsec()} as a double.</p>
     */
    @Override
    public double doubleValue() {
        return getTimeUsec();
    }

    /**
     * Gets fractional microseconds portion of time stamp.
     *
     * @return fractional microseconds
     */
    public long getUsecPart() {
        return usecs;
    }

    /**
     * Gets fractional microseconds portion of time stamp after previous second.
     * Converts mmm.uuu representation of timestamp (where mmm is millisecond
     * timestamp and uuu is fractional microseconds) to sss.uuuuuu representation
     * (where sss is seconds timestamp and uuuuuu is fractional milliseconds and
     * microseconds added together as microseconds) and returns uuuuuu portion.
     *
     * @return fractional microseconds
     */
    public long getSecUsecPart() {
        int msec = (int) (msecs - (msecs / 1000) * 1000);
        return (msec * 1000) + usecs;
    }

    /**
     * Adds the specified UsecTimestamp to this one.
     *
     * @param other timestamp to add to current one
     */
    public void add(UsecTimestamp other) {
        add(other.msecs, other.usecs);
    }

    /**
     * Adds the specified time values to this UsecTimestamp.
     *
     * @param msecs milliseconds value to add
     * @param usecs microseconds value to add
     * @throws IllegalArgumentException if any arguments are negative
     */
    public void add(long msecs, long usecs) {
        if (msecs < 0)
            throw new IllegalArgumentException("msecs must be non-negative");
        if (usecs < 0)
            throw new IllegalArgumentException("usecs must be non-negative");

        if (usecs > 999) {
            long ms = usecs / 1000;
            msecs += ms;
            usecs -= ms * 1000;
        }

        this.msecs += msecs;
        this.usecs += usecs;

        if (this.usecs > 999) {
            long ms = (this.usecs / 1000);

            this.msecs += ms;
            this.usecs -= ms * 1000;
        }
    }

    /**
     * Subtracts the specified UsecTimestamp from this one (e.g. {@code x.subtract(y)} means {@code x - y}).
     *
     * @param other timestamp to subtract from current one
     */
    public void subtract(UsecTimestamp other) {
        subtract(other.msecs, other.usecs);
    }

    /**
     * Subtracts the specified time values from this UsecTimestamp.
     *
     * @param msecs milliseconds value to subtract
     * @param usecs microseconds value to subtract
     * @throws IllegalArgumentException if any arguments are negative
     */
    public void subtract(long msecs, long usecs) {
        if (msecs < 0)
            throw new IllegalArgumentException("msecs must be non-negative");
        if (usecs < 0)
            throw new IllegalArgumentException("usecs must be non-negative");

        if (usecs > 999) {
            long ms = usecs / 1000;
            msecs += ms;
            usecs -= ms * 1000;
        }

        long thisMsecs = this.msecs;
        long thisUsecs = this.usecs;

        if (thisUsecs < usecs) {
            thisMsecs--;
            thisUsecs += 1000;
        }

        this.msecs = thisMsecs - msecs;
        this.usecs = thisUsecs - (int) usecs;
    }

    /**
     * Computes the difference between this timestamp and the specified one
     * (as this - other).  It relates to {@link Comparable#compareTo(Object)}
     * such that if {@code x.compareTo(y)} returns a negative number implying that
     * {@code x} comes before {@code y} (that is, {@code x < y}),
     * then {@code x.difference(y)} returns a negative number.
     *
     * @param other other UsecTimestamp instance
     * @return difference, in microseconds, between two timestamps
     */
    public long difference(UsecTimestamp other) {

        long thisMsecs = this.msecs;
        long thisUsecs = this.usecs;
        long otherMsecs = other.msecs;
        long otherUsecs = other.usecs;

        if (thisUsecs < otherUsecs) {
            thisMsecs--;
            thisUsecs += 1000;
        }

        return ((thisMsecs - otherMsecs) * 1000) + (thisUsecs - otherUsecs);
    }

    /**
     * Returns the string representation of the current timestamp, with a given time zone.
     *
     * @param tz format current time based on a given timezone.
     * @return formatted date/time string based on default pattern and given timezone
     */
    public static String getTimeStamp(TimeZone tz) {
        return getTimeStamp(null, tz, TimeService.currentTimeMillis(), 0);
    }

    /**
     * Returns the string representation of the current timestamp.
     *
     * @return formatted date/time string based on default pattern
     */
    public static String getTimeStamp() {
        return getTimeStamp(null, TimeService.currentTimeMillis(), 0);
    }

    /**
     * Returns the string representation of the current timestamp based on the given
     * format pattern.
     *
     * @param pattern format pattern
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(String pattern) {
        return getTimeStamp(pattern, TimeService.currentTimeMillis(), 0);
    }

    /**
     * Returns the string representation of the timestamp based on the given
     * format pattern, milliseconds.
     *
     * @param pattern format pattern
     * @param usecs milliseconds
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(String pattern, long usecs) {
        long msecs = usecs / 1000L;
        return getTimeStamp(pattern, msecs, (usecs - msecs * 1000));
    }

    /**
     * Returns the string representation of the timestamp based on the default
     * format pattern, microseconds.
     *
     * @param usecs microseconds
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(long usecs) {
        long msecs = usecs / 1000L;
        return getTimeStamp(null, msecs, (usecs - msecs * 1000));
    }

    /**
     * Returns the string representation of the timestamp based on the default
     * format pattern, milliseconds and microseconds.
     *
     * @param msecs milliseconds
     * @param usecs microseconds
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(long msecs, long usecs) {
        return getTimeStamp(null, msecs, usecs);
    }

    /**
     * Returns the string representation of the timestamp based on the specified
     * format pattern, milliseconds and microseconds, default timezone.
     *
     * @param pattern format pattern
     * @param msecs milliseconds
     * @param usecs microseconds
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(String pattern, long msecs, long usecs) {
        return getTimeStamp(pattern, TimeZone.getDefault(), msecs, usecs);
    }

    /**
     * Returns the string representation of the timestamp based on the specified
     * format pattern, milliseconds and microseconds.
     *
     * @param pattern format pattern
     * @param tz time zone
     * @param msecs milliseconds
     * @param usecs microseconds
     * @return formatted date/time string based on pattern
     */
    public static String getTimeStamp(String pattern, TimeZone tz, long msecs, long usecs) {
        String tsStr = null;

        if (pattern == null) {
            SimpleDateFormat df = new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss.SSS" + String.format("%03d", usecs) + " z");
            df.setTimeZone(tz);
            tsStr = df.format(new Date(msecs));
        }

        if (tsStr == null) {
            int fracSecPos = pattern == null ? -1 : pattern.indexOf('S');
            if (fracSecPos < 0) {
                SimpleDateFormat df = new SimpleDateFormat(pattern);
                df.setTimeZone(tz);
                tsStr = df.format(new Date(msecs));
            }
        }

        if (tsStr == null) {
            String usecStr = String.format("%03d", usecs);
            pattern = pattern.replaceFirst("SS*", "SSS" + usecStr);
            SimpleDateFormat df = new SimpleDateFormat(pattern);
            df.setTimeZone(tz);
            tsStr = df.format(new Date(msecs));
        }

        return tsStr.replace(" Z", " 00:00");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int compareTo(UsecTimestamp other) {
        if (msecs < other.msecs)
            return -1;
        if (msecs > other.msecs)
            return 1;
        if (usecs < other.usecs)
            return -1;
        if (usecs > other.usecs)
            return 1;

        return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        long result = 1;

        result = prime * result + (int) (msecs ^ (msecs >>> 32));
        result = prime * result + usecs;

        return (int) result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof UsecTimestamp))
            return false;

        final UsecTimestamp other = (UsecTimestamp) obj;

        if (msecs != other.msecs)
            return false;

        if (usecs != other.usecs)
            return false;

        return true;
    }

    /**
     * {@inheritDoc}
     * <p>Returns the string representation of this timestamp in the default timezone.</p>
     */
    @Override
    public String toString() {
        return getTimeStamp(msecs, usecs);
    }

    /**
     * Returns the string representation of this timestamp in the specified timezone.
     *
     * @param tz timezone
     * @return formatted date/time string in specified timezone
     */
    public String toString(TimeZone tz) {
        return getTimeStamp(null, tz, msecs, usecs);
    }

    /**
     * Returns the string representation of this timestamp based on the specified
     * format pattern in the default timezone.
     *
     * @param pattern format pattern
     * @return formatted date/time string based on pattern
     */
    public String toString(String pattern) {
        return getTimeStamp(pattern, msecs, usecs);
    }

    /**
     * Returns the string representation of this timestamp based on the specified
     * format pattern in the specified timezone.
     *
     * @param pattern format pattern
     * @param tz timezone
     * @return formatted date/time string based on pattern
     */
    public String toString(String pattern, TimeZone tz) {
        return getTimeStamp(pattern, tz, msecs, usecs);
    }
}