/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 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
* @since 2.0
* @version $Id: DateUtils.java 1056840 2011-01-09 00:12:23Z niallp $
*/
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");
}
}