/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Time.java
* Created: 15-Oct-2003
* By: Rick Cameron
*/
package org.openquark.util.time;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.ibm.icu.util.Calendar;
/**
* Class Time
*
* This class is an Abstract Data Type that represents a point in real-world time.
* As an ADT it is immutable. This also has benefits for its use in CAL.
*
* An instance represents a point in time that is a certain number of ticks before or after the base time.
* The base time is 00:00:00 UTC 1 Jan 1970. A tick is 100 ns. Using a 64-bit counter, this gives a range of +/-29,000 years.
*
* The type is meant to be portable to other languages & environments. In particular, it can be easily ported to C#/.NET.
*/
public final class Time implements Comparable<Time>, Serializable {
static final long serialVersionUID = -5414456666706739082L;
/** the base time of java.util.Date, relative to the base time of this class */
private static final long javaUtilDateBase = 0;
private static final int ticksPerMillisecond = 10000;
/**/ static final int ticksPerSecond = 10000000;
/**/ static final long ticksPerDay = ticksPerSecond * 86400L;
private static final int nanosecondsPerTick = 100;
/** base time is 00:00:00 UTC 1 Jan 1970, unit is 100 ns */
private final long ticks;
/**
* Method now
*
* @return Returns a Time representing the current time
*/
public static Time now () {
return fromDate (new Date ());
}
/**
* Method fromDate
*
* @param date
* @return Returns a Time the corresponds to the given Date
*/
public static Time fromDate (Date date) {
return new Time (javaUtilDateMillisecondsToTicks(date.getTime()));
}
/**
* Method fromTimeStamp
*
* @param timeStamp
* @return Returns a Time the corresponds to the given Timestamp
*/
public static Time fromTimeStamp (Timestamp timeStamp) {
// Get the number of ticks from the timestamp and truncate this result to the nearest second.
// The Timestamp.getTime() method will include part of the nanosecond fraction in the result.
// This needs to be removed so that the full nanosecond fraction can be converted to ticks.)
long ticks = javaUtilDateMillisecondsToTicks(timeStamp.getTime());
// Truncate the tick value to the nearest second.
// Watch out for negative tick values.
if (ticks < 0) {
ticks -= ticksPerSecond - 1;
}
ticks /= ticksPerSecond;
ticks *= ticksPerSecond;
// Include the contribution of the nanosecond fraction in ticks.
long nanoFraction = timeStamp.getNanos();
ticks += (nanoFraction + nanosecondsPerTick / 2) / nanosecondsPerTick;
return new Time(ticks);
}
/**
* Method javaUtilDateMillisecondsToTicks
*
* @param millis
* @return Returns the tick count that corresponds to the given millisecond count, adjusting base times
*/
private static long javaUtilDateMillisecondsToTicks (long millis) {
return millis * ticksPerMillisecond + javaUtilDateBase;
}
/**
* Method ticksToJavaUtilDateMilliseconds
*
* @param ticks
* @return Returns the millisecond count that corresponds to the given tick count, adjusting base times
*/
private static long ticksToJavaUtilDateMilliseconds (long ticks) {
return ((ticks - javaUtilDateBase) + ticksPerMillisecond / 2) / ticksPerMillisecond;
}
/**
* Constructor Time
*
* Constructs a Time from a year, month, day, hour, minute, second and ticks in the Gregorian calendar
* using the specifed time zone.
*
* @param year - Gregorian year
* @param month - Gregorian month (1-origin)
* @param day - Gregorian day (1-origin)
* @param hour - hour
* @param minute - minute
* @param second - second
* @param ticks - ticks (100 ns units)
* @param tz - time zone
*/
public Time (int year, int month, int day, int hour, int minute, int second, int ticks, TimeZone tz) {
Calendar cal = Calendar.getInstance(tz.toICUTimeZone());
cal.set(year, month - 1, day, hour, minute, second);
cal.set(Calendar.MILLISECOND, 0);
long milliseconds = cal.getTimeInMillis();
this.ticks = javaUtilDateMillisecondsToTicks(milliseconds) + ticks;
}
/**
* Constructor Time
*
* Constructs a Time from a year, month, day, hour, minute, second and ticks in the Gregorian calendar & UTC
*
* @param year - Gregorian year
* @param month - Gregorian month (1-origin)
* @param day - Gregorian day (1-origin)
* @param hour - UTC hour
* @param minute - UTC minute
* @param second - UTC second
* @param ticks - UTC ticks (100 ns units)
*/
@SuppressWarnings(value="deprecation") //this is the only efficient way to do the conversion
public Time (int year, int month, int day, int hour, int minute, int second, int ticks) {
long milliseconds = Date.UTC(year - 1900, month - 1, day, hour, minute, second);
this.ticks = javaUtilDateMillisecondsToTicks(milliseconds) + ticks;
}
/**
* Constructor Time
*
* Constructs a Time from a year, month, day, hour, minute and second in the Gregorian calendar & UTC
*
* @param year - Gregorian year
* @param month - Gregorian month (1-origin)
* @param day - Gregorian day (1-origin)
* @param hour - UTC hour
* @param minute - UTC minute
* @param second - UTC second
*/
public Time (int year, int month, int day, int hour, int minute, int second) {
this (year, month, day, hour, minute, second, 0);
}
/**
* Constructor Time
*
* Constructs a Time from a tick count (the number of 100 ns units from the epoch base time)
*
* @param ticks
*/
public Time (long ticks) {
this.ticks = ticks;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
return obj instanceof Time && ticks == ((Time)obj).ticks;
}
/**
* Method compareTo
*
* @param other
* @return Returns 1 if this Time is after the given one, -1 if it is before, and 0 if they are equal
* {@inheritDoc}
*/
public int compareTo(Time other) {
long otherTicks = other.ticks;
return (ticks == otherTicks) ? 0 : (ticks < otherTicks ? -1 : 1);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
//this is equivalent to new Long (ticks).hashCode()
//the hash function for Long is defined in the api for Long.hashCode.
return (int)(ticks ^ (ticks >>> 32));
}
/**
* Method toDate
*
* @return Returns the nearest Date to the time specified by this object
*/
public Date toDate () {
return new Date (ticksToJavaUtilDateMilliseconds(ticks));
}
/**
* Get the day of the year, removes the time of day and returns just the (UTC) date.
* @see java.sql.Date
* @return java.sql.Date
*/
public java.sql.Date trimToSQLDate() {
long dayTicks = ticks / ticksPerDay;
dayTicks *= ticksPerDay;
return new java.sql.Date (ticksToJavaUtilDateMilliseconds(dayTicks));
}
/**
* Get the time of day, removes the Day / Month / Year and returns just the time of day.
* @see java.sql.Time
* @return java.sql.Time
*/
public java.sql.Time trimToSQLTime() {
long timeTick = ticks % ticksPerDay;
return new java.sql.Time (ticksToJavaUtilDateMilliseconds(timeTick));
}
/**
* Returns the year, month, day, hour, minute, seconds, and ticks from the time value
* using the specified timezone.
* @param tz the timezone for which the time parts should be extracted
* @return Returns the year, month, day, hour, minute, seconds, and ticks from the time value
*/
public int [] timeParts (TimeZone tz) {
int [] result = new int [7]; // year, month, day, hour, minute, second, ticks
long truncatedTicks = (ticks / ticksPerSecond) * ticksPerSecond;
Calendar utcCalendar = Calendar.getInstance(tz.toICUTimeZone());
utcCalendar.setTimeInMillis(ticksToJavaUtilDateMilliseconds(truncatedTicks));
result [0] = utcCalendar.get(Calendar.YEAR);
result [1] = utcCalendar.get(Calendar.MONTH) + 1;
result [2] = utcCalendar.get(Calendar.DATE);
result [3] = utcCalendar.get(Calendar.HOUR_OF_DAY);
result [4] = utcCalendar.get(Calendar.MINUTE);
result [5] = utcCalendar.get(Calendar.SECOND);
result [6] = (int) (ticks % ticksPerSecond);
return result;
}
/**
* Returns the year, month, day, hour, minute, seconds, and ticks from the time value
* using the specified timezone as a list of Integers.
* @param tz the timezone for which the time parts should be extracted
* @return Returns the year, month, day, hour, minute, seconds, and ticks from the time value as a list of Integers
*/
public List<Integer> timePartsList (TimeZone tz) {
int [] parts = timeParts (tz);
List<Integer> partsList = new ArrayList<Integer>();
for (int i = 0, n = parts.length; i < n; ++i) {
partsList.add(new Integer(parts[i]));
}
return partsList;
}
/**
* Method getDayOfWeek
*
* @param tz
*
* @return Returns an int representing the day of the week in the given time zone, where 0 = Monday
*/
public int getDayOfWeek (TimeZone tz ) {
long truncatedTicks = (ticks / ticksPerSecond) * ticksPerSecond;
Calendar utcCalendar = Calendar.getInstance(tz.toICUTimeZone());
utcCalendar.setTimeInMillis(ticksToJavaUtilDateMilliseconds(truncatedTicks));
int icuDayOfWeek = utcCalendar.get(Calendar.DAY_OF_WEEK);
return ((icuDayOfWeek - Calendar.MONDAY) + 7) % 7;
}
/**
* Method toUTC
*
* @return Returns the year, month, day, hour, minute, seconds, and ticks from the time value for UTC
*/
public int [] toUTC () {
return timeParts(TimeZone.utc());
}
/**
* Method getTicks
*
* @return Returns the number of ticks since the epoch base time
*/
public long getTicks () {
return ticks;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// TODO rbc: add in the ticks
return toDate ().toString();
}
/**
* Method subtract
*
* @param t
* @return Returns the Duration that represents the difference between this Time and the given Time
*/
public Duration subtract (Time t) {
return new Duration (ticks - t.ticks);
}
/**
* Method add
*
* @param d
* @return Returns the Duration that represents the sum of this Time and the given Time
*/
public Time add (Duration d) {
return new Time (ticks + d.getTicks());
}
/**
* Returns a string representing the serialized form of the Time value.
*/
public String toSerializedForm() {
// TODO rbc: use a nicer format
return Long.toString(getTicks());
}
/**
* Returns a Time value based on the serialized value provided.
*/
public static Time fromSerializedForm(String serializedTime) {
// TODO: what should happen if the serialized value isn't in the correct format?
return new Time (Long.parseLong(serializedTime));
}
}