// // DateTime.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.text.ParseException; import java.text.FieldPosition; /** * DateTime is a class of objects for holding date and time information. * DateTime objects are immutable.<P> * * Internally, the object uses seconds since the epoch * (1970-01-01 00:00:00Z) as the temporal reference. * @see java.lang.System#currentTimeMillis() * */ public class DateTime extends Real { /** * default for serializable classes */ private static final long serialVersionUID = 1L; /** This is around so we can use a different date formatter */ private static Class dateFormatClass; /** * Default Time Format Pattern (yyyy-MM-dd HH:mm:ss) */ public static final String DEFAULT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; /** * Default Time Zone (GMT) */ public static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT"); // Time related variables private static String formatPattern = DEFAULT_TIME_FORMAT; private static TimeZone timeZone = DEFAULT_TIMEZONE; //Don't create this right away now private GregorianCalendar utcCalendar; private static final double secondsPerDay = (double) (24 * 60 * 60); /** * Construct a DateTime object and initialize it using a VisAD Real. * Unless the units of the Real specify otherwise, the Real's value * is assumed to be seconds since the Epoch (i.e. 1970-01-01 00:00:00Z). * * @param real Real value in a temporal unit. * * @throws VisADException unit conversion problem */ public DateTime(Real real) throws VisADException { super( RealType.Time, real.getValue(CommonUnit.secondsSinceTheEpoch), CommonUnit.secondsSinceTheEpoch,null); //We create and set the utcCalendar when we need it // set up in terms of java date // utcCalendar.setTime(new Date(Math.round(getValue()*1000.))); } /** * Construct a DateTime object and initialize it with the seconds since * January 1, 1970 00:00:00Z. * * @param seconds number of seconds since 1970-01-01 00:00:00Z. * * @throws VisADException unit conversion problem */ public DateTime(double seconds) throws VisADException { super(RealType.Time, seconds, CommonUnit.secondsSinceTheEpoch,null, false); // this(seconds, CommonUnit.secondsSinceTheEpoch); } /** * Construct a DateTime object from a tim value and a Unit * @param timeValue value of time in timeUnits * @param timeUnits units of value */ public DateTime(double timeValue, Unit timeUnits) throws VisADException { this(new Real(RealType.Time, timeValue, timeUnits)); } /** * Construct a DateTime object and initialize it with a Java date. * * @param date date object * * @throws VisADException unit conversion problem */ public DateTime(Date date) throws VisADException { //Just go directly to the super with the Date value super( RealType.Time, date.getTime()/1000., CommonUnit.secondsSinceTheEpoch, null, false); } /** * Construct a DateTime object and initialize it to the current date/time. * * @throws VisADException unit conversion problem */ public DateTime() throws VisADException { this(new Real(RealType.Time, System.currentTimeMillis()/1000., CommonUnit.secondsSinceTheEpoch)); } /** * Construct a DateTime object initialized with a year, day of * the year, and seconds in the day. * * @param year year - use negative year to indicated BC * @param day day of the year * @param seconds seconds in the day * * @throws VisADException invalid day or seconds. Days must be * greater than zero and seconds must be greater * than zero and less than or equal to the * seconds in a day. */ public DateTime(int year, int day, double seconds) throws VisADException { this(fromYearDaySeconds(year, day, seconds)); } /** * Return a Real object whose value is the seconds since the Epoch * initialized with a year, day of the year, and seconds in the day. * * @param year year - use negative year to indicated BC * @param day day of the year * @param seconds seconds in the day * * @throws VisADException invalid day or seconds. Days must be * greater than zero and seconds must be greater * than zero and less than or equal to the * seconds in a day. */ public static Real fromYearDaySeconds(int year, int day, double seconds) throws VisADException { /* Comment out for now - may want to revisit DRM - 1999-05-06 Handle in trusted method to allow for BC years (year <= 0). if (year < 1) { throw new VisADException( "DateTime.fromYearDaySeconds: invalid year"); } int dayLimit = getCalendar().isLeapYear(year) ? 366 : 365; */ // require positive day if (day < 1) { throw new VisADException( "DateTime.fromYearDaySeconds: invalid day"); } // require positive seconds and no more than are in a day. if (seconds > secondsPerDay || seconds < 0.0) { throw new VisADException( "DateTime.fromYearDaySeconds: invalid seconds"); } return fromYearDaySecondsTrusted(year, day, seconds); } /** trusted method for initializers */ private static Real fromYearDaySecondsTrusted(int year, int day, double seconds) throws VisADException { GregorianCalendar cal = new GregorianCalendar(); cal.clear(); cal.setTimeZone(DEFAULT_TIMEZONE); // use GMT as default if (year == 0) year = -1; // set to 1 BC cal.set(Calendar.ERA, year < 0 ? GregorianCalendar.BC : GregorianCalendar.AD); cal.set(Calendar.YEAR, Math.abs(year)); /* allow us to specify # of days since the year began without having worry about leap years and seconds since the day began, instead of in the minute. Saves on some calculations. */ cal.setLenient(true); cal.set(Calendar.DAY_OF_YEAR, day); int temp = (int) Math.round(seconds * 1000); int secs = temp/1000; int millis = temp%1000; cal.set(Calendar.SECOND, secs); cal.set(Calendar.MILLISECOND, millis); return new Real( RealType.Time, cal.getTime().getTime()/1000., CommonUnit.secondsSinceTheEpoch ); } /** * Get a Real representing the number of seconds since * the epoch. * * @return this object */ public Real getReal() { return this; } /** * Create, if needed, and return the utcCalendar * * @returns The Calendar object to use */ private Calendar getCalendar() { if(utcCalendar == null) { utcCalendar =new GregorianCalendar(DEFAULT_TIMEZONE); utcCalendar.setTime(new Date(Math.round(getValue()*1000.))); } return utcCalendar; } /** * Return a string representation of this DateTime. Unless the * setFormatPattern() and/or setTimeZone() methods were used, the * default it the ISO 8601 complete date plus hours, minutes and seconds * and a time zone of UTC. * See <a href="http://www.w3.org/TR/NOTE-datetime"> * http://www.w3.org/TR/NOTE-datetime</a> * @see #setFormatPattern * @see #setFormatTimeZone * * @return String representing the date/time. Default is * <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z) */ public String toString() { String pat = formatPattern; if (formatPattern.equals(DEFAULT_TIME_FORMAT)) { if (timeZone.equals(DEFAULT_TIMEZONE)) { pat = DEFAULT_TIME_FORMAT+"'Z'"; } else { pat = pat + " z"; } if (getCalendar().get(Calendar.ERA) == GregorianCalendar.BC) { pat = pat + " 'BCE'"; } } return formattedString(pat, timeZone); } /** * Gets a string that represents just the value portion of this * DateTime -- but with full semantics. * * @return String representing the date/time in the form * <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z) */ public String toValueString() { return toString(); } /** * Return a string representation of this DateTime from a user * specified format. The pattern uses the time format syntax * of java.text.SimpleDateFormat and the time zone is any of the * valid java.util.TimeZone values. * @see java.text.SimpleDateFormat * @see java.util.TimeZone * * @param pattern time format string * @param timezone time zone to use * @return String representing the date/time in the form specified * by the pattern. */ public String formattedString(String pattern, TimeZone timezone) { StringBuffer buf = new StringBuffer(); DateFormat sdf = null; if(dateFormatClass!=null) { try { sdf = (DateFormat) dateFormatClass.newInstance(); } catch(Exception ie) { throw new IllegalStateException("Error creating a DateFormat from:" + dateFormatClass.getName() +" " + ie); } } else { sdf = new SimpleDateFormat(); } sdf.setTimeZone(timezone); if (pattern != null&& sdf instanceof SimpleDateFormat) { ((SimpleDateFormat)sdf).applyPattern(pattern); } return (sdf.format(getCalendar().getTime(), buf, new FieldPosition(0))).toString(); } /** * Return a string representing the "date" portion of this DateTime * * @return String representing the date (UTC) in the form * yyyy-MM-dd (ex: 1999-05-04). */ public String dateString() { String pattern = (getCalendar().get(Calendar.ERA) == GregorianCalendar.BC) ? "yyyy-MM-dd 'BCE'" : "yyyy-MM-dd"; return formattedString(pattern, DEFAULT_TIMEZONE); } /** * Return a string representing the "time" portion of this DateTime * * @return String representing the time (UTC) in the form * HH:mm:ssZ (ex: 15:27:08Z) */ public String timeString() { return formattedString("HH:mm:ss'Z'", DEFAULT_TIMEZONE); } /** * You can override the DateFormat by specifying a class. * * @param dateFormatClass The class. This must be derived from java.text.DateFormat */ public static void setDateFormatClass(Class dateFormatClass) { if(!java.text.DateFormat.class.isAssignableFrom(dateFormatClass)) { throw new IllegalArgumentException("Not a DateFormat class: " + dateFormatClass.getName()); } DateTime.dateFormatClass = dateFormatClass; } /** * Set the format of the output of the toString() method. All DateTime * objects created in the JVM once this is set will use the new format * so be very careful in your use of this method. * * The pattern uses the time format syntax of java.text.SimpleDateFormat. * @see java.text.SimpleDateFormat * If you want to use a time zone other than the default, * @see #setFormatTimeZone * * @param pattern time format string */ public static void setFormatPattern(String pattern) { formatPattern = pattern; } /** * Return the format pattern used in the output of the toString() method. * The pattern uses the time format syntax of java.text.SimpleDateFormat. * @see java.text.SimpleDateFormat * * @return time format pattern */ public static String getFormatPattern() { return formatPattern; } /** * Set the TimeZone of the output of the toString() method. All DateTime * objects created in the JVM once this is set will use the new TimeZone * so be very careful in your use of this method. * * The time zone is any of the valid java.util.TimeZone values. * @see java.util.TimeZone * * @param tz time zone */ public static void setFormatTimeZone(TimeZone tz) { timeZone = tz; } /** * Return the TimeZone used in the output of the toString() method. * * @return time zone */ public static TimeZone getFormatTimeZone() { return timeZone; } /** * Reset the format of the output of the toString() method to the default - * <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z) and * the TimeZone to UTC. */ public static void resetFormat() { formatPattern = DEFAULT_TIME_FORMAT; timeZone = DEFAULT_TIMEZONE; } /** * Create a DateTime object from a string specification * @param dateString date string specification in format pattern * defined for DateTime in this JVM * * @throws VisADException formatting problem * @see #setFormatPattern */ public static DateTime createDateTime(String dateString) throws VisADException { return createDateTime(dateString, formatPattern, DEFAULT_TIMEZONE); } /** * Create a DateTime object from a string specification using the * supplied pattern and default timezone. * @param dateString date string specification * @param format string * * @throws VisADException formatting problem */ public static DateTime createDateTime(String dateString, String format) throws VisADException { return createDateTime(dateString, format, DEFAULT_TIMEZONE); } /** * Create a DateTime object from a string specification using the * supplied pattern and timezone. * @param dateString date string specification * @param format format string * @param timezone TimeZone to use * * @throws VisADException formatting problem */ public static DateTime createDateTime(String dateString, String format, TimeZone timezone) throws VisADException { Date d; try { SimpleDateFormat sdf = new SimpleDateFormat(); sdf.setTimeZone(timezone); sdf.applyPattern(format); d = sdf.parse(dateString); } catch (ParseException pe) { throw new VisADException("invalid date string: " + dateString); } return new DateTime(d); } /** * Implement Comparable interface * * @param oo Object for comparison - should be DateTime */ public int compareTo(Object oo) { return super.compareTo(oo); } /** * Create a Gridded1DDoubleSet from an array of DateTimes * * @param times array of DateTimes. Array cannot be null or only * have one entry. * * @return Gridded1DDouble set representing the array * @throws VisADException couldn't create the GriddedDoubleSet */ public static Gridded1DDoubleSet makeTimeSet(DateTime[] times) throws VisADException { Arrays.sort(times); double[][] timeValues = new double[1][times.length]; for (int i = 0; i < times.length; i++) timeValues[0][i] = times[i].getValue(CommonUnit.secondsSinceTheEpoch); return new Gridded1DDoubleSet(RealType.Time, timeValues, times.length); } /** * Create a Gridded1DDoubleSet from an array of doubles of seconds * since the epoch. * * @param times array of times in seconds since the epoch. Array * cannot be null or only have one entry. * * @return set representing the array as a Gridded1DDoubleSet * @throws VisADException couldn't create the GriddedDoubleSet */ public static Gridded1DDoubleSet makeTimeSet(double[] times) throws VisADException { Arrays.sort(times); double[][] alltimes = new double[1][times.length]; for (int i = 0; i < times.length; i++) alltimes[0][i] = times[i]; return new Gridded1DDoubleSet(RealType.Time, alltimes, times.length); } /** * Create an array of DateTimes from a Gridded1DSet of times. * * @param timeSet Gridded1DSet of times * * @throws VisADException invalid time set or couldn't create DateTimes */ public static DateTime[] timeSetToArray(Gridded1DSet timeSet) throws VisADException { Unit unit = timeSet.getSetUnits()[0]; if (!Unit.canConvert(unit, CommonUnit.secondsSinceTheEpoch)) { throw new VisADException( "Invalid Units for timeSet"); } double[][] values; if (!unit.equals(CommonUnit.secondsSinceTheEpoch)) values = Unit.convertTuple(timeSet.getDoubles(), new Unit[] {unit}, new Unit[] {CommonUnit.secondsSinceTheEpoch}, false); else values = timeSet.getDoubles(); DateTime[] times = new DateTime[timeSet.getLength()]; for (int i = 0; i < timeSet.getLength(); i++) times[i] = new DateTime(values[0][i]); return times; } /** * run 'java visad.DateTime' to test the DateTime class */ public static void main(String args[]) throws VisADException { Real r; DateTime a; System.out.println( "\nInitialized using DateTime(1959, 284, 36600.):"); a = new DateTime(1959, 284, 36600.); System.out.println( "\n\ttoString() = " + a + "\n\tdateString() = " + a.dateString() + "\n\ttimeString() = " + a.timeString() + "\n\tformattedString() = " + a.formattedString("(EEE) dd-MMM-yy hh:mm:SS.sss z", TimeZone.getTimeZone("EST")) + "\n\t (using pattern " + "'(EEE) dd-MMM-yy hh:mm:SS.sss z' and timezone 'EST')"); // test using DateTime(Real r) where r has correct units and type System.out.println("\nIncrementing 5 times by 20 days each time:\n"); for (int i = 0; i < 5; i++) { r = new Real(RealType.Time, a.getValue() + 20 * secondsPerDay, CommonUnit.secondsSinceTheEpoch); a = new DateTime(r); System.out.println("\t" + a); } // test for backward compatibility System.out.println("\nInitialized using Real of RealType.Time" + " but no Unit (backward compatibility):"); r = new Real(RealType.Time, a.getValue() + secondsPerDay); a = new DateTime(r); System.out.println("\n\t" + a); // try BC date System.out.println( "\nInitialized with a BCE date DateTime(-5, 196, 24493.):"); a = new DateTime(-5, 193, 24493.); System.out.println( "\n\ttoString() = " + a + "\n\tdateString() = " + a.dateString() + "\n\ttimeString() = " + a.timeString()); // test using Date() values Date date = new Date(); a = new DateTime(date); System.out.println("\nInitialized with current Date(): " + a); a = new DateTime(date.getTime()/1000.); System.out.println( "\nInitialized with current seconds since the epoch: " + a + "\n"); a = DateTime.createDateTime(a.toString()); System.out.println("\nUsing createDateTime with string of current Date(): " + a); } /* Here's the output: java visad.DateTime Initialized using DateTime(1959, 284, 36600.): toString() = 1959-10-11 10:10:00Z dateString() = 1959-10-11 timeString() = 10:10:00Z formattedString() = (Sun) 11-Oct-59 06:10:00.000 EDT (using pattern '(EEE) dd-MMM-yy hh:mm:SS.sss z' and timezone 'EST') Incrementing 5 times by 20 days each time: 1959-10-31 10:10:00Z 1959-11-20 10:10:00Z 1959-12-10 10:10:00Z 1959-12-30 10:10:00Z 1960-01-19 10:10:00Z Initialized using Real of RealType.Time but no Unit (backward compatibility): 1960-01-20 10:10:00Z Initialized with a BCE date DateTime(-5, 196, 24493.): toString() = 0005-07-11 06:48:13Z BCE dateString() = 0005-07-11 BCE timeString() = 06:48:13Z Initialized with current Date(): 1999-05-06 23:01:41Z Initialized with current seconds since the epoch: 1999-05-06 23:01:41Z */ }