package org.rrd4j.core.timespec; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import org.junit.Before; import org.junit.Test; import org.rrd4j.core.timespec.TimeParser; import org.rrd4j.core.timespec.TimeSpec; /** * Mostly about testing TimeParser; relies on TimeSpec to be working as well (and to some degree exercises it implicitly; * if you break TimeSpec, these tests may fail when doing relative times (e.g start = X, end = start+100 and the like) * * @author cmiskell * */ public class TimeParserTest { private final static int ONE_HOUR_IN_MILLIS=60*60*1000; private final static int ONE_DAY_IN_MILLIS=24 * ONE_HOUR_IN_MILLIS; //private final static int ONE_NIGHT_IN_PARIS = 0x7a57e1e55; @Before public void setup() throws InterruptedException { //Yeah, this looks weird, but there's method to my madness. // Many of these tests create Calendars based on the current time, then use the // parsing code which will assume something about when 'now' is, often the current day/week // If we're running around midnight and the Calendar gets created on one day and the parsing // happens on the next, you'll get spurious failures, which would be really annoying and hard // to replicate. Initial tests show that most tests take ~100ms, so if it's within 10 seconds // of midnight, wait for 30 seconds (and print a message saing why) Calendar now = new GregorianCalendar(); if(now.get(Calendar.HOUR_OF_DAY) == 23 && now.get(Calendar.MINUTE) > 59 && now.get(Calendar.SECOND) > 50) { Thread.sleep(30000); } } private long[] parseTimes(String startTime, String endTime) { TimeParser startParser = new TimeParser(startTime); TimeParser endParser = new TimeParser(endTime); TimeSpec specStart = startParser.parse(); TimeSpec specEnd = endParser.parse(); return TimeSpec.getTimestamps(specStart, specEnd); } /** * Set all fields smaller than an hour to 0. * @param now */ private void setSubHourFieldsZero(Calendar cal) { cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); } /** * Set all fields smaller than a day to 0 (rounding down to midnight effectively) * @param now */ private void setSubDayFieldsZero(Calendar cal) { setSubHourFieldsZero(cal); cal.set(Calendar.HOUR_OF_DAY,0); } /** * Kinda like the JUnit asserts for doubles, which allows an "epsilon" * But this is for integers, and with a specific description in the assert * just for timestamps. * * All times are expected in milliseconds * * @param expected - expected value * @param actual - actual value * @param epsilon - the maximum difference * @param desc - some description of the time. Usually "start" or "end", could be others */ private void assertTimestampsEqualWithEpsilon(long expected, long actual, int epsilon, String desc) { assertTrue("Expected a "+desc+" time within "+epsilon+"ms of "+ expected + " but got " + actual, Math.abs(actual - expected) < epsilon); } /** * Test the specification of just an "hour" (assumed today) for * both start and end, using the 24hour clock */ @Test public void test24HourClockHourTodayStartEndTime() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 8); //8am setSubHourFieldsZero(now); Date startDate = now.getTime(); now.set(Calendar.HOUR_OF_DAY, 16); //4pm setSubHourFieldsZero(now); Date endDate = now.getTime(); long[] result = this.parseTimes("08", "16"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test the specification of just an "hour" (assumed today) start using "midnight", end = now */ @Test public void testMidnightToday() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 0); //midnight setSubHourFieldsZero(now); Date startDate = now.getTime(); long[] result = this.parseTimes("midnight", "16"); long start = result[0] * 1000; assertEquals(startDate.getTime(), start); } /** * Test the specification of just an "hour" (assumed today) start using "noon", end = now */ @Test public void testNoonToday() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 12); //noon setSubHourFieldsZero(now); Date startDate = now.getTime(); long[] result = this.parseTimes("noon", "16"); long start = result[0] * 1000; assertEquals(startDate.getTime(), start); } /** * Test the specification of just an "hour" (assumed today) for * both start and end, using am/pm designators */ @Test public void testAMPMClockHourTodayStartEndTime() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 8); //8am setSubHourFieldsZero(now); Date startDate = now.getTime(); now.set(Calendar.HOUR_OF_DAY, 16); //4pm setSubHourFieldsZero(now); Date endDate = now.getTime(); long[] result = this.parseTimes("8am", "4pm"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test that we can explicitly use the term "today", e.g. "today 9am" * NB: This was failing in 1.5.12, so well worth testing :) */ @Test public void testTodayTime() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 9); //9am setSubHourFieldsZero(now); Date startDate = now.getTime(); now.set(Calendar.HOUR_OF_DAY, 17); //5pm setSubHourFieldsZero(now); Date endDate = now.getTime(); long[] result = this.parseTimes("9am today", "5pm today"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test that we can explicitly use the term "yesterday", e.g. "yesterday 9am" * NB: This was failing in 1.5.12, so well worth testing :) */ @Test public void testYesterdayTime() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 9); //9am setSubHourFieldsZero(now); Date startDate = new Date(now.getTimeInMillis()-ONE_DAY_IN_MILLIS); now.set(Calendar.HOUR_OF_DAY, 17); //5pm setSubHourFieldsZero(now); Date endDate = new Date(now.getTimeInMillis()-ONE_DAY_IN_MILLIS); long[] result = this.parseTimes("9am yesterday", "5pm yesterday"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test that we can explicitly use the term "tomorrow", e.g. "tomorrow 9am" * NB: This was failing in 1.5.12, so well worth testing :) */ @Test public void testTomorrowTime() { Calendar now = new GregorianCalendar(); now.set(Calendar.HOUR_OF_DAY, 9); //9am setSubHourFieldsZero(now); Date startDate = new Date(now.getTimeInMillis()+ONE_DAY_IN_MILLIS); now.set(Calendar.HOUR_OF_DAY, 17); //5pm setSubHourFieldsZero(now); Date endDate = new Date(now.getTimeInMillis()+ONE_DAY_IN_MILLIS); long[] result = this.parseTimes("9am tomorrow", "5pm tomorrow"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Tests a simple negative hour offset * * Test the simple start=-1h example from rrdfetch man page. * End is "now" (implied if missing in most user interfaces), * start should be 1 hour prior to now */ @Test public void testSimpleNegativeOffset() { Calendar now = new GregorianCalendar(); Date endDate = now.getTime(); Date startDate = new Date(now.getTimeInMillis()-ONE_HOUR_IN_MILLIS); long[] result = this.parseTimes("-1h", "now"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test a start relative to an end that isn't now */ @Test public void testRelativeStartOffsetEnd() { Calendar now = new GregorianCalendar(); Date endDate = new Date(now.getTimeInMillis()-ONE_HOUR_IN_MILLIS); Date startDate = new Date(now.getTimeInMillis()-(3*ONE_HOUR_IN_MILLIS)); //End is 1 hour ago; start is 2 hours before that long[] result = this.parseTimes("end-2h", "-1h"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test a start relative to an end that isn't now */ @Test public void testRelativeStartOffsetEndAbbreviatedEnd() { Calendar now = new GregorianCalendar(); Date endDate = new Date(now.getTimeInMillis()-ONE_HOUR_IN_MILLIS); Date startDate = new Date(now.getTimeInMillis()-(3*ONE_HOUR_IN_MILLIS)); //End is 1 hour ago; start is 2 hours before that long[] result = this.parseTimes("e-2h", "-1h"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test an end relative to a start that isn't now */ @Test public void testRelativeEndOffsetStart() { Calendar now = new GregorianCalendar(); Date endDate = new Date(now.getTimeInMillis()-(2*ONE_HOUR_IN_MILLIS)); Date startDate = new Date(now.getTimeInMillis()-(4*ONE_HOUR_IN_MILLIS)); long[] result = this.parseTimes("-4h", "start+2h"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test an end relative to a start that isn't now - abbreviated start (s) */ @Test public void testRelativeEndOffsetStartAbbreviatedStart() { Calendar now = new GregorianCalendar(); Date endDate = new Date(now.getTimeInMillis()-(2*ONE_HOUR_IN_MILLIS)); Date startDate = new Date(now.getTimeInMillis()-(4*ONE_HOUR_IN_MILLIS)); long[] result = this.parseTimes("-4h", "s+2h"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test hour:min, and hour.min syntaxes */ @Test public void testHourMinuteSyntax() { Calendar now = new GregorianCalendar(); setSubHourFieldsZero(now); now.set(Calendar.HOUR_OF_DAY, 8); now.set(Calendar.MINUTE, 30); Date startDate = now.getTime(); now.set(Calendar.HOUR_OF_DAY, 16); now.set(Calendar.MINUTE, 45); Date endDate = now.getTime(); //Mixed syntaxes FTW; two tests in one //This also exercises the test of the order of parsing (time then day). If // that order is wrong, 8.30 could be (and was at one point) interpreted as a day.month long[] result = this.parseTimes("8.30", "16:45"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test a plain date specified as DD.MM.YYYY */ @Test public void testDateWithDots() { //Remember: 0-based month Calendar cal = new GregorianCalendar(1980,0,1); Date startDate = cal.getTime(); cal = new GregorianCalendar(1980,11,15); Date endDate = cal.getTime(); //Start is a simple one; end ensures we have our days/months around the right way. long[] result = this.parseTimes("00:00 01.01.1980", "00:00 15.12.1980"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test a plain date specified as DD/MM/YYYY */ @Test public void testDateWithSlashes() { //Remember: 0-based month Calendar cal = new GregorianCalendar(1980,0,1); Date startDate = cal.getTime(); cal = new GregorianCalendar(1980,11,15); Date endDate = cal.getTime(); //Start is a simple one; end ensures we have our days/months around the right way. long[] result = this.parseTimes("00:00 01/01/1980", "00:00 12/15/1980"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test a plain date specified as YYYYMMDD */ @Test public void testDateWithNoDelimiters() { //Remember: 0-based month Calendar cal = new GregorianCalendar(1980,0,1); Date startDate = cal.getTime(); cal = new GregorianCalendar(1980,11,15); Date endDate = cal.getTime(); //Start is a simple one; end ensures we have our days/months around the right way. long[] result = this.parseTimes("00:00 19800101", "00:00 19801215"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test named month dates with no year * * NB: Seems silly to test all, just test that an arbitrary one works, in both short and long form * * If we find actual problems with specific months, we can add more tests */ @Test public void testNamedMonthsNoYear() { //Remember: 0-based month -- 2 = March Calendar cal = new GregorianCalendar(); cal.set(Calendar.MONTH, 2); cal.set(Calendar.DAY_OF_MONTH, 1); this.setSubDayFieldsZero(cal); Date startDate = cal.getTime(); //10 = November cal = new GregorianCalendar(); cal.set(Calendar.MONTH, 10); cal.set(Calendar.DAY_OF_MONTH, 15); this.setSubDayFieldsZero(cal); Date endDate = cal.getTime(); //one short, one long month name long[] result = this.parseTimes("00:00 Mar 1 ", "00:00 November 15"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test named month dates with 2 digit years * * NB: Seems silly to test all, just test that an arbitrary one works, in both short and long form * If we find actual problems with specific months, we can add more tests */ @Test public void testNamedMonthsTwoDigitYear() { //Remember: 0-based month -- 1 = Feb Calendar cal = new GregorianCalendar(1980,1,2); Date startDate = cal.getTime(); //9 = October cal = new GregorianCalendar(1980,9,16); Date endDate = cal.getTime(); long[] result = this.parseTimes("00:00 Feb 2 80", "00:00 October 16 80"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test named month dates with 4 digit years * * NB: Seems silly to test all, just test that an arbitrary one works, in both short and long form * * If we find actual problems with specific months, we can add more tests */ @Test public void testNamedMonthsFourDigitYear() { //Remember: 0-based month -- 3 = April Calendar cal = new GregorianCalendar(1980,3,6); Date startDate = cal.getTime(); //8 = Sept cal = new GregorianCalendar(1980,8,17); Date endDate = cal.getTime(); long[] result = this.parseTimes("00:00 Apr 6 1980", "00:00 September 17 1980"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(startDate.getTime(), start); assertEquals(endDate.getTime(), end); } /** * Test day of week specification. The expected behaviour is annoyingly murky; if these tests start failing * give serious consideration to fixing the tests rather than the underlying code. * */ @Test public void testDayOfWeekTimeSpec() { Calendar cal = new GregorianCalendar(Locale.US); cal.set(Calendar.DAY_OF_WEEK, Calendar.THURSDAY); cal.set(Calendar.HOUR_OF_DAY, 12); this.setSubHourFieldsZero(cal); Date startDate = cal.getTime(); cal = new GregorianCalendar(Locale.US); cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); cal.set(Calendar.HOUR_OF_DAY, 18); this.setSubHourFieldsZero(cal); Date endDate = cal.getTime(); long[] result = this.parseTimes("noon Thursday", "6pm Friday"); long start = result[0] * 1000; long end = result[1] * 1000; assertEquals(start, startDate.getTime()); assertEquals(end, endDate.getTime()); } /** * Test some basic time offsets */ @Test public void testTimeOffsets1() { Calendar cal = new GregorianCalendar(); cal.setTimeInMillis(cal.getTimeInMillis()-60000); Date startDate = cal.getTime(); cal = new GregorianCalendar(); cal.setTimeInMillis(cal.getTimeInMillis()-10000); Date endDate = cal.getTime(); long[] result = this.parseTimes("now - 1minute", "now-10 seconds"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); } /** * Test some basic time offsets * NB: Due to it's use of a "day" offset, this may fail around daylight savings time. * Maybe (Depends how the parsing code constructs it's dates) */ @Test public void testTimeOffsets2() { Calendar cal = new GregorianCalendar(); cal.setTimeInMillis(cal.getTimeInMillis()-ONE_DAY_IN_MILLIS); Date startDate = cal.getTime(); cal = new GregorianCalendar(); cal.setTimeInMillis(cal.getTimeInMillis()-(3*ONE_HOUR_IN_MILLIS)); Date endDate = cal.getTime(); long[] result = this.parseTimes("now - 1 day", "now-3 hours"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); } /** * Test some basic time offsets */ @Test public void testTimeOffsets3() { Calendar cal = new GregorianCalendar(); cal.set(Calendar.MONTH, 6); cal.set(Calendar.DAY_OF_MONTH, 12); cal.add(Calendar.MONTH, -1); Date startDate = cal.getTime(); cal = new GregorianCalendar(); cal.set(Calendar.MONTH, 6); cal.set(Calendar.DAY_OF_MONTH, 12); cal.add(Calendar.WEEK_OF_YEAR, -3); Date endDate = cal.getTime(); long[] result = this.parseTimes("Jul 12 - 1 month", "Jul 12 - 3 weeks"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); } /** * Test another basic time offset */ @Test public void testTimeOffsets4() { //Month 6 = July (0 offset) Calendar cal = new GregorianCalendar(1980, 6, 12); Date endDate = cal.getTime(); cal = new GregorianCalendar(1979, 6, 12); Date startDate = cal.getTime(); long[] result = this.parseTimes("end - 1 year", "00:00 12.07.1980"); long start = result[0] * 1000; long end = result[1] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); assertTimestampsEqualWithEpsilon(endDate.getTime(), end, 1000, "end"); } /** * Test some complex offset examples (per the rrdfetch man page) */ @Test public void complexTest1() { Calendar cal = new GregorianCalendar(); cal.set(Calendar.HOUR_OF_DAY, 9); cal.add(Calendar.DAY_OF_YEAR, -1); setSubHourFieldsZero(cal); Date startDate = cal.getTime(); long[] result = this.parseTimes("noon yesterday -3hours", "now"); long start = result[0] * 1000; assertEquals(startDate.getTime(), start); } /** * Test some more complex offset examples */ @Test public void complexTest2() { Calendar cal = new GregorianCalendar(); cal.add(Calendar.HOUR, -5); cal.add(Calendar.MINUTE, -45); Date startDate = cal.getTime(); long[] result = this.parseTimes("-5h45min", "now"); long start = result[0] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } /** * Test some more complex offset examples */ @Test public void complexTest3() { Calendar cal = new GregorianCalendar(); cal.add(Calendar.MONTH, -5); cal.add(Calendar.WEEK_OF_YEAR, -1); cal.add(Calendar.DAY_OF_YEAR, -2); Date startDate = cal.getTime(); long[] result = this.parseTimes("-5mon1w2d", "now"); long start = result[0] * 1000; assertTimestampsEqualWithEpsilon(startDate.getTime(), start, 1000, "start"); } }