Mega Code Archive

 
Categories / Java / Development Class
 

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));     } }*/