A utility class for representing a span of time.
/*
* Copyright 2009 David Jurgens
*
* This file is part of the S-Space package and is covered under the terms and
* conditions therein.
*
* The S-Space package is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation and distributed hereunder to you.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND NO REPRESENTATIONS OR WARRANTIES,
* EXPRESS OR IMPLIED ARE MADE. BY WAY OF EXAMPLE, BUT NOT LIMITATION, WE MAKE
* NO REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY
* PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE OR DOCUMENTATION
* WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER
* RIGHTS.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//package edu.ucla.sspace.util;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A utility class for representing a span of time. Instance of this class can
* be used to determine whether two moments fall within the time span.<p>
*
* Time spans are expressed as a combination of time units and their associated
* amounts. Each amount may add up to be more than one of a larger time unit.
* For example a time unit may be expressed as 1 month and 6 weeks. Time
* amounts that are not used should be set to 0, e.g. a time span representing a
* single day would have only {@code days} set to 1 and all other time units as
* 0.<p>
*
* Note that when the overloaded {@code Date} and {@code long} methods are used,
* the instances are converted to the JVM's current calendar system. This may
* have a slight, noticeable affect due to daylight savings or any other
* calendar-specific differences to how a date is calculated.
*
* @see Calendar
* @see Date
*/
public class TimeSpan {
/**
* The pattern for recognizing an amount of time as a number followed by a
* single character denoting the unit
*/
private static final Pattern TIME_SPAN_PATTERN =
Pattern.compile("\\d+[a-zA-Z]");
/**
* The number of years in this time span
*/
private final int years;
/**
* The number of months in this time span
*/
private final int months;
/**
* The number of weeks in this time span
*/
private final int weeks;
/**
* The number of days in this time span
*/
private final int days;
/**
* The number of hours in this time span
*/
private final int hours;
/**
* Creates a time span using the date string to specify the time units and
* amounts. Time units are specified using a single lower case letter of
* the word for the time unit, e.g. {@code y} for year, {@code m} for month.
*
* @param timespan a string containing numbers each followed by a single
* character to denote the time unit
*
* @throws IllegalArgumentException if <ul> <li> any of the parameters are
* negative <li> if any of the time units are specified more than
* once</ul>
*/
public TimeSpan(String timespan) {
Matcher matcher = TIME_SPAN_PATTERN.matcher(timespan);
// Keep track of which time units have been set so far using a bit flag
// pattern. Each of the 5 units is stored as a single bit in order of
// length, with longest first.
int unitBitFlags = 0;
// Assign default values of 0 to all of the time ranges before hand,
// then overwite those with what data the timespan string contains
int y = 0;
int m = 0;
int w = 0;
int d = 0;
int h = 0;
int prevEnd = 0;
while (matcher.find()) {
// check that the next time unit has a proper format by ensuring
// that all the patterns occur with no character gaps, e.g. 30d is
// valid but 30dd will cause an error from the extra 'd'.
if (matcher.start() != prevEnd) {
throw new IllegalArgumentException(
"invalid time unit format: " + timespan);
}
prevEnd = matcher.end();
String lengthStr =
timespan.substring(matcher.start(), matcher.end() -1);
int length = Integer.parseInt(lengthStr);
checkDuration(length);
char timeUnit = timespan.charAt(matcher.end() - 1);
// Update the appropriate time based on the unit
switch (timeUnit) {
case 'y':
checkSetTwice(unitBitFlags, 0, "years");
unitBitFlags |= (1 << 0);
y = length;
break;
case 'm':
checkSetTwice(unitBitFlags, 1, "months");
unitBitFlags |= (1 << 1);
m = length;
break;
case 'w':
checkSetTwice(unitBitFlags, 2, "weeks");
unitBitFlags |= (1 << 2);
w = length;
break;
case 'd':
checkSetTwice(unitBitFlags, 3, "days");
unitBitFlags |= (1 << 3);
d = length;
break;
case 'h':
checkSetTwice(unitBitFlags, 4, "hours");
unitBitFlags |= (1 << 4);
h = length;
break;
default:
throw new IllegalArgumentException(
"Unknown time unit: " + timeUnit);
}
}
// update the final variables;
years = y;
months = m;
weeks = w;
days = d;
hours = h;
}
/**
* Creates a time span for the specified duration.
*
* @param years the number of years for this time span
* @param months the number of years for this time span
* @param weeks the number of years for this time span
* @param days the number of years for this time span
* @param hours the number of years for this time span
*
* @throws IllegalArgumentException if any of the parameters are negative
*/
public TimeSpan(int years, int months, int weeks, int days, int hours) {
checkDuration(years);
checkDuration(months);
checkDuration(weeks);
checkDuration(days);
checkDuration(hours);
this.years = years;
this.months = months;
this.weeks = weeks;
this.days = days;
this.hours = hours;
}
/**
* Adds the duration of this time span to the provided {@code Calendar}
* instance, moving it forward in time.
*
* @param c the calendar whose date will be moved forward by the duration of
* this time span
*/
public void addTo(Calendar c) {
c.add(Calendar.YEAR, years);
c.add(Calendar.MONTH, months);
c.add(Calendar.WEEK_OF_YEAR, weeks);
c.add(Calendar.DAY_OF_YEAR, days);
c.add(Calendar.HOUR_OF_DAY, hours);
}
/**
* Adds the duration of this time span to the provided {@code Date}
* instance, moving it forward in time.
*
* @param d the date whose value will be moved forward by the duration of
* this time span
*/
public void addTo(Date d) {
Calendar c = Calendar.getInstance();
c.setTime(d);
addTo(c);
d.setTime(c.getTime().getTime());
}
/**
* Checks whether the index is already set in the bit flags and throws an
* exception if so.
*
* @param bigFlag an {code int} bit sequence, where each bit represents a a
* time span value whose value is {@code 1} if that value has been
* set
* @param index the index of the field whose value is to be checked whether
* it has already been set
* @param field the name of the index, which is used in the exception
* message
*
* @throws IllegalArgumentException if the value for the field has already
* been set
*/
private static void checkSetTwice(int bitFlag, int index, String field) {
// check that the field's index has not already been set
if ((bitFlag & (1 << index)) != 0) {
throw new IllegalArgumentException(field + " is set twice");
}
}
/**
* Throws an exception if the duration is negative
*/
private static void checkDuration(int duration) {
if (duration < 0)
throw new IllegalArgumentException(
"Duration must be non-negative");
}
/**
* Returns the day component of this time span. This value does not reflect
* the total number of days that make up this time span, but rather how many
* days were specified in addition to the other time components to comprise
* the total duration.
*
* @return the day component of this time span
*/
public int getDays() {
return days;
}
/**
* Returns the hour component of this time span. This value does not
* reflect the total number of hours that make up this time span, but rather
* how many hours were specified in addition to the other time components to
* comprise the total duration.
*
* @return the hour component of this time span
*/
public int getHours() {
return hours;
}
/**
* Returns the month component of this time span. This value does not
* reflect the total number of months that make up this time span, but
* rather how many months were specified in addition to the other time
* components to comprise the total duration.
*
* @return the month component of this time span
*/
public int getMonths() {
return months;
}
/**
* Returns the week component of this time span. This value does not
* reflect the total number of weeks that make up this time span, but rather
* how many weeks were specified in addition to the other time components to
* comprise the total duration.
*
* @return the week component of this time span
*/
public int getWeeks() {
return weeks;
}
/**
* Returns the year component of this time span. This value does not
* reflect the total number of years that make up this time span, but rather
* how many years were specified in addition to the other time components to
* comprise the total duration.
*
* @return the year component of this time span
*/
public int getYears() {
return years;
}
/**
* Returns {@code true} if the end date occurs after the start date during
* the period of time represented by this time span.
*/
public boolean insideRange(Calendar startDate,
Calendar endDate) {
// make a copy of the start time so that it is safe to modify it without
// affecting the input parameter
Calendar mutableStartDate = (Calendar)(startDate.clone());
return isInRange(mutableStartDate, endDate);
}
/**
* Returns {@code true} if the end date occurs after the start date during
* the period of time represented by this time span.
*
* @param mutableStartDate a <i>mutable<i> {@code Calendar} object that will
* be changed to the ending time of this time range as a side effect
* of this method
*/
private boolean isInRange(Calendar mutableStartDate,
Calendar endDate) {
// ensure that the ending date does not occur before the time span would
// have started
if (endDate.before(mutableStartDate))
return false;
// update the start date to be the date at the end of the time span
Calendar tsEnd = mutableStartDate;
tsEnd.add(Calendar.YEAR, years);
tsEnd.add(Calendar.MONTH, months);
tsEnd.add(Calendar.WEEK_OF_YEAR, weeks);
tsEnd.add(Calendar.DAY_OF_YEAR, days);
tsEnd.add(Calendar.HOUR, hours);
return endDate.before(tsEnd);
}
/**
* Returns {@code true} if the end date occurs after the start date during
* the period of time represented by this time span.
*/
public boolean insideRange(Date startDate, Date endDate) {
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
c1.setTime(startDate);
c2.setTime(endDate);
return isInRange(c1, c2);
}
/**
* Returns {@code true} if the end date occurs after the start date during
* the period of time represented by this time span.
*/
public boolean insideRange(long startDate, long endDate) {
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
c1.setTimeInMillis(startDate);
c2.setTimeInMillis(endDate);
return isInRange(c1, c2);
}
public String toString() {
return String.format("TimeSpan: %dy%dm%dw%dd%dh", years, months,
weeks, days, hours);
}
}
/*
* Copyright 2009 David Jurgens
*
* This file is part of the S-Space package and is covered under the terms and
* conditions therein.
*
* The S-Space package is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation and distributed hereunder to you.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND NO REPRESENTATIONS OR WARRANTIES,
* EXPRESS OR IMPLIED ARE MADE. BY WAY OF EXAMPLE, BUT NOT LIMITATION, WE MAKE
* NO REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY
* PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE OR DOCUMENTATION
* WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER
* RIGHTS.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
package edu.ucla.sspace.util;
import java.util.Calendar;
import java.util.Date;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
// * Tests for the {@link TimeSpan} class.
@SuppressWarnings("deprecation")
public class TimeSpanTests {
@Test public void testAddCalendar() {
TimeSpan ts = new TimeSpan("1y");
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
ts.addTo(c);
assertEquals(year + 1, c.get(Calendar.YEAR));
ts = new TimeSpan("1m");
for (int i = 0; i < 12; ++i) {
int month = c.get(Calendar.MONTH);
ts.addTo(c);
assertEquals((month + 1) % 12, c.get(Calendar.MONTH));
}
}
@SuppressWarnings("deprecated")
@Test public void testAddDate() {
TimeSpan ts = new TimeSpan("1y");
Date d = new Date();
int year = d.getYear();
ts.addTo(d);
assertEquals(year + 1, d.getYear());
ts = new TimeSpan("1m");
for (int i = 0; i < 12; ++i) {
int month = d.getMonth();
ts.addTo(d);
assertEquals((month + 1) % 12, d.getMonth());
}
}
@Test public void testStringConstructor() {
TimeSpan ts = new TimeSpan("1y");
ts = new TimeSpan("1m");
ts = new TimeSpan("1w");
ts = new TimeSpan("1d");
ts = new TimeSpan("1h");
}
@Test public void testStringConstructorCombined() {
TimeSpan ts = new TimeSpan("1y1m1w1d1h");
assertEquals("TimeSpan: 1y1m1w1d1h", ts.toString());
}
@Test public void testStringConstructorCombinedRandomOrder() {
TimeSpan ts = new TimeSpan("1h1d1m1y1w");
assertEquals("TimeSpan: 1y1m1w1d1h", ts.toString());
}
@Test(expected=IllegalArgumentException.class)
public void testStringConstructorRepeat() {
TimeSpan ts = new TimeSpan("1y1y");
}
@Test(expected=IllegalArgumentException.class)
public void testStringConstructorUnknownType() {
TimeSpan ts = new TimeSpan("1z");
}
@Test public void testYear() {
TimeSpan ts = new TimeSpan("1y");
Calendar now = Calendar.getInstance();
Calendar lessThanYearFromNow = Calendar.getInstance();
//lessThanYearFromNow.add(Calendar.YEAR, 1);
lessThanYearFromNow.add(Calendar.MONTH, 1);
assertTrue(ts.insideRange(now, lessThanYearFromNow));
}
@Test public void testYearOutside() {
TimeSpan ts = new TimeSpan("1y");
Calendar now = Calendar.getInstance();
Calendar yearFromNow = Calendar.getInstance();
yearFromNow.add(Calendar.YEAR, 1);
assertFalse(ts.insideRange(now, yearFromNow));
}
@Test public void testMonth() {
TimeSpan ts = new TimeSpan("1m");
Calendar now = Calendar.getInstance();
Calendar lessThanMonthFromNow = Calendar.getInstance();
lessThanMonthFromNow.add(Calendar.WEEK_OF_YEAR, 1);
assertTrue(ts.insideRange(now, lessThanMonthFromNow));
}
@Test public void testMonthOutside() {
TimeSpan ts = new TimeSpan("1m");
Calendar now = Calendar.getInstance();
Calendar monthFromNow = Calendar.getInstance();
monthFromNow.add(Calendar.MONTH, 1);
assertFalse(ts.insideRange(now, monthFromNow));
}
@Test public void testWeek() {
TimeSpan ts = new TimeSpan("1w");
Calendar now = Calendar.getInstance();
Calendar lessThanWeekFromNow = Calendar.getInstance();
lessThanWeekFromNow.add(Calendar.DAY_OF_YEAR, 1);
assertTrue(ts.insideRange(now, lessThanWeekFromNow));
}
@Test public void testWeekOutside() {
TimeSpan ts = new TimeSpan("1w");
Calendar now = Calendar.getInstance();
Calendar weekFromNow = Calendar.getInstance();
weekFromNow.add(Calendar.WEEK_OF_YEAR, 1);
assertFalse(ts.insideRange(now, weekFromNow));
}
@Test public void testDay() {
TimeSpan ts = new TimeSpan("1d");
Calendar now = Calendar.getInstance();
Calendar lessThanDayFromNow = Calendar.getInstance();
lessThanDayFromNow.add(Calendar.HOUR, 1);
assertTrue(ts.insideRange(now, lessThanDayFromNow));
}
@Test public void testDayOutside() {
TimeSpan ts = new TimeSpan("1d");
Calendar now = Calendar.getInstance();
Calendar dayFromNow = Calendar.getInstance();
dayFromNow.add(Calendar.DAY_OF_YEAR, 1);
assertFalse(ts.insideRange(now, dayFromNow));
}
@Test public void testHour() {
TimeSpan ts = new TimeSpan("1h");
Calendar now = Calendar.getInstance();
Calendar lessThanHourFromNow = Calendar.getInstance();
lessThanHourFromNow.add(Calendar.MINUTE, 1);
assertTrue(ts.insideRange(now, lessThanHourFromNow));
}
@Test public void testHourOutside() {
TimeSpan ts = new TimeSpan("1h");
Calendar now = Calendar.getInstance();
Calendar hourFromNow = Calendar.getInstance();
hourFromNow.add(Calendar.HOUR, 1);
assertFalse(ts.insideRange(now, hourFromNow));
}
@Test public void testEndBeforeStart() {
TimeSpan ts = new TimeSpan("1h");
Calendar now = Calendar.getInstance();
Calendar before = Calendar.getInstance();
before.add(Calendar.HOUR, -1);
assertFalse(ts.insideRange(now, before));
}
}*/
Related examples in the same category