loci.common.DateTools Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ome-common Show documentation
Show all versions of ome-common Show documentation
Contains common I/O, date parsing, and XML processing classes.
The newest version!
/*
* #%L
* Common package for I/O and related utilities
* %%
* Copyright (C) 2005 - 2016 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. 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.
*
* 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 HOLDERS 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.
* #L%
*/
package loci.common;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.IllegalInstantException;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A utility class with convenience methods for working with dates.
*
* @author Curtis Rueden ctrueden at wisc.edu
* @author Chris Allan callan at blackcat.ca
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public final class DateTools {
// -- Constants --
/** Timestamp formats. */
public static final int UNIX = 0; // January 1, 1970
public static final int COBOL = 1; // January 1, 1601
public static final int MICROSOFT = 2; // December 30, 1899
public static final int ZVI = 3;
public static final int ALT_ZVI = 4;
/** Milliseconds until UNIX epoch. */
public static final long UNIX_EPOCH = 0;
public static final long COBOL_EPOCH = 11644473600000L;
public static final long MICROSOFT_EPOCH = 2209143600000L;
public static final long ZVI_EPOCH = 2921084975759000L;
public static final long ALT_ZVI_EPOCH = 2921084284761000L;
/** ISO 8601 date output formatter with milliseconds. */
public static final String ISO8601_FORMAT_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS";
/** ISO 8601 date output formatter without milliseconds. */
public static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
/** Human readable timestamp string */
public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
/** Human readable timestamp filename string */
public static final String FILENAME_FORMAT = "yyyy-MM-dd_HH-mm-ss";
/** ISO 8601 date formatter with milliseconds. */
private static final DateTimeFormatter ISO8601_FORMATTER_MS =
DateTimeFormat.forPattern(ISO8601_FORMAT_MS);
/** ISO 8601 date formatter without milliseconds. */
private static final DateTimeFormatter ISO8601_FORMATTER =
DateTimeFormat.forPattern(ISO8601_FORMAT);
/** Human readable timestamp formatter. */
private static final DateTimeFormatter TIMESTAMP_FORMATTER =
DateTimeFormat.forPattern(TIMESTAMP_FORMAT);
/** Human readable timestamp filename formatter. */
private static final DateTimeFormatter FILENAME_FORMATTER =
DateTimeFormat.forPattern(FILENAME_FORMAT);
/** Logger for this class. */
private static final Logger LOGGER = LoggerFactory.getLogger(DateTools.class);
// -- Constructor --
private DateTools() { }
// -- Date handling --
/**
* Converts from two-word tick representation to milliseconds.
* Mainly useful in conjunction with COBOL date conversion.
*
* @param hi the upper 32 bits of the tick count
* @param lo the lower 32 bits of the tick count
* @return the number of milliseconds corresponding to the tick count,
* where 1 tick = 100 ns
*/
public static long getMillisFromTicks(long hi, long lo) {
long ticks = ((hi << 32) | lo);
return ticks / 10000; // 100 ns = 0.0001 ms
}
/**
* Converts the given timestamp into an ISO8601 date.
*
* @param stamp the format-dependent timestamp in milliseconds
* @param format the format in which stamp
is stored.
* This is used to select the epoch value used for normalizing
* the timestamp to milliseconds since the UNIX epoch. Valid
* values are #UNIX, #COBOL, #MICROSOFT, #ZVI, and #ALT_ZVI.
* @return an ISO 8601 formatted timestamp
* @see #UNIX_EPOCH
* @see #COBOL_EPOCH
* @see #MICROSOFT_EPOCH
* @see #ZVI_EPOCH
* @see #ALT_ZVI_EPOCH
*/
public static String convertDate(long stamp, int format) {
return convertDate(stamp, format, ISO8601_FORMAT);
}
/**
* Converts the given timestamp into a date string with the given format.
*
* @param stamp the format-dependent timestamp in milliseconds
* @param format the format in which stamp
is stored.
* This is used to select the epoch value used for normalizing
* the timestamp to milliseconds since the UNIX epoch. Valid
* values are #UNIX, #COBOL, #MICROSOFT, #ZVI, and #ALT_ZVI.
* @param outputFormat the pattern used for formatting the timestamp
* @return a timestamp in the specified output format
* @see #UNIX_EPOCH
* @see #COBOL_EPOCH
* @see #MICROSOFT_EPOCH
* @see #ZVI_EPOCH
* @see #ALT_ZVI_EPOCH
* @see DateTimeFormat
*/
public static String convertDate(long stamp, int format, String outputFormat)
{
return convertDate(stamp, format, outputFormat, false);
}
/**
* Converts the given timestamp into a date string with the given format.
*
* If correctTimeZoneForGMT is set, then the timestamp will be interpreted
* as being relative to GMT and not the local time zone.
*
* @param stamp the format-dependent timestamp in milliseconds
* @param format the format in which stamp
is stored.
* This is used to select the epoch value used for normalizing
* the timestamp to milliseconds since the UNIX epoch. Valid
* values are #UNIX, #COBOL, #MICROSOFT, #ZVI, and #ALT_ZVI.
* @param outputFormat the pattern used for formatting the timestamp
* @param correctTimeZoneForGMT true if the timestamp is relative to GMT
* @return a timestamp in the specified output format
* @see #UNIX_EPOCH
* @see #COBOL_EPOCH
* @see #MICROSOFT_EPOCH
* @see #ZVI_EPOCH
* @see #ALT_ZVI_EPOCH
* @see DateTimeFormat
*/
public static String convertDate(long stamp, int format, String outputFormat,
boolean correctTimeZoneForGMT)
{
// see http://www.merlyn.demon.co.uk/critdate.htm for more information on
// dates than you will ever need (or want)
long ms = stamp;
switch (format) {
case UNIX:
ms -= UNIX_EPOCH;
break;
case COBOL:
ms -= COBOL_EPOCH;
break;
case MICROSOFT:
ms -= MICROSOFT_EPOCH;
break;
case ZVI:
ms -= ZVI_EPOCH;
break;
case ALT_ZVI:
ms -= ALT_ZVI_EPOCH;
break;
}
final DateTimeFormatter fmt = DateTimeFormat.forPattern(outputFormat);
try {
if (correctTimeZoneForGMT) {
DateTimeZone tz = DateTimeZone.getDefault();
ms = tz.convertLocalToUTC(ms, false);
}
}
catch (ArithmeticException e) {}
catch (IllegalInstantException e) {}
DateTime d = new DateTime(ms, DateTimeZone.UTC);
return fmt.print(d);
}
/**
* Parse the given date as a Joda instant
*
* @param date The date to parse as a Joda timestamp
* @param format The date format to parse the string date
* @param separator The separator for milliseconds
* @return the Joda Instant object representing the timestamp
* @see Instant
*/
protected static Instant parseDate(String date, String format, String separator) {
long ms = 0L;
int msSeparator = 0;
String newDate = date.trim();
if (separator != null) {
msSeparator = date.lastIndexOf(separator);
}
if (msSeparator > 0) {
newDate = date.substring(0, msSeparator);
String msString = date.substring(msSeparator + 1);
// Handle formats ending with another pattern, e.g. "SSS aa" or "SSSZ"
int postmsSeparator = 0;
for (int pos=0; pos 0) {
try {
ms = Long.parseLong(msString.substring(0, postmsSeparator));
}
catch (NumberFormatException e) {
LOGGER.debug("Failed to parse milliseconds from '{}'", ms);
}
newDate = newDate + msString.substring(postmsSeparator);
}
else {
try {
ms = Long.parseLong(msString);
}
catch (NumberFormatException e) {
LOGGER.debug("Failed to parse milliseconds from '{}'", ms);
}
}
}
final DateTimeFormatter parser =
DateTimeFormat.forPattern(format).withZone(DateTimeZone.UTC);
Instant timestamp = null;
try {
timestamp = Instant.parse(newDate, parser);
timestamp = timestamp.plus(ms);
}
catch (IllegalArgumentException e) {
LOGGER.debug("Invalid timestamp '{}' for current locale", date);
// if the date couldn't be parsed with the current locale,
// try to parse with an English-language locale
// this is helpful for dates that use a three-letter month
DateTimeFormatter usParser = parser.withLocale(Locale.US);
try {
timestamp = Instant.parse(newDate, usParser);
timestamp = timestamp.plus(ms);
}
catch (IllegalArgumentException|UnsupportedOperationException exc) {
LOGGER.debug("Could not parse timestamp '{}' with EN_US locale",
date, exc);
}
}
catch (UnsupportedOperationException e) {
LOGGER.debug("Error parsing timestamp '{}'", date, e);
}
return timestamp;
}
/**
* Formats the given date as an ISO 8601 date.
* Delegates to {@link #formatDate(String, String, boolean)}, with the
* {@code lenient} flag set to false.
*
* @param date The date to format as ISO 8601
* @param format The date format to parse the string date
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String format) {
return formatDate(date, format, false);
}
/**
* Formats the given date as an ISO 8601 date.
* Delegates to {@link #formatDate(String, String, boolean, String)} with the
* {@code lenient} flag set to false.
*
* @param date The date to format as ISO 8601
* @param format The date format to parse the string date
* @param separator The separator for milliseconds
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String format, String separator) {
return formatDate(date, format, false, separator);
}
/**
* Formats the given date as an ISO 8601 date.
*
* @param date The date to format as ISO 8601.
* @param format The date format to parse the string date
* @param lenient Whether or not to leniently parse the date.
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String format, boolean lenient) {
return formatDate(date, format, false, null);
}
/**
* Formats the given date as an ISO 8601 date.
*
* @param date The date to format as ISO 8601
* @param format The date format to parse the string date
* @param lenient Whether or not to leniently parse the date.
* @param separator The separator for milliseconds
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String format, boolean lenient, String separator) {
if (date == null) return null;
Instant timestamp = parseDate(date, format, separator);
if (timestamp == null) {
return null;
}
final DateTimeFormatter isoformat;
if ((timestamp.getMillis() % 1000) != 0) {
isoformat = ISO8601_FORMATTER_MS;
}
else {
isoformat = ISO8601_FORMATTER;
}
return isoformat.print(timestamp);
}
/**
* Formats the given date as an ISO 8601 date.
*
* Delegates to {@link #formatDate(String, String[], boolean, String)} with
* {@code lenient} set to false and {@code separator} set to null.
*
* @param date The date to format as ISO 8601
* @param formats The date possible formats to parse the string date
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String[] formats) {
return formatDate(date, formats, false, null);
}
/**
* Formats the given date as an ISO 8601 date.
*
* Delegates to {@link #formatDate(String, String[], boolean, String)} with
* {@code separator} set to null.
*
* @param date The date to format as ISO 8601.
* @param formats The date possible formats to parse the string date
* @param lenient Whether or not to leniently parse the date.
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String[] formats,
boolean lenient)
{
return formatDate(date, formats, lenient, null);
}
/**
* Formats the given date as an ISO 8601 date.
* Delegates to {@link #formatDate(String, String[], boolean, String)} with
* {@code lenient} set to false.
*
* @param date The date to format as ISO 8601
* @param formats The date possible formats to parse the string date
* @param separator The separator for milliseconds
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String[] formats, String separator) {
return formatDate(date, formats, false, separator);
}
/**
* Formats the given date as an ISO 8601 date.
*
* @param date The date to format as ISO 8601.
* @param formats The date possible formats to parse the string date
* @param lenient Whether or not to leniently parse the date.
* @param separator The separator for milliseconds
* @return an ISO 8601 formatted timestamp
*/
public static String formatDate(String date, String[] formats,
boolean lenient, String separator)
{
for (int i=0; i