All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.commons.date.DateUtil Maven / Gradle / Ivy

/*
 * Copyright 1997-2008 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.cq.commons.date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TimeZone;

/**
 * DateUtil is a utility class that provides easy access to
 * commonly used dates and for parsing/reading ISO8601 date strings.
 */
public class DateUtil {

    /**
     * default logger
     */
    private static final Logger log = LoggerFactory.getLogger(DateUtil.class);

    /**
     * First day of the week. Defaults to Monday.
     */
    private final int firstDayOfWeek;
    
    /**
     * Initializes a DateUtil with the first day of week being the Monday.
     */
    public DateUtil() {
        this.firstDayOfWeek = Calendar.MONDAY;
    }

    /**
     * Initializes a DateUtil with a custom first day of the week.
     *
     * @param firstDayOfWeek the first day of the week.
     * @see Calendar#DAY_OF_WEEK
     */
    public DateUtil(int firstDayOfWeek) {
        this.firstDayOfWeek = firstDayOfWeek;
    }

    /**
     * @return calendar that represents the beginning of the last year.
     */
    public final Calendar getLastYearStart() {
        Calendar c = getYearStart();
        c.add(Calendar.YEAR, -1);
        return c;
    }

    /**
     * @return calendar that represents the beginning of this year.
     */
    public final Calendar getYearStart() {
        Calendar c = getMonthStart();
        c.set(Calendar.MONTH, 0);
        return c;
    }

    /**
     * @return calendar that represents the date three months ago.
     */
    public final Calendar getThreeMonthsAgo() {
        Calendar c = getToday();
        c.add(Calendar.MONTH, -3);
        return c;
    }

    /**
     * @return calendar that represents the beginning of this month.
     */
    public final Calendar getMonthStart() {
        Calendar c = getToday();
        c.set(Calendar.DAY_OF_MONTH, 1);
        return c;
    }

    /**
     * @return calendar that represents the start of this week. Depends on
     * {@link Calendar#setFirstDayOfWeek(int)}!
     */
    public final Calendar getWeekStart() {
        Calendar c = getToday();
        int firstDayOfWeek = c.getFirstDayOfWeek();
        int thisDayOfWeek = c.get(Calendar.DAY_OF_WEEK);
        if (thisDayOfWeek < firstDayOfWeek) {
            firstDayOfWeek -= 7;
        }
        c.add(Calendar.DAY_OF_MONTH, -(thisDayOfWeek - firstDayOfWeek));
        return c;
    }

    /**
     * @return calendar that represents the current date.
     */
    public final Calendar getToday() {
        Calendar c = getNow();
        c.set(Calendar.MILLISECOND, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.HOUR_OF_DAY, 0);
        return c;
    }

    /**
     * @return calendar that represents the current time.
     */
    public final Calendar getNow() {
        Calendar now = Calendar.getInstance();
        now.setFirstDayOfWeek(firstDayOfWeek);
        return now;
    }

    /**
     * Parse the given string in ISO 8601 format and build a Calendar object. If
     * no timezone is given in the string, it will use the system's default
     * timezone.
     * 
     * @param iso8601Date
     *            the date in ISO 8601 format
     * @return a Calendar instance
     * @exception InvalidDateException
     *                if the date string is not valid
     */
    public static Calendar parseISO8601(String iso8601Date) throws InvalidDateException {
        return parseISO8601(iso8601Date, null);
    }

    /**
     * Parse the given string in ISO 8601 format and build a Calendar object.
     * Allows to define the timezone if the string does not contain a timezone
     * reference. Otherwise the returned calendar will always have the timezone
     * that is in the ISO 8601 string.
     * 
     * @param iso8601Date
     *            the date in ISO 8601 format
     * @param defaultTimeZone
     *            the timezone to use when the timezone is not specified in the
     *            iso-8601 string (if null, the system default timezone will be used)
     * @return a Calendar instance
     * @exception InvalidDateException
     *                if the date string is not valid
     */
    public static Calendar parseISO8601(String iso8601Date, TimeZone defaultTimeZone) throws InvalidDateException {
        // YYYY-MM-DDThh:mm:ss.sTZD
        StringTokenizer st = new StringTokenizer(iso8601Date, "-T:.+Z", true);
        
        if (defaultTimeZone == null) {
            defaultTimeZone = TimeZone.getDefault();
        }
        
        Calendar calendar = Calendar.getInstance(defaultTimeZone);
        calendar.clear();
        try {
            // Year
            if (st.hasMoreTokens()) {
                int year = Integer.parseInt(st.nextToken());
                calendar.set(Calendar.YEAR, year);
            } else {
                return calendar;
            }
            // Month
            if (check(st, "-") && (st.hasMoreTokens())) {
                int month = Integer.parseInt(st.nextToken()) - 1;
                calendar.set(Calendar.MONTH, month);
            } else {
                return calendar;
            }
            // Day
            if (check(st, "-") && (st.hasMoreTokens())) {
                int day = Integer.parseInt(st.nextToken());
                calendar.set(Calendar.DAY_OF_MONTH, day);
            } else {
                return calendar;
            }
            // Hour
            if (check(st, "T") && (st.hasMoreTokens())) {
                int hour = Integer.parseInt(st.nextToken());
                calendar.set(Calendar.HOUR_OF_DAY, hour);
            } else {
                calendar.set(Calendar.HOUR_OF_DAY, 0);
                calendar.set(Calendar.MINUTE, 0);
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);
                return calendar;
            }
            // Minutes
            if (check(st, ":") && (st.hasMoreTokens())) {
                int minutes = Integer.parseInt(st.nextToken());
                calendar.set(Calendar.MINUTE, minutes);
            } else {
                calendar.set(Calendar.MINUTE, 0);
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);
                return calendar;
            }

            //
            // Not mandatory now
            //

            // Seconds
            if (!st.hasMoreTokens()) {
                return calendar;
            }
            String tok = st.nextToken();
            if (tok.equals(":")) { // seconds
                if (st.hasMoreTokens()) {
                    int secondes = Integer.parseInt(st.nextToken());
                    calendar.set(Calendar.SECOND, secondes);
                    if (!st.hasMoreTokens()) {
                        return calendar;
                    }
                    // frac sec
                    tok = st.nextToken();
                    if (tok.equals(".")) {
                        // bug fixed, thx to Martin Bottcher
                        String nt = st.nextToken();
                        while (nt.length() < 3) {
                            nt += "0";
                        }
                        nt = nt.substring(0, 3); // Cut trailing chars..
                        int millisec = Integer.parseInt(nt);
                        // int millisec = Integer.parseInt(st.nextToken()) * 10;
                        calendar.set(Calendar.MILLISECOND, millisec);
                        if (!st.hasMoreTokens()) {
                            return calendar;
                        }
                        tok = st.nextToken();
                    } else {
                        calendar.set(Calendar.MILLISECOND, 0);
                    }
                } else {
                    throw new InvalidDateException("No seconds specified");
                }
            } else {
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);
            }
            // Timezone
            String tzID;
            if (tok.equals("+") || tok.equals("-")) {
                tzID = "GMT" + tok;

                // offset to UTC specified in the format +00:00/-00:00
                if (!st.hasMoreTokens()) {
                    throw new InvalidDateException(
                            "Missing timezone hour field");
                }
                String tzhour = st.nextToken();
                String tzmin;
                if (check(st, ":") && (st.hasMoreTokens())) {
                    tzmin = st.nextToken();
                } else {
                    throw new InvalidDateException(
                            "Missing timezone minute field");
                }
                tzID = tzID + tzhour + ":" + tzmin;
            } else if (tok.equals("Z")) {
                tzID = "GMT";
            } else {
                // invalid time zone designator
                throw new InvalidDateException("only Z, + or - allowed in timezone");
            }
            
            TimeZone tz = TimeZone.getTimeZone(tzID);
            // verify id of returned time zone (getTimeZone defaults to "GMT")
            if (tz.getID().equals("GMT") && !tzID.equals("GMT")) {
                // invalid time zone
                throw new InvalidDateException("only Z, + or - allowed in timezone");
            }
            
            // setTimeZone() has the same effect as if we would have called it
            // before the set() methods; so it does *not* trigger a recalculation
            // of the date according to the new timezone, as if would be the case
            // when calling this method later. The java.util.Calendar javadoc says:
            /*
             * Any field values set in a Calendar will not be interpreted until
             * it needs to calculate its time value (milliseconds from the
             * Epoch) or values of the calendar fields. Calling the get,
             * getTimeInMillis, getTime, add and roll involves such calculation.
             */
            calendar.setTimeZone(tz);
            
        } catch (NumberFormatException ex) {
            throw new InvalidDateException("[" + ex.getMessage()
                    + "] is not an integer");
        }
        return calendar;
    }

    /**
     * Generate a full ISO 8601 date: "YYYY-MM-DDTHH:mm:ss.SSSZ".
     * 
     * @param calendar
     *            a Calendar instance
     * @return a string representing the date in the full ISO 8601 format:
     *         "YYYY-MM-DDTHH:mm:ss.SSSZ"
     */
    public static String getISO8601Date(Calendar calendar) {
        return getISO8601Date(calendar.getTime(), calendar.getTimeZone());
    }

    /**
     * Generate a full ISO 8601 date: "YYYY-MM-DDTHH:mm:ss.SSSZ". It assumes the
     * timezone of the date to be GMT (or UTC, "Z" at the end of ISO8601).
     * 
     * @param date
     *            a Date instance representing a UTC time
     * @return a string representing the date in the full ISO 8601 format:
     *         "YYYY-MM-DDTHH:mm:ss.SSSZ"
     */
    public static String getISO8601Date(Date date) {
        return getISO8601Date(date, TimeZone.getTimeZone("GMT"));
    }

    /**
     * Generate a full ISO 8601 date: "YYYY-MM-DDTHH:mm:ss.SSSZ". It uses the
     * given timezone for interpreting the date value.
     * 
     * @param date
     *            a Date instance
     * @param timeZone
     *            the timeZone of the date
     * 
     * @return a string representing the date in the full ISO 8601 format:
     *         "YYYY-MM-DDTHH:mm:ss.SSSZ"
     */
    public static String getISO8601Date(Date date, TimeZone timeZone) {
        Calendar calendar = new GregorianCalendar(timeZone);
        calendar.setTime(date);
        StringBuffer buffer = new StringBuffer(getISO8601DateNoTime(date, timeZone));
        buffer.append("T");
        buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
        buffer.append(":");
        buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
        buffer.append(":");
        buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
        buffer.append(".");
        buffer.append(threeDigit(calendar.get(Calendar.MILLISECOND)));
        buffer.append("Z");
        return buffer.toString();
    }
    
    /**
     * Generate a ISO 8601 date with date and time, but without the milliseconds
     * part: "YYYY-MM-DDTHH:mm:ssZ".
     * 
     * @param calendar
     *            a Calendar instance
     * @return a string representing date and time except the milliseconds in
     *         the ISO 8601 format: "YYYY-MM-DDTHH:mm:ssZ"
     */
    public static String getISO8601DateAndTimeNoMillis(Calendar calendar) {
        return getISO8601DateAndTimeNoMillis(calendar.getTime(), calendar.getTimeZone());
    }

    /**
     * Generate a ISO 8601 date with date and time, but without the milliseconds
     * part: "YYYY-MM-DDTHH:mm:ssZ". It assumes the timezone of the date to be
     * GMT (or UTC, "Z" at the end of ISO8601).
     * 
     * @param date
     *            a Date instance representing a UTC time
     * @return a string representing date and time except the milliseconds in
     *         the ISO 8601 format: "YYYY-MM-DDTHH:mm:ssZ"
     */
    public static String getISO8601DateAndTimeNoMillis(Date date) {
        return getISO8601DateAndTimeNoMillis(date, TimeZone.getTimeZone("GMT"));
    }

    /**
     * Generate a ISO 8601 date with date and time, but without the milliseconds
     * part: "YYYY-MM-DDTHH:mm:ssZ". It uses the given timezone for interpreting
     * the date value.
     * 
     * @param date
     *            a Date instance
     * @param timeZone
     *            the timeZone of the date
     * @return a string representing date and time except the milliseconds in
     *         the ISO 8601 format: "YYYY-MM-DDTHH:mm:ssZ"
     */
    public static String getISO8601DateAndTimeNoMillis(Date date, TimeZone timeZone) {
        Calendar calendar = new GregorianCalendar(timeZone);
        calendar.setTime(date);
        StringBuffer buffer = new StringBuffer(getISO8601DateNoTime(date, timeZone));
        buffer.append("T");
        buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
        buffer.append(":");
        buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
        buffer.append(":");
        buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
        buffer.append("Z");
        return buffer.toString();
    }

    /**
     * Generate a ISO 8601 date in the pure date form: "YYYY-MM-DD"
     * 
     * @param calendar
     *            a Calendar instance
     * @return a string representing only the date in the ISO 8601 format:
     *         "YYYY-MM-DD"
     */
    public static String getISO8601DateNoTime(Calendar calendar) {
        return getISO8601DateNoTime(calendar.getTime(), calendar.getTimeZone());
    }

    /**
     * Generate a ISO 8601 date in the pure date form: "YYYY-MM-DD". It assumes
     * the timezone of the date to be GMT (or UTC, "Z" at the end of ISO8601).
     * 
     * @param date
     *            a Date instance representing a UTC time.
     * @return a string representing only the date in the ISO 8601 format:
     *         "YYYY-MM-DD"
     */
    public static String getISO8601DateNoTime(Date date) {
        return getISO8601DateNoTime(date, TimeZone.getTimeZone("GMT"));
    }

    /**
     * Generate a ISO 8601 date in the pure date form: "YYYY-MM-DD". It uses the
     * given timezone for interpreting the date value.
     * 
     * @param date
     *            a Date instance representing a date
     * @param timeZone
     *            the timeZone of the date
     * @return a string representing only the date in the ISO 8601 format:
     *         "YYYY-MM-DD"
     */
    public static String getISO8601DateNoTime(Date date, TimeZone timeZone) {
        Calendar calendar = new GregorianCalendar(timeZone);
        calendar.setTime(date);
        StringBuffer buffer = new StringBuffer();
        buffer.append(calendar.get(Calendar.YEAR));
        buffer.append("-");
        buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
        buffer.append("-");
        buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
        return buffer.toString();
    }

    private static String twoDigit(int i) {
        if (i >= 0 && i < 10) {
            return "0" + String.valueOf(i);
        }
        return String.valueOf(i);
    }

    private static String threeDigit(int i) {
        if (i >= 0 && i < 10) {
            return "00" + String.valueOf(i);
        }
        if (i < 100) {
            return "0" + String.valueOf(i);
        }
        return String.valueOf(i);
    }

    private static boolean check(StringTokenizer st, String token)
            throws InvalidDateException {
        try {
            if (st.nextToken().equals(token)) {
                return true;
            } else {
                throw new InvalidDateException("Missing [" + token + "]");
            }
        } catch (NoSuchElementException ex) {
            return false;
        }
    }

    /**
     * Returns a date format object, based on a date pattern and a Locale
     *
     * @param pattern Date pattern
     * @param locale Date format localisation
     * @return Date format or null
     */
    public static DateFormat getDateFormat(String pattern, Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }

        // Try to build date format
        try {
            return new SimpleDateFormat(pattern, locale);
        } catch (Exception e) {
            // Wrong date format provided
            if (pattern != null) {
                log.warn("Invalid date pattern '" + pattern + "': " + e.getMessage());
            }
            return null;
        }
    }

    /**
     * Returns a date format object, based on either a custom date pattern or a default date pattern, and a Locale
     *
     * @param pattern        User provided date pattern
     * @param defaultPattern Default date format used if the user provided date pattern was not valid
     * @param locale Date format localisation
     * @return Custom date format (if valid) or default date format
     */
    public static DateFormat getDateFormat(String pattern, String defaultPattern, Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }

        // Get custom date format
        DateFormat dateFormat = getDateFormat(pattern, locale);

        if (dateFormat == null) {
            // Date format was not valid, using default date pattern
            dateFormat = getDateFormat(defaultPattern, locale);
        }

        return dateFormat;
    }

    /**
     * Returns a date format object, based on either a custom date pattern or a default date pattern, and a Locale
     *
     * @param pattern        User provided date pattern
     * @param defaultDateFormat Default date format used if the user provided date pattern was not valid
     * @param locale Date format localisation
     * @return Custom date format (if valid) or default date format
     */
    public static DateFormat getDateFormat(String pattern, DateFormat defaultDateFormat, Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }

        // Get custom date format
        DateFormat dateFormat = getDateFormat(pattern, locale);

        return dateFormat != null ? dateFormat : defaultDateFormat;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy