/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.epl.datetime.calop; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; /** * <p>A suite of utilities surrounding the use of the * {@link java.util.Calendar} and {@link java.util.Date} object.</p> * <p>DateUtils contains a lot of common methods considering manipulations * of Dates or Calendars. Some methods require some extra explanation. * The truncate, ceiling and round methods could be considered the Math.floor(), * Math.ceil() or Math.round versions for dates * This way date-FIELDS will be ignored in bottom-up order. * As a complement to these methods we've introduced some fragment-methods. * With these methods the Date-FIELDS will be ignored in top-down order. * Since a date without a year is not a valid date, you have to decide in what * kind of date-field you want your result, for instance milliseconds or days. * </p> * * @author Apache Software Foundation * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a> * @author Janek Bogucki * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a> * @author Phil Steitz * @author Robert Scholte * @version $Id: DateUtils.java 1056840 2011-01-09 00:12:23Z niallp $ * @since 2.0 */ public class ApacheCommonsDateUtils { /** * The UTC time zone (often referred to as GMT). */ public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT"); /** * Number of milliseconds in a standard second. * * @since 2.1 */ public static final long MILLIS_PER_SECOND = 1000; /** * Number of milliseconds in a standard minute. * * @since 2.1 */ public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND; /** * Number of milliseconds in a standard hour. * * @since 2.1 */ public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE; /** * Number of milliseconds in a standard day. * * @since 2.1 */ public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR; /** * This is half a month, so this represents whether a date is in the top * or bottom half of the month. */ public final static int SEMI_MONTH = 1001; private static final int[][] FIELDS = { {Calendar.MILLISECOND}, {Calendar.SECOND}, {Calendar.MINUTE}, {Calendar.HOUR_OF_DAY, Calendar.HOUR}, {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */ }, {Calendar.MONTH, ApacheCommonsDateUtils.SEMI_MONTH}, {Calendar.YEAR}, {Calendar.ERA}}; /** * Constant marker for truncating */ public final static int MODIFY_TRUNCATE = 0; /** * Constant marker for rounding */ public final static int MODIFY_ROUND = 1; /** * Constant marker for ceiling */ public final static int MODIFY_CEILING = 2; /** * <p>Internal calculation method.</p> * * @param val the calendar * @param field the field constant * @param modType type to truncate, round or ceiling * @throws ArithmeticException if the year is over 280 million */ public static void modify(Calendar val, int field, int modType) { if (val.get(Calendar.YEAR) > 280000000) { throw new ArithmeticException("Calendar value too large for accurate calculations"); } if (field == Calendar.MILLISECOND) { return; } // ----------------- Fix for LANG-59 ---------------------- START --------------- // see http://issues.apache.org/jira/browse/LANG-59 // // Manually truncate milliseconds, seconds and minutes, rather than using // Calendar methods. Date date = val.getTime(); long time = date.getTime(); boolean done = false; // truncate milliseconds int millisecs = val.get(Calendar.MILLISECOND); if (MODIFY_TRUNCATE == modType || millisecs < 500) { time = time - millisecs; } if (field == Calendar.SECOND) { done = true; } // truncate seconds int seconds = val.get(Calendar.SECOND); if (!done && (MODIFY_TRUNCATE == modType || seconds < 30)) { time = time - (seconds * 1000L); } if (field == Calendar.MINUTE) { done = true; } // truncate minutes int minutes = val.get(Calendar.MINUTE); if (!done && (MODIFY_TRUNCATE == modType || minutes < 30)) { time = time - (minutes * 60000L); } // reset time if (date.getTime() != time) { date.setTime(time); val.setTime(date); } // ----------------- Fix for LANG-59 ----------------------- END ---------------- boolean roundUp = false; for (int i = 0; i < FIELDS.length; i++) { for (int j = 0; j < FIELDS[i].length; j++) { if (FIELDS[i][j] == field) { //This is our field... we stop looping if (modType == MODIFY_CEILING || (modType == MODIFY_ROUND && roundUp)) { if (field == ApacheCommonsDateUtils.SEMI_MONTH) { //This is a special case that's hard to generalize //If the date is 1, we round up to 16, otherwise // we subtract 15 days and add 1 month if (val.get(Calendar.DATE) == 1) { val.add(Calendar.DATE, 15); } else { val.add(Calendar.DATE, -15); val.add(Calendar.MONTH, 1); } // ----------------- Fix for LANG-440 ---------------------- START --------------- } else if (field == Calendar.AM_PM) { // This is a special case // If the time is 0, we round up to 12, otherwise // we subtract 12 hours and add 1 day if (val.get(Calendar.HOUR_OF_DAY) == 0) { val.add(Calendar.HOUR_OF_DAY, 12); } else { val.add(Calendar.HOUR_OF_DAY, -12); val.add(Calendar.DATE, 1); } // ----------------- Fix for LANG-440 ---------------------- END --------------- } else { //We need at add one to this field since the // last number causes us to round up val.add(FIELDS[i][0], 1); } } return; } } //We have various FIELDS that are not easy roundings int offset = 0; boolean offsetSet = false; //These are special types of FIELDS that require different rounding rules switch (field) { case ApacheCommonsDateUtils.SEMI_MONTH: if (FIELDS[i][0] == Calendar.DATE) { //If we're going to drop the DATE field's value, // we want to do this our own way. //We need to subtrace 1 since the date has a minimum of 1 offset = val.get(Calendar.DATE) - 1; //If we're above 15 days adjustment, that means we're in the // bottom half of the month and should stay accordingly. if (offset >= 15) { offset -= 15; } //Record whether we're in the top or bottom half of that range roundUp = offset > 7; offsetSet = true; } break; case Calendar.AM_PM: if (FIELDS[i][0] == Calendar.HOUR_OF_DAY) { //If we're going to drop the HOUR field's value, // we want to do this our own way. offset = val.get(Calendar.HOUR_OF_DAY); if (offset >= 12) { offset -= 12; } roundUp = offset >= 6; offsetSet = true; } break; } if (!offsetSet) { int min = val.getActualMinimum(FIELDS[i][0]); int max = val.getActualMaximum(FIELDS[i][0]); //Calculate the offset from the minimum allowed value offset = val.get(FIELDS[i][0]) - min; //Set roundUp if this is more than half way between the minimum and maximum roundUp = offset > ((max - min) / 2); } //We need to remove this field if (offset != 0) { val.set(FIELDS[i][0], val.get(FIELDS[i][0]) - offset); } } throw new IllegalArgumentException("The field " + field + " is not supported"); } }