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