/**
* ***************************************************************************
* Copyright (c) 2010 Qcadoo Limited
* Project: Qcadoo Framework
* Version: 1.4
*
* This file is part of Qcadoo.
*
* Qcadoo is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
* ***************************************************************************
*/
package com.qcadoo.localization.api.utils;
import static com.google.common.base.Optional.of;
import static com.qcadoo.commons.functional.Either.left;
import static com.qcadoo.commons.functional.Either.right;
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.joda.time.DateTime;
import com.google.common.base.Optional;
import com.qcadoo.commons.functional.Either;
/**
* Utility for date localization.
*
* @since 0.4.0
*/
public final class DateUtils {
private static final String PARSE_EXCEPTION_MSG = "Can't parse date from value '%s'";
private static final String L_WRONG_DATE = "wrong date";
/**
* Date format.
*/
public static final String L_DATE_FORMAT = "yyyy-MM-dd";
/**
* Date-time format.
*/
public static final String L_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* Date-time format for report files.
*/
public static final String L_REPORT_DATE_TIME_FORMAT = "yyyy_MM_dd_HH_mm_ss";
private static final String[] SUPPORTED_PATTERNS = new String[] { L_DATE_TIME_FORMAT, "yyyy-MM-dd HH:mm:",
"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:", "yyyy-MM-dd HH", "yyyy-MM-dd", "yyyy-MM-", "yyyy-MM", "yyyy-", "yyyy" };
private DateUtils() {
}
/**
* Parse string into date, with autocomplete missing month, day, hour, minute and second.
*
* Examples with up-complete:
*
* <ul>
* <li>2010: 2010-12-31 23:59:59</li>
* <li>2010-03: 2010-03-31 23:59:59</li>
* <li>2010-03-06: 2010-03-06 23:59:59</li>
* <li>2010-03-06 19: 2010-03-06 19:59:59</li>
* <li>2010-03-06 19:30: 2010-03-06 19:30:59</li>
* <li>2010-03-06 19:30:20: 2010-03-06 19:30:20</li>
* </ul>
*
* Examples with down-complete:
*
* <ul>
* <li>2010: 2010-01-01 00:00:00</li>
* <li>2010-03: 2010-03-01 00:00:00</li>
* <li>2010-03-06: 2010-03-06 00:00:00</li>
* <li>2010-03-06 19: 2010-03-06 19:00:00</li>
* <li>2010-03-06 19:30: 2010-03-06 19:30:00</li>
* <li>2010-03-06 19:30:20: 2010-03-06 19:30:20</li>
* </ul>
*
* @param dateExpression
* string with date expression
* @param upComplete
* true if up-complete, otherwise down-complete
* @return parsed date
* @throws ParseException
* if year, month, day, hour, minute or second is invalid or when year is < 1500 or > 2500
*/
public static Date parseAndComplete(final String dateExpression, final boolean upComplete) throws ParseException {
final String trimmedDateExpression = StringUtils.trim(dateExpression);
DateTime parsedDate = new DateTime(org.apache.commons.lang3.time.DateUtils.parseDateStrictly(trimmedDateExpression,
SUPPORTED_PATTERNS));
final String[] dateAndTime = trimmedDateExpression.split(" ");
if (dateAndTime.length > 2 || parsedDate.getYear() < 1500 || parsedDate.getYear() > 2500) {
throw new ParseException(L_WRONG_DATE, 1);
}
return round(parsedDate, upComplete, dateAndTime).toDate();
}
private static DateTime round(final DateTime dateTime, final boolean upComplete, final String[] dateAndTime) {
if (!upComplete) {
return dateTime;
}
DateTime roundedDateTime = dateTime;
if (dateAndTime.length > 0 && StringUtils.isNotBlank(dateAndTime[0])) {
roundedDateTime = roundUpDate(roundedDateTime, dateAndTime[0]);
if (dateAndTime.length > 1 && StringUtils.isNotBlank(dateAndTime[1])) {
roundedDateTime = roundUpTime(roundedDateTime, dateAndTime[1]);
} else {
roundedDateTime = roundedDateTime.withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59);
}
}
return roundedDateTime.withMillisOfSecond(999);
}
private static DateTime roundUpDate(final DateTime dateTime, final String dateExpressionPart) {
DateTime roundedDate = dateTime;
final String[] date = dateExpressionPart.split("-");
if (date.length < 3 || StringUtils.isBlank(date[2])) {
final int day = roundedDate.dayOfMonth().getMaximumValue();
roundedDate = roundedDate.withDayOfMonth(day);
}
if (date.length < 2 || StringUtils.isBlank(date[1])) {
roundedDate = roundedDate.withMonthOfYear(12);
}
return roundedDate;
}
private static DateTime roundUpTime(final DateTime dateTime, final String timeExpressionPart) {
DateTime roundedDate = dateTime;
final String[] time = timeExpressionPart.split(":");
if (time.length < 1 || StringUtils.isBlank(time[0])) {
roundedDate = roundedDate.withHourOfDay(23);
}
if (time.length < 2 || StringUtils.isBlank(time[1])) {
roundedDate = roundedDate.withMinuteOfHour(59);
}
if (time.length < 3 || StringUtils.isBlank(time[2])) {
roundedDate = roundedDate.withSecondOfMinute(59);
}
return roundedDate;
}
/**
* Get date's String value in {@value DateUtils#L_DATE_TIME_FORMAT} format
*
* @param date
* date to be formatted
* @return date as String in {@value DateUtils#L_DATE_TIME_FORMAT} format or empty string if date is null
*/
public static String toDateTimeString(final Date date) {
return formatDate(date, DateUtils.L_DATE_TIME_FORMAT);
}
/**
* Get date's String value in {@value DateUtils#L_DATE_FORMAT} format
*
* @param date
* date to be formatted
* @return date as String in {@value DateUtils#L_DATE_FORMAT} format or empty string if date is null
*/
public static String toDateString(final Date date) {
return formatDate(date, DateUtils.L_DATE_FORMAT);
}
private static String formatDate(final Date date, final String pattern) {
if (date == null) {
return "";
}
return DateFormatUtils.format(date, pattern);
}
/**
* Parse date from object
*
* @param value
* object to be parsed. Supported argument types:
* <ul>
* <li>String - representing date in {@value DateUtils#L_DATE_TIME_FORMAT} or {@value DateUtils#L_DATE_FORMAT}
* format</li>
* <li>Date</li>
* <li>Number - containing number of milliseconds from 1st January 1970 00:00:00.000 GMT</li>
* </ul>
* @return Date parsed from given object or null if object is null
* @throws IllegalArgumentException
* if value is unsupported or has incorrect format
*/
public static Date parseDate(final Object value) {
Date date = null;
if (value instanceof String) {
if (StringUtils.isNotBlank((String) value)) {
try {
date = org.apache.commons.lang3.time.DateUtils.parseDateStrictly((String) value, new String[] {
DateUtils.L_DATE_TIME_FORMAT, DateUtils.L_DATE_FORMAT });
} catch (ParseException e) {
throw new IllegalArgumentException(String.format(PARSE_EXCEPTION_MSG, value), e);
}
}
} else if (value instanceof Date) {
// Date is mutable, make defensive copy to disallow implicit ('silent') modifications of the original one
date = new Date(((Date) value).getTime());
} else if (value instanceof Number) {
date = new Date(((Number) value).longValue());
} else if (value != null) {
throw new IllegalArgumentException(String.format(PARSE_EXCEPTION_MSG, value));
}
return date;
}
public static Either<? extends Exception, Optional<DateTime>> tryParse(final Object value) {
if (value instanceof String) {
if (StringUtils.isNotBlank((String) value)) {
try {
Date date = org.apache.commons.lang3.time.DateUtils.parseDateStrictly((String) value, new String[] {
DateUtils.L_DATE_TIME_FORMAT, DateUtils.L_DATE_FORMAT });
return right(of(new DateTime(date)));
} catch (ParseException e) {
return left(new IllegalArgumentException(String.format(PARSE_EXCEPTION_MSG, value), e));
}
}
} else if (value instanceof Date) {
return right(of(new DateTime(value)));
} else if (value instanceof Number) {
return right(of(new DateTime(new Date(((Number) value).longValue()))));
} else if (value != null) {
return left(new IllegalArgumentException(String.format(PARSE_EXCEPTION_MSG, value)));
}
return right(Optional.<DateTime> absent());
}
public static Date copy(final Date date) {
if (date == null) {
return null;
}
return new Date(date.getTime());
}
}