/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ows.kvp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import junit.framework.TestCase;
import org.geotools.util.DateRange;
/**
* Test for the time parameter in a WMS request.
*
* @author Cedric Briancon
* @author Simone Giannecchini, GeoSolutions SAS
* @author Jonathan Meyer, Applied Information Sciences, jon@gisjedi.com
*/
public class TimeKvpParserTest extends TestCase {
/**
* A time period for testing.
*/
private final static String PERIOD = "2007-01-01T12Z/2007-01-31T12Z/P1DT12H";
private final static String CONTINUOUS_PERIOD = "2007-01-01T12Z/2007-01-31T12Z";
private final static String CONTINUOUS_PERIOD_TIME_DURATION = "2007-01-01T12Z/P1DT1H";
private final static String CONTINUOUS_PERIOD_INVALID_DURATION = "P1D/P1DT1H";
private final static String CONTINUOUS_RELATIVE_PERIOD_H = "PT2H/PRESENT";
private final static String CONTINUOUS_RELATIVE_PERIOD_D = "P10D/PRESENT";
private final static String CONTINUOUS_RELATIVE_PERIOD_W = "P2W/PRESENT";
/**
* Format of dates.
*/
private final static DateFormat format;
static {
format = new SimpleDateFormat("yyyy-MM-dd'T'HH'Z'");
format.setTimeZone(TimeParser.UTC_TZ);
}
public void testReducedAccuracyYear() throws Exception {
Calendar c = new GregorianCalendar();
c.setTimeZone(TimeParser.UTC_TZ);
DateRange year = (DateRange) TimeParser.getFuzzyDate("2000");
c.clear();
c.set(Calendar.YEAR, 2000);
assertRangeStarts(year, c.getTime());
c.set(Calendar.YEAR, 2001);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(year, c.getTime());
year = (DateRange) TimeParser.getFuzzyDate("2001");
c.clear();
c.set(Calendar.YEAR, 2001);
assertRangeStarts(year, c.getTime());
c.set(Calendar.YEAR, 2002);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(year, c.getTime());
year = (DateRange) TimeParser.getFuzzyDate("-6052");
c.clear();
c.set(Calendar.ERA, GregorianCalendar.BC);
c.set(Calendar.YEAR, 6053);
assertRangeStarts(year, c.getTime());
c.set(Calendar.YEAR, 6052);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(year, c.getTime());
}
public void testReducedAccuracyHour() throws Exception {
Calendar c = new GregorianCalendar();
c.setTimeZone(TimeParser.UTC_TZ);
c.clear();
DateRange hour = (DateRange) TimeParser.getFuzzyDate("2000-04-04T12Z");
c.set(Calendar.YEAR, 2000);
c.set(Calendar.MONTH, 3); // 0-indexed
c.set(Calendar.DAY_OF_MONTH, 4);
c.set(Calendar.HOUR_OF_DAY, 12);
assertRangeStarts(hour, c.getTime());
c.add(Calendar.HOUR_OF_DAY, 1);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(hour, c.getTime());
hour = (DateRange) TimeParser.getFuzzyDate("2005-12-31T23Z"); // selected due to leapsecond at 23:59:60 UTC
c.clear();
c.set(Calendar.YEAR, 2005);
c.set(Calendar.MONTH, 11);
c.set(Calendar.DAY_OF_MONTH, 31);
c.set(Calendar.HOUR_OF_DAY, 23);
assertRangeStarts(hour, c.getTime());
c.add(Calendar.HOUR_OF_DAY, 1);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(hour, c.getTime());
hour = (DateRange) TimeParser.getFuzzyDate("-25-06-08T17Z");
c.clear();
c.set(Calendar.ERA, GregorianCalendar.BC);
c.set(Calendar.YEAR, 26);
c.set(Calendar.MONTH, 5);
c.set(Calendar.DAY_OF_MONTH, 8);
c.set(Calendar.HOUR_OF_DAY, 17);
assertRangeStarts(hour, c.getTime());
c.add(Calendar.HOUR_OF_DAY, 1);
c.add(Calendar.MILLISECOND, -1);
assertRangeEnds(hour, c.getTime());
}
public void testReducedAccuracyMilliseconds() throws Exception {
Calendar c = new GregorianCalendar();
c.setTimeZone(TimeParser.UTC_TZ);
c.clear();
Date instant = (Date) TimeParser.getFuzzyDate("2000-04-04T12:00:00.000Z");
c.set(Calendar.YEAR, 2000);
c.set(Calendar.MONTH, 3); // 0-indexed
c.set(Calendar.DAY_OF_MONTH, 4);
c.set(Calendar.HOUR_OF_DAY, 12);
assertEquals(instant, c.getTime());
instant = (Date) TimeParser.getFuzzyDate("2005-12-31T23:59:60.000Z"); // selected due to leapsecond at 23:59:60 UTC
c.clear();
c.set(Calendar.YEAR, 2005);
c.set(Calendar.MONTH, 11);
c.set(Calendar.DAY_OF_MONTH, 31);
c.set(Calendar.HOUR_OF_DAY, 23);
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 60);
assertEquals(instant, c.getTime());
instant = (Date) TimeParser.getFuzzyDate("-25-06-08T17:15:00.123Z");
c.clear();
c.set(Calendar.ERA, GregorianCalendar.BC);
c.set(Calendar.YEAR, 26);
c.set(Calendar.MONTH, 5);
c.set(Calendar.DAY_OF_MONTH, 8);
c.set(Calendar.HOUR_OF_DAY, 17);
c.set(Calendar.MINUTE, 15);
c.set(Calendar.MILLISECOND, 123);
assertEquals(instant, c.getTime());
}
/**
* Tests only the increment part of the time parameter.
*
* @throws ParseException if the string can't be parsed.
*/
public void testPeriod() throws ParseException {
final long millisInDay = TimeParser.MILLIS_IN_DAY;
assertEquals( millisInDay, TimeParser.parsePeriod("P1D"));
assertEquals( 3*millisInDay, TimeParser.parsePeriod("P3D"));
assertEquals( 14*millisInDay, TimeParser.parsePeriod("P2W"));
assertEquals( 8*millisInDay, TimeParser.parsePeriod("P1W1D"));
assertEquals( millisInDay, TimeParser.parsePeriod("PT24H"));
assertEquals(Math.round(1.5*millisInDay), TimeParser.parsePeriod("P1.5D"));
}
/**
* Compares the dates obtained by parsing the time parameter with the expected values.
*
* @throws ParseException if the string can't be parsed.
*/
public void testInterval() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse(PERIOD));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertInstant(format.parse("2007-01-01T12Z"), l.get(0));
assertInstant(format.parse("2007-01-03T00Z"), l.get(1));
assertInstant(format.parse("2007-01-04T12Z"), l.get(2));
assertInstant(format.parse("2007-01-06T00Z"), l.get(3));
assertInstant(format.parse("2007-01-07T12Z"), l.get(4));
assertInstant(format.parse("2007-01-09T00Z"), l.get(5));
assertInstant(format.parse("2007-01-10T12Z"), l.get(6));
assertInstant(format.parse("2007-01-12T00Z"), l.get(7));
l = new ArrayList((Collection) timeKvpParser.parse("2007-01-01T12Z/2007-01-31T12Z/PT01S"));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.size()==100);
assertInstant(format.parse("2007-01-01T12Z"), l.get(0));
}
public void testContinuousInterval() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_PERIOD));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
final DateRange range = (DateRange) l.get(0);
assertEquals(format.parse("2007-01-01T12Z"), range.getMinValue());
Date end = format.parse("2007-01-31T13Z");
end.setTime(end.getTime() - 1);
assertEquals(end, range.getMaxValue());
}
public void testContinuousIntervalDuration() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_PERIOD_TIME_DURATION));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
final DateRange range = (DateRange) l.get(0);
assertEquals(format.parse("2007-01-01T12Z"), range.getMinValue());
Date end = format.parse("2007-01-02T13Z");
assertEquals(end, range.getMaxValue());
}
public void testInvalidDualDuration() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
boolean exception = false;
try {
timeKvpParser.parse(CONTINUOUS_PERIOD_INVALID_DURATION);
// Verify that an exception was encountered for the invalid duration
fail("No exception thrown for invalid duration");
} catch (ParseException ex) {
assertTrue(ex.getMessage().startsWith("Invalid time period"));
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testContinuousRelativeInterval() throws ParseException {
final int millisInDay = (int) TimeParser.MILLIS_IN_DAY;
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
Calendar back;
Calendar now;
Calendar check;
List<Collection> l;
DateRange range;
do {
now = Calendar.getInstance();
l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_RELATIVE_PERIOD_H));
check = Calendar.getInstance();
now.set(Calendar.MILLISECOND, 0);
check.set(Calendar.MILLISECOND, 0);
} while (!now.equals(check));
back = (Calendar) now.clone();
back.add(Calendar.HOUR, -2);
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
range = (DateRange) l.get(0);
assertEquals(back.getTime(), range.getMinValue());
assertEquals(now.getTime(), range.getMaxValue());
do {
now = Calendar.getInstance();
l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_RELATIVE_PERIOD_D));
check = Calendar.getInstance();
now.set(Calendar.MILLISECOND, 0);
check.set(Calendar.MILLISECOND, 0);
} while (!now.equals(check));
back = (Calendar) now.clone();
back.add(Calendar.MILLISECOND, millisInDay * -10);
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
range = (DateRange) l.get(0);
assertEquals(back.getTime(), range.getMinValue());
assertEquals(now.getTime(), range.getMaxValue());
do {
now = Calendar.getInstance();
l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_RELATIVE_PERIOD_W));
check = Calendar.getInstance();
now.set(Calendar.MILLISECOND, 0);
check.set(Calendar.MILLISECOND, 0);
} while (!now.equals(check));
back = (Calendar) now.clone();
back.add(Calendar.MILLISECOND, millisInDay * -2 * 7);
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
range = (DateRange) l.get(0);
assertEquals(back.getTime(), range.getMinValue());
assertEquals(now.getTime(), range.getMaxValue());
}
public void testMixedValues() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_PERIOD+",2007-02-01T12Z"));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.get(0) instanceof DateRange);
final DateRange range=(DateRange) l.get(0);
assertEquals(format.parse("2007-01-01T12Z"), range.getMinValue());
Date end = format.parse("2007-01-31T13Z");
end.setTime(end.getTime() - 1);
assertEquals(end, range.getMaxValue());
assertRange((DateRange)l.get(1), format.parse("2007-02-01T12Z"), format.parse("2007-02-01T13Z"));
}
public void testInclusions() throws ParseException {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse(CONTINUOUS_PERIOD+",2007-01-29T12Z," +
"2007-01-12T12Z,2007-01-17T12Z,2007-01-01T12Z/2007-01-15T12Z"));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.size()==1);
assertTrue(l.get(0) instanceof DateRange);
final DateRange range=(DateRange) l.get(0);
assertRange(range, format.parse("2007-01-01T12Z"), format.parse("2007-01-31T13Z"));
}
public void testOrderedValues() throws Exception {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
List l = new ArrayList((Collection) timeKvpParser.parse("2007-01-29T12Z,2007-01-12T12Z," +
"2007-01-17T12Z,2007-01-01T12Z,2007-01-05T12Z"));
// Verify that the list contains at least one element.
assertFalse(l.isEmpty());
assertTrue(l.size()==5);
assertRange((DateRange) l.get(0), format.parse("2007-01-01T12Z"), format.parse("2007-01-01T13Z"));
assertRange((DateRange) l.get(1), format.parse("2007-01-05T12Z"), format.parse("2007-01-05T13Z"));
assertRange((DateRange) l.get(2), format.parse("2007-01-12T12Z"), format.parse("2007-01-12T13Z"));
assertRange((DateRange) l.get(3), format.parse("2007-01-17T12Z"), format.parse("2007-01-17T13Z"));
assertRange((DateRange) l.get(4), format.parse("2007-01-29T12Z"), format.parse("2007-01-29T13Z"));
}
public void testNegativeYearCompliance() throws Exception {
TimeKvpParser timeKvpParser = new TimeKvpParser("TIME");
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
// base assertion - 0001 is year 1
DateRange date = (DateRange) ((List)timeKvpParser.parse("01-06-01")).get(0);
cal.setTime(date.getMinValue());
assertEquals(1, cal.get(Calendar.YEAR));
assertEquals(GregorianCalendar.AD, cal.get(Calendar.ERA));
date = (DateRange) ((List)timeKvpParser.parse("00-06-01")).get(0);
cal.setTime(date.getMinValue());
// calendar calls it year 1, ISO spec means it's year 0
// but we're just parsing here...
assertEquals(1, cal.get(Calendar.YEAR));
assertEquals(GregorianCalendar.BC, cal.get(Calendar.ERA));
// so, the next year should be 2
date = (DateRange) ((List)timeKvpParser.parse("-01-06-01")).get(0);
cal.setTime(date.getMinValue());
assertEquals(2, cal.get(Calendar.YEAR));
assertEquals(GregorianCalendar.BC, cal.get(Calendar.ERA));
// now, big negative year compliance (see the spec, appendix D 2.2, pp 57-58)
date = (DateRange) ((List)timeKvpParser.parse("-18000-06-01")).get(0);
cal.setTime(date.getMinValue());
assertEquals(18001, cal.get(Calendar.YEAR));
assertEquals(GregorianCalendar.BC, cal.get(Calendar.ERA));
}
private static void assertInstant(Date expected, Object object) {
if (object instanceof DateRange) {
assertEquals(object + " Should start at", expected, ((DateRange)object).getMinValue());
assertEquals(object + " Should end at", expected, ((DateRange)object).getMaxValue());
} else if (object instanceof Date) {
assertEquals(expected, object);
} else {
fail("Should have a DateRange: " + object);
}
}
private static void assertRange(DateRange range, Date start, Date end) {
assertRangeStarts(range, start);
assertRangeEnds(range, new Date(end.getTime() - 1));
}
public static void assertRangeLength(DateRange range, long expectedLength) {
if (range.getMinValue() == null)
fail("Expected finite range, saw: " + range);
if (range.getMaxValue() == null)
fail("Expected finite range, saw: " + range);
long min = range.getMinValue().getTime();
long max = range.getMaxValue().getTime();
assertEquals("Range " + range + " should have length", expectedLength, max - min);
}
public static void assertRangeStarts(DateRange range, Date expectedStart) {
if (range.getMinValue() == null)
fail("Expected valid start date in range " + range);
assertEquals("Range " + range + " should have start", expectedStart, range.getMinValue());
}
public static void assertRangeEnds(DateRange range, Date expectedEnd) {
if (range.getMaxValue() == null)
fail("Expected valid end date in range " + range);
assertEquals("Range " + range + " should have end", expectedEnd, range.getMaxValue());
}
}