/*******************************************************************************
* Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package de.gebit.integrity.utils;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import de.gebit.integrity.dsl.DateAndTimeValue;
import de.gebit.integrity.dsl.DateValue;
import de.gebit.integrity.dsl.EuropeanDateAnd12HrsTimeValue;
import de.gebit.integrity.dsl.EuropeanDateAnd24HrsTimeValue;
import de.gebit.integrity.dsl.EuropeanDateValue;
import de.gebit.integrity.dsl.IsoDateAndTimeValue;
import de.gebit.integrity.dsl.IsoDateValue;
import de.gebit.integrity.dsl.IsoTimeValue;
import de.gebit.integrity.dsl.Simple12HrsTimeValue;
import de.gebit.integrity.dsl.Simple24HrsTimeValue;
import de.gebit.integrity.dsl.TimeValue;
import de.gebit.integrity.dsl.USDateAnd12HrsTimeValue;
import de.gebit.integrity.dsl.USDateValue;
/**
* A utility class to handle date/time parsing and other date stuff. Sorry for the uglyness, but date/time handling just
* IS ugly crap, especially when using Java's built-in functionality :-(.
*
* @author Rene Schneider - initial API and implementation
*
*/
public final class DateUtil {
private DateUtil() {
// nothing to do
}
/**
* Creates a preconfigured {@link SimpleDateFormat} for the given format string which can be used by the class
* internally.
*
* @param aFormatString
* the format string to use
* @return the date format instance
*/
private static SimpleDateFormat getSimpleDateFormat(String aFormatString) {
SimpleDateFormat tempFormat = new SimpleDateFormat(aFormatString);
tempFormat.setLenient(false);
return tempFormat;
}
/**
* Converts a given date value to a {@link Calendar}.
*
* @param aValue
* the date value
* @return the calendar set to the specified date
* @throws ParseException
* if the value cannot be parsed because it is of wrong format or depicts an illegal date/time
*/
public static Calendar convertDateValue(DateValue aValue) throws ParseException {
if (aValue instanceof IsoDateValue) {
return parseIsoDateAndTimeString(aValue.getDateValue(), null);
} else if (aValue instanceof EuropeanDateValue) {
return parseDateOrTimeString(aValue.getDateValue(), "dd.MM.yyyy");
} else if (aValue instanceof USDateValue) {
return parseDateOrTimeString(aValue.getDateValue(), "MM/dd/yyyy");
} else {
throw new UnsupportedOperationException("Someone forgot to implement a new date format!");
}
}
/**
* Converts a given time value to a {@link Calendar}.
*
* @param aValue
* the time value
* @return the calendar set to the specified date
* @throws ParseException
* if the value cannot be parsed because it is of wrong format or depicts an illegal date/time
*/
public static Calendar convertTimeValue(TimeValue aValue) throws ParseException {
if (aValue instanceof IsoTimeValue) {
return parseIsoDateAndTimeString(null, aValue.getTimeValue());
} else if (aValue instanceof Simple24HrsTimeValue) {
return parseEuropeanDateAnd24HrsTimeString(null, aValue.getTimeValue());
} else if (aValue instanceof Simple12HrsTimeValue) {
return parseEuropeanDateAnd12HrsTimeString(null, aValue.getTimeValue());
} else {
throw new UnsupportedOperationException("Someone forgot to implement a new time format!");
}
}
/**
* Converts a given date and time to a {@link Calendar}.
*
* @param aValue
* the date value
* @return the calendar set to the specified date
* @throws ParseException
* if the value cannot be parsed because it is of wrong format or depicts an illegal date/time
*/
public static Calendar convertDateAndTimeValue(DateAndTimeValue aValue) throws ParseException {
if (aValue instanceof IsoDateAndTimeValue) {
return parseIsoDateAndTimeString(aValue.getDateValue(), aValue.getTimeValue());
} else if (aValue instanceof EuropeanDateAnd24HrsTimeValue) {
return parseEuropeanDateAnd24HrsTimeString(aValue.getDateValue(), aValue.getTimeValue());
} else if (aValue instanceof EuropeanDateAnd12HrsTimeValue) {
return parseEuropeanDateAnd12HrsTimeString(aValue.getDateValue(), aValue.getTimeValue());
} else if (aValue instanceof USDateAnd12HrsTimeValue) {
return parseUSDateAnd12HrsTimeString(aValue.getDateValue(), aValue.getTimeValue());
} else {
throw new UnsupportedOperationException("Someone forgot to implement a new time format!");
}
}
/**
* Strips the time information from a given {@link Date} (sets all time fields to zero).
*
* @param aDate
* the date
* @return a new Date without time information
*/
public static Date stripTimeFromDate(Date aDate) {
Calendar tempCalendar = Calendar.getInstance();
tempCalendar.setTime(aDate);
tempCalendar.set(Calendar.HOUR_OF_DAY, 0);
tempCalendar.set(Calendar.MINUTE, 0);
tempCalendar.set(Calendar.SECOND, 0);
tempCalendar.set(Calendar.MILLISECOND, 0);
return tempCalendar.getTime();
}
/**
* Strips the date information from a given {@link Date} (sets all time fields to zero).
*
* @param aTime
* the time
* @return a new Time without the date information
*/
public static Date stripDateFromTime(Date aTime) {
if (aTime == null) {
return null;
}
Calendar tempCalendar = Calendar.getInstance();
tempCalendar.setTime(aTime);
tempCalendar.set(Calendar.YEAR, 0);
tempCalendar.set(Calendar.MONTH, 0);
tempCalendar.set(Calendar.DAY_OF_MONTH, 0);
return tempCalendar.getTime();
}
private static Calendar parseDateOrTimeString(String aDateString, String aFormatString) throws ParseException {
Calendar tempCalendar = Calendar.getInstance();
tempCalendar.setTime(getSimpleDateFormat(aFormatString).parse(aDateString));
return tempCalendar;
}
private static Calendar parseEuropeanDateAnd24HrsTimeString(String aDateString, String aTimeString)
throws ParseException {
String tempStringToParse;
if (aDateString != null) {
tempStringToParse = aDateString;
} else {
// use the common "zero" date if no date was given.
tempStringToParse = "01.01.1970";
}
// append a divider
tempStringToParse += "T";
if (aTimeString.length() < 6) {
// append seconds if they're not given; they're optional, but if not present :00 is assumed
tempStringToParse += aTimeString + ":00.000";
} else {
if (aTimeString.length() < 9) {
// append milliseconds if they're not given
tempStringToParse += aTimeString + ".000";
} else {
tempStringToParse += aTimeString;
}
}
return parseDateOrTimeString(tempStringToParse, "dd.MM.yyyy'T'HH:mm:ss.SSS");
}
private static Calendar parseEuropeanDateAnd12HrsTimeString(String aDateString, String aTimeString)
throws ParseException {
String tempStringToParse;
if (aDateString != null) {
tempStringToParse = aDateString;
} else {
// use the common "zero" date if no date was given.
tempStringToParse = "01.01.1970";
}
// append a divider
tempStringToParse += "T";
if (aTimeString.length() < 8) {
// inject seconds if they're not given; they're optional, but if not present :00 is assumed
tempStringToParse += aTimeString.substring(0, aTimeString.length() - 2) + ":00.000"
+ aTimeString.substring(aTimeString.length() - 2);
} else {
if (aTimeString.length() < 11) {
// inject just milliseconds
tempStringToParse += aTimeString.substring(0, aTimeString.length() - 2) + ".000"
+ aTimeString.substring(aTimeString.length() - 2);
} else {
tempStringToParse += aTimeString;
}
}
return parseDateOrTimeString(tempStringToParse, "dd.MM.yyyy'T'hh:mm:ss.SSSaa");
}
private static Calendar parseUSDateAnd12HrsTimeString(String aDateString, String aTimeString) throws ParseException {
String tempStringToParse;
if (aDateString != null) {
tempStringToParse = aDateString;
} else {
// use the common "zero" date if no date was given.
tempStringToParse = "01/01/1970";
}
// append a divider
tempStringToParse += "T";
if (aTimeString.length() < 8) {
// inject seconds if they're not given; they're optional, but if not present :00 is assumed
tempStringToParse += aTimeString.substring(0, aTimeString.length() - 2) + ":00.000"
+ aTimeString.substring(aTimeString.length() - 2);
} else {
if (aTimeString.length() < 11) {
// inject just milliseconds
tempStringToParse += aTimeString.substring(0, aTimeString.length() - 2) + ".000"
+ aTimeString.substring(aTimeString.length() - 2);
} else {
tempStringToParse += aTimeString;
}
}
return parseDateOrTimeString(tempStringToParse, "MM/dd/yyyy'T'hh:mm:ss.SSSaa");
}
private static Calendar parseIsoDateAndTimeString(String aDateString, String aTimeString) throws ParseException {
String tempDateValue = aDateString;
if (tempDateValue == null) {
// in case no date is given, use the "zero" date
tempDateValue = "1970-01-01";
}
String tempTimeValue;
if (aTimeString == null) {
// in case no time is given, use the "zero" time
tempTimeValue = "T00:00:00.000";
} else {
tempTimeValue = aTimeString;
if (!tempTimeValue.startsWith("T")) {
tempTimeValue = "T" + tempTimeValue;
}
// handle Zulu time
tempTimeValue = tempTimeValue.replace("Z", "+0000");
}
boolean tempHasTimezone = tempTimeValue.contains("+") | tempTimeValue.contains("-");
boolean tempHasSeconds = (!tempHasTimezone && tempTimeValue.length() > 6)
| (tempHasTimezone && tempTimeValue.length() > 12);
if (tempHasTimezone) {
if (tempTimeValue.charAt(tempTimeValue.length() - 3) == ':') {
// remove the optional colon in the timezone, if present
tempTimeValue = tempTimeValue.substring(0, tempTimeValue.length() - 3)
+ tempTimeValue.substring(tempTimeValue.length() - 2, tempTimeValue.length());
}
}
if (tempHasSeconds) {
if (!tempTimeValue.contains(".")) {
// inject milliseconds, if none are present but seconds are given
if (tempHasTimezone) {
tempTimeValue = tempTimeValue.substring(0, tempTimeValue.length() - 5) + ".000"
+ tempTimeValue.substring(tempTimeValue.length() - 5);
} else {
tempTimeValue += ".000";
}
}
}
Calendar tempCalendar;
if (tempHasTimezone) {
if (tempTimeValue.charAt(tempTimeValue.length() - 3) == ':') {
// remove the optional colon in the timezone, if present
tempTimeValue = tempTimeValue.substring(0, tempTimeValue.length() - 3)
+ tempTimeValue.substring(tempTimeValue.length() - 2, tempTimeValue.length());
}
tempCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"
+ tempTimeValue.substring(tempHasSeconds ? 9 : 6)));
if (tempHasSeconds) {
tempCalendar.setTime(getSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").parse(
tempDateValue + tempTimeValue));
} else {
tempCalendar.setTime(getSimpleDateFormat("yyyy-MM-dd'T'HH:mmZ").parse(tempDateValue + tempTimeValue));
}
} else {
tempCalendar = Calendar.getInstance();
if (tempHasSeconds) {
tempCalendar.setTime(getSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse(
tempDateValue + tempTimeValue));
} else {
tempCalendar.setTime(getSimpleDateFormat("yyyy-MM-dd'T'HH:mm").parse(tempDateValue + tempTimeValue));
}
}
return tempCalendar;
}
/**
* Performs date formatting using the provided format, but injects millisecond precision if the millisecond value is
* not "000". This is intended to be used with the standard date/time formats returned by DateFormat factory
* methods, which don't include milliseconds in most cases unfortunately. It employs a rather ugly hack to enhance
* the pattern which only works with {@link SimpleDateFormat}, but as far as I know there's no better method to
* achieve this result.
*
* @param aFormat
* the base format
* @param aDate
* the date to format
* @return the formatted string
*/
public static String formatDateWithMilliseconds(DateFormat aFormat, Date aDate) {
DateFormat tempFormat = aFormat;
if (aDate.getTime() % 1000 != 0) {
if (aFormat instanceof SimpleDateFormat) {
Field tempField;
try {
tempField = SimpleDateFormat.class.getDeclaredField("pattern");
tempField.setAccessible(true);
String tempPattern = (String) tempField.get(aFormat);
tempPattern = tempPattern.replace(":ss", ":ss.SSS");
tempFormat = new SimpleDateFormat(tempPattern);
} catch (SecurityException exc) {
exc.printStackTrace();
} catch (NoSuchFieldException exc) {
exc.printStackTrace();
} catch (IllegalArgumentException exc) {
exc.printStackTrace();
} catch (IllegalAccessException exc) {
exc.printStackTrace();
}
}
}
return tempFormat.format(aDate);
}
/**
* Converts a duration in nanoseconds to a human-readable, nicely formatted string.
*
* @param aTimespan
* the timespan in nanoseconds
* @param aShortFormat
* if true, a short format for the units is chosen
* @param aLongFormat
* if true, a long format for the units is chosen
* @return the formatted string
*/
public static String convertNanosecondTimespanToHumanReadableFormat(long aTimespan, boolean aShortFormat,
boolean aLongFormat) {
if (aTimespan < TimeUnit.SECONDS.toMillis(1)) {
return aTimespan + (aShortFormat ? "ms" : aLongFormat ? " milliseconds" : "ms");
} else {
StringBuilder tempBuilder = new StringBuilder();
if (aTimespan >= TimeUnit.DAYS.toNanos(1)) {
tempBuilder.append(TimeUnit.NANOSECONDS.toDays(aTimespan)
+ (aShortFormat ? "d" : aLongFormat ? " days" : " days"));
}
if (aTimespan >= TimeUnit.HOURS.toNanos(1)) {
if (tempBuilder.length() > 0) {
tempBuilder.append(" ");
}
tempBuilder.append(TimeUnit.NANOSECONDS.toHours(aTimespan % TimeUnit.DAYS.toNanos(1))
+ (aShortFormat ? "h" : aLongFormat ? " hours" : "hrs"));
}
if (aTimespan >= TimeUnit.MINUTES.toNanos(1)) {
if (tempBuilder.length() > 0) {
tempBuilder.append(" ");
}
tempBuilder.append(TimeUnit.NANOSECONDS.toMinutes(aTimespan % TimeUnit.HOURS.toNanos(1))
+ (aShortFormat ? "m" : aLongFormat ? " minutes" : "min"));
}
if (aTimespan >= TimeUnit.SECONDS.toNanos(1)) {
if (tempBuilder.length() > 0) {
tempBuilder.append(" ");
}
tempBuilder.append(TimeUnit.NANOSECONDS.toSeconds(aTimespan % TimeUnit.MINUTES.toNanos(1))
+ (aShortFormat ? "s" : aLongFormat ? " seconds" : "sec"));
}
if (aTimespan >= TimeUnit.MILLISECONDS.toNanos(1) && aTimespan < TimeUnit.MINUTES.toNanos(1)) {
if (tempBuilder.length() > 0) {
tempBuilder.append(" ");
}
tempBuilder.append(TimeUnit.NANOSECONDS.toMillis(aTimespan % TimeUnit.SECONDS.toNanos(1))
+ (aShortFormat ? "ms" : aLongFormat ? " milliseconds" : "msecs"));
}
return tempBuilder.toString();
}
}
}