All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.nrc.cadc.date.DateUtil Maven / Gradle / Ivy

The newest version!
/*
************************************************************************
*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
*
*  (c) 2016.                            (c) 2016.
*  Government of Canada                 Gouvernement du Canada
*  National Research Council            Conseil national de recherches
*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
*  All rights reserved                  Tous droits réservés
*                                       
*  NRC disclaims any warranties,        Le CNRC dénie toute garantie
*  expressed, implied, or               énoncée, implicite ou légale,
*  statutory, of any kind with          de quelque nature que ce
*  respect to the software,             soit, concernant le logiciel,
*  including without limitation         y compris sans restriction
*  any warranty of merchantability      toute garantie de valeur
*  or fitness for a particular          marchande ou de pertinence
*  purpose. NRC shall not be            pour un usage particulier.
*  liable in any event for any          Le CNRC ne pourra en aucun cas
*  damages, whether direct or           être tenu responsable de tout
*  indirect, special or general,        dommage, direct ou indirect,
*  consequential or incidental,         particulier ou général,
*  arising from the use of the          accessoire ou fortuit, résultant
*  software.  Neither the name          de l'utilisation du logiciel. Ni
*  of the National Research             le nom du Conseil National de
*  Council of Canada nor the            Recherches du Canada ni les noms
*  names of its contributors may        de ses  participants ne peuvent
*  be used to endorse or promote        être utilisés pour approuver ou
*  products derived from this           promouvoir les produits dérivés
*  software without specific prior      de ce logiciel sans autorisation
*  written permission.                  préalable et particulière
*                                       par écrit.
*                                       
*  This file is part of the             Ce fichier fait partie du projet
*  OpenCADC project.                    OpenCADC.
*                                       
*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
*  you can redistribute it and/or       vous pouvez le redistribuer ou le
*  modify it under the terms of         modifier suivant les termes de
*  the GNU Affero General Public        la “GNU Affero General Public
*  License as published by the          License” telle que publiée
*  Free Software Foundation,            par la Free Software Foundation
*  either version 3 of the              : soit la version 3 de cette
*  License, or (at your option)         licence, soit (à votre gré)
*  any later version.                   toute version ultérieure.
*                                       
*  OpenCADC is distributed in the       OpenCADC est distribué
*  hope that it will be useful,         dans l’espoir qu’il vous
*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
*  without even the implied             GARANTIE : sans même la garantie
*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
*  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
*  General Public License for           Générale Publique GNU Affero
*  more details.                        pour plus de détails.
*                                       
*  You should have received             Vous devriez avoir reçu une
*  a copy of the GNU Affero             copie de la Licence Générale
*  General Public License along         Publique GNU Affero avec
*  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
*  .      pas le cas, consultez :
*                                       .
*
*  $Revision: 5 $
*
************************************************************************
*/

package ca.nrc.cadc.date;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * Date conversion utility.
 * 

* WARNING: The underlying SimpleDateFormat instances are NOT thread safe. *

* * @version $Version$ * @author pdowler */ public class DateUtil { /** * Pseudo-ISO8601 datetime format with milliseconds. This is nice for display as * it leaves a space between the date and time parts. */ public static final String ISO_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; /** * Pseudo-ISO8601 datetime format with milliseconds and explicit timezone. This * is nice for display as it leaves a space between the date and time parts. */ public static final String ISO_DATE_FORMAT_TZ = "yyyy-MM-dd HH:mm:ss.SSSZ"; /** * IVOA standard datetime format string with milliseconds. Apparently the IVOA * went rogue (with respect to ISO8601) by mandating UTC only and then dropping * the Z timezone descriptor from the format string. */ public static final String IVOA_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; // public static final String IVOA_DATE_FORMAT = ISO8601_DATE_FORMAT_MSZ; /** * ISO8601 UTC datetime format without milliseconds. */ public static final String ISO8601_DATE_FORMAT_Z = "yyyy-MM-dd'T'HH:mm:ss'Z'"; /** * ISO8601 UTC datetime format with milliseconds. */ public static final String ISO8601_DATE_FORMAT_MSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; /** * ISO8601 local datetime format without milliseconds. */ public static final String ISO8601_DATE_FORMAT_LOCAL = "yyyy-MM-dd'T'HH:mm:ss"; /** * ISO8601 local datetime format with milliseconds. */ public static final String ISO8601_DATE_FORMAT_MSLOCAL = "yyyy-MM-dd'T'HH:mm:ss.SSS"; public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z"; public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public static final TimeZone LOCAL = TimeZone.getDefault(); private static final double FROM_JULIAN_DATE = 2400000.5D; private static final String[] FITS_TIME_UNITS = new String[] { "s", "min", "h", "d", "a", "yr", "cy" }; private static final double[] FITS_TIME_MULTIPLIERS = new double[] { 1.0D, 60.0D, 60.0D * 60.0D, 60.0D * 60.0D * 24.0D, 365.25D * 60.0D * 60.0D * 24.0D, 365.25D * 60.0D * 60.0D * 24.0D, 100.0D * 365.25D * 60.0D * 60.0D * 24.0D }; /** * Create a new DateFormat object with the specified format and timezone. If the * format is null it defaults to ISO format (without required TZ). If the time * zone is null it defaults to UTC for ISO8601_DATE_FORMAT_Z, * ISO8601_DATE_FORMAT_MSZ, and IVOA_DATE_FORMAT and must be UTC for the first * two of these. *

* WARNING: The underlying SimpleDateFormat instance is NOT thread safe. *

* * @param format String format * @param tz TimeZone instance (optional) * @return DateFormat instance */ public static DateFormat getDateFormat(String format, TimeZone tz) { if (format == null) { format = ISO_DATE_FORMAT; // best display format } if (format.equals(ISO8601_DATE_FORMAT_Z) || format.equals(ISO8601_DATE_FORMAT_MSZ)) { if (tz == null) { tz = UTC; } else if (!UTC.equals(tz)) { throw new IllegalArgumentException("Cannot use format " + format + " with non-UTC timezone"); } } // NOTE: since the IVOA_DATE_FORMAT is identical to ISO8601_DATE_FORMAT_MSLOCAL // we cannot enforce the correct // timezone // else if ( format.equals(ISO8601_DATE_FORMAT_LOCAL) || // format.equals(ISO8601_DATE_FORMAT_MSLOCAL) ) // { // if (tz == null) // tz = LOCAL; // else if ( !LOCAL.equals(tz) ) // throw new IllegalArgumentException("Cannot use format " + format + " with // non-LOCAL timezone"); // } // Explicitly set the formatting locale in SimpleDateFormat. // The HTTP_DATE_FORMAT string throws an 'Unparseable date' error in Java 11. // The error was not thrown in Java 8. // Setting the locale in the SimpleDateFormat constructor to CANADA // or CANADA_FRENCH throws a parse error. // Setting the locale to ENGLISH, US, or UK, does not. SimpleDateFormat ret = new SimpleDateFormat(format, Locale.ENGLISH); ret.setLenient(false); if (tz != null) { ret.setTimeZone(tz); } return ret; } /** * Sloppy parsing. This method makes several attempts to parse the supplied date * string before giving up. if the initial parse fails, it tries to append 0 * milliseconds to the time, then a time of 0:0:0, then 0:0:0.0, and the it * tries setting the DateFormat to lenient. * * @param s string representation of the date * @param fmt the DateFormat to use * @return the Date * @throws java.text.ParseException the argument cannot be parsed after all * attempts */ public static Date flexToDate(String s, DateFormat fmt) throws ParseException { // try to parse ParseException orig = null; NumberFormatException origN = null; try { return fmt.parse(s); } catch (ParseException pex) { orig = pex; } catch (NumberFormatException nex) { origN = nex; } // missing milliseconds? try { return fmt.parse(s + ".0"); } catch (ParseException ignore) { // do nothing } catch (NumberFormatException ignore) { // do nothing } // missing time? try { return fmt.parse(s + "T0:0:0.0"); } catch (ParseException ignore) { // do nothing } catch (NumberFormatException ignore) { //do nothing } try { return fmt.parse(s + " 0:0:0.0"); } catch (ParseException ignore) { // do nothing } catch (NumberFormatException ignore) { // do nothing } if (orig != null) { throw orig; } throw new ParseException("failed to parse '" + s + "': " + origN, 0); } /** * Convert the argument date string to a vanilla java.util.Date. The input * object can be a java.sql.Date (or subclass) or java.sql.Timestamp (or * subclass). * * @param date the original date * @return a Date * @throws UnsupportedOperationException If the given object cannot be converted into a date. */ public static Date toDate(Object date) { if (date == null) { return null; } if (date instanceof java.sql.Timestamp) { java.sql.Timestamp ts = (java.sql.Timestamp) date; long millis = ts.getTime(); // NOTE: On DB2 the millis is only to the second and all the fractional // part is in the nanos // int nanos = ts.getNanos(); // millis += (long) (nanos / 1000000); return new Date(millis); } if (date instanceof java.sql.Date) { java.sql.Date sd = (java.sql.Date) date; return new Date(sd.getTime()); } if (date instanceof Date) { return (Date) date; } throw new UnsupportedOperationException( "failed to convert " + date.getClass().getName() + " to java.util.Date"); } /** * Obtain the Date from the given DMF seconds. * * @param dmfSeconds The DMF Seconds value. * @return Date object from the dmf Seconds. * @deprecated No longer used. */ @Deprecated public static Date toDate(final long dmfSeconds) { return new Date((dmfSeconds * 1000L) + getDMFEpoch().getTime()); } /** * Convert a Modified Julian Date to a date in the UTC timezone. * * @param mjd the MJD value * @return a Date in the UTC timezone * @deprecated Use DateUtil.fromModifiedJulianDate(double) */ @Deprecated public static Date toDate(double mjd) { return DateUtil.fromModifiedJulianDate(mjd); } /** * Convert a Modified Julian Date to a date in the UTC timezone. * * @param modifiedJulianDate The MJD value. * @return Date instance. Never null. */ public static Date fromModifiedJulianDate(final double modifiedJulianDate) { final int[] ymd = slaDjcl(modifiedJulianDate); // fraction of a day double frac = modifiedJulianDate - ((double) (long) modifiedJulianDate); int hh = (int) (frac * 24); // fraction of an hour frac = frac * 24.0 - hh; int mm = (int) (frac * 60); // fraction of a minute frac = frac * 60.0 - mm; int ss = (int) (frac * 60); // fraction of a second frac = frac * 60.0 - ss; int ms = (int) (frac * 1000); Calendar cal = Calendar.getInstance(UTC); cal.set(Calendar.YEAR, ymd[0]); cal.set(Calendar.MONTH, ymd[1] - 1); // Calendar is 0-based cal.set(Calendar.DAY_OF_MONTH, ymd[2]); cal.set(Calendar.HOUR_OF_DAY, hh); cal.set(Calendar.MINUTE, mm); cal.set(Calendar.SECOND, ss); cal.set(Calendar.MILLISECOND, ms); return cal.getTime(); } /** * Convert Julian to MJD. * @param julianDate The Julian date. * @return MJD double */ public static double toModifiedJulianDate(final double julianDate) { return julianDate - FROM_JULIAN_DATE; } /** * Convert a date in the UTC timezone to Modified Julian Date. Note that the * double datatype for MJD does not have enough digits for successful round-trip * conversion of all possible Date values (i.e. it has less that microsecond * precision). * * @param date a date in the UTC timezone * @return the MJD value */ public static double toModifiedJulianDate(Date date) { return toModifiedJulianDate(date, UTC); } /** * Convert a date in the specified timezone to Modified Julian Date. * * @param date The Date object to convert. * @param timezone The expected TimeZone. * @return number of days */ public static double toModifiedJulianDate(Date date, TimeZone timezone) { Calendar cal = Calendar.getInstance(timezone); cal.clear(); cal.setTime(date); int yr = cal.get(Calendar.YEAR); int mo = cal.get(Calendar.MONTH) + 1; // Calendar is 0-based int dy = cal.get(Calendar.DAY_OF_MONTH); double days = slaCldj(yr, mo, dy); int hh = cal.get(Calendar.HOUR_OF_DAY); int mm = cal.get(Calendar.MINUTE); int ss = cal.get(Calendar.SECOND); int ms = cal.get(Calendar.MILLISECOND); double seconds = hh * 3600.0 + mm * 60.0 + ss + ms / 1000.0; return days + seconds / 86400.0; } /** * Convert to seconds from the given Time Unit as specified in the FITS paper for Time Coordinate representation. * @param value The value to convert. * @param unit The unit that the value is in. * @return Double seconds. */ public static double toSeconds(final double value, final String unit) { final List unitList = Arrays.asList(FITS_TIME_UNITS); if (unitList.contains(unit)) { return value * FITS_TIME_MULTIPLIERS[unitList.indexOf(unit)]; } else { throw new IllegalArgumentException("Unsupported or unknown unit " + unit); } } /* Month lengths in days */ private static final int[] mtab = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // private static double slaCldj( int iy, int im, int id, double *djm, int *j ) private static double slaCldj(int iy, int im, int id) { /* ** - - - - - - - - s l a C l d j - - - - - - - - ** ** Gregorian calendar to Modified Julian Date. ** ** Given: iy,im,id int year, month, day in Gregorian calendar ** ** Returned: *djm double Modified Julian Date (JD-2400000.5) for 0 hrs *j int * status: 0 = OK 1 = bad year (MJD not computed) 2 = bad month (MJD not * computed) 3 = bad day (MJD computed) ** ** The year must be -4699 (i.e. 4700BC) or later. ** ** The algorithm is derived from that of Hatcher 1984 (QJRAS 25, 53-55). ** ** Last revision: 29 August 1994 ** ** Copyright P.T.Wallace. All rights reserved. */ // System.out.println("[slaCldj] " + iy + ", " + im + ", " + id); /* Validate year */ // if ( iy < -4699 ) { *j = 1; return; } if (iy < -4699) { throw new IllegalArgumentException("bad year"); } /* Validate month */ // if ( ( im < 1 ) || ( im > 12 ) ) { *j = 2; return; } if ((im < 1) || (im > 12)) { throw new IllegalArgumentException("bad month"); } /* Allow for leap year */ mtab[1] = (((iy % 4) == 0) && (((iy % 100) != 0) || ((iy % 400) == 0))) ? 29 : 28; /* Validate day */ // *j = ( id < 1 || id > mtab[im-1] ) ? 3 : 0; if (id < 1 || id > mtab[im - 1]) { throw new IllegalArgumentException("bad day"); /* Lengthen year and month numbers to avoid overflow */ } long iyL = (long) iy; long imL = (long) im; /* Perform the conversion */ return (double) ((1461L * (iyL - (12L - imL) / 10L + 4712L)) / 4L + (306L * ((imL + 9L) % 12L) + 5L) / 10L - (3L * ((iyL - (12L - imL) / 10L + 4900L) / 100L)) / 4L + (long) id - 2399904L); } // void slaDjcl ( double djm, int *iy, int *im, int *id, double *fd, int *j) private static int[] slaDjcl(double djm) { /* ** - - - - - - - - s l a D j c l - - - - - - - - ** ** Modified Julian Date to Gregorian year, month, day, and fraction of a day. ** ** Given: djm double Modified Julian Date (JD-2400000.5) ** ** Returned: *iy int year *im int month *id int day *fd double fraction of day ** *j int status: -1 = unacceptable date (before 4701BC March 1) ** ** The algorithm is derived from that of Hatcher 1984 (QJRAS 25, 53-55). ** ** Defined in slamac.h: dmod ** ** Last revision: 12 March 1998 ** ** Copyright P.T.Wallace. All rights reserved. */ // System.out.println("[slaDjcl] " + djm); // double f, d; // double f; /* Check if date is acceptable */ if ((djm <= -2395520.0) || (djm >= 1e9)) { // { // *j = -1; // return; throw new IllegalArgumentException("MJD out of valid range"); // } // else // { // *j = 0; /* Separate day and fraction */ // f = dmod ( djm, 1.0 ); // if ( f < 0.0 ) f += 1.0; // d = djm - f; // d = dnint ( d ); } long ld = (long) djm; /* Express day in Gregorian calendar */ // jd = (long) dnint ( d ) + 2400001; long jd = ld + 2400001L; long n4; n4 = 4L * (jd + ((6L * ((4L * jd - 17918L) / 146097L)) / 4L + 1L) / 2L - 37L); long nd10 = 10L * (((n4 - 237L) % 1461L) / 4L) + 5L; // *iy = (int) (n4/1461L-4712L); // *im = (int) (((nd10/306L+2L)%12L)+1L); // *id = (int) ((nd10%306L)/10L+1L); // *fd = f; int[] ret = new int[3]; ret[0] = (int) (n4 / 1461L - 4712L); ret[1] = (int) (((nd10 / 306L + 2L) % 12L) + 1L); ret[2] = (int) ((nd10 % 306L) / 10L + 1L); // *j = 0; return ret; // } } /** * Obtain the Date object representing the DMF Epoch of January 1st, 1980. * * @return Date object. * @deprecated No longer used. */ @Deprecated public static Date getDMFEpoch() { final Calendar cal = Calendar.getInstance(UTC); cal.set(1980, Calendar.JANUARY, 1, 0, 0, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy