/*******************************************************************************
 * Copyright 2019 2Fi Business Solutions Ltd.
 *
 * This code is part of the Core project.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.core.utils;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Pattern;

import org.apache.commons.lang3.time.FastDateFormat;

/**
 * Utility class for date parsing and formatting.
 *
 * @see FastDateFormat
 * @see <a href="http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html">ISO 8601</a>
 * @see <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a>
 *
 * @author Patrick
 */
public abstract class DateUtils extends org.apache.commons.lang3.time.DateUtils {

	/** Used to format double digit hours, minutes, and seconds */
	private static final DecimalFormat DF00 = new DecimalFormat("00");

	/** RegEx pattern to test 24-hour 4-digit time format */
	private static final Pattern PATTERN_24HR_TIME_STRING = Pattern.compile("^(([0-1][0-9])|(2[0-3]))[0-5][0-9]$");

	/**
	 * HK date formatter for date without time zone. The format used is <tt>dd/MM/yyyy</tt>.
	 */
	public static final FastDateFormat HK_DATE_FORMAT = FastDateFormat.getInstance("dd/MM/yyyy");

	/**
	 * HK date formatter for datetime. The format used is <tt>dd/MM/yyyy HH:mm:ss</tt>.
	 */
	public static final FastDateFormat HK_DATETIME_FORMAT = FastDateFormat.getInstance("dd/MM/yyyy HH:mm:ss");

	/**
	 * SQL date formatter for date without time zone. The format used is <tt>yyyy-MM-dd</tt>.
	 */
	public static final FastDateFormat SQL_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd");

	/**
	 * Ext JS ISO8601 formatter for date-time with time zone. The format used is <tt>yyyy-MM-dd'T'HH:mm:ssZZ</tt>.
	 */
	public static final FastDateFormat ISO_JS_DATETIME_TIME_ZONE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

	/**
	 * Default parse patterns
	 * <ol>
	 * <li>dd/MM/yyyy
	 * <li>dd/MM/yyyy HH:mm:ss
	 * <li>yyyy-MM-dd
	 * <li>yyyy-MM-dd'T'HH:mm:ss
	 * <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
	 * <li>yyyy-MM-dd'T'HH:mm:ss.SSSZ
	 * </ol>
	 */
	public static final String[] PARSE_PATTERNS = { "dd/MM/yyyy","dd/MM/yyyy HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSSZ" };

	/**
	 * PayPal parse patterns
	 * <ol>
	 * <li>HH:mm:ss MMM dd, yyyy z
	 * </ol>
	 */
	public static final String[] PARSE_PATTERNS_PAYPAL = { "HH:mm:ss MMM dd, yyyy z" };

	/**
	 * Formats a Date as String using the default pattern
	 *
	 * @param date
	 *            the <code>date</code> object, may be {@code null}
	 *
	 * @return the formatted date string, or <code>""<code> if <code>date</code> is {@code null}
	 */
	public static String formatDate(Date date) {
		return formatDate(date, StringUtils.EMPTY);
	}

	/**
	 * Formats a Date as String using the default pattern, with a fallback default value if the date is null
	 *
	 * @param date
	 *            the <code>date</code> object, may be {@code null}
	 * @param defaultValue
	 *            the value to return if <code>date</code> is {@code null}
	 *
	 * @return the formatted date string, or the default value if <code>date</code> is {@code null}
	 */
	public static String formatDate(Date date, String defaultValue) {
		if (date == null) return defaultValue;
		return HK_DATE_FORMAT.format(date);
	}

	/**
	 * Formats a Date as String using the provided pattern, with a fallback default value if the date is null
	 *
	 * @param date
	 *            the <code>date</code> object, may be {@code null}
	 * @param pattern
	 *            {@link java.text.SimpleDateFormat} compatible pattern
	 * @param defaultValue
	 *            the value to return if <code>date</code> is {@code null}
	 *
	 * @return the formatted date string, or the default value if <code>date</code> is {@code null}
	 *
	 * @throws IllegalArgumentException
	 *             if pattern is invalid
	 */
	public static String formatDate(Date date, String pattern, String defaultValue) {
		if (date == null) return defaultValue;
		if (HK_DATE_FORMAT.getPattern().equals(pattern)) {
			return HK_DATE_FORMAT.format(date);
		} else if (SQL_DATE_FORMAT.getPattern().equals(pattern)) {
			return SQL_DATE_FORMAT.format(date);
		} else {
			return FastDateFormat.getInstance(pattern).format(date);
		}
	}

	/**
	 * <p>
	 * Parses a string representing a date by trying a variety of different parsers.
	 * </p>
	 * <p>
	 * The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match,
	 * the default value is returned.
	 * </p>
	 * <p>
	 * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
	 * </p>
	 *
	 * @param str
	 *            the date to parse, not null
	 * @param parsePatterns
	 *            the date format patterns to use, see {@link SimpleDateFormat}, not null
	 * @param defaultValue
	 *            the default value to use as fallback
	 *
	 * @return the parsed {@link Date} object
	 *
	 * @see DateUtils#parseDateStrictly(String, String[])
	 */
	public static Date parseDateStrictly(String str, String[] parsePatterns, Date defaultValue) {
		if (str == null) return defaultValue;
		try {
			return parseDateStrictly(str, parsePatterns);
		} catch (ParseException e) {
			return defaultValue;
		}
	}

	/**
	 * <p>
	 * Parses a string representing a date by trying a variety of different parsers.
	 * </p>
	 * <p>
	 * The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match,
	 * the default value is returned.
	 * </p>
	 * <p>
	 * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
	 * </p>
	 *
	 * @param str
	 *            the date to parse, not null
	 * @param defaultValue
	 *            the default value to use as fallback
	 *
	 * @return the parsed {@link Date} object
	 *
	 * @see DateUtils#parseDateStrictly(String, String[])
	 */
	public static Date parseDateStrictly(String str, Date defaultValue) {
		return parseDateStrictly(str, PARSE_PATTERNS, defaultValue);
	}

	/**
	 * Clone a date
	 *
	 * @return The cloned date, or null if input date is null
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Date> T clone(Date date) {
		return (date == null) ? null : (T) date.clone();
	}

	/**
	 * Check if the time is valid 24-hour time
	 */
	@Deprecated
	public static boolean isValid24HourTime(int time) {
		int hour = time / 100;
		int minute = time - hour * 100;

		// invalid hour
		if (hour < 0 || hour > 23) return false;

		// invalid minute
		if (minute < 0 || minute > 59) return false;

		return true;
	}

	/**
	 * Check if the time string is valid 24-hour format (4 digit without any symbols)
	 */
	public static final boolean isValid24HourTime(String timeString) {
		if (timeString == null || timeString.length() != 4) {
			// if it's null or not 4-digit
			return false;
		} else {
			// else check against RegEx
			return PATTERN_24HR_TIME_STRING.matcher(timeString).matches();
		}
	}

	/**
	 * Converts 24-hour time string to a long value in millisecond
	 *
	 * @param str
	 *            24-hour time string in the format of "hh:mm" or "hhmm"
	 */
	public static long convertTimeStringToMillisecond(String str) {
		if (StringUtils.isNotBlank(str)) {
			str = StringUtils.remove(str, ':');
			int time = NumberUtils.toInt(str);
			int hour = time / 100;
			int minute = time - hour * 100;
			return hour * 3600000 + minute * 60000;
		}
		return 0L;
	}

	/**
	 * Returns a new Date object by adding the time value from a 24-hour time string
	 *
	 * @param clazz
	 *            the Date class to return
	 * @param date
	 *            the Date object that the time to add to, will not be changed
	 * @param str
	 *            24-hour time string in the format of "hh:mm" or "hhmm"
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Date> T addTimeToDate(Class<T> clazz, Date date, String str) {
		if (date != null && StringUtils.isNotBlank(str)) {
			Calendar c = Calendar.getInstance();
			c.setTime(date);
			c.set(Calendar.HOUR_OF_DAY, 0);
			c.set(Calendar.MINUTE, 0);
			c.set(Calendar.SECOND, 0);
			c.set(Calendar.MILLISECOND, 0);
			long time = c.getTimeInMillis() + convertTimeStringToMillisecond(str);
			if (clazz == Date.class) {
				return (T) new Date(time);
			} else if (clazz == java.sql.Date.class) {
				return (T) new java.sql.Date(time);
			} else if (clazz == Timestamp.class) {
				return (T) new java.sql.Timestamp(time);
			} else {
				throw new UnsupportedOperationException(clazz.getName() + " is not supported.");
			}
		}
		return null;
	}

	/**
	 * Returns today with only the date component
	 */
	public static Date getToday() {
		return truncate(new Date(), Calendar.DAY_OF_MONTH);
	}

	/**
	 * Returns the week number within the current year (e.g. if the date is within the first week of the year, it will return 1)
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static final int getWeekOfYear(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar.get(Calendar.WEEK_OF_YEAR);
	}

	/**
	 * Returns the week number within the current year (e.g. if the date is within the first week of the year, it will return 1), where you can specify the
	 * first day of the week
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 * @param firstDayOfWeek
	 *            the first day of the week (e.g. <code>Calendar.SUNDAY</code>, <code>Calendar.MONDAY</code>, etc)
	 */
	public static final int getWeekOfYear(Date date, int firstDayOfWeek) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.setFirstDayOfWeek(firstDayOfWeek);
		return calendar.get(Calendar.WEEK_OF_YEAR);
	}

	/**
	 * Returns {@code true} if the {@code date} is earlier than today (ignoring the time component), returns {@code false} if later than or same as today
	 */
	public static boolean isEarlierThanToday(Date date) {
		return truncate(date, Calendar.DAY_OF_MONTH).getTime() < getToday().getTime();
	}

	/**
	 * Returns {@code true} if the {@code date} is later than today (ignoring the time component), returns {@code false} if earlier than or same as today
	 */
	public static boolean isLaterThanToday(Date date) {
		return truncate(date, Calendar.DAY_OF_MONTH).getTime() > getToday().getTime();
	}

	/**
	 * Returns {@code true} if date {@code a} is earlier than date {@code b} (ignoring the time component), returns {@code false} if later than or same day
	 */
	public static boolean isEarlierThan(Date a, Date b) {
		return truncate(a, Calendar.DAY_OF_MONTH).getTime() < truncate(b, Calendar.DAY_OF_MONTH).getTime();
	}

	/**
	 * Returns {@code true} if date {@code a} is later than date {@code b} (ignoring the time component), returns {@code false} if earlier than or same day
	 */
	public static boolean isLaterThan(Date a, Date b) {
		return truncate(a, Calendar.DAY_OF_MONTH).getTime() > truncate(b, Calendar.DAY_OF_MONTH).getTime();
	}

	/**
	 * Returns {@code true} if date {@code a} is the same date as date {@code b} (ignoring the time component), else returns {@code false}
	 */
	public static boolean isSameDate(Date a, Date b) {
		return truncate(a, Calendar.DAY_OF_MONTH).getTime() == truncate(b, Calendar.DAY_OF_MONTH).getTime();
	}

	/**
	 * Calculate the difference in days of (toDate - fromDate), ignoring the time component of both dates.
	 */
	public static long getDiffInDays(Date fromDate, Date toDate) {
		// ignore the time component of both dates
		Date fromDateT = truncate(fromDate, Calendar.DAY_OF_MONTH);
		Date toDateT = truncate(toDate, Calendar.DAY_OF_MONTH);

		// calculate the difference in ms divided by MILLIS_PER_DAY, thus the difference in days
		return (toDateT.getTime() - fromDateT.getTime()) / MILLIS_PER_DAY;
	}

	/**
	 * Calculate the difference in minutes of (toDate - fromDate), by truncating up to the minute of both dates.
	 */
	public static final long getDiffInMins(Date fromDate, Date toDate) {
		// truncate up to the minute of both dates
		Date fromDateT = DateUtils.truncate(fromDate, Calendar.MINUTE);
		Date toDateT = DateUtils.truncate(toDate, Calendar.MINUTE);

		return (toDateT.getTime() - fromDateT.getTime()) / DateUtils.MILLIS_PER_MINUTE;
	}

	/**
	 * Calculate time diff in mins (same day or within 1 day)
	 */
	public static final int getDiffInMins(String timeFrom, String timeTo) {
		int diffMins = 0;

		// defaults to "0000" if empty
		if (StringUtils.isBlank(timeFrom)) timeFrom = "0000";
		if (StringUtils.isBlank(timeTo)) timeTo = "0000";

		if (!isValid24HourTime(timeFrom) || !isValid24HourTime(timeTo)) {
			return diffMins;
		}

		try {
			int timeFromHour = Integer.parseInt(timeFrom.substring(0, 2));
			int timeFromMin = Integer.parseInt(timeFrom.substring(2));
			int timeToHour = Integer.parseInt(timeTo.substring(0, 2));
			int timeToMin = Integer.parseInt(timeTo.substring(2));

			diffMins += 60 - timeFromMin;
			diffMins += timeToMin;
			diffMins += ((timeToHour - 1) - timeFromHour) * 60;

			if (timeToHour < timeFromHour) {
				// may be across a 00:00
				diffMins += 24 * 60;
			}
		} catch (Exception e) {
		}

		return diffMins;
	}

	public static final String get24HourTimeFromDate(Date date) {
		if (date == null) return StringUtils.EMPTY;
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return DF00.format(calendar.get(Calendar.HOUR_OF_DAY)) + DF00.format(calendar.get(Calendar.MINUTE));
	}

	public static final String get24HourTimeFromDateWithColon(Date date) {
		if (date == null) return StringUtils.EMPTY;
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return DF00.format(calendar.get(Calendar.HOUR_OF_DAY)) + ":" + DF00.format(calendar.get(Calendar.MINUTE));
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of time to the given calendar field to the {@code date} returning a new object (the original {@code date} object
	 * is unchanged), based on the calendar's rules.
	 * </p>
	 * <p>
	 * For example, to subtract 5 days from the {@code date}, you can achieve it by calling:<br>
	 * {@code add(date, Calendar.DAY_OF_MONTH, -5)}.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param calendarField
	 *            the calendar field to add to (e.g. {@code Calendar.DAY_OF_MONTH})
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Date> T add(Class<T> clazz, Date date, int calendarField, int amount) {
		if (date == null) {
			throw new IllegalArgumentException("The date must not be null");
		}
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(calendarField, amount);
		if (clazz == Date.class) {
			return (T) c.getTime();
		} else if (clazz == java.sql.Date.class) {
			return (T) new java.sql.Date(c.getTimeInMillis());
		} else if (clazz == Timestamp.class) {
			return (T) new java.sql.Timestamp(c.getTimeInMillis());
		} else {
			throw new UnsupportedOperationException(clazz.getName() + " is not supported.");
		}
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of years to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
	 * calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addYears(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.YEAR, amount);
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of months to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
	 * calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addMonths(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.MONTH, amount);
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of days to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
	 * calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addDays(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.DAY_OF_MONTH, amount);
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of hours to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
	 * calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addHours(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.HOUR_OF_DAY, amount);
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of minutes to the {@code date} returning a new object (the original {@code date} object is unchanged), based on
	 * the calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addMinutes(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.MINUTE, amount);
	}

	/**
	 * <p>
	 * Adds or subtracts the specified amount of seconds to the {@code date} returning a new object (the original {@code date} object is unchanged), based on
	 * the calendar's rules.
	 * </p>
	 *
	 * @param clazz
	 *            the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
	 * @param date
	 *            the {@code date}, not {@code null}
	 * @param amount
	 *            the amount to add, may be negative
	 *
	 * @return the new {@code date} object with the amount added
	 *
	 * @throws IllegalArgumentException
	 *             if the {@code date} is {@code null}
	 *
	 * @see Calendar#add(int, int)
	 */
	public static <T extends Date> T addSeconds(Class<T> clazz, Date date, int amount) {
		return add(clazz, date, Calendar.SECOND, amount);
	}

	/**
	 * Returns the day of the month represented by this <tt>Date</tt> object.
	 *
	 * @return a value between {@code 1} and {@code 31} representing the day of the month
	 *
	 * @see Calendar#get(Calendar.DAY_OF_MONTH)
	 */
	public static int getDay(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.get(Calendar.DAY_OF_MONTH);
	}

	/**
	 * Returns the day of the week represented by this <tt>Date</tt> object.
	 *
	 * @return a value from {@code 1} (Sunday) to {@code 7} (Saturday) representing the day of the week
	 *
	 * @see Calendar#get(Calendar.DAY_OF_WEEK)
	 */
	public static int getDayOfWeek(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.get(Calendar.DAY_OF_WEEK);
	}

	/**
	 * Returns a number representing the month represented by this <tt>Date</tt> object
	 *
	 * @return a value between {@code 0} and {@code 11} representing the month, with the value {@code 0} representing January
	 *
	 * @see Calendar#get(Calendar.MONTH)
	 */
	public static int getMonth(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.get(Calendar.MONTH);
	}

	/**
	 * Returns the year represented by this <tt>Date</tt> object
	 *
	 * @see Calendar#get(Calendar.YEAR)
	 */
	public static int getYear(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.get(Calendar.YEAR);
	}

	/**
	 * Returns the number of days of the month represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static int getNumberOfDaysOfMonth(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.getActualMaximum(Calendar.DAY_OF_MONTH);
	}

	/**
	 * Returns the number of days by range represented by this <tt>Date</tt> object
	 *
	 * @param dateFrom
	 *            Date Start From
	 * @param dateTo
	 *            Date End To
	 */
	public static int getNumberOfDaysByRange(Date dateFrom, Date dateTo) {
		dateFrom = DateUtils.truncate(dateFrom, Calendar.DAY_OF_MONTH);
		dateTo = DateUtils.truncate(dateTo, Calendar.DAY_OF_MONTH);

		Calendar fromCal = Calendar.getInstance();
		fromCal.setTime(dateFrom);

		Calendar toCal = Calendar.getInstance();
		toCal.setTime(dateTo);
		int count = 0;
		while (DateUtils.isEarlierThan(fromCal.getTime(), toCal.getTime())) {
			count++;
			fromCal.add(Calendar.DAY_OF_MONTH, 1);
		}
		return count;
	}

	/**
	 * Returns the first date of the month represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getFirstDateOfMonth(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_MONTH, 1);
		return calendar.getTime();
	}

	/**
	 * Returns the last date of the month represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getLastDateOfMonth(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
		return calendar.getTime();
	}

	/**
	 * Returns the first date of the last month represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getFirstDateOfLastMonth(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.MONTH, -1);
		c.set(Calendar.DAY_OF_MONTH, 1);
		return c.getTime();
	}

	/**
	 * Returns the last date of the last month represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getLastDateOfLastMonth(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.MONTH, -1);
		c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
		return c.getTime();
	}

	/**
	 * Returns the first date of the year
	 *
	 * @param year
	 *            the year
	 */
	public static Date getFirstDateOfYear(int year) {
		Calendar calendar = Calendar.getInstance();
		calendar.set(year, Calendar.JANUARY, 1, 0, 0, 0);
		return calendar.getTime();
	}

	/**
	 * Returns the last date of the year
	 *
	 * @param year
	 *            the year
	 */
	public static Date getLastDateOfYear(int year) {
		Calendar calendar = Calendar.getInstance();
		calendar.set(year, Calendar.DECEMBER, 31, 0, 0, 0);
		return calendar.getTime();
	}

	/**
	 * Returns the first date of the week represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getFirstDateOfWeek(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.DAY_OF_MONTH, -c.get(Calendar.DAY_OF_WEEK) + 1);
		return c.getTime();
	}

	/**
	 * Returns the first date of the week represented by this <tt>Date</tt> object, where you can specify the first day of the week
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 * @param firstDayOfWeek
	 *            the first day of the week (e.g. <code>Calendar.SUNDAY</code>, <code>Calendar.MONDAY</code>, etc)
	 */
	public static Date getFirstDateOfWeek(Date date, int firstDayOfWeek) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		int diff = c.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;
		if (diff < 0) diff += 7;
		c.add(Calendar.DAY_OF_MONTH, -diff);
		return c.getTime();
	}

	/**
	 * Returns the last date of the week represented by this <tt>Date</tt> object
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 */
	public static Date getLastDateOfWeek(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.DAY_OF_MONTH, 7 - c.get(Calendar.DAY_OF_WEEK));
		return c.getTime();
	}

	/**
	 * Returns the last date of the week represented by this <tt>Date</tt> object, where you can specify the first day of the week
	 *
	 * @param date
	 *            non-{@code null} {@code date} object
	 * @param firstDayOfWeek
	 *            the first day of the week (e.g. <code>Calendar.SUNDAY</code>, <code>Calendar.MONDAY</code>, etc)
	 */
	public static Date getLastDateOfWeek(Date date, int firstDayOfWeek) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		int diff = c.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;
		if (diff < 0) diff += 7;
		c.add(Calendar.DAY_OF_MONTH, 6 - diff);
		return c.getTime();
	}

	/**
	 * Returns the first date of month by year and month
	 *
	 * @param year
	 *            the year
	 * @param month
	 *            a value between {@code 1} and {@code 12} representing January to December
	 */
	public static Date getFirstDateOfMonthByYearAndMonth(int year, int month) {
		Calendar c = Calendar.getInstance();
		c.set(year, month - 1, 1, 0, 0, 0);
		return c.getTime();
	}

	/**
	 * Returns the last date of month by year and month
	 *
	 * @param year
	 *            the year
	 * @param month
	 *            a value between {@code 1} and {@code 12} representing January to December
	 */
	public static Date getLastDateOfMonthByYearAndMonth(int year, int month) {
		Calendar c = Calendar.getInstance();
		c.set(year, month - 1, 1, 0, 0, 0);
		c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
		return c.getTime();
	}

	/**
	 * Returns a new {@link java.sql.Date} object from the value of the {@link java.util.Date} object, returns {@code null} if {@code date} is null
	 *
	 * @param date
	 *            {@code date} object, may be {@code null}
	 */
	public static java.sql.Date toSqlDate(Date date) {
		if (date == null) return null;
		return new java.sql.Date(date.getTime());
	}

	/**
	 * Returns a new {@link java.sql.Timestamp} object from the value of the {@link java.util.Date} object, returns {@code null} if {@code date} is null
	 *
	 * @param date
	 *            {@code date} object, may be {@code null}
	 */
	public static java.sql.Timestamp toTimestamp(Date date) {
		if (date == null) return null;
		return new java.sql.Timestamp(date.getTime());
	}

	/**
	 * Returns the number of days in a month count by the Day of Week (Sunday to Saturday)
	 *
	 * @param year
	 *            4-digit year
	 * @param month
	 *            1 to 12 (Jan to Dec)
	 * @param weekDays
	 *            Calendar.SUNDAY to Calendar.SATURDAY
	 *
	 * @return the number of days in a month count by the Day of Week
	 */
	public static int countDaysByDayOfWeek(int year, int month, int... dayOfWeeks) {
		Calendar c = Calendar.getInstance();
		c.clear();
		c.set(year, month - 1, 1); // first day of month

		int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
		int daysInMonth = c.getActualMaximum(Calendar.DAY_OF_MONTH);

		int count = 0;
		for (int day = 1; day <= daysInMonth; day++) {
			for (int dow : dayOfWeeks) {
				if (dayOfWeek == dow) count++;
			}
			dayOfWeek++;
			if (dayOfWeek > Calendar.SATURDAY) dayOfWeek = Calendar.SUNDAY;
		}
		return count;
	}

	/**
	 * Returns the number of days in a month count by the Day of Week (Sunday to Saturday)
	 *
	 * @param dateFrom
	 *            Date Start From
	 * @param dateTo
	 *            Date End To
	 * @param weekDays
	 *            Calendar.SUNDAY to Calendar.SATURDAY
	 *
	 * @return the number of days in a month count by the Day of Week
	 */
	public static int countDaysByDayOfWeek(Date dateFrom, Date dateTo, int... dayOfWeeks) {
		dateFrom = DateUtils.truncate(dateFrom, Calendar.DAY_OF_MONTH);
		dateTo = DateUtils.truncate(dateTo, Calendar.DAY_OF_MONTH);

		Calendar fromCal = Calendar.getInstance();
		fromCal.setTime(dateFrom);

		Calendar toCal = Calendar.getInstance();
		toCal.setTime(dateTo);

		int count = 0;
		while (DateUtils.isEarlierThan(fromCal.getTime(), toCal.getTime())) {
			int dayOfWeek = fromCal.get(Calendar.DAY_OF_WEEK);
			for (int dow : dayOfWeeks) {
				if (dayOfWeek == dow) {
					count++;
					break;
				}
			}
			fromCal.add(Calendar.DAY_OF_MONTH, 1);
		}
		return count;
	}

	/**
	 * Returns {@code true} if date {@code a} is the same month and year as date {@code b} (ignoring the time component), else returns {@code false}
	 */
	public static boolean isSameMonthYear(Date a, Date b) {
		return getYear(a) == getYear(b) && getMonth(a) == getMonth(b);
	}

	/**
	 * Returns {@code true} if date {@code a} is the same week and year as date {@code b} (ignoring the time component), else returns {@code false}
	 */
	public static boolean isSameWeekYear(Date a, Date b) {
		return getYear(a) == getYear(b) && getWeekOfYear(a) == getWeekOfYear(b);
	}

	/**
	 * new Date() is deprecated
	 * 
	 * @param year
	 *            full year
	 * @param month
	 *            0-based
	 * @param date
	 *            1-based
	 * @return java.util.Date
	 */
	public static Date newDate(int year, int month, int date) {
		Calendar instance = Calendar.getInstance();
		instance.clear();
		instance.set(year, month, date);
		return instance.getTime();
	}

	/**
	 * new Date() is deprecated
	 * 
	 * @param year
	 *            full year
	 * @param month
	 *            0-based
	 * @param date
	 *            1-based
	 * @return java.util.Date
	 */
	public static Date newDate(int year, int month, int date, int hourOfDay, int minute) {
		Calendar instance = Calendar.getInstance();
		instance.clear();
		instance.set(year, month, date, hourOfDay, minute);
		return instance.getTime();
	}

	/**
	 * new Date() is deprecated
	 * 
	 * @param year
	 *            full year
	 * @param month
	 *            0-based
	 * @param date
	 *            1-based
	 * @return java.util.Date
	 */
	public static Date newDate(int year, int month, int date, int hourOfDay, int minute, int second) {
		Calendar instance = Calendar.getInstance();
		instance.clear();
		instance.set(year, month, date, hourOfDay, minute, second);
		return instance.getTime();
	}
}
