net.sf.jasperreports.functions.standard.DateTimeFunctions.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.functions.standard.DateTimeFunctions.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.functions.standard;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.DateTimeZone;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.joda.time.Months;
import org.joda.time.Weeks;
import org.joda.time.Years;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.functions.AbstractFunctionSupport;
import net.sf.jasperreports.functions.annotations.Function;
import net.sf.jasperreports.functions.annotations.FunctionCategories;
import net.sf.jasperreports.functions.annotations.FunctionParameter;
import net.sf.jasperreports.functions.annotations.FunctionParameters;
import net.sf.jasperreports.types.date.DateRange;
import net.sf.jasperreports.types.date.DateRangeBuilder;

/**
 * This class should maintain all function methods that belongs to the DateTime category.
 * 
 * @author Massimo Rabbi (mrabbi@users.sourceforge.net)
 */
@FunctionCategories({ DateTimeCategory.class })
public final class DateTimeFunctions extends AbstractFunctionSupport {
    private static final Log log = LogFactory.getLog(DateTimeFunctions.class);

    // ===================== TODAY function ===================== //
    /**
     * Returns the current date as date object.
     */
    @Function("TODAY")
    public Date TODAY() {
        return Calendar.getInstance(getReportTimeZone(), getReportLocale()).getTime();
    }

    // ===================== NOW function ===================== //
    /**
     * Returns the current instant as date object.
     */
    @Function("NOW")
    public Date NOW() {
        return TODAY();
    }

    // ===================== YEAR function ===================== //
    /**
     * Returns the year of a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("YEAR")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer YEAR(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.YEAR);
    }

    // ===================== MONTH function ===================== //
    /**
     * Returns the month of a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("MONTH")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer MONTH(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.MONTH) + 1; // January is 0
    }

    // ===================== DAY function ===================== //
    /**
     * 
     * Returns the day of a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("DAY")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer DAY(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.DAY_OF_MONTH);
    }

    // ===================== WEEKDAY function ===================== //
    /**
     * Returns the day of the week for a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("WEEKDAY")
    @FunctionParameters({ @FunctionParameter("dateObject"), @FunctionParameter("isSundayFirstDay") })
    public Integer WEEKDAY(Object dateObject) {
        return WEEKDAY(dateObject, false);
    }

    public Integer WEEKDAY(Object dateObject, Boolean isSundayFirstDay) {
        Integer dayOfWeek = getCalendarFieldFromDate(dateObject, Calendar.DAY_OF_WEEK);
        if (dayOfWeek == null) {
            if (log.isDebugEnabled()) {
                log.debug("Unable to get the correct day of the week.");
            }
            return null;
        }
        if (isSundayFirstDay) {
            // By default Sunday is considered first day in Java 
            // Calendar.SUNDAY should be a constant with value 1.
            // See the Calendar.DAY_OF_WEEK javadoc      
            return dayOfWeek;
        } else {
            // shift the days
            if (dayOfWeek == Calendar.SUNDAY) {
                return 7;
            } else {
                return dayOfWeek - 1;
            }
        }
    }

    // ===================== HOUR function ===================== //
    /**
     * Returns the hour (0-23) of the day for a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("HOUR")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer HOUR(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.HOUR_OF_DAY);
    }

    // ===================== MINUTE function ===================== //
    /**
     * Returns the minute (0-59) of the hour for a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("MINUTE")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer MINUTE(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.MINUTE);
    }

    // ===================== SECOND function ===================== //
    /**
     * Returns the second (0-59) of the minute for a given date. Date object can be a String, long value (milliseconds) or Date instance itself.
     */
    @Function("SECOND")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Integer SECOND(Object dateObject) {
        return getCalendarFieldFromDate(dateObject, Calendar.SECOND);
    }

    // ===================== DATE function ===================== //
    /**
     *Creates a date object using the specified information on day, month and year.
     */
    @Function("DATE")
    @FunctionParameters({ @FunctionParameter("year"), @FunctionParameter("month"),
            @FunctionParameter("dayOfMonth") })
    public Date DATE(Integer year, Integer month, Integer dayOfMonth) {
        if (year == null || month == null || dayOfMonth == null) {
            if (log.isDebugEnabled()) {
                log.debug("None of the arguments can be null.");
            }
            return null;
        }
        return new DateTime(year, month, dayOfMonth, 0, 0, 0, DateTimeZone.forTimeZone(getReportTimeZone()))
                .toDate();
    }

    // ===================== DATEVALUE function ===================== //
    /**
     * Gives the corresponding numeric value (long milliseconds) for a specified date object.
     */
    @Function("DATEVALUE")
    @FunctionParameters({ @FunctionParameter("dateObject") })
    public Long DATEVALUE(Object dateObject) {
        Date convertedDate = convertDateObject(dateObject);
        if (convertedDate != null) {
            return convertedDate.getTime();
        } else {
            logCannotConvertToDate();
            return null;
        }
    }

    // ===================== TIME function ===================== //
    /**
     * Returns a text string representing a time value (hours, seconds and minutes). If no specific pattern is specified a default formatter is used.
     */
    @Function("TIME")
    @FunctionParameters({ @FunctionParameter("hours"), @FunctionParameter("minutes"), @FunctionParameter("seconds"),
            @FunctionParameter("timePattern") })
    public static String TIME(Integer hours, Integer minutes, Integer seconds) {
        return TIME(hours, minutes, seconds, null);
    }

    public static String TIME(Integer hours, Integer minutes, Integer seconds, String timePattern) {
        if (hours == null || minutes == null || seconds == null) {
            if (log.isDebugEnabled()) {
                log.debug("None of the arguments can be null.");
            }
            return null;
        }
        LocalTime lt = new LocalTime(hours, minutes, seconds);
        if (timePattern == null) {
            return lt.toString(DateTimeFormat.longTime());
        } else {
            try {
                // Try to convert to a pattern
                DateTimeFormatter dtf = DateTimeFormat.forPattern(timePattern);
                return lt.toString(dtf);
            } catch (IllegalArgumentException ex) {
                // Fallback to the default solution
                return lt.toString(DateTimeFormat.longTime());
            }
        }
    }

    // ===================== EDATE function ===================== //
    /**
     * Returns a date a number of months away.
     */
    @Function("EDATE")
    @FunctionParameters({ @FunctionParameter("dateObject"), @FunctionParameter("months") })
    public Date EDATE(Object dateObject, Integer months) {
        Date convertedDate = convertDateObject(dateObject);
        if (convertedDate == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(convertedDate);
            dt = dt.plusMonths(months);
            return dt.toDate();
        }
    }

    // ===================== WORKDAY function ===================== //
    /**
     * Returns a date a number of workdays away. Saturday and Sundays are not considered working days.
     */
    @Function("WORKDAY")
    @FunctionParameters({ @FunctionParameter("dateObject"), @FunctionParameter("workdays") })
    public Date WORKDAY(Object dateObject, Integer workdays) {
        Date convertedDate = convertDateObject(dateObject);
        if (convertedDate == null) {
            logCannotConvertToDate();
            return null;
        } else {
            boolean lookBack = workdays < 0;
            DateTime cursorDT = new DateTime(convertedDate);
            int remainingDays = Math.abs(workdays);
            while (remainingDays > 0) {
                int dayOfWeek = cursorDT.getDayOfWeek();
                if (!(dayOfWeek == DateTimeConstants.SATURDAY || dayOfWeek == DateTimeConstants.SUNDAY)) {
                    // Decrement remaining days only when it is not Saturday or Sunday
                    remainingDays--;
                }
                if (!lookBack) {
                    cursorDT = dayOfWeek == DateTimeConstants.FRIDAY ? cursorDT.plusDays(3) : cursorDT.plusDays(1);
                } else {
                    cursorDT = dayOfWeek == DateTimeConstants.MONDAY ? cursorDT.minusDays(3)
                            : cursorDT.minusDays(1);
                }
            }
            return cursorDT.toDate();
        }
    }

    // ===================== NETWORKDAYS function ===================== //
    /**
     * Returns the number of working days between two dates (inclusive). Saturday and Sunday are not considered working days.
     */
    @Function("NETWORKDAYS")
    @FunctionParameters({ @FunctionParameter("startDate"), @FunctionParameter("endDate") })
    public Integer NETWORKDAYS(Object startDate, Object endDate) {
        Date startDateObj = convertDateObject(startDate);
        if (startDateObj == null) {
            logCannotConvertToDate();
            return null;
        }
        Date endDateObj = convertDateObject(endDate);
        if (endDateObj == null) {
            logCannotConvertToDate();
            return null;
        } else {
            LocalDate cursorLocalDate = new LocalDate(startDateObj);
            LocalDate endLocalDate = new LocalDate(endDateObj);
            int workingDays = 0;
            if (cursorLocalDate.isAfter(endLocalDate)) {
                // Swap data information
                LocalDate tmp = cursorLocalDate;
                cursorLocalDate = endLocalDate;
                endLocalDate = tmp;
            }
            while (Days.daysBetween(cursorLocalDate, endLocalDate).getDays() > 0) {
                int dayOfWeek = cursorLocalDate.getDayOfWeek();
                if (!(dayOfWeek == DateTimeConstants.SATURDAY || dayOfWeek == DateTimeConstants.SUNDAY)) {
                    workingDays++;
                }
                cursorLocalDate = cursorLocalDate.plusDays(1);
            }
            return workingDays;
        }
    }

    // ===================== DAYS function ===================== //
    /**
     * Returns the number of days between two dates.
     */
    @Function("DAYS")
    @FunctionParameters({ @FunctionParameter("startDate"), @FunctionParameter("endDate") })
    public Integer DAYS(Object startDate, Object endDate) {
        Date startDateObj = convertDateObject(startDate);
        if (startDateObj == null) {
            logCannotConvertToDate();
            return null;
        }
        Date endDateObj = convertDateObject(endDate);
        if (endDateObj == null) {
            logCannotConvertToDate();
            return null;
        } else {
            LocalDate dt1 = new LocalDate(startDateObj);
            LocalDate dt2 = new LocalDate(endDateObj);
            return Days.daysBetween(dt1, dt2).getDays();
        }
    }

    // ===================== DAYSINMONTH function ===================== //
    /**
     * Returns the number of days in a month.
     */
    @Function("DAYSINMONTH")
    @FunctionParameters({ @FunctionParameter("dateObj") })
    public Integer DAYSINMONTH(Object dateObj) {
        Date date = convertDateObject(dateObj);
        if (date == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(date);
            return dt.dayOfMonth().getMaximumValue();
        }
    }

    // ===================== DAYSINYEAR function ===================== //
    /**
     * Returns the number of days in a year.
     */
    @Function("DAYSINYEAR")
    @FunctionParameters({ @FunctionParameter("dateObj") })
    public Integer DAYSINYEAR(Object dateObj) {
        Date date = convertDateObject(dateObj);
        if (date == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(date);
            return dt.dayOfYear().getMaximumValue();
        }
    }

    // ===================== WEEKS function ===================== //
    /**
     * Returns the number of weeks between two dates.
     */
    @Function("WEEKS")
    @FunctionParameters({ @FunctionParameter("startDate"), @FunctionParameter("endDate") })
    public Integer WEEKS(Object startDate, Object endDate) {
        Date startDateObj = convertDateObject(startDate);
        if (startDateObj == null) {
            logCannotConvertToDate();
            return null;
        }
        Date endDateObj = convertDateObject(endDate);
        if (endDateObj == null) {
            logCannotConvertToDate();
            return null;
        } else {
            LocalDate dt1 = new LocalDate(startDateObj);
            LocalDate dt2 = new LocalDate(endDateObj);
            return Weeks.weeksBetween(dt1, dt2).getWeeks();
        }
    }

    // ===================== WEEKSINYEAR function ===================== //
    /**
     * Returns the number of weeks in a year.
     */
    @Function("WEEKSINYEAR")
    @FunctionParameters({ @FunctionParameter("dateObj") })
    public Integer WEEKSINYEAR(Object dateObj) {
        Date date = convertDateObject(dateObj);
        if (date == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(date);
            return dt.weekOfWeekyear().getMaximumValue();
        }
    }

    // ===================== WEEKNUM function ===================== //
    /**
     * Returns the week number of a given date.
     */
    @Function("WEEKNUM")
    @FunctionParameters({ @FunctionParameter("dateObj") })
    public Integer WEEKNUM(Object dateObj) {
        Date date = convertDateObject(dateObj);
        if (date == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(date);
            return dt.getWeekOfWeekyear();
        }
    }

    // ===================== MONTHS function ===================== //
    /**
     * Returns the number of months between two dates.
     */
    @Function("MONTHS")
    @FunctionParameters({ @FunctionParameter("startDate"), @FunctionParameter("endDate") })
    public Integer MONTHS(Object startDate, Object endDate) {
        Date startDateObj = convertDateObject(startDate);
        if (startDateObj == null) {
            logCannotConvertToDate();
            return null;
        }
        Date endDateObj = convertDateObject(endDate);
        if (endDateObj == null) {
            logCannotConvertToDate();
            return null;
        } else {
            LocalDate dt1 = new LocalDate(startDateObj);
            LocalDate dt2 = new LocalDate(endDateObj);
            return Months.monthsBetween(dt1, dt2).getMonths();
        }
    }

    // ===================== YEARS function ===================== //
    /**
     * Returns the number of years between two dates.
     */
    @Function("YEARS")
    @FunctionParameters({ @FunctionParameter("startDate"), @FunctionParameter("endDate") })
    public Integer YEARS(Object startDate, Object endDate) {
        Date startDateObj = convertDateObject(startDate);
        if (startDateObj == null) {
            logCannotConvertToDate();
            return null;
        }
        Date endDateObj = convertDateObject(endDate);
        if (endDateObj == null) {
            logCannotConvertToDate();
            return null;
        } else {
            LocalDate dt1 = new LocalDate(startDateObj);
            LocalDate dt2 = new LocalDate(endDateObj);
            return Years.yearsBetween(dt1, dt2).getYears();
        }
    }

    // ===================== ISLEAPYEAR function ===================== //
    /**
     * Checks if the given date occurs in a leap year.
     */
    @Function("ISLEAPYEAR")
    @FunctionParameters({ @FunctionParameter("dateObj") })
    public Boolean ISLEAPYEAR(Object dateObj) {
        Date date = convertDateObject(dateObj);
        if (date == null) {
            logCannotConvertToDate();
            return null;
        } else {
            DateTime dt = new DateTime(date);
            return dt.year().isLeap();
        }
    }

    // ===================== FORMAT function ===================== //
    /**
     * Format the specified date object using the chosen format pattern.
     */
    @Function("DATEFORMAT")
    @FunctionParameters({ @FunctionParameter("dateObj"), @FunctionParameter("formatPattern") })
    public String DATEFORMAT(Date dateObj, String formatPattern) {
        if (dateObj == null) {
            return null;
        } else {
            DateTimeFormatter formatter = DateTimeFormat.forPattern(formatPattern);
            formatter = formatter.withLocale(getReportLocale());
            return new DateTime(dateObj, DateTimeZone.forTimeZone(getReportTimeZone())).toString(formatter);
        }
    }

    // ===================== DATERANGE function ===================== //
    /**
     * Allows to create a JasperReports DateRange instance starting from either a String expression or a Date instance.
     */
    @Function("DATERANGE")
    @FunctionParameters({ @FunctionParameter("dateExprObj") })
    public DateRange DATERANGE(Object dateExprObj) {
        DateRangeBuilder dateRangeBuilder = null;
        if (dateExprObj instanceof String) {
            dateRangeBuilder = new DateRangeBuilder((String) dateExprObj);
        } else if (dateExprObj instanceof Date) {
            dateRangeBuilder = new DateRangeBuilder((Date) dateExprObj);
        } else {
            throw new IllegalArgumentException(
                    "The input parameter for DATERANGE function can be only a String or a Date object");
        }

        dateRangeBuilder.set(getReportLocale());
        dateRangeBuilder.set(getReportTimeZone());

        return dateRangeBuilder.toDateRange();
    }

    /* Internal private methods */

    /**
     * This methods tries to convert a generic object into a java.util.Date instance.
     * Supported types are for now String, Long values (time millis) and Date subtypes
     * like for example java.sql.Date.
     */
    private Date convertDateObject(Object dateObject) {
        if (dateObject == null) {
            if (log.isDebugEnabled()) {
                log.debug("The date object can not be null.");
            }
            return null;
        } else if (dateObject instanceof String) {
            // Try to convert using the different style for pattern.
            // We use MEDIUM as the first one because it is the DEFAULT
            int formatTypes[] = new int[] { DateFormat.MEDIUM, DateFormat.SHORT, DateFormat.LONG, DateFormat.FULL };
            for (int formatType : formatTypes) {
                try {
                    DateFormat df = DateFormat.getDateInstance(formatType, getReportLocale());
                    df.setTimeZone(getReportTimeZone());
                    return df.parse((String) dateObject);
                } catch (ParseException e) {
                    if (log.isDebugEnabled()) {
                        log.debug("Unable to parse the string as Date using the standard SimpleDateFormat.");
                    }
                }
            }
            return null;
        } else if (dateObject instanceof Long) {
            Calendar cal = Calendar.getInstance(getReportTimeZone(), getReportLocale());
            cal.setTimeInMillis((Long) dateObject);
            return cal.getTime();
        } else if (dateObject instanceof Date) {
            return (Date) dateObject;
        }
        if (log.isDebugEnabled()) {
            log.debug("The specified object is not among the allowed types for Date conversion.");
        }
        return null;
    }

    /*
     * Tries to recover a specific detail (given by Calendar field type) 
     * from an input date object.
     */
    private Integer getCalendarFieldFromDate(Object dateObject, int field) {
        Date convertedDate = convertDateObject(dateObject);
        if (convertedDate == null) {
            logCannotConvertToDate();
            return null;
        } else {
            Calendar cal = Calendar.getInstance(getReportTimeZone(), getReportLocale());
            cal.setTime(convertedDate);
            return cal.get(field);
        }
    }

    private static void logCannotConvertToDate() {
        if (log.isDebugEnabled()) {
            log.debug("Unable to convert to a valid Date instance.");
        }
    }

    /*
     * Tries to retrieve the {@link TimeZone} to be used in the report, 
     * using the parameter {@link JRParameter#REPORT_TIME_ZONE}. 
     * If not available it will default the {@link TimeZone#getDefault()} value.
     * 
     * @return the {@link TimeZone} instance to be used
     */
    private TimeZone getReportTimeZone() {
        TimeZone reportTimeZone = TimeZone.getDefault();
        if (getContext() != null) {
            reportTimeZone = (TimeZone) getContext().getParameterValue(JRParameter.REPORT_TIME_ZONE);
        }
        return reportTimeZone;
    }

    /*
     * Tries to retrieve the {@link Locale} to be used in the report, 
     * using the parameter {@link JRParameter#REPORT_LOCALE}. 
     * If not available it will default the {@link Locale#getDefault()} value.
     * 
     * @return the {@link Locale} instance to be used
     */
    private Locale getReportLocale() {
        Locale reportLocale = Locale.getDefault();
        if (getContext() != null) {
            reportLocale = (Locale) getContext().getParameterValue(JRParameter.REPORT_LOCALE);
        }
        return reportLocale;
    }
}