/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
package org.thymeleaf.util;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author Daniel Fernández
*
* @since 1.0
*
*/
public final class DateUtils {
private static final Map<DateFormatKey,DateFormat> dateFormats = new ConcurrentHashMap<DateFormatKey, DateFormat>(4, 0.9f, 2);
/*
* This SimpleDateFormat defines an almost-ISO8601 formatter.
*
* The correct ISO8601 format would be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", but the "X" pattern (which outputs the
* timezone as "+02:00" or "Z" instead of "+0200") was not added until Java SE 7. So the use of this
* SimpleDateFormat object requires additional post-processing.
*
* Note SimpleDateFormat objects are NOT thread-safe, so use of this object must be synchronized.
*/
private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ");
/**
*
* @param year year
* @param month month
* @param day day
* @return the result
* @since 1.1.2
*/
public static Calendar create(final Object year, final Object month, final Object day) {
return create(year, month, day, null, null, null, null, null, null);
}
/**
*
* @param year year
* @param month month
* @param day day
* @param hour hour
* @param minute minute
* @return the result
* @since 1.1.2
*/
public static Calendar create(final Object year, final Object month, final Object day,
final Object hour, final Object minute) {
return create(year, month, day, hour, minute, null, null, null, null);
}
/**
*
* @param year year
* @param month month
* @param day day
* @param hour hour
* @param minute minute
* @param second second
* @return the result
* @since 1.1.2
*/
public static Calendar create(final Object year, final Object month, final Object day,
final Object hour, final Object minute, final Object second) {
return create(year, month, day, hour, minute, second, null, null, null);
}
/**
*
* @param year year
* @param month month
* @param day day
* @param hour hour
* @param minute minute
* @param second second
* @param millisecond millisecond
* @return the result
* @since 1.1.2
*/
public static Calendar create(final Object year, final Object month, final Object day,
final Object hour, final Object minute, final Object second, final Object millisecond) {
return create(year, month, day, hour, minute, second, millisecond, null, null);
}
/**
*
* @param year year
* @param month month
* @param day day
* @param hour hour
* @param minute minute
* @param second second
* @param millisecond millisecond
* @param timeZone timeZone
* @return the result
* @since 2.1.0
*/
public static Calendar create(final Object year, final Object month, final Object day,
final Object hour, final Object minute, final Object second, final Object millisecond,
final Object timeZone) {
return create(year, month, day, hour, minute, second, millisecond, timeZone, null);
}
/**
*
* @param year year
* @param month month
* @param day day
* @param hour hour
* @param minute minute
* @param second second
* @param millisecond millisecond
* @param timeZone timeZone
* @param locale locale
* @return the result
* @since 2.1.0
*/
public static Calendar create(final Object year, final Object month, final Object day,
final Object hour, final Object minute, final Object second, final Object millisecond,
final Object timeZone, final Locale locale) {
final BigDecimal nYear =
(year == null?
null : EvaluationUtils.evaluateAsNumber(year));
final BigDecimal nMonth =
(month == null?
null : EvaluationUtils.evaluateAsNumber(month));
final BigDecimal nDay =
(day == null?
null : EvaluationUtils.evaluateAsNumber(day));
final BigDecimal nHour =
(hour == null?
null : EvaluationUtils.evaluateAsNumber(hour));
final BigDecimal nMinute =
(minute == null?
null : EvaluationUtils.evaluateAsNumber(minute));
final BigDecimal nSecond =
(second == null?
null : EvaluationUtils.evaluateAsNumber(second));
final BigDecimal nMillisecond =
(millisecond == null?
null : EvaluationUtils.evaluateAsNumber(millisecond));
final TimeZone tzTimeZone =
(timeZone != null?
(timeZone instanceof TimeZone?
(TimeZone) timeZone : TimeZone.getTimeZone(timeZone.toString())) :
null);
final Calendar cal;
if (tzTimeZone != null && locale != null) {
cal = Calendar.getInstance(tzTimeZone, locale);
} else if (tzTimeZone != null) {
cal = Calendar.getInstance(tzTimeZone);
} else if (locale != null) {
cal = Calendar.getInstance(locale);
} else {
cal = Calendar.getInstance();
}
if (nYear == null || nMonth == null || nDay == null) {
throw new IllegalArgumentException(
"Cannot create Calendar/Date object with null year (" + nYear + "), " +
"month (" + nMonth + ") or day (" + nDay + ")");
}
cal.set(Calendar.YEAR, nYear.intValue());
cal.set(Calendar.MONTH, nMonth.intValue() - 1);
cal.set(Calendar.DAY_OF_MONTH, nDay.intValue());
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
if (nHour != null && nMinute != null) {
cal.set(Calendar.HOUR_OF_DAY, nHour.intValue());
cal.set(Calendar.MINUTE, nMinute.intValue());
if (nSecond != null) {
cal.set(Calendar.SECOND, nSecond.intValue());
if (nMillisecond != null) {
cal.set(Calendar.MILLISECOND, nMillisecond.intValue());
}
} else if (nMillisecond != null){
throw new IllegalArgumentException(
"Calendar/Date object cannot be correctly created from a null second " +
"but non-null millisecond.");
}
} else if (nHour != null || nMinute != null) {
throw new IllegalArgumentException(
"Calendar/Date object can only be correctly created if hour (" + nHour + ") " +
"and minute (" + nMinute + ") are either both null or non-null.");
} else if (nSecond != null || nMillisecond != null) {
throw new IllegalArgumentException(
"Calendar/Date object cannot be correctly created from a null hour and " +
"minute but non-null second and/or millisecond.");
}
return cal;
}
/**
*
* @return the result
* @since 1.1.2
*/
public static Calendar createNow() {
return createNow(null, null);
}
/**
*
* @param timeZone timeZone
* @return the result
* @since 2.1.0
*/
public static Calendar createNow(final Object timeZone) {
return createNow(timeZone, null);
}
/**
*
* @param timeZone timeZone
* @param locale locale
* @return the result
* @since 2.1.0
*/
public static Calendar createNow(final Object timeZone, final Locale locale) {
final TimeZone tzTimeZone =
(timeZone != null?
(timeZone instanceof TimeZone?
(TimeZone) timeZone : TimeZone.getTimeZone(timeZone.toString())) :
null);
if (tzTimeZone != null && locale != null) {
return Calendar.getInstance(tzTimeZone, locale);
}
if (tzTimeZone != null) {
return Calendar.getInstance(tzTimeZone);
}
if (locale != null) {
return Calendar.getInstance(locale);
}
return Calendar.getInstance();
}
/**
*
* @return the result
* @since 1.1.2
*/
public static Calendar createToday() {
return createToday(null, null);
}
/**
*
* @param timeZone timeZone
* @return the result
* @since 2.1.0
*/
public static Calendar createToday(final Object timeZone) {
return createToday(timeZone, null);
}
/**
*
* @param timeZone timeZone
* @param locale locale
* @return the result
* @since 2.1.0
*/
public static Calendar createToday(final Object timeZone, final Locale locale) {
final Calendar cal = createNow(timeZone, locale);
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.HOUR_OF_DAY, 0);
return cal;
}
public static String format(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return formatDate(target, locale);
}
public static String format(final Object target, final String pattern, final Locale locale) {
Validate.notEmpty(pattern, "Pattern cannot be null or empty");
if (target == null) {
return null;
}
return formatDate(target, pattern, locale);
}
public static Integer day(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.DAY_OF_MONTH));
}
public static Integer month(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.MONTH) + 1);
}
public static String monthName(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return format(target, "MMMM", locale);
}
public static String monthNameShort(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return format(target, "MMM", locale);
}
public static Integer year(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.YEAR));
}
public static Integer dayOfWeek(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.DAY_OF_WEEK));
}
public static String dayOfWeekName(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return format(target, "EEEE", locale);
}
public static String dayOfWeekNameShort(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return format(target, "EEE", locale);
}
public static Integer hour(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.HOUR_OF_DAY));
}
public static Integer minute(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.MINUTE));
}
public static Integer second(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.SECOND));
}
public static Integer millisecond(final Object target) {
if (target == null) {
return null;
}
final Calendar cal = normalizeDate(target);
return Integer.valueOf(cal.get(Calendar.MILLISECOND));
}
private static Calendar normalizeDate(final Object target) {
if (target == null) {
return null;
}
if (target instanceof Calendar) {
return (Calendar) target;
} else if (target instanceof java.util.Date) {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(((java.util.Date)target).getTime());
return cal;
} else {
throw new IllegalArgumentException(
"Cannot normalize class \"" + target.getClass().getName() + "\" as a date");
}
}
private static String formatDate(final Object target, final Locale locale) {
if (target == null) {
return null;
}
return formatDate(target, null, locale);
}
private static String formatDate(final Object target, final String pattern, final Locale locale) {
Validate.notNull(locale, "Locale cannot be null");
if (target == null) {
return null;
}
final DateFormatKey key = new DateFormatKey(target, pattern, locale);
DateFormat dateFormat = dateFormats.get(key);
if (dateFormat == null) {
if (StringUtils.isEmptyOrWhitespace(pattern)) {
dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
} else {
dateFormat = new SimpleDateFormat(pattern, locale);
}
if (key.timeZone != null) {
dateFormat.setTimeZone(key.timeZone);
}
dateFormats.put(key, dateFormat);
}
if (target instanceof Calendar) {
synchronized (dateFormat) {
return dateFormat.format(((Calendar) target).getTime());
}
} else if (target instanceof java.util.Date) {
synchronized (dateFormat) {
return dateFormat.format((java.util.Date)target);
}
} else {
throw new IllegalArgumentException(
"Cannot format object of class \"" + target.getClass().getName() + "\" as a date");
}
}
/**
*
* @param target target
* @return the result
* @since 2.1.4
*/
public static String formatISO(final Object target) {
if (target == null) {
return null;
}
final java.util.Date targetDate;
if (target instanceof Calendar) {
targetDate = ((Calendar)target).getTime();
} else if (target instanceof java.util.Date) {
targetDate = (java.util.Date)target;
} else {
throw new IllegalArgumentException(
"Cannot format object of class \"" + target.getClass().getName() + "\" as a date");
}
final String formatted;
synchronized (ISO8601_DATE_FORMAT) {
formatted = ISO8601_DATE_FORMAT.format(targetDate);
}
final StringBuilder strBuilder = new StringBuilder(formatted.length() + 1);
strBuilder.append(formatted);
strBuilder.insert(26, ':');
return strBuilder.toString();
}
private DateUtils() {
super();
}
private static final class DateFormatKey {
final String format;
final TimeZone timeZone;
final Locale locale;
DateFormatKey(final Object target, final String format, final Locale locale) {
super();
Validate.notNull(locale, "Locale cannot be null");
this.format = format;
this.locale = locale;
if (target != null && target instanceof Calendar) {
this.timeZone = ((Calendar)target).getTimeZone();
} else {
this.timeZone = null;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.format == null) ? 0 : this.format.hashCode());
result = prime * result + this.locale.hashCode();
result = prime * result + ((this.timeZone == null) ? 0 : this.timeZone.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DateFormatKey other = (DateFormatKey) obj;
if (this.format == null) {
if (other.format != null) {
return false;
}
} else if (!this.format.equals(other.format)) {
return false;
}
if (this.timeZone == null) {
if (other.timeZone != null) {
return false;
}
} else if (!this.timeZone.equals(other.timeZone)) {
return false;
}
return this.locale.equals(other.locale);
}
}
}