From befe68f89eeb343f285e5c93d99028d33a01f09f Mon Sep 17 00:00:00 2001 From: "Larry Shatzer, Jr" Date: Tue, 4 Jan 2011 16:09:59 -0700 Subject: [PATCH 001/134] When specifying a named time, set seconds to 00. --- .../antlr3/com/joestelmach/natty/generated/DateParser.g | 8 ++++---- src/test/gunit/DateParser.testsuite | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index c17066ad..be917759 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -510,10 +510,10 @@ meridian_indicator ; named_time - : (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) AM_PM["pm"] - | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) AM_PM["am"] - | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) AM_PM["pm"] - | MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) AM_PM["am"] + : (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] ; time_zone diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index e6d1beff..1966240d 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -483,10 +483,10 @@ time: "8p" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) "8pm" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) "8 pm" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) -"noon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) pm) -"afternoon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) pm) -"midnight" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) am) -"mid-night" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) am) +"noon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm) +"afternoon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm) +"midnight" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am) +"mid-night" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am) hours: "-1" FAIL From 200c3ee006244cfae8c4f316ded38bc787c2b5eb Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 3 Feb 2011 18:46:46 -0500 Subject: [PATCH 002/134] donation badge --- README | 33 ++------------- src/test/java/com/natty/parse/ParserTest.java | 40 +++++++++---------- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/README b/README index 570e8f73..cc7f0231 100644 --- a/README +++ b/README @@ -1,35 +1,8 @@ Natty is a natural language date parser written in Java. Given a date expression, natty will apply standard language recognition and translation techniques to produce a list of corresponding dates with optional parse and -syntax information. Visit http://natty.joestelmach.com for complete documentation. +syntax information. -========== USAGE ========== +Visit http://natty.joestelmach.com for complete documentation. -import com.joestelmach.natty.*; - -Parser parser = new Parser(); -ParseResult result = parser.parse("next monday"); -List dates = result.getDates(); - -========== CHANGELOG ========== - -* v.0.2.1 - - - implemented beginning/end syntax: - 'beginning of next month' - 'start of last week' - 'last day of 3 octobers from now' - - - implemented day of month: - 'the thirtieth day of 3 aprils ago' - - - bug fix: when seeking to a month whose maximum number of days is less than - the current day, the parser would roll into the next month. - -* v.0.2.0 - - - implemented explicit relative dates like 'wed of next week' - -* v.0.1.0 - - initial release +Click here to lend your support to: Natty and make a donation at www.pledgie.com ! diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java index b3e570d3..afc25646 100644 --- a/src/test/java/com/natty/parse/ParserTest.java +++ b/src/test/java/com/natty/parse/ParserTest.java @@ -1,16 +1,13 @@ package com.natty.parse; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.Arrays; import java.util.Date; import java.util.List; -import junit.framework.Assert; - -import org.junit.Test; - import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; -import com.joestelmach.natty.WalkerState; /** * Runs the DateParser through it's paces @@ -19,22 +16,25 @@ */ public class ParserTest { - @Test - public void leadingSpace() { - Parser parser = new Parser(); - ParseResult result = parser.parse(" 02/18/1985 "); - Assert.assertEquals(1, result.getDates().size()); - } - - public static void main(String[] args) { - String inputString = "in the end of june"; - inputString = "the second day of april"; - inputString = "the thirtieth day of 3 aprils ago"; + public static void main(String[] args) throws Exception { Parser parser = new Parser(); parser.setDebug(true); - ParseResult result = parser.parse(inputString); - System.out.println(result.getSyntaxTree()); - List dateTimes = result.getDates(); - System.out.println(Arrays.toString(dateTimes.toArray())); + + BufferedReader in = new BufferedReader(new InputStreamReader(ParserTest.class.getResourceAsStream("/cpan.txt"))); + String line = null; + while((line = in.readLine()) != null) { + if(line.startsWith("!")) continue; + if(line.startsWith("#")) line = line.substring(1); + + System.out.print(line + " -> "); + try { + ParseResult result = parser.parse(line.trim()); + //System.out.println(result.getSyntaxTree()); + List dateTimes = result.getDates(); + System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); + } catch(Exception e) { + System.out.print("no good"); + } + } } } From 8e4ec318adbb347fc7c35753bb9d8516394d5209 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 3 Feb 2011 18:48:56 -0500 Subject: [PATCH 003/134] markdown --- README => README.markdown | 0 src/test/resources/cpan.txt | 329 ++++++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+) rename README => README.markdown (100%) create mode 100644 src/test/resources/cpan.txt diff --git a/README b/README.markdown similarity index 100% rename from README rename to README.markdown diff --git a/src/test/resources/cpan.txt b/src/test/resources/cpan.txt new file mode 100644 index 00000000..e68509e3 --- /dev/null +++ b/src/test/resources/cpan.txt @@ -0,0 +1,329 @@ +#now +#yesterday +#today +#tomorrow +#morning +#afternoon +#noon +#midnight +#yesterday at noon +#yesterday at midnight +#today at noon +#today at midnight +#tomorrow at noon +#tomorrow at midnight +#yesterday morning +#yesterday afternoon +#today morning +#today afternoon +#tomorrow morning +#tomorrow afternoon +#thursday morning +#thursday afternoon +#6:00 yesterday +#6:00 today +#6:00 tomorrow +#5am yesterday +#5am today +#5am tomorrow +#4pm yesterday +#4pm today +#4pm tomorrow +#this day +#next day +#last week +#this week +#next week +#last month +#this month +#next month +#last year +#this year +#next year +#last friday +#this friday +#next friday +#last week wednesday +#this week wednesday +#next week wednesday +#10 days ago +#10 weeks ago +#10 months ago +#10 years ago +#in 5 days +#in 5 weeks +#in 5 months +#in 5 years +#saturday +#sunday 11:00 +#yesterday at 4:00 +#today at 4:00 +#tomorrow at 4:00 +#yesterday at 6:45am +#today at 6:45am +#tomorrow at 6:45am +#yesterday at 6:45pm +#today at 6:45pm +#tomorrow at 6:45pm +#yesterday at 2:32 AM +#today at 2:32 AM +#tomorrow at 2:32 AM +#yesterday at 2:32 PM +#today at 2:32 PM +#tomorrow at 2:32 PM +#yesterday 02:32 +#today 02:32 +#tomorrow 02:32 +#yesterday 2:32am +#today 2:32am +#tomorrow 2:32am +#yesterday 2:32pm +#today 2:32pm +#tomorrow 2:32pm +#wednesday at 14:30 +#wednesday at 02:30am +#wednesday at 02:30pm +#wednesday 14:30 +#wednesday 02:30am +#wednesday 02:30pm +#friday 03:00 am +#friday 03:00 pm +#sunday at 05:00 am +#sunday at 05:00 pm +#november 3rd +#last june +#next october +#6 am +#5am +#5:30am +#8 pm +#4pm +#4:20pm +#06:56:06 am +#06:56:06 pm +#mon 2:35 +#1:00 sun +#1am sun +#1pm sun +#1:00 on sun +#1am on sun +#1pm on sun +#12:14 PM +#12:14 AM +#2 days before now +#2 weeks before now +#2 months before now +#2 years before now +#4 days from now +#4 weeks from now +#4 months from now +#4 years from now +#9 in the evening +#last sunday at 21:45 +#noon last friday +#midnight last friday +#noon this friday +#midnight this friday +#noon next friday +#midnight next friday +#last friday at 20:00 +#this friday at 20:00 +#next friday at 20:00 +#1:00 last friday +#1:00 this friday +#1:00 next friday +#1am last friday +#1am this friday +#1am next friday +#1pm last friday +#1pm this friday +#1pm next friday +#5 am last monday +#5 am this monday +#5 am next monday +#5 pm last monday +#5 pm this monday +#5 pm next monday +#last wednesday 7am +#this wednesday 7am +#next wednesday 7am +#last wednesday 7pm +#this wednesday 7pm +#next wednesday 7pm +#last tuesday 11 am +#this tuesday 11 am +#next tuesday 11 am +#last tuesday 11 pm +#this tuesday 11 pm +#next tuesday 11 pm +#yesterday at 13:00 +#today at 13:00 +#tomorrow at 13 +#2nd friday in august +#3rd wednesday in november +#6 mondays from now +#last thursday in april +#january 11 +#11 january +#18 oct 17:00 +#18 oct 5am +#18 oct 5pm +#18 oct 5 am +#18 oct 5 pm +#dec 25 +#feb 28 3:00 +#feb 28 3am +#feb 28 3pm +#feb 28 3 am +#feb 28 3 pm +#19:00 jul 1 +#7am jul 1 +#7pm jul 1 +#7 am jul 1 +#7 pm jul 1 +#may 27th +#march 1st 2009 +#february 14, 2004 +#jan 3 2010 +#3 jan 2000 +#2010 october 28 +#1/3 +#1/3 16:00 +#4:00 +#17:00 +#3:20:00 +#tues +#thurs + +// token additions +6 in the morning +4 in the afternoon +monday 6 in the morning +monday 4 in the afternoon +monday 9 in the evening +evening +this morning +this afternoon +this evening +yesterday evening +today evening +tomorrow evening +thursday evening +final thursday in april +final thurs in sep +yesterday @ noon + +// relative time +6 hours before yesterday +6 hours before tomorrow +3 hours after yesterday +3 hours after tomorrow +10 hours before noon +10 hours before midnight +5 hours after noon +5 hours after midnight +last second +this second +next second +last minute +this minute +next minute +last hour +this hour +next hour +10 seconds ago +10 minutes ago +10 hours ago +in 5 seconds +in 5 minutes +in 5 hours +yesterday 7 seconds ago +yesterday 7 minutes ago +yesterday 7 hours ago +tomorrow 3 seconds ago +tomorrow 3 minutes ago +tomorrow 3 hours ago +2 seconds before now +2 minutes before now +2 hours before now +4 seconds from now +4 minutes from now +4 hours from now + +monday last week +6th day last week +6th day this week +6th day next week +12th day last month +12th day this month +12th day next month +1st day last year +1st day this year +1st day next year +1st tuesday last november +1st tuesday this november +1st tuesday next november +11 january next year +11 january this year +11 january last year +tomorrow 1 year ago +saturday 3 months ago at 17:00 +saturday 3 months ago at 5:00am +saturday 3 months ago at 5:00pm +11 january 2 years ago +4th day last week +8th month last year +8th month this year +8th month next year +fri 3 months ago at 5am +wednesday 1 month ago at 8pm +march +2005 +October 2006 +27/5/1979 +-5min ++2d +5 mins ago +tues this week +last day +tuesday last week +tuesday this week +tuesday next week +2nd monday +100th day +4th february +yesterday 7 days ago +yesterday 7 weeks ago +yesterday 7 months ago +yesterday 7 years ago +tomorrow 3 days ago +tomorrow 3 weeks ago +tomorrow 3 months ago +tomorrow 3 years ago + +// timespans +monday to friday +1 April to 31 August +1999-12-31 to tomorrow +now to 2010-01-01 +2009-03-10 9:00 to 11:00 +26 oct 10:00 am to 11:00 am +jan 1 to 2 +16:00 nov 6 to 17:00 +may 2nd to 5th +100th day to 200th +6am dec 5 to 7am +1/3 to 2/3 +2/3 to in 1 week +3/3 21:00 to in 5 days +first day of 2009 to last day of 2009 +first day of may to last day of may +first to last day of 2008 +first to last day of september +for 4 seconds +for 4 minutes +for 4 hours +for 4 days +for 4 weeks +for 4 months +for 4 years \ No newline at end of file From dc83949ae59182da8bd445983400e0dbe1f6c9cc Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 3 Feb 2011 18:50:43 -0500 Subject: [PATCH 004/134] fixing badge --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index cc7f0231..a8e32d00 100644 --- a/README.markdown +++ b/README.markdown @@ -5,4 +5,4 @@ syntax information. Visit http://natty.joestelmach.com for complete documentation. -Click here to lend your support to: Natty and make a donation at www.pledgie.com ! +Click here to lend your support to: Natty and make a donation at www.pledgie.com ! From ad73b39ef479bfcd735787c1245dad83fbcb701b Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 3 Feb 2011 19:48:40 -0500 Subject: [PATCH 005/134] updating link --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index a8e32d00..7f8e2df1 100644 --- a/README.markdown +++ b/README.markdown @@ -3,6 +3,6 @@ expression, natty will apply standard language recognition and translation techniques to produce a list of corresponding dates with optional parse and syntax information. -Visit http://natty.joestelmach.com for complete documentation. +http://natty.joestelmach.com Click here to lend your support to: Natty and make a donation at www.pledgie.com ! From b93c72e143d12d4ac05cd6e6b000a515ee0bb296 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 4 Feb 2011 22:18:07 -0500 Subject: [PATCH 006/134] added morning and evening tokens and allowed them to be used as a time modifier. Added final and @ tokens --- .../joestelmach/natty/generated/DateLexer.g | 13 ++++--- .../joestelmach/natty/generated/DateParser.g | 8 +++- .../joestelmach/natty/generated/DateWalker.g | 2 +- src/test/gunit/DateParser.testsuite | 16 ++++++++ src/test/java/com/natty/parse/ParserTest.java | 38 +++++++++++++++++++ 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index d892d9dc..2dfdc26c 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -43,10 +43,11 @@ T : 't'; MILITARY_HOUR_SUFFIX : 'h'; -MIDNIGHT : 'midnight' | 'mid-night'; -NOON : 'noon' | 'afternoon' | 'after-noon'; -MORNING : 'morning'; -NIGHT : 'night'; +MIDNIGHT : 'midnight' | 'mid-night'; +NOON : 'noon' | 'afternoon' | 'after-noon'; +MORNING : 'morning'; +EVENING : 'evening'; +NIGHT : 'night'; UTC : 'utc' | 'gmt' | 'z'; EST : 'est' | 'edt' | 'et'; @@ -232,12 +233,12 @@ SINGLE_QUOTE : '\''; IN : 'in'; THE : 'the'; OR : 'or'; -AT : 'at'; +AT : 'at' | '@'; ON : 'on'; OF : 'of'; THIS : 'this'; THAT : 'that'; -LAST : 'last'; +LAST : 'last' | 'final'; NEXT : 'next'; PAST : 'past'; COMING : 'coming'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index be917759..be99c393 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -484,7 +484,7 @@ time | hours (WHITE_SPACE? meridian_indicator)? (WHITE_SPACE? time_zone)? -> ^(EXPLICIT_TIME hours ^(MINUTES_OF_HOUR INT["0"]) meridian_indicator? time_zone?) - | named_time (WHITE_SPACE time_zone)? + | (THIS WHITE_SPACE)? named_time (WHITE_SPACE time_zone)? -> ^(EXPLICIT_TIME named_time time_zone?) ; @@ -507,6 +507,11 @@ seconds meridian_indicator : AM -> AM_PM["am"] | PM -> AM_PM["pm"] + | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> AM_PM["am"] + | (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> AM_PM["pm"] + | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> AM_PM["pm"] + | AT? WHITE_SPACE NIGHT -> AM_PM["pm"] + ; named_time @@ -514,6 +519,7 @@ named_time | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] | MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | EVENING -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] ; time_zone diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 822f917b..cd86bc0f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -23,7 +23,7 @@ date_time @after { _walkerState.captureDateTime(); } - : ^(DATE_TIME date time?) + : ^(DATE_TIME date? time?) ; date diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index 1966240d..ea46f596 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -28,6 +28,22 @@ date_time: "wed of the week after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 4)))) "the 28th of the month after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 month) (EXPLICIT_SEEK (DAY_OF_MONTH 28)))) +"6 in the morning" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) +"4 in the afternoon" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm)) +"monday 6 in the morning" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) +"monday 4 in the afternoon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm)) +"monday 9 in the evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 0) pm)) +"evening" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"this morning" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"this afternoon" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) +"this evening" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"today evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"tomorrow evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"thursday evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) +"final thursday in april" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) +"final thurs in sep" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) +"tomorrow @ noon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) + date_time_alternative: "this wed. or next at 5pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))) "feb 28th or 2 days after" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28)))))) diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java index afc25646..6fa262a5 100644 --- a/src/test/java/com/natty/parse/ParserTest.java +++ b/src/test/java/com/natty/parse/ParserTest.java @@ -6,6 +6,9 @@ import java.util.Date; import java.util.List; +import org.junit.Ignore; +import org.junit.Test; + import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; @@ -15,11 +18,45 @@ * @author Joe Stelmach */ public class ParserTest { + @Ignore + @Test + public void test() { + + } public static void main(String[] args) throws Exception { Parser parser = new Parser(); parser.setDebug(true); + String[] strings = new String[]{ + "6 in the morning", + "4 in the afternoon", + "monday 6 in the morning", + "monday 4 in the afternoon", + "monday 9 in the evening", + "evening", + "this morning", + "this afternoon", + "this evening", + "today evening", + "tomorrow evening", + "thursday evening", + "final thursday in april", + "final thurs in sep", + "tomorrow @ noon" + }; + + for(String s:strings) { + //System.out.println("*****"); + System.out.println(s); + ParseResult result = parser.parse(s); + List dateTimes = result.getDates(); + //System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); + System.out.println(result.getSyntaxTree()); + //System.out.println("*****\n\n"); + } + + /* BufferedReader in = new BufferedReader(new InputStreamReader(ParserTest.class.getResourceAsStream("/cpan.txt"))); String line = null; while((line = in.readLine()) != null) { @@ -36,5 +73,6 @@ public static void main(String[] args) throws Exception { System.out.print("no good"); } } + */ } } From 137d1b35d89a941ee50118c6b145c5b25c387a33 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 5 Feb 2011 00:10:38 -0500 Subject: [PATCH 007/134] some renaming in preparation for relative times --- .../joestelmach/natty/generated/DateParser.g | 44 +++++++++---------- src/test/gunit/DateParser.testsuite | 2 +- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index be99c393..57c29c26 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -40,13 +40,9 @@ parse date_time : ( - (date (date_time_separator time)?)=> - date (date_time_separator time)? - - | (date) => date - - | time (time_date_separator date)? - ) -> ^(DATE_TIME date? time?) + (date)=> date (date_time_separator explicit_time)? + | explicit_time (time_date_separator date)? + ) -> ^(DATE_TIME date? explicit_time?) ; date_time_separator @@ -74,41 +70,41 @@ date_time_alternative : (alternative_day_of_week_list)=> alternative_day_of_week_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list) - // month day or day time + // month day or day explicit_time | (alternative_day_of_month_list)=> alternative_day_of_month_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) // date and time OR date and time - | (date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator time)?)+) => - date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator time)?)+ - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date time?)+) + | (date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator explicit_time)?)+) => + date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator explicit_time)?)+ + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) // date OR date time - | (date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator time)?) => - date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date time?)+) + | (date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator explicit_time)?) => + date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator explicit_time)? + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) // this wed. or next | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE OR WHITE_SPACE alternative_direction)=> - (THIS WHITE_SPACE)? day_of_week WHITE_SPACE OR WHITE_SPACE alternative_direction (date_time_separator time)? + (THIS WHITE_SPACE)? day_of_week WHITE_SPACE OR WHITE_SPACE alternative_direction (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) ) // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - | date WHITE_SPACE OR WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) time?))) + | date WHITE_SPACE OR WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) ; alternative_day_of_month_list - : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (WHITE_SPACE OR WHITE_SPACE relaxed_day_of_month)+) (date_time_separator time)?) - -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) time?)+ + : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (WHITE_SPACE OR WHITE_SPACE relaxed_day_of_month)+) (date_time_separator explicit_time)?) + -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ ; alternative_day_of_week_list - : alternative_direction WHITE_SPACE day_of_week (day_of_week_list_separator day_of_week)+ (date_time_separator time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) time?)+ + : alternative_direction WHITE_SPACE day_of_week (day_of_week_list_separator day_of_week)+ (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?)+ ; day_of_week_list_separator @@ -477,7 +473,7 @@ named_relative_date // ********** time rules ********** // a time with an hour, optional minutes, and optional meridian indicator -time +explicit_time : hours COLON? minutes (COLON? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? (WHITE_SPACE? time_zone)? -> ^(EXPLICIT_TIME hours minutes seconds? meridian_indicator? time_zone?) diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index ea46f596..aff183b7 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -480,7 +480,7 @@ named_relative_date: "yesterday" -> (RELATIVE_DATE (SEEK < by_day 1 day)) // ********** time tests ********** -time: +explicit_time: "0600h" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) "06:00h" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) "06:00 hours" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) From 377abefd64198703dfdecfb2ea1b9ffe6234198c Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 5 Feb 2011 00:32:53 -0500 Subject: [PATCH 008/134] basic structure of relative time parsing --- .../com/joestelmach/natty/generated/DateParser.g | 12 ++++++++++++ src/test/java/com/natty/parse/ParserTest.java | 16 +--------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 57c29c26..e24d9779 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -21,6 +21,7 @@ tokens { EXPLICIT_SEEK; SPAN; EXPLICIT_TIME; + RELATIVE_TIME; HOURS_OF_DAY; MINUTES_OF_HOUR; SECONDS_OF_MINUTE; @@ -43,6 +44,8 @@ date_time (date)=> date (date_time_separator explicit_time)? | explicit_time (time_date_separator date)? ) -> ^(DATE_TIME date? explicit_time?) + | relative_time + -> ^(DATE_TIME relative_time?) ; date_time_separator @@ -423,6 +426,10 @@ relative_target | relative_date_span ; +relative_time_target + : HOUR + ; + implicit_prefix : THIS -> DIRECTION[">"] SEEK_BY["by_day"] INT["0"] ; @@ -472,6 +479,11 @@ named_relative_date // ********** time rules ********** +relative_time + : spelled_or_int_optional_prefix WHITE_SPACE relative_time_target WHITE_SPACE relative_suffix + -> ^(RELATIVE_TIME ^(SEEK relative_suffix spelled_or_int_optional_prefix relative_time_target)) + ; + // a time with an hour, optional minutes, and optional meridian indicator explicit_time : hours COLON? minutes (COLON? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? (WHITE_SPACE? time_zone)? diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java index 6fa262a5..08fb168e 100644 --- a/src/test/java/com/natty/parse/ParserTest.java +++ b/src/test/java/com/natty/parse/ParserTest.java @@ -28,21 +28,7 @@ public static void main(String[] args) throws Exception { Parser parser = new Parser(); parser.setDebug(true); String[] strings = new String[]{ - "6 in the morning", - "4 in the afternoon", - "monday 6 in the morning", - "monday 4 in the afternoon", - "monday 9 in the evening", - "evening", - "this morning", - "this afternoon", - "this evening", - "today evening", - "tomorrow evening", - "thursday evening", - "final thursday in april", - "final thurs in sep", - "tomorrow @ noon" + "6 hours ago" }; for(String s:strings) { From b398c63d1495b37e04abfe5e122c6aa18e3734a2 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 6 Feb 2011 12:49:01 -0500 Subject: [PATCH 009/134] implemented relative times: '5 hrs from now', 'in 10 minutes', '2 hours before noon' --- .../joestelmach/natty/generated/DateLexer.g | 13 ++-- .../joestelmach/natty/generated/DateParser.g | 62 ++++++++++++++----- .../joestelmach/natty/generated/DateWalker.g | 20 ++++-- .../com/joestelmach/natty/WalkerState.java | 3 + src/test/gunit/DateParser.testsuite | 19 +++++- src/test/java/com/natty/parse/ParserTest.java | 39 ++++++++++-- 6 files changed, 126 insertions(+), 30 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 2dfdc26c..aebca89f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -25,11 +25,12 @@ THURSDAY : 'thursday' 's'? | 'thur' DOT? | 'thu' DOT? | 'thus' DOT? | 'thurs FRIDAY : 'friday' 's'? | 'fri' DOT? | 'fris' DOT?; SATURDAY : 'saturday' 's'? | 'sat' DOT? | 'sats' DOT? | 'weekend'; -HOUR : 'hour' | 'hours' ; -DAY : 'day' | 'days' ; -WEEK : 'week' | 'weeks' ; -MONTH : 'month' | 'months' ; -YEAR : 'year' | 'years' ; +HOUR : 'hour' | 'hours' | 'hr' | 'hrs'; +MINUTE : 'minute' | 'minutes' | 'min' | 'mins'; +DAY : 'day' | 'days' ; +WEEK : 'week' | 'weeks' | 'wks'; +MONTH : 'month' | 'months'; +YEAR : 'year' | 'years' | 'yrs'; TODAY : 'today'; TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow'; @@ -193,7 +194,7 @@ TWENTY : 'twenty'; THIRTY : 'thirty'; FIRST : 'first'; -SECOND : 'second'; +SECOND : 'second' | 'seconds' | 'sec' | 'secs'; THIRD : 'third'; FOURTH : 'fourth'; FIFTH : 'fifth'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index e24d9779..579eafd2 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -274,8 +274,8 @@ formal_date_separator relative_date // next wed, last month - : relative_prefix WHITE_SPACE relative_target - -> ^(RELATIVE_DATE ^(SEEK relative_prefix relative_target)) + : relative_date_prefix WHITE_SPACE relative_target + -> ^(RELATIVE_DATE ^(SEEK relative_date_prefix relative_target)) // this month, this week | implicit_prefix WHITE_SPACE relative_target @@ -287,8 +287,8 @@ relative_date -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) // one month from now - | spelled_or_int_optional_prefix WHITE_SPACE relative_target WHITE_SPACE relative_suffix - -> ^(RELATIVE_DATE ^(SEEK relative_suffix spelled_or_int_optional_prefix relative_target)) + | spelled_or_int_optional_prefix WHITE_SPACE relative_target WHITE_SPACE relative_date_suffix + -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix relative_target)) // the week after next | (THE WHITE_SPACE)? relative_date_span WHITE_SPACE AFTER WHITE_SPACE NEXT @@ -304,9 +304,9 @@ relative_date explicit_relative_date // 1st of three months ago, 10th of 3 octobers from now, the last monday in 2 novembers ago : explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix - WHITE_SPACE explicit_relative_month WHITE_SPACE relative_suffix + WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix -> ^(RELATIVE_DATE - ^(SEEK relative_suffix spelled_or_int_optional_prefix explicit_relative_month) + ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) explicit_day_of_month_part) // 10th of next month, 31st of last month, 10th of next october, 30th of this month, the last thursday of last november @@ -330,9 +330,9 @@ explicit_relative_date // monday of 2 weeks ago, tuesday of 3 weeks from now | explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix - WHITE_SPACE WEEK WHITE_SPACE relative_suffix + WHITE_SPACE WEEK WHITE_SPACE relative_date_suffix -> ^(RELATIVE_DATE - ^(SEEK relative_suffix spelled_or_int_optional_prefix SPAN["week"]) + ^(SEEK relative_date_suffix spelled_or_int_optional_prefix SPAN["week"]) explicit_day_of_week_part) // monday of the week after next @@ -427,14 +427,20 @@ relative_target ; relative_time_target - : HOUR + : relative_time_span + ; + +relative_time_span + : HOUR -> SPAN["hour"] + | MINUTE -> SPAN["minute"] + | SECOND -> SPAN["second"] ; implicit_prefix : THIS -> DIRECTION[">"] SEEK_BY["by_day"] INT["0"] ; -relative_prefix +relative_date_prefix : (THIS WHITE_SPACE)? LAST -> DIRECTION["<"] SEEK_BY["by_week"] INT["1"] | (THIS WHITE_SPACE)? NEXT -> DIRECTION[">"] SEEK_BY["by_week"] INT["1"] | (THIS WHITE_SPACE)? PAST -> DIRECTION["<"] SEEK_BY["by_day"] INT["1"] @@ -445,15 +451,35 @@ relative_prefix ; prefix - : relative_prefix + : relative_date_prefix | implicit_prefix ; -relative_suffix +relative_date_suffix + // from now, after today : (FROM | AFTER) WHITE_SPACE (NOW | TODAY) -> DIRECTION[">"] SEEK_BY["by_day"] | AGO -> DIRECTION["<"] SEEK_BY["by_day"] ; +relative_time_suffix + // from now, after today, before noon, after 4pm + : (FROM | AFTER) (WHITE_SPACE relative_time_suffix_anchor)? + -> DIRECTION[">"] SEEK_BY["by_day"] relative_time_suffix_anchor + + // before noon, before 3pm + | BEFORE (WHITE_SPACE relative_time_suffix_anchor)? + -> DIRECTION["<"] SEEK_BY["by_day"] relative_time_suffix_anchor + + | AGO + -> DIRECTION["<"] SEEK_BY["by_day"] + ; + +relative_time_suffix_anchor + : named_relative_time + | explicit_time + -> ^(EXPLICIT_SEEK explicit_time) + ; + relative_date_span : DAY -> SPAN["day"] | WEEK -> SPAN["week"] @@ -477,11 +503,19 @@ named_relative_date | YESTERDAY -> ^(RELATIVE_DATE ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["1"] SPAN["day"])) ; +named_relative_time + : NOW -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"])) + ; + // ********** time rules ********** relative_time - : spelled_or_int_optional_prefix WHITE_SPACE relative_time_target WHITE_SPACE relative_suffix - -> ^(RELATIVE_TIME ^(SEEK relative_suffix spelled_or_int_optional_prefix relative_time_target)) + // 10 hours ago, 20 minutes before noon + : spelled_or_int_optional_prefix WHITE_SPACE relative_time_target WHITE_SPACE relative_time_suffix + -> ^(RELATIVE_TIME ^(SEEK relative_time_suffix spelled_or_int_optional_prefix relative_time_target)) + + | IN WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE relative_time_target + -> ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_target)) ; // a time with an hour, optional minutes, and optional meridian indicator diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index cd86bc0f..463ccd77 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -46,23 +46,33 @@ explicit_date {_walkerState.setExplicitDate($month.text, $dom.text, $dow.text, $year.text);} ; + time + : explicit_time + | relative_time + ; + +explicit_time : ^(EXPLICIT_TIME ^(HOURS_OF_DAY hours=INT) ^(MINUTES_OF_HOUR minutes=INT) (^(SECONDS_OF_MINUTE seconds=INT))? AM_PM? (zone=ZONE | zone=ZONE_OFFSET)?) {_walkerState.setExplicitTime($hours.text, $minutes.text, $seconds.text, $AM_PM.text, $zone.text);} ; +relative_time + : ^(RELATIVE_TIME seek) + ; + seek - : ^(SEEK DIRECTION by=SEEK_BY amount=INT ^(DAY_OF_WEEK day=INT) date?) + : ^(SEEK DIRECTION by=SEEK_BY amount=INT ^(DAY_OF_WEEK day=INT)) {_walkerState.seekToDayOfWeek($DIRECTION.text, $by.text, $amount.text, $day.text);} | ^(SEEK DIRECTION SEEK_BY amount=INT ^(MONTH_OF_YEAR month=INT)) {_walkerState.seekToMonth($DIRECTION.text, $amount.text, $month.text);} - | ^(SEEK DIRECTION SEEK_BY INT SPAN) + | ^(SEEK DIRECTION SEEK_BY (explicit_seek | relative_date)? INT SPAN) {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SPAN.text);} - | ^(SEEK DIRECTION SEEK_BY INT date) + | ^(SEEK DIRECTION SEEK_BY INT) {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SEEK_BY.text);} ; @@ -78,4 +88,6 @@ explicit_seek | ^(EXPLICIT_SEEK index=INT ^(DAY_OF_WEEK day=INT)) {_walkerState.setDayOfWeekIndex($index.text, $day.text);} - ; + + | ^(EXPLICIT_SEEK explicit_time) + ; \ No newline at end of file diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index a4d38b4b..d335399e 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -164,6 +164,9 @@ public void seekBySpan(String direction, String seekAmount, String span) { span.equals("week") ? Calendar.WEEK_OF_YEAR : span.equals("month") ? Calendar.MONTH : span.equals("year") ? Calendar.YEAR : + span.equals("hour") ? Calendar.HOUR: + span.equals("minute") ? Calendar.MINUTE: + span.equals("second") ? Calendar.SECOND: null; if(field > 0) _calendar.add(field, seekAmountInt * sign); } diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index aff183b7..5ab5ed75 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -44,6 +44,21 @@ date_time: "final thurs in sep" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) "tomorrow @ noon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) +"6 hours ago" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day 6 hour))) +"10 hrs before noon" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 10 hour))) +"10 hr before midnight" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 10 hour))) +"5 hours after noon" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 5 hour))) +"5 hours after midnight" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 5 hour))) +"in 5 seconds" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 second))) +"in 5 minutes" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 minute))) +"in 5 hours" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 hour))) +"4 secs from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second))) +"4 sec from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second))) +"4 minutes from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) +"4 mins from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) +"4 min from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) +"4 hours from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 hour))) + date_time_alternative: "this wed. or next at 5pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))) "feb 28th or 2 days after" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28)))))) @@ -358,7 +373,7 @@ relative_target: implicit_prefix: "this" -> "> by_day 0" -relative_prefix: +relative_date_prefix: "this last" -> "< by_week 1" "last" -> "< by_week 1" "this past" -> "< by_day 1" @@ -374,7 +389,7 @@ relative_prefix: "3" -> "> by_day 3" "twenty-eight" -> "> by_day 28" -relative_suffix: +relative_date_suffix: "from now" -> "> by_day" "ago" -> "< by_day" diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java index 08fb168e..408f4708 100644 --- a/src/test/java/com/natty/parse/ParserTest.java +++ b/src/test/java/com/natty/parse/ParserTest.java @@ -4,8 +4,11 @@ import java.io.InputStreamReader; import java.util.Arrays; import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; +import junit.framework.Assert; + import org.junit.Ignore; import org.junit.Test; @@ -20,15 +23,43 @@ public class ParserTest { @Ignore @Test - public void test() { - + public void testRelativeTimes() { + //Date currentDate = new Date(); + Assert.assertNotNull(parseSingleDate("6 days ago")); + } + + private Date parseSingleDate(String value) { + Parser parser = new Parser(); + ParseResult result = parser.parse(value); + List dateTimes = result.getDates(); + return dateTimes.get(0); } public static void main(String[] args) throws Exception { Parser parser = new Parser(); parser.setDebug(true); String[] strings = new String[]{ - "6 hours ago" + + //"6 days ago", + //"6 hours ago", + //"10 hours before noon", + //"10 hours before midnight", + //"5 hours after noon", + //"5 hours after midnight", + //"in 5 seconds", + //"in 5 minutes", + //"in 5 hours", + //"4 seconds from now", + "4 min from now", + "4 hrs from now", + "4 sec from now", + + //"this second", + //"next second", + //"this minute", + //"next minute", + //"this hour", + //"next hour", }; for(String s:strings) { @@ -36,7 +67,7 @@ public static void main(String[] args) throws Exception { System.out.println(s); ParseResult result = parser.parse(s); List dateTimes = result.getDates(); - //System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); + System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); System.out.println(result.getSyntaxTree()); //System.out.println("*****\n\n"); } From f28c810fcf164b0b9f3c0cf25b51b86b027d8d75 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 6 Feb 2011 23:07:56 -0500 Subject: [PATCH 010/134] initial range support as collection of dates --- .../joestelmach/natty/generated/DateLexer.g | 1 + .../joestelmach/natty/generated/DateParser.g | 36 ++++++---- .../java/com/joestelmach/natty/Parser.java | 3 +- src/test/gunit/DateParser.testsuite | 13 ++++ src/test/java/com/natty/parse/ParserTest.java | 69 ++++++++++++------- 5 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index aebca89f..6a01df7b 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -234,6 +234,7 @@ SINGLE_QUOTE : '\''; IN : 'in'; THE : 'the'; OR : 'or'; +TO : 'to'; AT : 'at' | '@'; ON : 'on'; OF : 'of'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 579eafd2..456c1a6b 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -77,31 +77,41 @@ date_time_alternative | (alternative_day_of_month_list)=> alternative_day_of_month_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) - // date and time OR date and time - | (date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator explicit_time)?)+) => - date (WHITE_SPACE OR WHITE_SPACE date (date_time_separator explicit_time)?)+ + // date and time OR date and time, 1/2 at 2pm or 1/3 at 3pm + | (date (WHITE_SPACE (OR | TO) WHITE_SPACE date (date_time_separator explicit_time)?)+) => + date (WHITE_SPACE (OR | TO) WHITE_SPACE date (date_time_separator explicit_time)?)+ -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) - // date OR date time - | (date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator explicit_time)?) => - date (WHITE_SPACE OR WHITE_SPACE date)+ (date_time_separator explicit_time)? + // date OR date time, 1/2 to 1/4 at 6pm + | (date (WHITE_SPACE (OR | TO) WHITE_SPACE date)+ (date_time_separator explicit_time)?) => + date (WHITE_SPACE (OR | TO) WHITE_SPACE date)+ (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) + + // date time or time + | (date date_time_separator explicit_time WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time)=> + date date_time_separator explicit_time WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time) ^(DATE_TIME date $end_time)) + + // time date or time + | (explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time)=> + explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time) ^(DATE_TIME date $end_time)) // this wed. or next - | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE OR WHITE_SPACE alternative_direction)=> - (THIS WHITE_SPACE)? day_of_week WHITE_SPACE OR WHITE_SPACE alternative_direction (date_time_separator explicit_time)? + | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction)=> + (THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) ) // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - | date WHITE_SPACE OR WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? + | date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) ; alternative_day_of_month_list - : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (WHITE_SPACE OR WHITE_SPACE relaxed_day_of_month)+) (date_time_separator explicit_time)?) + : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (WHITE_SPACE (OR | TO) WHITE_SPACE relaxed_day_of_month)+) (date_time_separator explicit_time)?) -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ ; @@ -111,12 +121,12 @@ alternative_day_of_week_list ; day_of_week_list_separator - : COMMA (WHITE_SPACE | WHITE_SPACE OR WHITE_SPACE) - | WHITE_SPACE OR WHITE_SPACE + : COMMA (WHITE_SPACE | WHITE_SPACE (OR | TO) WHITE_SPACE) + | WHITE_SPACE (OR | TO) WHITE_SPACE ; date_list_separator - : (COMMA WHITE_SPACE?) | (WHITE_SPACE OR WHITE_SPACE) + : (COMMA WHITE_SPACE?) | (WHITE_SPACE (OR | TO) WHITE_SPACE) ; alternative_direction diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 0456ab19..ff8ad1f0 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -81,7 +81,8 @@ public ParseResult parse(final String inputString) { // or parse without debug else { DateParser parser = new DateParser(tokens); - DateParser.parse_return parseReturn = parser.parse(); + //DateParser.parse_return parseReturn = parser.parse(); + DateParser.date_time_alternative_return parseReturn = parser.date_time_alternative(); tree = (Tree) parseReturn.getTree(); } diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index 5ab5ed75..5dd45698 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -69,6 +69,19 @@ date_time_alternative: "next wed, thurs, or fri at 6pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 6))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm))) "10/10 or 12/30 or 10/15 at 5pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 30)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 15)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))) +"monday to friday" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 6))))) +"1999-12-31 to tomorrow" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 1999))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)))) +"now to 2010-01-01" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 01) (DAY_OF_MONTH 01) (YEAR_OF 2010)))) +"2009-03-10 9:00 to 11:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00)))) +"26 oct 10:00 am to 11:00 am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00) am))) +"jan 1 to 2" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 1))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2)))) +"16:00 nov 6 to 17:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 16) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 17) (MINUTES_OF_HOUR 00)))) +"may 2nd to 5th" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 2))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 5)))) +"6am dec 5 to 7am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) am))) +"1/3 to 2/3" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 3))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3)))) +"2/3 to in 1 week" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 week)))) +"first day of may to last day of may" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))))) + date_time_separator: "," OK ", " OK diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java index 408f4708..9288f41c 100644 --- a/src/test/java/com/natty/parse/ParserTest.java +++ b/src/test/java/com/natty/parse/ParserTest.java @@ -1,10 +1,7 @@ package com.natty.parse; -import java.io.BufferedReader; -import java.io.InputStreamReader; import java.util.Arrays; import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; import junit.framework.Assert; @@ -12,6 +9,7 @@ import org.junit.Ignore; import org.junit.Test; +import com.joestelmach.natty.ParseLocation; import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; @@ -39,37 +37,56 @@ public static void main(String[] args) throws Exception { Parser parser = new Parser(); parser.setDebug(true); String[] strings = new String[]{ - - //"6 days ago", - //"6 hours ago", - //"10 hours before noon", - //"10 hours before midnight", - //"5 hours after noon", - //"5 hours after midnight", - //"in 5 seconds", - //"in 5 minutes", - //"in 5 hours", - //"4 seconds from now", - "4 min from now", - "4 hrs from now", - "4 sec from now", - - //"this second", - //"next second", - //"this minute", - //"next minute", - //"this hour", - //"next hour", + "6 in the morning", + "4 in the afternoon", + "monday 6 in the morning", + "monday 4 in the afternoon", + "monday 9 in the evening", + "evening", + "this morning", + "this afternoon", + "this evening", + "today evening", + "tomorrow evening", + "thursday evening", + "final thursday in april", + "final thurs in sep", + "tomorrow @ noon", + "4th february ", + "10 hours before noon", + "10 hours before midnight", + "5 hours after noon", + "5 hours after midnight", + "in 5 seconds", + "in 5 minutes", + "in 5 hours", + "4 seconds from now", + "4 minutes from now", + "4 hours from now", + "monday to friday", + "1 April to 31 August", + "1999-12-31 to tomorrow", + "now to 2010-01-01", + "2009-03-10 9:00 to 11:00", + "26 oct 10:00 am to 11:00 am", + "jan 1 to 2", + "16:00 nov 6 to 17:00", + "may 2nd to 5th", + "6am dec 5 to 7am", + "1/3 to 2/3", + "2/3 to in 1 week", + "3/3 21:00 to in 5 days", + "first day of may to last day of may", }; for(String s:strings) { - //System.out.println("*****"); + System.out.println("*****"); System.out.println(s); ParseResult result = parser.parse(s); List dateTimes = result.getDates(); System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); System.out.println(result.getSyntaxTree()); - //System.out.println("*****\n\n"); + System.out.println("*****\n\n"); } From 12788f02478fd7494312e5546483b464adbc13ed Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 6 Feb 2011 23:09:27 -0500 Subject: [PATCH 011/134] bumping up version to 0.3-SNAPSHOT --- README.markdown | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index 7f8e2df1..303341b4 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,7 @@ -Natty is a natural language date parser written in Java. Given a date -expression, natty will apply standard language recognition and translation -techniques to produce a list of corresponding dates with optional parse and -syntax information. +Natty is a natural language date parser written in Java. Given a date
+expression, natty will apply standard language recognition and translation
+techniques to produce a list of corresponding dates with optional parse and
+syntax information.
http://natty.joestelmach.com diff --git a/pom.xml b/pom.xml index b995ea01..7f5becb8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.2.2-SNAPSHOT + 3.0-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From e427aff33508d7e7b376688d574f112cc443f1bb Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 6 Feb 2011 23:10:25 -0500 Subject: [PATCH 012/134] make that 0.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f5becb8..9f083adf 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 3.0-SNAPSHOT + 0.3-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 7049d991c5c582426af23fc558c2a2b4d3e8665d Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 7 Feb 2011 21:47:54 -0500 Subject: [PATCH 013/134] swapping donation link --- README.markdown | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 303341b4..daf1612b 100644 --- a/README.markdown +++ b/README.markdown @@ -5,4 +5,9 @@ syntax information.
http://natty.joestelmach.com -Click here to lend your support to: Natty and make a donation at www.pledgie.com ! +
+ + + + +
From 8ec5555494501242e4a63576524896087aa38765 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 7 Feb 2011 21:50:40 -0500 Subject: [PATCH 014/134] swapping donation link --- README.markdown => README.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.markdown => README.html (100%) diff --git a/README.markdown b/README.html similarity index 100% rename from README.markdown rename to README.html From daea9c22520a109146ab5ad48bbfb4de1839739f Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 7 Feb 2011 21:51:22 -0500 Subject: [PATCH 015/134] swapping donation link --- README.html => README | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.html => README (100%) diff --git a/README.html b/README similarity index 100% rename from README.html rename to README From 38d0cc97747e18825cfb101fa1d07eaed9369202 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 7 Feb 2011 22:01:52 -0500 Subject: [PATCH 016/134] giving up on the donate button --- README | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/README b/README index daf1612b..69d4bdd0 100644 --- a/README +++ b/README @@ -1,13 +1,7 @@ -Natty is a natural language date parser written in Java. Given a date
-expression, natty will apply standard language recognition and translation
-techniques to produce a list of corresponding dates with optional parse and
-syntax information.
+Natty is a natural language date parser written in Java. Given a date +expression, natty will apply standard language recognition and translation +techniques to produce a list of corresponding dates with optional parse and +syntax information. -http://natty.joestelmach.com - -
- - - - -
+Complete documentation can be found here: +http://natty.joestelmach.com From 7445d7ae81c5b98109385ff712697cd7e00c54ba Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 9 Feb 2011 01:00:02 -0500 Subject: [PATCH 017/134] unit tests are now on par with the grammar tests --- .../joestelmach/natty/generated/DateParser.g | 6 +- .../joestelmach/natty/generated/DateWalker.g | 4 +- .../com/joestelmach/natty/CalendarSource.java | 28 ++++ .../java/com/joestelmach/natty/Parser.java | 3 +- .../com/joestelmach/natty/WalkerState.java | 55 +++++-- src/test/gunit/DateParser.testsuite | 5 - .../java/com/natty/parse/AbstractTest.java | 151 ++++++++++++++++++ src/test/java/com/natty/parse/DateTest.java | 146 +++++++++++++++++ .../java/com/natty/parse/DateTimeTest.java | 95 +++++++++++ src/test/java/com/natty/parse/ParserTest.java | 112 ------------- src/test/java/com/natty/parse/TimeTest.java | 67 ++++++++ 11 files changed, 532 insertions(+), 140 deletions(-) create mode 100644 src/main/java/com/joestelmach/natty/CalendarSource.java create mode 100644 src/test/java/com/natty/parse/AbstractTest.java create mode 100644 src/test/java/com/natty/parse/DateTest.java create mode 100644 src/test/java/com/natty/parse/DateTimeTest.java delete mode 100644 src/test/java/com/natty/parse/ParserTest.java create mode 100644 src/test/java/com/natty/parse/TimeTest.java diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 456c1a6b..516523d0 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -562,7 +562,7 @@ meridian_indicator | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> AM_PM["am"] | (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> AM_PM["pm"] | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> AM_PM["pm"] - | AT? WHITE_SPACE NIGHT -> AM_PM["pm"] + | (AT WHITE_SPACE)? NIGHT -> AM_PM["pm"] ; @@ -570,8 +570,8 @@ named_time : (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] - | MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] - | EVENING -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | (AT WHITE_SPACE)? MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] ; time_zone diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 463ccd77..02f99972 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -63,7 +63,7 @@ relative_time ; seek - : ^(SEEK DIRECTION by=SEEK_BY amount=INT ^(DAY_OF_WEEK day=INT)) + : ^(SEEK DIRECTION by=SEEK_BY amount=INT ^(DAY_OF_WEEK day=INT) date?) {_walkerState.seekToDayOfWeek($DIRECTION.text, $by.text, $amount.text, $day.text);} | ^(SEEK DIRECTION SEEK_BY amount=INT ^(MONTH_OF_YEAR month=INT)) @@ -72,7 +72,7 @@ seek | ^(SEEK DIRECTION SEEK_BY (explicit_seek | relative_date)? INT SPAN) {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SPAN.text);} - | ^(SEEK DIRECTION SEEK_BY INT) + | ^(SEEK DIRECTION SEEK_BY INT date) {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SEEK_BY.text);} ; diff --git a/src/main/java/com/joestelmach/natty/CalendarSource.java b/src/main/java/com/joestelmach/natty/CalendarSource.java new file mode 100644 index 00000000..27768cbe --- /dev/null +++ b/src/main/java/com/joestelmach/natty/CalendarSource.java @@ -0,0 +1,28 @@ +package com.joestelmach.natty; + +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * Responsible for generating new Calendars that represent + * the current point in time. This is neccessary so we can + * manipulate what the software thinks is the 'current' + * time, which may be different than the system time + * + * @author Joe Stelmach + */ +public class CalendarSource { + private static Date _baseDate; + + public static void setBaseDate(Date baseDate) { + _baseDate = baseDate; + } + + public static GregorianCalendar getCurrentCalendar() { + GregorianCalendar calendar = new GregorianCalendar(); + if(_baseDate != null) { + calendar.setTime(_baseDate); + } + return calendar; + } +} diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index ff8ad1f0..0456ab19 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -81,8 +81,7 @@ public ParseResult parse(final String inputString) { // or parse without debug else { DateParser parser = new DateParser(tokens); - //DateParser.parse_return parseReturn = parser.parse(); - DateParser.date_time_alternative_return parseReturn = parser.date_time_alternative(); + DateParser.parse_return parseReturn = parser.parse(); tree = (Tree) parseReturn.getTree(); } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index d335399e..142d1811 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -11,10 +11,19 @@ * @author Joe Stelmach */ public class WalkerState { + + private static final int TWO_DIGIT_YEAR_CENTURY_THRESHOLD = 20; + private static final String MONTH = "month"; + private static final String DAY = "day"; + private static final String YEAR = "year"; + private static final String WEEK = "week"; + private static final String HOUR = "hour"; + private static final String MINUTE = "minute"; + private static final String SECOND = "second"; + private GregorianCalendar _calendar; private TimeZone _defaultTimeZone; private int _currentYear; - private static final int TWO_DIGIT_YEAR_CENTURY_THRESHOLD = 20; private List _currentDateTimes; /** @@ -131,11 +140,13 @@ public void seekToMonth(String direction, String seekAmount, String month) { _calendar.set(Calendar.DAY_OF_MONTH, 1); // seek to the appropriate year - int currentMonth = _calendar.get(Calendar.MONTH) + 1; - int sign = direction.equals(">") ? 1 : -1; - int numYearsToShift = seekAmountInt + - (currentMonth <= monthInt ? sign > 0 ? -1 : 0 : sign > 0 ? 0 : -1); - _calendar.add(Calendar.YEAR, (numYearsToShift * sign)); + if(seekAmountInt > 0) { + int currentMonth = _calendar.get(Calendar.MONTH) + 1; + int sign = direction.equals(">") ? 1 : -1; + int numYearsToShift = seekAmountInt + + (currentMonth <= monthInt ? sign > 0 ? -1 : 0 : sign > 0 ? 0 : -1); + _calendar.add(Calendar.YEAR, (numYearsToShift * sign)); + } // now set the month _calendar.set(Calendar.MONTH, monthInt -1); @@ -156,17 +167,19 @@ public void seekBySpan(String direction, String seekAmount, String span) { if(span.startsWith("by_")) span = span.substring(3); int seekAmountInt = Integer.parseInt(seekAmount); assert(direction.equals("<") || direction.equals(">")); - assert(span.equals("day") || span.equals("week") || span.equals("month") || span.equals("year")); + assert(span.equals(DAY) || span.equals(WEEK) || span.equals(MONTH) || + span.equals(YEAR) || span.equals(HOUR) || span.equals(MINUTE) || + span.equals(SECOND)); int sign = direction.equals(">") ? 1 : -1; int field = - span.equals("day") ? Calendar.DAY_OF_YEAR : - span.equals("week") ? Calendar.WEEK_OF_YEAR : - span.equals("month") ? Calendar.MONTH : - span.equals("year") ? Calendar.YEAR : - span.equals("hour") ? Calendar.HOUR: - span.equals("minute") ? Calendar.MINUTE: - span.equals("second") ? Calendar.SECOND: + span.equals(DAY) ? Calendar.DAY_OF_YEAR : + span.equals(WEEK) ? Calendar.WEEK_OF_YEAR : + span.equals(MONTH) ? Calendar.MONTH : + span.equals(YEAR) ? Calendar.YEAR : + span.equals(HOUR) ? Calendar.HOUR: + span.equals(MINUTE) ? Calendar.MINUTE: + span.equals(SECOND) ? Calendar.SECOND: null; if(field > 0) _calendar.add(field, seekAmountInt * sign); } @@ -289,7 +302,7 @@ public void setExplicitTime(String hours, String minutes, String seconds, String // hours greater than 12 are in 24-hour time if(hoursInt <= 12) { int amPmInt = amPm == null ? - (hoursInt > 12 ? Calendar.PM : Calendar.AM) : + (hoursInt >= 12 ? Calendar.PM : Calendar.AM) : amPm.equals("pm") ? Calendar.PM : Calendar.AM; _calendar.set(Calendar.AM_PM, amPmInt); @@ -303,6 +316,9 @@ public void setExplicitTime(String hours, String minutes, String seconds, String assert(secondsInt >= 0 && secondsInt < 60); _calendar.set(Calendar.SECOND, secondsInt); } + else { + _calendar.set(Calendar.SECOND, 0); + } _calendar.set(Calendar.MINUTE, minutesInt); } @@ -327,7 +343,14 @@ public List getDateTimes() { * Resets the calendar */ private void resetCalender() { - _calendar = new GregorianCalendar(); + _calendar = getCalendar(); _currentYear = _calendar.get(Calendar.YEAR); } + + /** + * @return the current calendar + */ + protected GregorianCalendar getCalendar() { + return CalendarSource.getCurrentCalendar(); + } } \ No newline at end of file diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index 5dd45698..6385ebbe 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -33,13 +33,8 @@ date_time: "monday 6 in the morning" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) "monday 4 in the afternoon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm)) "monday 9 in the evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 0) pm)) -"evening" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) "this morning" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) "this afternoon" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) -"this evening" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) -"today evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) -"tomorrow evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) -"thursday evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) "final thursday in april" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) "final thurs in sep" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) "tomorrow @ noon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) diff --git a/src/test/java/com/natty/parse/AbstractTest.java b/src/test/java/com/natty/parse/AbstractTest.java new file mode 100644 index 00000000..082ab0f3 --- /dev/null +++ b/src/test/java/com/natty/parse/AbstractTest.java @@ -0,0 +1,151 @@ +package com.natty.parse; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; + +import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.ParseResult; +import com.joestelmach.natty.Parser; + +public abstract class AbstractTest { + private static final Calendar _calendar = Calendar.getInstance(); + private static final Parser _parser = new Parser(); + + /** + * Resets the calendar source time before each test + */ + @Before + public void before() { + CalendarSource.setBaseDate(null); + } + + /** + * Parses the given value into a collection of dates + * + * @param value + * @return + */ + protected List parseCollection(String value) { + ParseResult result = _parser.parse(value); + return result.getDates(); + } + + /** + * Parses the given value, asserting that one and only one date is produced. + * + * @param value + * @return + */ + protected Date parseSingleDate(String value) { + List dates = parseCollection(value); + Assert.assertEquals(1, dates.size()); + return dates.get(0); + } + + /** + * Asserts that the given string value parses down to the given + * month, day, and year values. + * + * @param value + * @param month + * @param day + * @param year + */ + protected void validateDate(String value, int month, int day, int year) { + Date date = parseSingleDate(value); + validateDate(date, month, day, year); + } + + /** + * Asserts that the given date contains the given attributes + * + * @param date + * @param month + * @param day + * @param year + */ + protected void validateDate(Date date, int month, int day, int year) { + _calendar.setTime(date); + Assert.assertEquals(month -1, _calendar.get(Calendar.MONTH)); + Assert.assertEquals(day, _calendar.get(Calendar.DAY_OF_MONTH)); + Assert.assertEquals(year, _calendar.get(Calendar.YEAR)); + } + + /** + * + * Asserts that the given string value parses down to the given + * hours, minutes, and seconds + * + * @param value + * @param hours + * @param minutes + * @param seconds + */ + protected void validateTime(String value, int hours, int minutes, int seconds) { + Date date = parseSingleDate(value); + validateTime(date, hours, minutes, seconds); + } + + /** + * + * Asserts that the given date contains the given time attributes + * + * @param date + * @param hours + * @param minutes + * @param seconds + */ + protected void validateTime(Date date, int hours, int minutes, int seconds) { + _calendar.setTime(date); + Assert.assertEquals(hours, _calendar.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(minutes, _calendar.get(Calendar.MINUTE)); + Assert.assertEquals(seconds, _calendar.get(Calendar.SECOND)); + } + + /** + * + * Asserts that the given string value parses down to the given + * month, day, year, hours, minutes, and seconds + * + * @param value + * @param month + * @param day + * @param year + * @param hours + * @param minutes + * @param seconds + */ + protected void validateDateTime(String value, int month, int day, int year, + int hours, int minutes, int seconds) { + + Date date = parseSingleDate(value); + validateDateTime(date, month, day, year, hours, minutes, seconds); + } + + /** + * Asserts that the given date contains the given attributes + * + * @param date + * @param month + * @param day + * @param year + * @param hours + * @param minutes + * @param seconds + */ + protected void validateDateTime(Date date, int month, int day, int year, + int hours, int minutes, int seconds) { + + _calendar.setTime(date); + Assert.assertEquals(month -1, _calendar.get(Calendar.MONTH)); + Assert.assertEquals(day, _calendar.get(Calendar.DAY_OF_MONTH)); + Assert.assertEquals(year, _calendar.get(Calendar.YEAR)); + Assert.assertEquals(hours, _calendar.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(minutes, _calendar.get(Calendar.MINUTE)); + Assert.assertEquals(seconds, _calendar.get(Calendar.SECOND)); + } +} diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java new file mode 100644 index 00000000..cbf5bf6f --- /dev/null +++ b/src/test/java/com/natty/parse/DateTest.java @@ -0,0 +1,146 @@ +package com.natty.parse; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.ParseResult; +import com.joestelmach.natty.Parser; + +/** + * Runs the parser through the various date formats + * + * @author Joe Stelmach + */ +public class DateTest extends AbstractTest { + + @Test + public void testFormal() { + validateDate("1978-01-28", 1, 28, 1978); + validateDate("2009-10-10", 10, 10, 2009); + validateDate("1980-1-2", 1, 2, 1980); + validateDate("12/12/12", 12, 12, 2012); + validateDate("3/4", 3, 4, Calendar.getInstance().get(Calendar.YEAR)); + validateDate("sun, 11/21/2010", 11, 21, 2010); + } + + @Test + public void testRelaxed() { + validateDate("oct 1, 1980", 10, 1, 1980); + validateDate("oct. 1, 1980", 10, 1, 1980); + validateDate("oct 1,1980", 10, 1, 1980); + validateDate("1st oct in the year '89", 10, 1, 1989); + validateDate("thirty first of december 80", 12, 31, 1980); + validateDate("the first of december in the year 1980", 12, 1, 1980); + validateDate("the 2 of february in the year 1980", 2, 2, 1980); + validateDate("the 2nd of february in the year 1980", 2, 2, 1980); + validateDate("the second of february in the year 1980", 2, 2, 1980); + validateDate("jan. 2nd", 1, 2, Calendar.getInstance().get(Calendar.YEAR)); + validateDate("sun, nov 21 2010", 11, 21, 2010); + } + + @Test + public void testExplicitRelative() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("2/28/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("final thursday in april", 4, 28, 2011); + validateDate("final thurs in sep", 9, 29, 2011); + validateDate("4th february ", 2, 4, 2011); + } + + @Test + public void testRelative() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("2/28/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("yesterday", 2, 27, 2011); + validateDate("tomorrow", 3, 1, 2011); + validateDate("in 3 days", 3, 3, 2011); + validateDate("3 days ago", 2, 25, 2011); + validateDate("in 3 weeks", 3, 21, 2011); + validateDate("four weeks ago", 1, 31, 2011); + validateDate("in 3 months", 5, 28, 2011); + validateDate("three months ago", 11, 28, 2010); + validateDate("in 3 years", 2, 28, 2014); + validateDate("seven years ago", 2, 28, 2004); + validateDate("60 years ago", 2, 28, 1951); + validateDate("32 days ago", 1, 27, 2011); + validateDate("next monday", 3, 7, 2011); + validateDate("next mon", 3, 7, 2011); + validateDate("4 mondays from now", 3, 28, 2011); + validateDate("4 mondays from today", 3, 28, 2011); + validateDate("next weekend", 3, 12, 2011); + validateDate("six mondays ago", 1, 17, 2011); + validateDate("last monday", 2, 21, 2011); + validateDate("last mon", 2, 21, 2011); + validateDate("this past mon", 2, 21, 2011); + validateDate("this coming mon", 3, 7, 2011); + validateDate("this upcoming mon", 3, 7, 2011); + validateDate("next thurs", 3, 10, 2011); + validateDate("next month", 3, 28, 2011); + validateDate("last month", 1, 28, 2011); + validateDate("next week", 3, 7, 2011); + validateDate("last week", 2, 21, 2011); + validateDate("next year", 2, 28, 2012); + validateDate("last year", 2, 28, 2010); + } + + @Test + public void testRange() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("1/02/2011"); + CalendarSource.setBaseDate(reference); + + List dates = parseCollection("monday to friday"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 3, 2011); + validateDate(dates.get(1), 1, 7, 2011); + + dates = parseCollection("1999-12-31 to tomorrow"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 12, 31, 1999); + validateDate(dates.get(1), 1, 3, 2011); + + dates = parseCollection("now to 2010-01-01"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 1, 1, 2010); + + dates = parseCollection("jan 1 to 2"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 1, 2011); + validateDate(dates.get(1), 1, 2, 2011); + + dates = parseCollection("may 2nd to 5th"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 5, 2, 2011); + validateDate(dates.get(1), 5, 5, 2011); + + dates = parseCollection("1/3 to 2/3"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 3, 2011); + validateDate(dates.get(1), 2, 3, 2011); + + dates = parseCollection("2/3 to in one week"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 3, 2011); + validateDate(dates.get(1), 1, 9, 2011); + + dates = parseCollection("first day of may to last day of may"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 5, 1, 2011); + validateDate(dates.get(1), 5, 31, 2011); + } + + public static void main(String[] args) { + Parser parser = new Parser(); + ParseResult result = parser.parse("evening"); + System.out.println(result.getSyntaxTree()); + } +} diff --git a/src/test/java/com/natty/parse/DateTimeTest.java b/src/test/java/com/natty/parse/DateTimeTest.java new file mode 100644 index 00000000..b3c78085 --- /dev/null +++ b/src/test/java/com/natty/parse/DateTimeTest.java @@ -0,0 +1,95 @@ +package com.natty.parse; + +import java.text.DateFormat; +import java.util.Date; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.joestelmach.natty.CalendarSource; + +/** + * Runs the parser through the various datetime formats + * + * @author Joe Stelmach + */ +public class DateTimeTest extends AbstractTest { + @Test + public void testSpecific() { + validateDateTime("1st oct in the year '89 1300 hours", 10, 1, 1989, 13, 0, 0); + validateDateTime("1st oct in the year '89 at 1300 hours", 10, 1, 1989, 13, 0, 0); + validateDateTime("1st oct in the year '89, 13:00", 10, 1, 1989, 13, 0, 0); + validateDateTime("1st oct in the year '89,13:00", 10, 1, 1989, 13, 0, 0); + validateDateTime("1st oct in the year '89, at 13:00", 10, 1, 1989, 13, 0, 0); + validateDateTime("3am on oct 1st 2010", 10, 1, 2010, 3, 0, 0); + validateDateTime("3am, october first 2010", 10, 1, 2010, 3, 0, 0); + validateDateTime("3am,october first 2010", 10, 1, 2010, 3, 0, 0); + validateDateTime("3am, on october first 2010", 10, 1, 2010, 3, 0, 0); + validateDateTime("3am october first 2010", 10, 1, 2010, 3, 0, 0); + } + + @Test + public void testRelative() throws Exception { + Date reference = DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.SHORT).parse("2/24/2011 12:00 am"); + CalendarSource.setBaseDate(reference); + + validateDateTime("seven years ago at 3pm", 2, 24, 2004, 15, 0, 0); + validateDateTime("next wed. at 5pm", 3, 2, 2011, 17, 0, 0); + validateDateTime("3 days after next wed at 6a", 3, 5, 2011, 6, 0, 0); + validateDateTime("8pm on the sunday after next wed", 3, 6, 2011, 20, 0, 0); + validateDateTime("two days after today @ 6p", 2, 26, 2011, 18, 0, 0); + validateDateTime("two days from today @ 6p", 2, 26, 2011, 18, 0, 0); + validateDateTime("11:59 on 3 sundays after next wed", 3, 20, 2011, 11, 59, 0); + validateDateTime("the day after next 6pm", 2, 26, 2011, 18, 0, 0); + validateDateTime("the week after next 2a", 3, 10, 2011, 2, 0, 0); + validateDateTime("the month after next 0700", 4, 24, 2011, 7, 0, 0); + validateDateTime("the year after next @ midnight", 2, 24, 2013, 0, 0, 0); + validateDateTime("wed of the week after next in the evening", 3, 9, 2011, 19, 0, 0); + validateDateTime("the 28th of the month after next in the morning", 4, 28, 2011, 8, 0, 0); + validateDateTime("this morning", 2, 24, 2011, 8, 0, 0); + validateDateTime("this afternoon", 2, 24, 2011, 12, 0, 0); + validateDateTime("this evening", 2, 24, 2011, 19, 0, 0); + validateDateTime("today evening", 2, 24, 2011, 19, 0, 0); + validateDateTime("tomorrow evening", 2, 25, 2011, 19, 0, 0); + validateDateTime("friday evening", 2, 25, 2011, 19, 0, 0); + validateDateTime("monday 6 in the morning", 2, 28, 2011, 6, 0, 0); + validateDateTime("monday 4 in the afternoon", 2, 28, 2011, 16, 0, 0); + validateDateTime("monday 9 in the evening", 2, 28, 2011, 21, 0, 0); + validateDateTime("tomorrow @ noon", 2, 25, 2011, 12, 0, 0); + } + + @Test + public void testRange() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("6/12/2010"); + CalendarSource.setBaseDate(reference); + + List dates = parseCollection("2009-03-10 9:00 to 11:00"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 3, 10, 2009, 9, 0, 0); + validateDateTime(dates.get(1), 3, 10, 2009, 11, 0, 0); + + dates = parseCollection("26 oct 10:00 am to 11:00 am"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 10, 26, 2010, 10, 0, 0); + validateDateTime(dates.get(1), 10, 26, 2010, 11, 0, 0); + + dates = parseCollection("16:00 nov 6 to 17:00"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 11, 6, 2010, 16, 0, 0); + validateDateTime(dates.get(1), 11, 6, 2010, 17, 0, 0); + + dates = parseCollection("6am dec 5 to 7am"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 12, 5, 2010, 6, 0, 0); + validateDateTime(dates.get(1), 12, 5, 2010, 7, 0, 0); + + /* + dates = parseCollection("3/3 21:00 to in 5 days"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 3, 3, 2010, 21, 0, 0); + validateDateTime(dates.get(1), 6, 17, 2010, 21, 0, 0); + */ + } +} diff --git a/src/test/java/com/natty/parse/ParserTest.java b/src/test/java/com/natty/parse/ParserTest.java deleted file mode 100644 index 9288f41c..00000000 --- a/src/test/java/com/natty/parse/ParserTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.natty.parse; - -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import junit.framework.Assert; - -import org.junit.Ignore; -import org.junit.Test; - -import com.joestelmach.natty.ParseLocation; -import com.joestelmach.natty.ParseResult; -import com.joestelmach.natty.Parser; - -/** - * Runs the DateParser through it's paces - * - * @author Joe Stelmach - */ -public class ParserTest { - @Ignore - @Test - public void testRelativeTimes() { - //Date currentDate = new Date(); - Assert.assertNotNull(parseSingleDate("6 days ago")); - } - - private Date parseSingleDate(String value) { - Parser parser = new Parser(); - ParseResult result = parser.parse(value); - List dateTimes = result.getDates(); - return dateTimes.get(0); - } - - public static void main(String[] args) throws Exception { - Parser parser = new Parser(); - parser.setDebug(true); - String[] strings = new String[]{ - "6 in the morning", - "4 in the afternoon", - "monday 6 in the morning", - "monday 4 in the afternoon", - "monday 9 in the evening", - "evening", - "this morning", - "this afternoon", - "this evening", - "today evening", - "tomorrow evening", - "thursday evening", - "final thursday in april", - "final thurs in sep", - "tomorrow @ noon", - "4th february ", - "10 hours before noon", - "10 hours before midnight", - "5 hours after noon", - "5 hours after midnight", - "in 5 seconds", - "in 5 minutes", - "in 5 hours", - "4 seconds from now", - "4 minutes from now", - "4 hours from now", - "monday to friday", - "1 April to 31 August", - "1999-12-31 to tomorrow", - "now to 2010-01-01", - "2009-03-10 9:00 to 11:00", - "26 oct 10:00 am to 11:00 am", - "jan 1 to 2", - "16:00 nov 6 to 17:00", - "may 2nd to 5th", - "6am dec 5 to 7am", - "1/3 to 2/3", - "2/3 to in 1 week", - "3/3 21:00 to in 5 days", - "first day of may to last day of may", - }; - - for(String s:strings) { - System.out.println("*****"); - System.out.println(s); - ParseResult result = parser.parse(s); - List dateTimes = result.getDates(); - System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); - System.out.println(result.getSyntaxTree()); - System.out.println("*****\n\n"); - } - - - /* - BufferedReader in = new BufferedReader(new InputStreamReader(ParserTest.class.getResourceAsStream("/cpan.txt"))); - String line = null; - while((line = in.readLine()) != null) { - if(line.startsWith("!")) continue; - if(line.startsWith("#")) line = line.substring(1); - - System.out.print(line + " -> "); - try { - ParseResult result = parser.parse(line.trim()); - //System.out.println(result.getSyntaxTree()); - List dateTimes = result.getDates(); - System.out.print(Arrays.toString(dateTimes.toArray()) + "\n"); - } catch(Exception e) { - System.out.print("no good"); - } - } - */ - } -} diff --git a/src/test/java/com/natty/parse/TimeTest.java b/src/test/java/com/natty/parse/TimeTest.java new file mode 100644 index 00000000..6777672b --- /dev/null +++ b/src/test/java/com/natty/parse/TimeTest.java @@ -0,0 +1,67 @@ +package com.natty.parse; + +import java.text.DateFormat; + +import org.junit.Test; + +import com.joestelmach.natty.CalendarSource; + +/** + * Runs the parser through the various time formats + * + * @author Joe Stelmach + */ +public class TimeTest extends AbstractTest { + + /** + * Runs the parser through the various time formats + * @throws Exception + */ + @Test + public void testFormal() throws Exception { + validateTime("0600h", 6, 0, 0); + validateTime("06:00h", 6, 0, 0); + validateTime("06:00 hours", 6, 0, 0); + validateTime("0000", 0, 0, 0); + validateTime("0700h", 7, 0, 0); + validateTime("6pm", 18, 0, 0); + validateTime("5:30 a.m.", 5, 30, 0); + validateTime("5", 5, 0, 0); + validateTime("12:59", 12, 59, 0); + validateTime("23:59:28", 23, 59, 28); + validateTime("00:00", 0, 0, 0); + validateTime("10:00am", 10, 0, 0); + validateTime("10a", 10, 0, 0); + validateTime("10am", 10, 0, 0); + validateTime("10", 10, 0, 0); + validateTime("8p", 20, 0, 0); + validateTime("8pm", 20, 0, 0); + validateTime("8 pm", 20, 0, 0); + } + + @Test + public void testRelaxed() throws Exception { + validateTime("noon", 12, 0, 0); + validateTime("afternoon", 12, 0, 0); + validateTime("midnight", 0, 0, 0); + validateTime("mid-night", 0, 0, 0); + validateTime("6 in the morning", 6, 0, 0); + validateTime("4 in the afternoon", 16, 0, 0); + validateTime("evening", 19, 0, 0); + validateTime("10 hours before noon", 2, 0, 0); + validateTime("10 hours before midnight", 14, 0, 0); + validateTime("5 hours after noon", 17, 0, 0); + validateTime("5 hours after midnight", 5, 0, 0); + } + + @Test + public void testRelative() throws Exception { + CalendarSource.setBaseDate(DateFormat.getTimeInstance(DateFormat.SHORT).parse("12:00 pm")); + validateTime("in 5 seconds", 12, 0, 5); + validateTime("in 5 minutes", 12, 5, 0); + validateTime("in 5 hours", 17, 0, 0); + validateTime("4 seconds from now", 12, 0, 4); + validateTime("4 minutes from now", 12, 4, 0); + validateTime("4 hours from now", 16, 0, 0); + } +} From 75b92536f1b6a7e93302c5cfc39022fa79672028 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 10 Feb 2011 02:25:26 -0500 Subject: [PATCH 018/134] significant progress towards searching for dates within larger strings --- .../joestelmach/natty/generated/DateLexer.g | 8 +++ .../joestelmach/natty/generated/DateParser.g | 69 +++++++++++-------- .../com/joestelmach/natty/ParseListener.java | 4 ++ .../java/com/joestelmach/natty/Parser.java | 5 +- src/test/java/com/natty/parse/DateTest.java | 28 +++++++- 5 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 6a01df7b..5c38b242 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -258,6 +258,14 @@ WHITE_SPACE : (DOT | SPACE)+ ; +UNKNOWN + : ('a'..'z')+ | UNKNOWN_CHAR + ; + +fragment UNKNOWN_CHAR + : ~(SPACE | DOT) + ; + fragment DIGIT : '0'..'9'; fragment SPACE : ' ' | '\t' | '\n' | '\r' ; \ No newline at end of file diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 516523d0..5558e906 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -35,8 +35,19 @@ tokens { } parse - : (date_time_alternative)=> date_time_alternative - | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) + : (text ((entry)=>entry | known_token)?)+ -> entry* + ; + +entry + : date_time_alternative + ; + +known_token + : ~(UNKNOWN | WHITE_SPACE) + ; + +text + : WHITE_SPACE (UNKNOWN WHITE_SPACE)+ ; date_time @@ -60,31 +71,40 @@ time_date_separator ; date - : (formal_date)=> formal_date - | (relaxed_date)=> relaxed_date - | explicit_relative_date + : explicit_relative_date + | formal_date + | relaxed_date | relative_date | global_date_prefix WHITE_SPACE date -> ^(RELATIVE_DATE ^(SEEK global_date_prefix date)) ; date_time_alternative - // "next wed or thurs" , "next wed, thurs, or fri" - : (alternative_day_of_week_list)=> alternative_day_of_week_list + + // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after + : (date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)?)=> + date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) + + // "next wed or thurs" , "next wed, thurs, or fri" + | (alternative_day_of_week_list)=> alternative_day_of_week_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list) // month day or day explicit_time | (alternative_day_of_month_list)=> alternative_day_of_month_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) - - // date and time OR date and time, 1/2 at 2pm or 1/3 at 3pm - | (date (WHITE_SPACE (OR | TO) WHITE_SPACE date (date_time_separator explicit_time)?)+) => - date (WHITE_SPACE (OR | TO) WHITE_SPACE date (date_time_separator explicit_time)?)+ - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) - + + // this wed. or next + | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)?)=> + (THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)? + -> ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) + ) + // date OR date time, 1/2 to 1/4 at 6pm - | (date (WHITE_SPACE (OR | TO) WHITE_SPACE date)+ (date_time_separator explicit_time)?) => - date (WHITE_SPACE (OR | TO) WHITE_SPACE date)+ (date_time_separator explicit_time)? + | (date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)?)=> + date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) // date time or time @@ -96,18 +116,13 @@ date_time_alternative | (explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time)=> explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time) ^(DATE_TIME date $end_time)) - - // this wed. or next - | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction)=> - (THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) - ) - - // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - | date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) + + | (date_time WHITE_SPACE TO WHITE_SPACE date_time)=> + date_time WHITE_SPACE TO WHITE_SPACE date_time + -> ^(DATE_TIME_ALTERNATIVE date_time date_time) + + + | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) ; alternative_day_of_month_list diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index ba17bff8..f61109fe 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -43,12 +43,16 @@ public class ParseListener extends BlankDebugEventListener { INTERESTING_RULES.put("day_of_week", "weekday"); INTERESTING_RULES.put("date", "date"); INTERESTING_RULES.put("date_time_alternative", "alternative"); + INTERESTING_RULES.put("date_time", "date_time"); INTERESTING_RULES.put("alternative_direction", "direction"); INTERESTING_RULES.put("hours", "hours"); INTERESTING_RULES.put("minutes", "minutes"); INTERESTING_RULES.put("meridian_indicator", "am/pm"); INTERESTING_RULES.put("time_zone", "zone"); INTERESTING_RULES.put("time", "time"); + INTERESTING_RULES.put("text", "text"); + INTERESTING_RULES.put("entry", "entry"); + INTERESTING_RULES.put("known_token", "known_token"); } private int backtracking = 0; diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 0456ab19..7343df4d 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -28,6 +28,8 @@ public class Parser { private boolean _debug; private ParseListener _debugListener; + private static final String PREFIX_SUFFIX = " text "; + private static final Logger _logger = Logger.getLogger(Parser.class.getName()); /** @@ -63,8 +65,9 @@ public ParseResult parse(final String inputString) { ParseResult result = new ParseResult(); try { // lex + String cleanedInputString = PREFIX_SUFFIX + inputString.trim() + PREFIX_SUFFIX; ANTLRInputStream input = new ANTLRNoCaseInputStream( - new ByteArrayInputStream(inputString.trim().getBytes())); + new ByteArrayInputStream(cleanedInputString.getBytes())); DateLexer lexer = new DateLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index cbf5bf6f..7c65df1a 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -1,5 +1,6 @@ package com.natty.parse; +import java.io.ByteArrayInputStream; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; @@ -7,11 +8,23 @@ import junit.framework.Assert; +import org.antlr.runtime.ANTLRInputStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.antlr.runtime.tree.Tree; import org.junit.Test; +import com.joestelmach.natty.ANTLRNoCaseInputStream; import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.ParseListener; +import com.joestelmach.natty.ParseLocation; import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; +import com.joestelmach.natty.generated.DateLexer; +import com.joestelmach.natty.generated.DateWalker; +import com.joestelmach.natty.generated.DebugDateParser; +import com.joestelmach.natty.generated.TreeRewrite; /** * Runs the parser through the various date formats @@ -136,11 +149,22 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 5, 1, 2011); validateDate(dates.get(1), 5, 31, 2011); + + dates = parseCollection("feb 28th or 2 days after"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 28, 2011); + validateDate(dates.get(1), 3, 2, 2011); + } - public static void main(String[] args) { + public static void main(String[] args) throws Exception{ + String inputString = "foo bar feb 12 th blah blah"; Parser parser = new Parser(); - ParseResult result = parser.parse("evening"); + ParseResult result = parser.parse(inputString); System.out.println(result.getSyntaxTree()); + System.out.println(result.getDates().get(0)); + //for(ParseLocation l:result.getParseLocations()) { + //System.out.println(l.getRuleName()); + //} } } From 5619bd42a45d56b36d1a52a5b417fe6cca63832e Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 10 Feb 2011 19:27:00 -0500 Subject: [PATCH 019/134] removed some parsing rules in favor of smarter capturing logic --- .../joestelmach/natty/generated/DateParser.g | 27 +++++-------- .../java/com/joestelmach/natty/Parser.java | 4 +- .../com/joestelmach/natty/WalkerState.java | 38 ++++++++++++++++++- src/test/gunit/DateParser.testsuite | 8 ++-- src/test/java/com/natty/parse/DateTest.java | 10 ++--- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 5558e906..d25bfbb2 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -35,7 +35,10 @@ tokens { } parse - : (text ((entry)=>entry | known_token)?)+ -> entry* + //: (text ((entry)=>entry | (WHITE_SPACE | known_token)?))+ -> entry* + //: ((WHITE_SPACE UNKNOWN)+ WHITE_SPACE ((entry)=>entry | known_token))+ -> entry* + //: (((entry)=>entry | known_token | UNKNOWN) WHITE_SPACE)+ -> entry* + : date_time_alternative ; entry @@ -80,9 +83,8 @@ date ; date_time_alternative - // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - : (date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)?)=> + : (date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix)=> date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) @@ -90,38 +92,29 @@ date_time_alternative | (alternative_day_of_week_list)=> alternative_day_of_week_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list) - // month day or day explicit_time + // feb 16, 17, or 18 | (alternative_day_of_month_list)=> alternative_day_of_month_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) // this wed. or next - | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)?)=> + | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction)=> (THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) ) - // date OR date time, 1/2 to 1/4 at 6pm + // 1/2 or 1/4 or 1/6 at 6pm | (date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)?)=> date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) - // date time or time - | (date date_time_separator explicit_time WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time)=> - date date_time_separator explicit_time WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time) ^(DATE_TIME date $end_time)) - - // time date or time - | (explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time)=> - explicit_time time_date_separator date WHITE_SPACE (OR | TO) WHITE_SPACE end_time=explicit_time - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time) ^(DATE_TIME date $end_time)) - + // catch all date_time to date_time range | (date_time WHITE_SPACE TO WHITE_SPACE date_time)=> date_time WHITE_SPACE TO WHITE_SPACE date_time -> ^(DATE_TIME_ALTERNATIVE date_time date_time) - + // single date_time | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) ; diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 7343df4d..1b20d861 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -28,7 +28,6 @@ public class Parser { private boolean _debug; private ParseListener _debugListener; - private static final String PREFIX_SUFFIX = " text "; private static final Logger _logger = Logger.getLogger(Parser.class.getName()); @@ -65,9 +64,8 @@ public ParseResult parse(final String inputString) { ParseResult result = new ParseResult(); try { // lex - String cleanedInputString = PREFIX_SUFFIX + inputString.trim() + PREFIX_SUFFIX; ANTLRInputStream input = new ANTLRNoCaseInputStream( - new ByteArrayInputStream(cleanedInputString.getBytes())); + new ByteArrayInputStream(inputString.trim().getBytes())); DateLexer lexer = new DateLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 142d1811..bdeaf908 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.TimeZone; +import org.omg.CORBA._IDLTypeStub; + /** * @author Joe Stelmach */ @@ -25,6 +27,7 @@ public class WalkerState { private TimeZone _defaultTimeZone; private int _currentYear; private List _currentDateTimes; + private boolean _firstDateInvocation = true; /** * Creates a new WalkerState representing the start of @@ -64,6 +67,8 @@ public void seekToDayOfWeek(String direction, String seekType, String seekAmount assert(seekType.equals("by_day") || seekType.equals("by_week")); assert(dayOfWeekInt >= 1 && dayOfWeekInt <= 7); + markDateInvocation(); + int sign = direction.equals(">") ? 1 : -1; if(seekType.equals("by_week")) { // set our calendar to this weeks requested day of the week, @@ -95,6 +100,9 @@ else if(seekType.equals("by_day")) { public void seekToDayOfMonth(String dayOfMonth) { int dayOfMonthInt = Integer.parseInt(dayOfMonth); assert(dayOfMonthInt >= 1 && dayOfMonthInt <= 31); + + markDateInvocation(); + dayOfMonthInt = Math.min(dayOfMonthInt, _calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); _calendar.set(Calendar.DAY_OF_MONTH, dayOfMonthInt); } @@ -107,6 +115,8 @@ public void seekToYear(String year) { int yearInt = Integer.parseInt(year); assert(yearInt > 0 && yearInt < 9999); + markDateInvocation(); + // two digit years require us to choose a reasonable century. if(year.length() == 2) { int century = (yearInt > ((_currentYear - 2000) + TWO_DIGIT_YEAR_CENTURY_THRESHOLD)) ? 1900 : 2000; @@ -134,6 +144,8 @@ public void seekToMonth(String direction, String seekAmount, String month) { assert(direction.equals("<") || direction.equals(">")); assert(monthInt >= 1 && monthInt <= 12); + markDateInvocation(); + // set the day to the first of month. This step is necessary because if we seek to the // current day of a month whose number of days is less than the current day, we will // pushed into the next month. @@ -171,6 +183,10 @@ public void seekBySpan(String direction, String seekAmount, String span) { span.equals(YEAR) || span.equals(HOUR) || span.equals(MINUTE) || span.equals(SECOND)); + boolean isDateSeek = span.equals(DAY) || span.equals(WEEK) || + span.equals(MONTH) || span.equals(YEAR); + if(isDateSeek) markDateInvocation(); + int sign = direction.equals(">") ? 1 : -1; int field = span.equals(DAY) ? Calendar.DAY_OF_YEAR : @@ -197,6 +213,8 @@ public void setDayOfWeekIndex(String index, String dayOfWeek) { int dayOfWeekInt = Integer.parseInt(dayOfWeek); assert(dayOfWeekInt >= 1 && dayOfWeekInt <= 7); + markDateInvocation(); + // seek to the first day of the current month _calendar.set(Calendar.DAY_OF_MONTH, 1); @@ -241,6 +259,8 @@ public void setExplicitDate(String month, String dayOfMonth, String dayOfWeek, S int dayOfMonthInt = Integer.parseInt(dayOfMonth); assert(dayOfMonthInt > 0 && dayOfMonthInt <= 31); + markDateInvocation(); + _calendar.set(Calendar.MONTH, monthInt - 1); _calendar.set(Calendar.DAY_OF_MONTH, dayOfMonthInt); @@ -329,7 +349,12 @@ public void setExplicitTime(String hours, String minutes, String seconds, String public void captureDateTime() { Date date = _calendar.getTime(); _currentDateTimes.add(date); - resetCalender(); + + // once a date time is captured, we don't reset the calendar just yet + // until the first date seek invocation occurrs. This allows us to keep + // the date fixed while seeking to a different time after the capture + // e.g. feb 28th at 6pm or 7pm + _firstDateInvocation = true; } /** @@ -347,6 +372,17 @@ private void resetCalender() { _currentYear = _calendar.get(Calendar.YEAR); } + /** + * ensures that the first invocation of a date seeking + * rule is captured + */ + private void markDateInvocation() { + if(_firstDateInvocation) { + resetCalender(); + _firstDateInvocation = false; + } + } + /** * @return the current calendar */ diff --git a/src/test/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite index 6385ebbe..0ea69e24 100644 --- a/src/test/gunit/DateParser.testsuite +++ b/src/test/gunit/DateParser.testsuite @@ -67,12 +67,12 @@ date_time_alternative: "monday to friday" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 6))))) "1999-12-31 to tomorrow" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 1999))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)))) "now to 2010-01-01" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 01) (DAY_OF_MONTH 01) (YEAR_OF 2010)))) -"2009-03-10 9:00 to 11:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00)))) -"26 oct 10:00 am to 11:00 am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00) am))) +"2009-03-10 9:00 to 11:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00)))) +"26 oct 10:00 am to 11:00 am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00) am))) "jan 1 to 2" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 1))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2)))) -"16:00 nov 6 to 17:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 16) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 17) (MINUTES_OF_HOUR 00)))) +"16:00 nov 6 to 17:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 16) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 17) (MINUTES_OF_HOUR 00)))) "may 2nd to 5th" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 2))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 5)))) -"6am dec 5 to 7am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) am))) +"6am dec 5 to 7am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) am))) "1/3 to 2/3" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 3))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3)))) "2/3 to in 1 week" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 week)))) "first day of may to last day of may" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))))) diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index 7c65df1a..5328d559 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -151,18 +151,18 @@ public void testRange() throws Exception { validateDate(dates.get(1), 5, 31, 2011); dates = parseCollection("feb 28th or 2 days after"); - Assert.assertEquals(2, dates.size()); - validateDate(dates.get(0), 2, 28, 2011); - validateDate(dates.get(1), 3, 2, 2011); + //Assert.assertEquals(2, dates.size()); + //validateDate(dates.get(0), 2, 28, 2011); + //validateDate(dates.get(1), 3, 2, 2011); } public static void main(String[] args) throws Exception{ - String inputString = "foo bar feb 12 th blah blah"; + String inputString = "2009-03-10 9:00 to 11:00"; Parser parser = new Parser(); ParseResult result = parser.parse(inputString); System.out.println(result.getSyntaxTree()); - System.out.println(result.getDates().get(0)); + System.out.println(result.getDates()); //for(ParseLocation l:result.getParseLocations()) { //System.out.println(l.getRuleName()); //} From 83f6d1ed0a029b6f560eb4d6bcac1922097e0509 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 10 Feb 2011 21:37:19 -0500 Subject: [PATCH 020/134] successfully detecting multiple, non-contiguous groups of dates within larger strings. Necessary tree walking changes are on the way --- pom.xml | 2 + .../joestelmach/natty/generated/DateLexer.g | 2 +- .../joestelmach/natty/generated/DateParser.g | 42 ++++++---------- .../java/com/joestelmach/natty/Parser.java | 5 +- .../com/joestelmach/natty/WalkerState.java | 48 +++++++++++-------- src/test/java/com/natty/parse/DateTest.java | 25 ++-------- 6 files changed, 54 insertions(+), 70 deletions(-) diff --git a/pom.xml b/pom.xml index 9f083adf..3c0a0ed6 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,7 @@ false src/main/java + 100000 com/joestelmach/natty/generated/DebugDateParser.g com/joestelmach/natty/generated/DebugDateWalker.g @@ -165,6 +166,7 @@ true src/main/java + 100000 com/joestelmach/natty/generated/DebugDateParser.g com/joestelmach/natty/generated/DebugDateWalker.g diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 5c38b242..94d34ff0 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -259,7 +259,7 @@ WHITE_SPACE ; UNKNOWN - : ('a'..'z')+ | UNKNOWN_CHAR + : UNKNOWN_CHAR ; fragment UNKNOWN_CHAR diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index d25bfbb2..059cd432 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -35,14 +35,7 @@ tokens { } parse - //: (text ((entry)=>entry | (WHITE_SPACE | known_token)?))+ -> entry* - //: ((WHITE_SPACE UNKNOWN)+ WHITE_SPACE ((entry)=>entry | known_token))+ -> entry* - //: (((entry)=>entry | known_token | UNKNOWN) WHITE_SPACE)+ -> entry* - : date_time_alternative - ; - -entry - : date_time_alternative + : text (((date_time_alternative)=>date_time_alternative | known_token) text)+ -> date_time_alternative* ; known_token @@ -50,7 +43,7 @@ known_token ; text - : WHITE_SPACE (UNKNOWN WHITE_SPACE)+ + : WHITE_SPACE (UNKNOWN+ WHITE_SPACE)+ ; date_time @@ -58,8 +51,7 @@ date_time (date)=> date (date_time_separator explicit_time)? | explicit_time (time_date_separator date)? ) -> ^(DATE_TIME date? explicit_time?) - | relative_time - -> ^(DATE_TIME relative_time?) + | relative_time -> ^(DATE_TIME relative_time?) ; date_time_separator @@ -187,10 +179,6 @@ prefix_direction // relaxed date with a spelled-out or abbreviated month relaxed_date : ( - // this is a bit tricky since a time can be placed directly after a date, and a year - // can look like a time (four digits, no colon i.e. 0500) Since a year would be more - // common in this context, we choose to swallow the year as part of the date. - // The 31st of April in the year 2008 // RFC822 style: Fri, 21 Nov 1997 (relaxed_day_of_week? relaxed_day_of_month_prefix? relaxed_day_of_month @@ -199,15 +187,11 @@ relaxed_date WHITE_SPACE (OF WHITE_SPACE)? relaxed_month relaxed_year_prefix relaxed_year // above without the year restriction - | relaxed_day_of_week? relaxed_day_of_month_prefix? relaxed_day_of_month WHITE_SPACE (OF WHITE_SPACE)? relaxed_month + | relaxed_day_of_week? relaxed_day_of_month_prefix? relaxed_day_of_month + WHITE_SPACE (OF WHITE_SPACE)? relaxed_month - // Jan 21, 1997 - // Sun, Nov 21 - | (relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month relaxed_year_prefix relaxed_year)=> - relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month relaxed_year_prefix relaxed_year - - // jan 1st, february 28th - | relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month + // Jan 21, 1997 Sun, Nov 21 + | relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (relaxed_year_prefix relaxed_year)? ) -> ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month relaxed_day_of_week? relaxed_year?) ; @@ -321,14 +305,16 @@ relative_date explicit_relative_date // 1st of three months ago, 10th of 3 octobers from now, the last monday in 2 novembers ago - : explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix + : (explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix)=> + explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) explicit_day_of_month_part) // 10th of next month, 31st of last month, 10th of next october, 30th of this month, the last thursday of last november - | explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month + | (explicit_day_of_month_part WHITE_SPACE prefix)=> + explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month -> ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) explicit_day_of_month_part) @@ -341,13 +327,15 @@ explicit_relative_date explicit_day_of_month_part) // monday of last week, tuesday of next week - | explicit_day_of_week_part WHITE_SPACE prefix WHITE_SPACE WEEK + | (explicit_day_of_week_part WHITE_SPACE prefix WHITE_SPACE WEEK)=> + explicit_day_of_week_part WHITE_SPACE prefix WHITE_SPACE WEEK -> ^(RELATIVE_DATE ^(SEEK prefix SPAN["week"]) explicit_day_of_week_part) // monday of 2 weeks ago, tuesday of 3 weeks from now - | explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix + | (explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix)=> + explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE WEEK WHITE_SPACE relative_date_suffix -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix SPAN["week"]) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 1b20d861..4b4ac5b2 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -29,6 +29,7 @@ public class Parser { private boolean _debug; private ParseListener _debugListener; + private static final String MARKER = " ___ "; private static final Logger _logger = Logger.getLogger(Parser.class.getName()); /** @@ -63,9 +64,11 @@ public void setDebug(final boolean debug) { public ParseResult parse(final String inputString) { ParseResult result = new ParseResult(); try { + String cleanedInput = MARKER + inputString.trim() + MARKER; + // lex ANTLRInputStream input = new ANTLRNoCaseInputStream( - new ByteArrayInputStream(inputString.trim().getBytes())); + new ByteArrayInputStream(cleanedInput.getBytes())); DateLexer lexer = new DateLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index bdeaf908..aa3b350f 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -7,8 +7,6 @@ import java.util.List; import java.util.TimeZone; -import org.omg.CORBA._IDLTypeStub; - /** * @author Joe Stelmach */ @@ -22,6 +20,16 @@ public class WalkerState { private static final String HOUR = "hour"; private static final String MINUTE = "minute"; private static final String SECOND = "second"; + private static final String AM = "am"; + private static final String PM = "pm"; + private static final String DIR_LEFT = "<"; + private static final String DIR_RIGHT = ">"; + private static final String SEEK_PREFIX = "by_"; + private static final String SEEK_BY_DAY = "by_day"; + private static final String SEEK_BY_WEEK = "by_week"; + private static final String PLUS = "+"; + private static final String MINUS = "-"; + private static final String GMT = "GMT"; private GregorianCalendar _calendar; private TimeZone _defaultTimeZone; @@ -63,21 +71,21 @@ public void setDefaultTimeZone(final TimeZone zone) { public void seekToDayOfWeek(String direction, String seekType, String seekAmount, String dayOfWeek) { int dayOfWeekInt = Integer.parseInt(dayOfWeek); int seekAmountInt = Integer.parseInt(seekAmount); - assert(direction.equals("<") || direction.equals(">")); - assert(seekType.equals("by_day") || seekType.equals("by_week")); + assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); + assert(seekType.equals(SEEK_BY_DAY) || seekType.equals(SEEK_BY_WEEK)); assert(dayOfWeekInt >= 1 && dayOfWeekInt <= 7); markDateInvocation(); - int sign = direction.equals(">") ? 1 : -1; - if(seekType.equals("by_week")) { + int sign = direction.equals(DIR_RIGHT) ? 1 : -1; + if(seekType.equals(SEEK_BY_WEEK)) { // set our calendar to this weeks requested day of the week, // then add or subtract the week(s) _calendar.set(Calendar.DAY_OF_WEEK, dayOfWeekInt); _calendar.add(Calendar.DAY_OF_YEAR, seekAmountInt * 7 * sign); } - else if(seekType.equals("by_day")) { + else if(seekType.equals(SEEK_BY_DAY)) { // find the closest day do { _calendar.roll(Calendar.DAY_OF_YEAR, sign); @@ -141,7 +149,7 @@ public void seekToYear(String year) { public void seekToMonth(String direction, String seekAmount, String month) { int seekAmountInt = Integer.parseInt(seekAmount); int monthInt = Integer.parseInt(month); - assert(direction.equals("<") || direction.equals(">")); + assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); assert(monthInt >= 1 && monthInt <= 12); markDateInvocation(); @@ -154,7 +162,7 @@ public void seekToMonth(String direction, String seekAmount, String month) { // seek to the appropriate year if(seekAmountInt > 0) { int currentMonth = _calendar.get(Calendar.MONTH) + 1; - int sign = direction.equals(">") ? 1 : -1; + int sign = direction.equals(DIR_RIGHT) ? 1 : -1; int numYearsToShift = seekAmountInt + (currentMonth <= monthInt ? sign > 0 ? -1 : 0 : sign > 0 ? 0 : -1); _calendar.add(Calendar.YEAR, (numYearsToShift * sign)); @@ -176,9 +184,9 @@ public void seekToMonth(String direction, String seekAmount, String month) { * @param span */ public void seekBySpan(String direction, String seekAmount, String span) { - if(span.startsWith("by_")) span = span.substring(3); + if(span.startsWith(SEEK_PREFIX)) span = span.substring(3); int seekAmountInt = Integer.parseInt(seekAmount); - assert(direction.equals("<") || direction.equals(">")); + assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); assert(span.equals(DAY) || span.equals(WEEK) || span.equals(MONTH) || span.equals(YEAR) || span.equals(HOUR) || span.equals(MINUTE) || span.equals(SECOND)); @@ -187,7 +195,7 @@ public void seekBySpan(String direction, String seekAmount, String span) { span.equals(MONTH) || span.equals(YEAR); if(isDateSeek) markDateInvocation(); - int sign = direction.equals(">") ? 1 : -1; + int sign = direction.equals(DIR_RIGHT) ? 1 : -1; int field = span.equals(DAY) ? Calendar.DAY_OF_YEAR : span.equals(WEEK) ? Calendar.WEEK_OF_YEAR : @@ -300,7 +308,7 @@ else if(dayOfWeek != null) { public void setExplicitTime(String hours, String minutes, String seconds, String amPm, String zoneString) { int hoursInt = Integer.parseInt(hours); int minutesInt = Integer.parseInt(minutes); - assert(amPm == null || amPm.equals("am") || amPm.equals("pm")); + assert(amPm == null || amPm.equals(AM) || amPm.equals(PM)); assert(hoursInt >= 0 && hoursInt <= 23); assert(minutesInt >= 0 && minutesInt < 60); @@ -310,8 +318,8 @@ public void setExplicitTime(String hours, String minutes, String seconds, String // if no explicit zone is given, we use our own TimeZone zone = null; if(zoneString != null) { - if(zoneString.startsWith("+") || zoneString.startsWith("-")) { - zoneString = "GMT" + zoneString; + if(zoneString.startsWith(PLUS) || zoneString.startsWith(MINUS)) { + zoneString = GMT + zoneString; } zone = TimeZone.getTimeZone(zoneString); } @@ -323,7 +331,7 @@ public void setExplicitTime(String hours, String minutes, String seconds, String if(hoursInt <= 12) { int amPmInt = amPm == null ? (hoursInt >= 12 ? Calendar.PM : Calendar.AM) : - amPm.equals("pm") ? Calendar.PM : Calendar.AM; + amPm.equals(PM) ? Calendar.PM : Calendar.AM; _calendar.set(Calendar.AM_PM, amPmInt); // calendar is whacky at 12 o'clock (must use 0) @@ -350,10 +358,10 @@ public void captureDateTime() { Date date = _calendar.getTime(); _currentDateTimes.add(date); - // once a date time is captured, we don't reset the calendar just yet - // until the first date seek invocation occurrs. This allows us to keep - // the date fixed while seeking to a different time after the capture - // e.g. feb 28th at 6pm or 7pm + // once a date time is captured, we don't reset the calendar until + // the first date seek invocation occurrs. This allows us to keep + // the date fixed while seeking to a different time after the + // capture. For example: feb 28th at 6pm or 7pm. _firstDateInvocation = true; } diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index 5328d559..1969663e 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -1,6 +1,5 @@ package com.natty.parse; -import java.io.ByteArrayInputStream; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; @@ -8,23 +7,11 @@ import junit.framework.Assert; -import org.antlr.runtime.ANTLRInputStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.tree.CommonTree; -import org.antlr.runtime.tree.CommonTreeNodeStream; -import org.antlr.runtime.tree.Tree; import org.junit.Test; -import com.joestelmach.natty.ANTLRNoCaseInputStream; import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.ParseListener; -import com.joestelmach.natty.ParseLocation; import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; -import com.joestelmach.natty.generated.DateLexer; -import com.joestelmach.natty.generated.DateWalker; -import com.joestelmach.natty.generated.DebugDateParser; -import com.joestelmach.natty.generated.TreeRewrite; /** * Runs the parser through the various date formats @@ -151,20 +138,16 @@ public void testRange() throws Exception { validateDate(dates.get(1), 5, 31, 2011); dates = parseCollection("feb 28th or 2 days after"); - //Assert.assertEquals(2, dates.size()); - //validateDate(dates.get(0), 2, 28, 2011); - //validateDate(dates.get(1), 3, 2, 2011); - + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 28, 2011); + validateDate(dates.get(1), 3, 2, 2011); } public static void main(String[] args) throws Exception{ - String inputString = "2009-03-10 9:00 to 11:00"; + String inputString = "foo 2009-03-10 9:00 to 11:00 bar on baz 12/12 on asfdladsf a afasdfdasf sadf dasfsda"; Parser parser = new Parser(); ParseResult result = parser.parse(inputString); System.out.println(result.getSyntaxTree()); System.out.println(result.getDates()); - //for(ParseLocation l:result.getParseLocations()) { - //System.out.println(l.getRuleName()); - //} } } From a66516c666e8fdcdeb6d16b025edd555aa51ef0c Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 11 Feb 2011 00:35:23 -0500 Subject: [PATCH 021/134] handling formats like 'tues this week' --- src/main/antlr3/com/joestelmach/natty/generated/DateParser.g | 4 ++-- src/test/java/com/natty/parse/DateTest.java | 5 ++++- src/test/java/com/natty/parse/DateTimeTest.java | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 059cd432..c6bac2ae 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -196,7 +196,7 @@ relaxed_date ; relaxed_day_of_week - : (prefix WHITE_SPACE)? day_of_week ((COMMA WHITE_SPACE?) | WHITE_SPACE) -> day_of_week + : (prefix WHITE_SPACE)? day_of_week COMMA? WHITE_SPACE? -> day_of_week ; relaxed_day_of_month_prefix @@ -378,7 +378,7 @@ explicit_day_of_month_part explicit_day_of_week_part // monday of, tuesday of - : (THE WHITE_SPACE)? relaxed_day_of_week (IN | OF) + : (THE WHITE_SPACE)? relaxed_day_of_week (IN | OF)? -> ^(EXPLICIT_SEEK relaxed_day_of_week) // in the end of, at the beginning of diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index 1969663e..7dc3c52b 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -90,6 +90,9 @@ public void testRelative() throws Exception { validateDate("last week", 2, 21, 2011); validateDate("next year", 2, 28, 2012); validateDate("last year", 2, 28, 2010); + validateDate("tues this week", 3, 1, 2011); + validateDate("tuesday this week", 3, 1, 2011); + validateDate("tuesday next week", 3, 8, 2011); } @Test @@ -144,7 +147,7 @@ public void testRange() throws Exception { } public static void main(String[] args) throws Exception{ - String inputString = "foo 2009-03-10 9:00 to 11:00 bar on baz 12/12 on asfdladsf a afasdfdasf sadf dasfsda"; + String inputString = "3/3 21:00 to in 5 days"; Parser parser = new Parser(); ParseResult result = parser.parse(inputString); System.out.println(result.getSyntaxTree()); diff --git a/src/test/java/com/natty/parse/DateTimeTest.java b/src/test/java/com/natty/parse/DateTimeTest.java index b3c78085..1a6b564b 100644 --- a/src/test/java/com/natty/parse/DateTimeTest.java +++ b/src/test/java/com/natty/parse/DateTimeTest.java @@ -86,7 +86,7 @@ public void testRange() throws Exception { validateDateTime(dates.get(1), 12, 5, 2010, 7, 0, 0); /* - dates = parseCollection("3/3 21:00 to in 5 days"); + List dates = parseCollection("3/3 21:00 to in 5 days"); Assert.assertEquals(2, dates.size()); validateDateTime(dates.get(0), 3, 3, 2010, 21, 0, 0); validateDateTime(dates.get(1), 6, 17, 2010, 21, 0, 0); From 1febe37295af1b4617f634e8b3d9f3bc8aa28f7e Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 12 Feb 2011 01:05:25 -0500 Subject: [PATCH 022/134] persisting a given time across all dates in a group. for example: '3/3 21:00 to in 5 days' --- .../com/joestelmach/natty/WalkerState.java | 40 ++++++++++++++----- .../java/com/natty/parse/DateTimeTest.java | 4 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index aa3b350f..f15d5335 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -35,7 +35,8 @@ public class WalkerState { private TimeZone _defaultTimeZone; private int _currentYear; private List _currentDateTimes; - private boolean _firstDateInvocation = true; + private boolean _firstDateInvocationInGroup = true; + private boolean _timeGivenInGroup = false; /** * Creates a new WalkerState representing the start of @@ -193,7 +194,7 @@ public void seekBySpan(String direction, String seekAmount, String span) { boolean isDateSeek = span.equals(DAY) || span.equals(WEEK) || span.equals(MONTH) || span.equals(YEAR); - if(isDateSeek) markDateInvocation(); + if(isDateSeek) markDateInvocation(); else markTimeInvocation(); int sign = direction.equals(DIR_RIGHT) ? 1 : -1; int field = @@ -312,6 +313,8 @@ public void setExplicitTime(String hours, String minutes, String seconds, String assert(hoursInt >= 0 && hoursInt <= 23); assert(minutesInt >= 0 && minutesInt < 60); + markTimeInvocation(); + // reset milliseconds to 0 _calendar.set(Calendar.MILLISECOND, 0); @@ -357,12 +360,7 @@ public void setExplicitTime(String hours, String minutes, String seconds, String public void captureDateTime() { Date date = _calendar.getTime(); _currentDateTimes.add(date); - - // once a date time is captured, we don't reset the calendar until - // the first date seek invocation occurrs. This allows us to keep - // the date fixed while seeking to a different time after the - // capture. For example: feb 28th at 6pm or 7pm. - _firstDateInvocation = true; + _firstDateInvocationInGroup = true; } /** @@ -385,12 +383,32 @@ private void resetCalender() { * rule is captured */ private void markDateInvocation() { - if(_firstDateInvocation) { - resetCalender(); - _firstDateInvocation = false; + if(_firstDateInvocationInGroup) { + // if a time has been given within the current date group, + // we capture the current time before resetting the calendar + if(_timeGivenInGroup) { + int hours = _calendar.get(Calendar.HOUR_OF_DAY); + int minutes = _calendar.get(Calendar.MINUTE); + int seconds = _calendar.get(Calendar.SECOND); + resetCalender(); + _calendar.set(Calendar.HOUR_OF_DAY, hours); + _calendar.set(Calendar.MINUTE, minutes); + _calendar.set(Calendar.SECOND, seconds); + } + else { + resetCalender(); + } + _firstDateInvocationInGroup = false; } } + /** + * + */ + private void markTimeInvocation() { + _timeGivenInGroup = true; + } + /** * @return the current calendar */ diff --git a/src/test/java/com/natty/parse/DateTimeTest.java b/src/test/java/com/natty/parse/DateTimeTest.java index 1a6b564b..74f0ff6f 100644 --- a/src/test/java/com/natty/parse/DateTimeTest.java +++ b/src/test/java/com/natty/parse/DateTimeTest.java @@ -85,11 +85,9 @@ public void testRange() throws Exception { validateDateTime(dates.get(0), 12, 5, 2010, 6, 0, 0); validateDateTime(dates.get(1), 12, 5, 2010, 7, 0, 0); - /* - List dates = parseCollection("3/3 21:00 to in 5 days"); + dates = parseCollection("3/3 21:00 to in 5 days"); Assert.assertEquals(2, dates.size()); validateDateTime(dates.get(0), 3, 3, 2010, 21, 0, 0); validateDateTime(dates.get(1), 6, 17, 2010, 21, 0, 0); - */ } } From 5b0e3803f367d67cf9b9bedb30dbea91a389141a Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 13 Feb 2011 01:06:09 -0500 Subject: [PATCH 023/134] added 'and' and 'through' as conjunctions --- .../joestelmach/natty/generated/DateLexer.g | 2 ++ .../joestelmach/natty/generated/DateParser.g | 29 +++++++++---------- .../java/com/joestelmach/natty/Parser.java | 2 +- src/test/java/com/natty/parse/DateTest.java | 12 +++++++- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 94d34ff0..5f2eac73 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -234,7 +234,9 @@ SINGLE_QUOTE : '\''; IN : 'in'; THE : 'the'; OR : 'or'; +AND : 'and'; TO : 'to'; +THROUGH : 'through'; AT : 'at' | '@'; ON : 'on'; OF : 'of'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index c6bac2ae..7af0cb0f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -76,8 +76,8 @@ date date_time_alternative // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - : (date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix)=> - date WHITE_SPACE (OR | TO) WHITE_SPACE global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? + : (date conjunction global_date_prefix)=> + date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) // "next wed or thurs" , "next wed, thurs, or fri" @@ -89,29 +89,33 @@ date_time_alternative -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) // this wed. or next - | ((THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction)=> - (THIS WHITE_SPACE)? day_of_week WHITE_SPACE (OR | TO) WHITE_SPACE alternative_direction (date_time_separator explicit_time)? + | ((THIS WHITE_SPACE)? day_of_week conjunction alternative_direction)=> + (THIS WHITE_SPACE)? day_of_week conjunction alternative_direction (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) ) // 1/2 or 1/4 or 1/6 at 6pm - | (date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)?)=> - date (WHITE_SPACE (TO | OR) WHITE_SPACE date)+ (date_time_separator explicit_time)? + | (date (conjunction date)+ (date_time_separator explicit_time)?)=> + date (conjunction date)+ (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) // catch all date_time to date_time range - | (date_time WHITE_SPACE TO WHITE_SPACE date_time)=> - date_time WHITE_SPACE TO WHITE_SPACE date_time + | (date_time conjunction date_time)=> + date_time conjunction date_time -> ^(DATE_TIME_ALTERNATIVE date_time date_time) // single date_time | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) ; +conjunction + : WHITE_SPACE (AND | OR | TO | THROUGH) WHITE_SPACE + ; + alternative_day_of_month_list - : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (WHITE_SPACE (OR | TO) WHITE_SPACE relaxed_day_of_month)+) (date_time_separator explicit_time)?) + : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (conjunction relaxed_day_of_month)+) (date_time_separator explicit_time)?) -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ ; @@ -121,12 +125,7 @@ alternative_day_of_week_list ; day_of_week_list_separator - : COMMA (WHITE_SPACE | WHITE_SPACE (OR | TO) WHITE_SPACE) - | WHITE_SPACE (OR | TO) WHITE_SPACE - ; - -date_list_separator - : (COMMA WHITE_SPACE?) | (WHITE_SPACE (OR | TO) WHITE_SPACE) + : COMMA (WHITE_SPACE | conjunction) | conjunction ; alternative_direction diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 4b4ac5b2..585e75c8 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -44,7 +44,7 @@ public Parser(TimeZone defaultTimeZone) { * Creates a new parser with no explicit default time zone (default will be US/Eastern) */ public Parser() { - _defaultTimeZone = TimeZone.getTimeZone("America/New_York"); + _defaultTimeZone = TimeZone.getDefault(); } /** diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index 7dc3c52b..a26ac254 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -144,10 +144,20 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 2, 28, 2011); validateDate(dates.get(1), 3, 2, 2011); + + dates = parseCollection("tomorrow at 10 and monday at 11"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 3, 2011); + validateDate(dates.get(1), 1, 3, 2011); + + dates = parseCollection("tomorrow at 10 through tues at 11"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 3, 2011); + validateDate(dates.get(1), 1, 4, 2011); } public static void main(String[] args) throws Exception{ - String inputString = "3/3 21:00 to in 5 days"; + String inputString = "tomorrow at 10 and monday at 11"; Parser parser = new Parser(); ParseResult result = parser.parse(inputString); System.out.println(result.getSyntaxTree()); From 0707e31b47ae1becaa9a477555c627c5bb4edf17 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 13 Feb 2011 14:01:18 -0500 Subject: [PATCH 024/134] implemented day of year and enhanced the day of month: 'first to last day of 2009', 'first to last day of 2 septembers from now' --- .../joestelmach/natty/generated/DateLexer.g | 38 +++--- .../joestelmach/natty/generated/DateParser.g | 115 +++++++++++++++--- .../joestelmach/natty/generated/DateWalker.g | 11 +- .../joestelmach/natty/generated/TreeRewrite.g | 9 ++ .../com/joestelmach/natty/WalkerState.java | 17 +++ src/test/java/com/natty/parse/DateTest.java | 39 +++++- 6 files changed, 186 insertions(+), 43 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 5f2eac73..da3b3b42 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -4,26 +4,26 @@ lexer grammar DateLexer; // ********** date rules ********** -JANUARY : 'january' 's'? | 'jan' DOT?; -FEBRUARY : 'february' 's'? | 'feb' DOT?; -MARCH : 'march' 'es'? | 'mar' DOT?; -APRIL : 'april' 's'? | 'apr' DOT?; -MAY : 'may' 's'?; -JUNE : 'june' 's'? | 'jun' DOT?; -JULY : 'july' 's'? | 'jul' DOT?; -AUGUST : 'august' 's'? | 'aug' DOT?; -SEPTEMBER : 'september' 's'? | 'sep' DOT? | 'sept' DOT?; -OCTOBER : 'october' 's'? | 'oct' DOT?; -NOVEMBER : 'november' 's'? | 'nov' DOT?; -DECEMBER : 'december' 's'? | 'dec' DOT?; +JANUARY : 'january' 's'? | 'jan' DOT?; +FEBRUARY : 'february' 's'? | 'feb' DOT?; +MARCH : 'march' 'es'? | 'mar' DOT?; +APRIL : 'april' 's'? | 'apr' DOT?; +MAY : 'may' 's'?; +JUNE : 'june' 's'? | 'jun' DOT?; +JULY : 'july' 's'? | 'jul' DOT?; +AUGUST : 'august' 's'? | 'aug' DOT?; +SEPTEMBER : 'september' 's'? | 'sep' DOT? | 'sept' DOT?; +OCTOBER : 'october' 's'? | 'oct' DOT?; +NOVEMBER : 'november' 's'? | 'nov' DOT?; +DECEMBER : 'december' 's'? | 'dec' DOT?; -SUNDAY : 'sunday' 's'? | 'sun' DOT? | 'suns' DOT?; -MONDAY : 'monday' 's'? | 'mon' DOT? | 'mons' DOT?; -TUESDAY : 'tuesday' 's'? | 'tues' DOT? | 'tue' DOT?; -WEDNESDAY : 'wednesday' 's'? | 'wed' DOT? | 'weds' DOT?; -THURSDAY : 'thursday' 's'? | 'thur' DOT? | 'thu' DOT? | 'thus' DOT? | 'thurs' DOT?; -FRIDAY : 'friday' 's'? | 'fri' DOT? | 'fris' DOT?; -SATURDAY : 'saturday' 's'? | 'sat' DOT? | 'sats' DOT? | 'weekend'; +SUNDAY : 'sunday' 's'? | 'sun' DOT? | 'suns' DOT?; +MONDAY : 'monday' 's'? | 'mon' DOT? | 'mons' DOT?; +TUESDAY : 'tuesday' 's'? | 'tues' DOT? | 'tue' DOT?; +WEDNESDAY : 'wednesday' 's'? | 'wed' DOT? | 'weds' DOT?; +THURSDAY : 'thursday' 's'? | 'thur' DOT? | 'thu' DOT? | 'thus' DOT? | 'thurs' DOT?; +FRIDAY : 'friday' 's'? | 'fri' DOT? | 'fris' DOT?; +SATURDAY : 'saturday' 's'? | 'sat' DOT? | 'sats' DOT? | 'weekend'; HOUR : 'hour' | 'hours' | 'hr' | 'hrs'; MINUTE : 'minute' | 'minutes' | 'min' | 'mins'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 7af0cb0f..95e90927 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -10,6 +10,7 @@ tokens { MONTH_OF_YEAR; DAY_OF_MONTH; DAY_OF_WEEK; + DAY_OF_YEAR; YEAR_OF; DATE_TIME; DATE_TIME_ALTERNATIVE; @@ -66,15 +67,16 @@ time_date_separator ; date - : explicit_relative_date - | formal_date + : formal_date | relaxed_date | relative_date + | explicit_relative_date | global_date_prefix WHITE_SPACE date -> ^(RELATIVE_DATE ^(SEEK global_date_prefix date)) ; date_time_alternative + // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after : (date conjunction global_date_prefix)=> date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? @@ -101,6 +103,13 @@ date_time_alternative date (conjunction date)+ (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) + // first or last day of 2009 + | (explicit_day_of_year_part conjunction explicit_day_of_year_part WHITE_SPACE relaxed_year)=> + first=explicit_day_of_year_part conjunction second=explicit_day_of_year_part WHITE_SPACE relaxed_year + -> ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $first)) + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $second))) + // catch all date_time to date_time range | (date_time conjunction date_time)=> date_time conjunction date_time @@ -115,8 +124,28 @@ conjunction ; alternative_day_of_month_list + // mon may 15 or tues may 16 : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (conjunction relaxed_day_of_month)+) (date_time_separator explicit_time)?) -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ + + // first or last day of september + | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE explicit_relative_month)=> + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE explicit_relative_month + -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $first)) + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $second)) + + // first or last day of next september + | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month)=> + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $first)) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $second)) + + // first or last day of 2 septembers from now + | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix)=> + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE + spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $first)) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $second)) ; alternative_day_of_week_list @@ -225,6 +254,15 @@ relaxed_day_of_month -> ^(DAY_OF_MONTH spelled_first_to_thirty_first) ; +// TODO expand these to 366 +relaxed_day_of_year + : spelled_or_int_01_to_31_optional_prefix + -> ^(DAY_OF_YEAR spelled_or_int_01_to_31_optional_prefix) + + | spelled_first_to_thirty_first + -> ^(DAY_OF_YEAR spelled_first_to_thirty_first) + ; + relaxed_year : SINGLE_QUOTE? int_00_to_99_mandatory_prefix -> ^(YEAR_OF int_00_to_99_mandatory_prefix) @@ -307,9 +345,7 @@ explicit_relative_date : (explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix)=> explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix - -> ^(RELATIVE_DATE - ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) - explicit_day_of_month_part) + -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) explicit_day_of_month_part) // 10th of next month, 31st of last month, 10th of next october, 30th of this month, the last thursday of last november | (explicit_day_of_month_part WHITE_SPACE prefix)=> @@ -359,19 +395,23 @@ explicit_relative_date -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] relaxed_month) explicit_day_of_month_part) + + // the first day of 2009 + | explicit_day_of_year_part WHITE_SPACE relaxed_year + -> ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) explicit_day_of_year_part) ; explicit_day_of_month_part // first of, 10th of, 31st of, - : (THE WHITE_SPACE)? relaxed_day_of_month WHITE_SPACE (IN | OF) + : (THE WHITE_SPACE)? relaxed_day_of_month (WHITE_SPACE (IN | OF))? -> ^(EXPLICIT_SEEK relaxed_day_of_month) // the last thursday - | (THE WHITE_SPACE)? relative_occurrence_index WHITE_SPACE day_of_week WHITE_SPACE (IN | OF) + | (THE WHITE_SPACE)? relative_occurrence_index WHITE_SPACE day_of_week (WHITE_SPACE (IN | OF))? -> ^(EXPLICIT_SEEK relative_occurrence_index day_of_week) // in the start of, at the beginning of, the end of, last day of, first day of - | (((IN | AT) WHITE_SPACE)? THE WHITE_SPACE)? explicit_day_of_month_bound WHITE_SPACE (OF | IN) + | (((IN | AT) WHITE_SPACE)? THE WHITE_SPACE)? explicit_day_of_month_bound (WHITE_SPACE (OF | IN))? -> explicit_day_of_month_bound ; @@ -385,6 +425,31 @@ explicit_day_of_week_part -> explicit_day_of_week_bound ; +explicit_day_of_year_part + // last of, first of, 15th day of + : (THE WHITE_SPACE)? relaxed_day_of_year (WHITE_SPACE (IN | OF))? + -> ^(EXPLICIT_SEEK relaxed_day_of_year) + + // in the start of, at the beginning of, the end of, last day of, first day of + | (((IN | AT) WHITE_SPACE)? THE WHITE_SPACE)? explicit_day_of_year_bound (WHITE_SPACE (OF | IN))? + -> explicit_day_of_year_bound + ; + +// the lower or upper bound when talking about days in a year +explicit_day_of_year_bound + // beginning, start + : (BEGINNING | START) + -> ^(EXPLICIT_SEEK ^(DAY_OF_YEAR INT["1"])) + + // first day, 2nd day, etc + | (spelled_first_to_thirty_first WHITE_SPACE DAY) + -> ^(EXPLICIT_SEEK ^(DAY_OF_YEAR spelled_first_to_thirty_first)) + + // end, last day + | (END | (LAST WHITE_SPACE DAY)) + -> ^(EXPLICIT_SEEK ^(DAY_OF_YEAR INT["366"])) + ; + // the lower or upper bound when talking about days in a month explicit_day_of_month_bound // beginning, start @@ -671,18 +736,28 @@ spelled_one_to_thirty_one | SEVENTEEN -> INT["17"] | EIGHTEEN -> INT["18"] | NINETEEN -> INT["19"] - | TWENTY -> INT["20"] - | TWENTY (DASH | WHITE_SPACE)? ONE -> INT["21"] - | TWENTY (DASH | WHITE_SPACE)? TWO -> INT["22"] - | TWENTY (DASH | WHITE_SPACE)? THREE -> INT["23"] - | TWENTY (DASH | WHITE_SPACE)? FOUR -> INT["24"] - | TWENTY (DASH | WHITE_SPACE)? FIVE -> INT["25"] - | TWENTY (DASH | WHITE_SPACE)? SIX -> INT["26"] - | TWENTY (DASH | WHITE_SPACE)? SEVEN -> INT["27"] - | TWENTY (DASH | WHITE_SPACE)? EIGHT -> INT["28"] - | TWENTY (DASH | WHITE_SPACE)? NINE -> INT["29"] - | THIRTY -> INT["30"] - | THIRTY (DASH | WHITE_SPACE)? ONE -> INT["31"] + | (TWENTY WHITE_SPACE ONE)=> TWENTY WHITE_SPACE ONE -> INT["21"] + | TWENTY DASH? ONE -> INT["21"] + | (TWENTY WHITE_SPACE TWO)=> TWENTY WHITE_SPACE TWO -> INT["22"] + | TWENTY DASH? TWO -> INT["22"] + | (TWENTY WHITE_SPACE THREE)=> TWENTY WHITE_SPACE THREE -> INT["23"] + | TWENTY DASH? THREE -> INT["23"] + | (TWENTY WHITE_SPACE FOUR)=> TWENTY WHITE_SPACE FOUR -> INT["24"] + | TWENTY DASH? FOUR -> INT["24"] + | (TWENTY WHITE_SPACE FIVE)=> TWENTY WHITE_SPACE FIVE -> INT["25"] + | TWENTY DASH? FIVE -> INT["25"] + | (TWENTY WHITE_SPACE SIX)=> TWENTY WHITE_SPACE SIX -> INT["26"] + | TWENTY DASH? SIX -> INT["26"] + | (TWENTY WHITE_SPACE SEVEN)=> TWENTY WHITE_SPACE SEVEN -> INT["27"] + | TWENTY DASH? SEVEN -> INT["27"] + | (TWENTY WHITE_SPACE EIGHT)=> TWENTY WHITE_SPACE EIGHT -> INT["28"] + | TWENTY DASH? EIGHT -> INT["28"] + | (TWENTY WHITE_SPACE NINE)=> TWENTY WHITE_SPACE NINE -> INT["29"] + | TWENTY DASH? NINE -> INT["29"] + | TWENTY -> INT["20"] + | (THIRTY WHITE_SPACE ONE)=> THIRTY WHITE_SPACE ONE -> INT["31"] + | THIRTY DASH? ONE -> INT["31"] + | THIRTY -> INT["30"] ; // a spelled number in sequence between first and thirty-first diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 02f99972..89c76e37 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -32,7 +32,7 @@ date ; relative_date - : ^(RELATIVE_DATE seek explicit_seek*) + : ^(RELATIVE_DATE seek? explicit_seek*) ; week_index @@ -46,7 +46,6 @@ explicit_date {_walkerState.setExplicitDate($month.text, $dom.text, $dow.text, $year.text);} ; - time : explicit_time | relative_time @@ -77,12 +76,18 @@ seek ; explicit_seek - : ^(EXPLICIT_SEEK ^(DAY_OF_MONTH month=INT)) + : ^(EXPLICIT_SEEK ^(MONTH_OF_YEAR day=INT)) + {_walkerState.seekToMonth(">", "0", $day.text);} + + | ^(EXPLICIT_SEEK ^(DAY_OF_MONTH month=INT)) {_walkerState.seekToDayOfMonth($month.text);} | ^(EXPLICIT_SEEK ^(DAY_OF_WEEK day=INT)) {_walkerState.seekToDayOfWeek(">", "by_week", "0", $day.text);} + | ^(EXPLICIT_SEEK ^(DAY_OF_YEAR day=INT)) + {_walkerState.seekToDayOfYear($day.text);} + | ^(EXPLICIT_SEEK ^(YEAR_OF year=INT)) {_walkerState.seekToYear($year.text);} diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index bad47b16..13231900 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -12,4 +12,13 @@ options { topdown : ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK INT) ^(DAY_OF_WEEK dow=INT)) -> ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK $dow)) + + | ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_MONTH INT) ^(DAY_OF_MONTH dow=INT)) + -> ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_MONTH $dow)) + + | ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR INT) ^(MONTH_OF_YEAR dow=INT)) + -> ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR $dow)) + + | ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR INT) amount=INT ^(MONTH_OF_YEAR dow=INT)) + -> ^(SEEK DIRECTION SEEK_BY $amount ^(MONTH_OF_YEAR $dow)) ; \ No newline at end of file diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index f15d5335..30b1e136 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -116,6 +116,23 @@ public void seekToDayOfMonth(String dayOfMonth) { _calendar.set(Calendar.DAY_OF_MONTH, dayOfMonthInt); } + /** + * Seeks to the given day within the current year + * @param dayOfYear the day of the year to seek to, represented as an integer + * from 1 to 366. Must be guaranteed to parse as an Integer. If this day is + * beyond the last day of the current year, the actual last day of the year + * will be used. + */ + public void seekToDayOfYear(String dayOfYear) { + int dayOfYearInt = Integer.parseInt(dayOfYear); + assert(dayOfYearInt >= 1 && dayOfYearInt <= 366); + + markDateInvocation(); + + dayOfYearInt = Math.min(dayOfYearInt, _calendar.getActualMaximum(Calendar.DAY_OF_YEAR)); + _calendar.set(Calendar.DAY_OF_YEAR, dayOfYearInt); + } + /** * * @param year diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index a26ac254..e18545c2 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.ParseLocation; import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; @@ -154,12 +155,48 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 1, 3, 2011); validateDate(dates.get(1), 1, 4, 2011); + + dates = parseCollection("first day of 2009 to last day of 2009"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 1, 2009); + validateDate(dates.get(1), 12, 31, 2009); + + dates = parseCollection("first to last day of 2008"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 1, 2008); + validateDate(dates.get(1), 12, 31, 2008); + + dates = parseCollection("first to last day of september"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 9, 1, 2011); + validateDate(dates.get(1), 9, 30, 2011); + + dates = parseCollection("first to last day of this september"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 9, 1, 2011); + validateDate(dates.get(1), 9, 30, 2011); + + dates = parseCollection("first to last day of 2 septembers ago"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 9, 1, 2009); + validateDate(dates.get(1), 9, 30, 2009); + + dates = parseCollection("first to last day of 2 septembers from now"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 9, 1, 2012); + validateDate(dates.get(1), 9, 30, 2012); } public static void main(String[] args) throws Exception{ - String inputString = "tomorrow at 10 and monday at 11"; + //String inputString = "first to last day of this september"; + String inputString = "first to last day of september"; + //String inputString = "1st oct in the year '89 1300 hours"; Parser parser = new Parser(); + parser.setDebug(true); ParseResult result = parser.parse(inputString); + for(ParseLocation loc:result.getParseLocations()) { + System.out.println(loc.getRuleName()); + } System.out.println(result.getSyntaxTree()); System.out.println(result.getDates()); } From 221da105759d5b40b63999112aa8db78f28030ab Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 16 Feb 2011 00:06:38 -0500 Subject: [PATCH 025/134] new search functionality is operational --- pom.xml | 46 +-------------- .../joestelmach/natty/generated/DateWalker.g | 7 +++ .../java/com/joestelmach/natty/DateGroup.java | 49 ++++++++++++++++ .../com/joestelmach/natty/ParseListener.java | 50 ++++++++++------ .../com/joestelmach/natty/ParseLocation.java | 9 +++ .../com/joestelmach/natty/ParseResult.java | 11 ++-- .../java/com/joestelmach/natty/Parser.java | 57 ++++++++----------- .../com/joestelmach/natty/WalkerState.java | 21 +++++-- src/test/{gunit => }/DateParser.testsuite | 0 .../java/com/natty/parse/AbstractTest.java | 4 +- src/test/java/com/natty/parse/DateTest.java | 19 +++---- 11 files changed, 153 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/joestelmach/natty/DateGroup.java rename src/test/{gunit => }/DateParser.testsuite (100%) diff --git a/pom.xml b/pom.xml index 3c0a0ed6..6dbd1ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -93,46 +93,6 @@ - - - maven-antrun-plugin - - - createDebugGrammars - validate - - run - - - - - - - - - - - - - - deleteDebugGrammars - test - - run - - - - - - - - - - - - - - org.antlr @@ -151,8 +111,7 @@ src/main/java 100000 - com/joestelmach/natty/generated/DebugDateParser.g - com/joestelmach/natty/generated/DebugDateWalker.g + com/joestelmach/natty/generated/DateParser.g @@ -168,8 +127,7 @@ src/main/java 100000 - com/joestelmach/natty/generated/DebugDateParser.g - com/joestelmach/natty/generated/DebugDateWalker.g + com/joestelmach/natty/generated/DateParser.g diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 89c76e37..9d6a0ae0 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -15,7 +15,14 @@ options { } } +list + : date_time_alternative+ + ; + date_time_alternative + @after { + _walkerState.captureDateGroup(); + } : ^(DATE_TIME_ALTERNATIVE date_time+) ; diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java new file mode 100644 index 00000000..1674c842 --- /dev/null +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -0,0 +1,49 @@ +package com.joestelmach.natty; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * + * @author Joe Stelmach + * + */ +public class DateGroup { + private List _dates; + private String _text; + private int _line; + private int _position; + + public DateGroup() { + _dates = new ArrayList(); + } + + public List getDates() { + return _dates; + } + public void addDate(Date date) { + _dates.add(date); + } + + public String getText() { + return _text; + } + public void setText(String text) { + _text = text; + } + + public int getLine() { + return _line; + } + public void setLine(int line) { + _line = line; + } + + public int getPosition() { + return _position; + } + public void setPosition(int position) { + _position = position; + } +} diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index f61109fe..7ecb1b92 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -50,24 +50,27 @@ public class ParseListener extends BlankDebugEventListener { INTERESTING_RULES.put("meridian_indicator", "am/pm"); INTERESTING_RULES.put("time_zone", "zone"); INTERESTING_RULES.put("time", "time"); - INTERESTING_RULES.put("text", "text"); - INTERESTING_RULES.put("entry", "entry"); - INTERESTING_RULES.put("known_token", "known_token"); } private int backtracking = 0; private Map>> _ruleMap; private List _locations; + private List _dateGroupLocations; public ParseListener() { _ruleMap = new HashMap>>(); _locations = new ArrayList(); + _dateGroupLocations = new ArrayList(); } public List getLocations() { return _locations; } + public List getDateGroupLocations() { + return _dateGroupLocations; + } + // don't add backtracking or cyclic DFA nodes public void enterDecision(int d) { backtracking++; @@ -93,22 +96,35 @@ public void exitRule(String filename, String ruleName) { if (backtracking > 0) return; List tokenList = _ruleMap.get(ruleName).pop(); - if(tokenList.size() > 0 && INTERESTING_RULES.keySet().contains(ruleName)) { + + if(tokenList.size() > 0) { + boolean isAlternative = ruleName.equals("date_time_alternative"); + boolean isInteresting = INTERESTING_RULES.keySet().contains(ruleName); + if(isAlternative || isInteresting) { + StringBuilder builder = new StringBuilder(); + for(Token token:tokenList) { + builder.append(token.getText()); + } + String text = builder.toString(); + int line = tokenList.get(0).getLine(); + int start = tokenList.get(0).getCharPositionInLine(); + int end = start + text.length(); - StringBuilder builder = new StringBuilder(); - for(Token token:tokenList) { - builder.append(token.getText()); - } - String text = builder.toString(); - int start = tokenList.get(0).getCharPositionInLine(); - int end = start + text.length(); + ParseLocation location = new ParseLocation(); + location.setRuleName(ruleName); + location.setText(text); + location.setLine(line); + location.setStart(start); + location.setEnd(end); - ParseLocation location = new ParseLocation(); - location.setRuleName(INTERESTING_RULES.get(ruleName)); - location.setText(text); - location.setStart(start); - location.setEnd(end); - _locations.add(location); + if(isAlternative) { + _dateGroupLocations.add(location); + } + + if(INTERESTING_RULES.keySet().contains(ruleName)) { + _locations.add(location); + } + } } } diff --git a/src/main/java/com/joestelmach/natty/ParseLocation.java b/src/main/java/com/joestelmach/natty/ParseLocation.java index 6eed1101..35ef524f 100644 --- a/src/main/java/com/joestelmach/natty/ParseLocation.java +++ b/src/main/java/com/joestelmach/natty/ParseLocation.java @@ -8,6 +8,7 @@ public class ParseLocation { private String _ruleName; private String _text; + private int _line; private int _start; private int _end; @@ -27,6 +28,14 @@ public void setText(String text) { _text = text; } + public int getLine() { + return _line; + } + + public void setLine(int line) { + _line = line; + } + public int getStart() { return _start; } diff --git a/src/main/java/com/joestelmach/natty/ParseResult.java b/src/main/java/com/joestelmach/natty/ParseResult.java index bca598b4..051bf225 100644 --- a/src/main/java/com/joestelmach/natty/ParseResult.java +++ b/src/main/java/com/joestelmach/natty/ParseResult.java @@ -1,6 +1,5 @@ package com.joestelmach.natty; -import java.util.Date; import java.util.List; /** @@ -10,16 +9,16 @@ */ public class ParseResult { - private List _dateTimes; + private List _dateGroups; private List _parseLocations; private String _syntaxTree; - public List getDates() { - return _dateTimes; + public List getDateGroups() { + return _dateGroups; } - public void setDateTimes(List dateTimes) { - _dateTimes = dateTimes; + public void setDateGroups(List dateGroups) { + _dateGroups = dateGroups; } public List getParseLocations() { diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 585e75c8..cc7680e6 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,7 +17,6 @@ import com.joestelmach.natty.generated.DateLexer; import com.joestelmach.natty.generated.DateParser; import com.joestelmach.natty.generated.DateWalker; -import com.joestelmach.natty.generated.DebugDateParser; import com.joestelmach.natty.generated.TreeRewrite; /** @@ -26,9 +26,6 @@ public class Parser { private TimeZone _defaultTimeZone; - private boolean _debug; - private ParseListener _debugListener; - private static final String MARKER = " ___ "; private static final Logger _logger = Logger.getLogger(Parser.class.getName()); @@ -47,22 +44,14 @@ public Parser() { _defaultTimeZone = TimeZone.getDefault(); } - /** - * Enables the collection of parse information - * @param debug - */ - public void setDebug(final boolean debug) { - _debug = debug; - } - /** * Parses the input string for a list of date times, assuming no * extraneous text is present. * @param inputString * @return */ - public ParseResult parse(final String inputString) { - ParseResult result = new ParseResult(); + public List parse(final String inputString) { + List groups = null; try { String cleanedInput = MARKER + inputString.trim() + MARKER; @@ -72,36 +61,36 @@ public ParseResult parse(final String inputString) { DateLexer lexer = new DateLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); - // parse with debug - Tree tree = null; - if(_debug) { - _debugListener = new ParseListener(); - DebugDateParser parser = new DebugDateParser(tokens, _debugListener); - DebugDateParser.parse_return parseReturn = parser.parse(); - tree = (Tree) parseReturn.getTree(); - result.setParseLocations(_debugListener.getLocations()); - } - - // or parse without debug - else { - DateParser parser = new DateParser(tokens); - DateParser.parse_return parseReturn = parser.parse(); - tree = (Tree) parseReturn.getTree(); - } + // parse + ParseListener listener = new ParseListener(); + DateParser parser = new DateParser(tokens, listener); + DateParser.parse_return parseReturn = parser.parse(); + Tree tree = (Tree) parseReturn.getTree(); // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); TreeRewrite s = new TreeRewrite(nodes); tree = (CommonTree)s.downup(tree); - result.setSyntaxTree(tree.toStringTree()); // and walk it nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(tokens); DateWalker walker = new DateWalker(nodes); walker.getState().setDefaultTimeZone(_defaultTimeZone); - walker.date_time_alternative(); - result.setDateTimes(walker.getState().getDateTimes()); + walker.list(); + + // run through the results and append the parse information + groups = walker.getState().getDateGroups(); + List groupLocations = listener.getDateGroupLocations(); + for(int i=0; i i) { + DateGroup group = groups.get(i); + ParseLocation location = groupLocations.get(i); + group.setLine(location.getLine()); + group.setText(location.getText()); + group.setPosition(location.getStart()); + } + } } catch(IOException e) { _logger.log(Level.SEVERE, "Could not read from input stream", e); @@ -110,6 +99,6 @@ public ParseResult parse(final String inputString) { _logger.log(Level.SEVERE, "Could not parse input", e); } - return result; + return groups; } } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 30b1e136..9b60e579 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -34,17 +34,20 @@ public class WalkerState { private GregorianCalendar _calendar; private TimeZone _defaultTimeZone; private int _currentYear; - private List _currentDateTimes; private boolean _firstDateInvocationInGroup = true; private boolean _timeGivenInGroup = false; + private List _dateGroups; + private DateGroup _currentDateGroup; + /** * Creates a new WalkerState representing the start of * the next hour from the current time */ public WalkerState() { resetCalender(); - _currentDateTimes = new ArrayList(); + _currentDateGroup = new DateGroup(); + _dateGroups = new ArrayList(); } public void setDefaultTimeZone(final TimeZone zone) { @@ -376,15 +379,23 @@ public void setExplicitTime(String hours, String minutes, String seconds, String */ public void captureDateTime() { Date date = _calendar.getTime(); - _currentDateTimes.add(date); + _currentDateGroup.addDate(date); _firstDateInvocationInGroup = true; } + /** + * + */ + public void captureDateGroup() { + _dateGroups.add(_currentDateGroup); + _currentDateGroup = new DateGroup(); + } + /** * @return the list of date times found */ - public List getDateTimes() { - return _currentDateTimes; + public List getDateGroups() { + return _dateGroups; } /** diff --git a/src/test/gunit/DateParser.testsuite b/src/test/DateParser.testsuite similarity index 100% rename from src/test/gunit/DateParser.testsuite rename to src/test/DateParser.testsuite diff --git a/src/test/java/com/natty/parse/AbstractTest.java b/src/test/java/com/natty/parse/AbstractTest.java index 082ab0f3..d06e7ac0 100644 --- a/src/test/java/com/natty/parse/AbstractTest.java +++ b/src/test/java/com/natty/parse/AbstractTest.java @@ -8,7 +8,6 @@ import org.junit.Before; import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.ParseResult; import com.joestelmach.natty.Parser; public abstract class AbstractTest { @@ -30,8 +29,7 @@ public void before() { * @return */ protected List parseCollection(String value) { - ParseResult result = _parser.parse(value); - return result.getDates(); + return _parser.parse(value).get(0).getDates(); } /** diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index e18545c2..827abbb8 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -10,8 +10,7 @@ import org.junit.Test; import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.ParseLocation; -import com.joestelmach.natty.ParseResult; +import com.joestelmach.natty.DateGroup; import com.joestelmach.natty.Parser; /** @@ -188,16 +187,14 @@ public void testRange() throws Exception { } public static void main(String[] args) throws Exception{ - //String inputString = "first to last day of this september"; - String inputString = "first to last day of september"; - //String inputString = "1st oct in the year '89 1300 hours"; + String inputString = "asdf dasf saf first to last day of september asdf asf oct 1st, 1980 asfda "; Parser parser = new Parser(); - parser.setDebug(true); - ParseResult result = parser.parse(inputString); - for(ParseLocation loc:result.getParseLocations()) { - System.out.println(loc.getRuleName()); + List groups = parser.parse(inputString); + for(DateGroup group:groups) { + System.out.println(group.getDates()); + System.out.println(group.getLine()); + System.out.println(group.getPosition()); + System.out.println(group.getText()); } - System.out.println(result.getSyntaxTree()); - System.out.println(result.getDates()); } } From 3761e74b6c35f72b0a22da4ff9397a2a7c4f32d3 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 16 Feb 2011 18:24:55 -0500 Subject: [PATCH 026/134] re-structuring antlr generation --- pom.xml | 45 +++++++------------ .../java/com/joestelmach/natty/Parser.java | 6 +++ src/test/java/com/natty/parse/DateTest.java | 2 +- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index 6dbd1ff8..f434e32a 100644 --- a/pom.xml +++ b/pom.xml @@ -100,55 +100,42 @@ 3.2 - - non-debug + debug antlr - false + true src/main/java - 100000 - - com/joestelmach/natty/generated/DateParser.g - + 10000 + + com/joestelmach/natty/generated/DateLexer.g + com/joestelmach/natty/generated/DateParser.g + - - + - debug + non-debug antlr - true + false src/main/java - 100000 + 10000 - com/joestelmach/natty/generated/DateParser.g + + com/joestelmach/natty/generated/DateLexer.g + com/joestelmach/natty/generated/DateParser.g + + - - - - org.antlr - maven-gunit-plugin - 3.2 - - - maven-gunit-plugin - test - - gunit - - - - diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index cc7680e6..75d0f68e 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -8,6 +8,7 @@ import java.util.logging.Logger; import org.antlr.runtime.ANTLRInputStream; +import org.antlr.runtime.CommonToken; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.CommonTree; @@ -64,6 +65,11 @@ public List parse(final String inputString) { // parse ParseListener listener = new ParseListener(); DateParser parser = new DateParser(tokens, listener); + + for(Object t:tokens.getTokens()) { + System.out.println(parser.getTokenNames()[((CommonToken)t).getType()]); + } + DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/natty/parse/DateTest.java index 827abbb8..0f3a8430 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/natty/parse/DateTest.java @@ -187,7 +187,7 @@ public void testRange() throws Exception { } public static void main(String[] args) throws Exception{ - String inputString = "asdf dasf saf first to last day of september asdf asf oct 1st, 1980 asfda "; + String inputString = "golf tomorrow at 9 AM ssdf pebble beach"; Parser parser = new Parser(); List groups = parser.parse(inputString); for(DateGroup group:groups) { From 0761dcc807fc208a23f06e4cc81068b068faab5c Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 20 Feb 2011 00:06:05 -0500 Subject: [PATCH 027/134] new approach to parsing within strings --- pom.xml | 6 +- .../joestelmach/natty/generated/DateLexer.g | 6 +- .../joestelmach/natty/generated/DateParser.g | 34 +- .../joestelmach/natty/generated/DateWalker.g | 7 - .../java/com/joestelmach/natty/DateGroup.java | 20 + .../joestelmach/natty/NattyTokenSource.java | 23 + .../com/joestelmach/natty/ParseListener.java | 9 +- .../com/joestelmach/natty/ParseResult.java | 39 - .../java/com/joestelmach/natty/Parser.java | 139 +++- .../com/joestelmach/natty/WalkerState.java | 22 +- .../natty}/AbstractTest.java | 2 +- .../parse => joestelmach/natty}/DateTest.java | 18 +- .../natty}/DateTimeTest.java | 3 +- .../parse => joestelmach/natty}/TimeTest.java | 2 +- .../grammar/AbstractGrammarTest.java | 70 ++ .../joestelmach/grammar/DateGrammarTest.java | 632 +++++++++++++++ .../grammar/NumericGrammarTest.java | 725 ++++++++++++++++++ .../joestelmach/grammar/TimeGrammarTest.java | 189 +++++ 18 files changed, 1804 insertions(+), 142 deletions(-) create mode 100644 src/main/java/com/joestelmach/natty/NattyTokenSource.java delete mode 100644 src/main/java/com/joestelmach/natty/ParseResult.java rename src/test/java/com/{natty/parse => joestelmach/natty}/AbstractTest.java (99%) rename src/test/java/com/{natty/parse => joestelmach/natty}/DateTest.java (95%) rename src/test/java/com/{natty/parse => joestelmach/natty}/DateTimeTest.java (99%) rename src/test/java/com/{natty/parse => joestelmach/natty}/TimeTest.java (98%) create mode 100644 src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java create mode 100644 src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java create mode 100644 src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java create mode 100644 src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java diff --git a/pom.xml b/pom.xml index f434e32a..6a38c608 100644 --- a/pom.xml +++ b/pom.xml @@ -107,12 +107,15 @@ true + src/main/antlr3/com/joestelmach/natty/generated/imports src/main/java 10000 com/joestelmach/natty/generated/DateLexer.g com/joestelmach/natty/generated/DateParser.g + + @@ -124,9 +127,8 @@ false src/main/java + src/main/antlr3/com/joestelmach/natty/generated/imports 10000 - - com/joestelmach/natty/generated/DateLexer.g com/joestelmach/natty/generated/DateParser.g diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index da3b3b42..ba14ab07 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -38,6 +38,9 @@ YESTERDAY : 'yesterday'; // ********** time rules ********** +AT : 'at' | '@'; +AFTER : 'after'; +PAST : 'past'; AM : 'am' | 'a.m.' | 'a'; PM : 'pm' | 'p.m.' | 'p'; T : 't'; @@ -237,21 +240,18 @@ OR : 'or'; AND : 'and'; TO : 'to'; THROUGH : 'through'; -AT : 'at' | '@'; ON : 'on'; OF : 'of'; THIS : 'this'; THAT : 'that'; LAST : 'last' | 'final'; NEXT : 'next'; -PAST : 'past'; COMING : 'coming'; UPCOMING : 'upcoming'; FROM : 'from'; NOW : 'now'; AGO : 'ago'; BEFORE : 'before'; -AFTER : 'after'; BEGINNING : 'beginning' | 'begining'; START : 'start'; END : 'end'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 95e90927..8f80f136 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -1,8 +1,8 @@ parser grammar DateParser; options { - output=AST; tokenVocab=DateLexer; + output=AST; } tokens { @@ -36,20 +36,12 @@ tokens { } parse - : text (((date_time_alternative)=>date_time_alternative | known_token) text)+ -> date_time_alternative* - ; - -known_token - : ~(UNKNOWN | WHITE_SPACE) - ; - -text - : WHITE_SPACE (UNKNOWN+ WHITE_SPACE)+ + : date_time_alternative ; date_time : ( - (date)=> date (date_time_separator explicit_time)? + (date)=>date (date_time_separator explicit_time)? | explicit_time (time_date_separator date)? ) -> ^(DATE_TIME date? explicit_time?) | relative_time -> ^(DATE_TIME relative_time?) @@ -130,22 +122,22 @@ alternative_day_of_month_list // first or last day of september | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE explicit_relative_month)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE explicit_relative_month - -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $first)) - ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $second)) + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE explicit_relative_month (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $first) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $second) explicit_time?) // first or last day of next september | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $first)) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $second)) + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $first) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $second) explicit_time?) // first or last day of 2 septembers from now | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix)=> first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE - spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $first)) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $second)) + spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $first) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $second) explicit_time?) ; alternative_day_of_week_list @@ -831,4 +823,4 @@ int_1_to_9 int_1_to_5 : INT_1 | INT_2 | INT_3 | INT_4 | INT_5 ; - \ No newline at end of file + diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 9d6a0ae0..89c76e37 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -15,14 +15,7 @@ options { } } -list - : date_time_alternative+ - ; - date_time_alternative - @after { - _walkerState.captureDateGroup(); - } : ^(DATE_TIME_ALTERNATIVE date_time+) ; diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index 1674c842..9e044ee4 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -4,6 +4,8 @@ import java.util.Date; import java.util.List; +import org.antlr.runtime.tree.Tree; + /** * * @author Joe Stelmach @@ -14,6 +16,8 @@ public class DateGroup { private String _text; private int _line; private int _position; + private List _parseLocations; + private Tree _syntaxTree; public DateGroup() { _dates = new ArrayList(); @@ -46,4 +50,20 @@ public int getPosition() { public void setPosition(int position) { _position = position; } + + public List getParseLocations() { + return _parseLocations; + } + + public void setParseLocations(List parseLocations) { + _parseLocations = parseLocations; + } + + public Tree getSyntaxTree() { + return _syntaxTree; + } + + public void setSyntaxTree(Tree syntaxTree) { + _syntaxTree = syntaxTree; + } } diff --git a/src/main/java/com/joestelmach/natty/NattyTokenSource.java b/src/main/java/com/joestelmach/natty/NattyTokenSource.java new file mode 100644 index 00000000..141e9555 --- /dev/null +++ b/src/main/java/com/joestelmach/natty/NattyTokenSource.java @@ -0,0 +1,23 @@ +package com.joestelmach.natty; + +import java.util.List; + +import org.antlr.runtime.Token; +import org.antlr.runtime.TokenSource; + +public class NattyTokenSource implements TokenSource { + private List _tokens; + private int _index = 0; + + public NattyTokenSource(List tokens) { + _tokens = tokens; + } + + public Token nextToken() { + return _tokens.size() > _index ? _tokens.get(_index++) : null; + } + + public String getSourceName() { + return "natty"; + } +} diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index 7ecb1b92..26849a01 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -55,20 +55,19 @@ public class ParseListener extends BlankDebugEventListener { private int backtracking = 0; private Map>> _ruleMap; private List _locations; - private List _dateGroupLocations; + private ParseLocation _dateGroupLocation; public ParseListener() { _ruleMap = new HashMap>>(); _locations = new ArrayList(); - _dateGroupLocations = new ArrayList(); } public List getLocations() { return _locations; } - public List getDateGroupLocations() { - return _dateGroupLocations; + public ParseLocation getDateGroupLocation() { + return _dateGroupLocation; } // don't add backtracking or cyclic DFA nodes @@ -118,7 +117,7 @@ public void exitRule(String filename, String ruleName) { location.setEnd(end); if(isAlternative) { - _dateGroupLocations.add(location); + _dateGroupLocation = location; } if(INTERESTING_RULES.keySet().contains(ruleName)) { diff --git a/src/main/java/com/joestelmach/natty/ParseResult.java b/src/main/java/com/joestelmach/natty/ParseResult.java deleted file mode 100644 index 051bf225..00000000 --- a/src/main/java/com/joestelmach/natty/ParseResult.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.joestelmach.natty; - -import java.util.List; - -/** - * Represents the result of a parse - * - * @author Joe Stelmach - */ -public class ParseResult { - - private List _dateGroups; - private List _parseLocations; - private String _syntaxTree; - - public List getDateGroups() { - return _dateGroups; - } - - public void setDateGroups(List dateGroups) { - _dateGroups = dateGroups; - } - - public List getParseLocations() { - return _parseLocations; - } - - public void setParseLocations(List parseLocations) { - _parseLocations = parseLocations; - } - - public String getSyntaxTree() { - return _syntaxTree; - } - - public void setSyntaxTree(String syntaxTree) { - _syntaxTree = syntaxTree; - } -} diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 75d0f68e..ed329c16 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -2,15 +2,17 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import org.antlr.runtime.ANTLRInputStream; -import org.antlr.runtime.CommonToken; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; +import org.antlr.runtime.Token; +import org.antlr.runtime.TokenStream; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.CommonTreeNodeStream; import org.antlr.runtime.tree.Tree; @@ -27,7 +29,6 @@ public class Parser { private TimeZone _defaultTimeZone; - private static final String MARKER = " ___ "; private static final Logger _logger = Logger.getLogger(Parser.class.getName()); /** @@ -46,30 +47,52 @@ public Parser() { } /** - * Parses the input string for a list of date times, assuming no - * extraneous text is present. - * @param inputString + * Parses the given input value for one or more groups of + * date alternatives + * + * @param value * @return */ - public List parse(final String inputString) { - List groups = null; + public List parse(String value) { + + // lex the input value to obtain our global token stream + ANTLRInputStream input = null; try { - String cleanedInput = MARKER + inputString.trim() + MARKER; - - // lex - ANTLRInputStream input = new ANTLRNoCaseInputStream( - new ByteArrayInputStream(cleanedInput.getBytes())); - DateLexer lexer = new DateLexer(input); - CommonTokenStream tokens = new CommonTokenStream(lexer); + input = new ANTLRNoCaseInputStream(new ByteArrayInputStream(value.trim().getBytes())); + } catch (IOException e) { + _logger.log(Level.SEVERE, "could not lex input", e); + } + DateLexer lexer = new DateLexer(input); + + // collect all sub-token streams that may include date information + List streams = collectTokenStreams(new CommonTokenStream(lexer)); + + // and parse each of them + List groups = new ArrayList(); + for(TokenStream stream:streams) { + DateGroup group = singleParse(stream); + if(group.getDates().size() > 0) { + groups.add(group); + } + } + return groups; + } + + /** + * Parses the token stream for a SINGLE date time alternative. This + * method assumes that the entire token stream represents date and or + * time information (no extraneous tokens) + * + * @param stream + * @return + */ + private DateGroup singleParse(TokenStream stream) { + DateGroup group = null; + try { // parse ParseListener listener = new ParseListener(); - DateParser parser = new DateParser(tokens, listener); - - for(Object t:tokens.getTokens()) { - System.out.println(parser.getTokenNames()[((CommonToken)t).getType()]); - } - + DateParser parser = new DateParser(stream, listener); DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); @@ -80,31 +103,75 @@ public List parse(final String inputString) { // and walk it nodes = new CommonTreeNodeStream(tree); - nodes.setTokenStream(tokens); + nodes.setTokenStream(stream); DateWalker walker = new DateWalker(nodes); walker.getState().setDefaultTimeZone(_defaultTimeZone); - walker.list(); + walker.date_time_alternative(); // run through the results and append the parse information - groups = walker.getState().getDateGroups(); - List groupLocations = listener.getDateGroupLocations(); - for(int i=0; i i) { - DateGroup group = groups.get(i); - ParseLocation location = groupLocations.get(i); - group.setLine(location.getLine()); - group.setText(location.getText()); - group.setPosition(location.getStart()); - } - } - - } catch(IOException e) { - _logger.log(Level.SEVERE, "Could not read from input stream", e); + group = walker.getState().getDateGroup(); + ParseLocation location = listener.getDateGroupLocation(); + group.setLine(location.getLine()); + group.setText(location.getText()); + group.setPosition(location.getStart()); + group.setSyntaxTree(tree); + group.setParseLocations(listener.getLocations()); } catch(RecognitionException e) { _logger.log(Level.SEVERE, "Could not parse input", e); } + return group; + } + + /** + * Scans the given token global token stream for a list of sub-token + * streams representing those portions of the global stream that + * may contain date time information + * + * @param stream + * @return + */ + private List collectTokenStreams(TokenStream stream) { + // now we'll walk through the token stream and build a collection + // of sub token streams that represent possible date locations + List currentGroup = null; + List groups = new ArrayList(); + Token currentToken; + while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { + // we're currently NOT collecting for a possible date group + if(currentGroup == null) { + // ignore white space in-between possible rules + if(currentToken.getType() != DateLexer.WHITE_SPACE) { + // if the token is a possible date start token, we start a new collection + currentGroup = new ArrayList(); + currentGroup.add(currentToken); + } + } + // we're currently collecting + else { + // preserve white space + if(currentToken.getType() == DateLexer.WHITE_SPACE) { + currentGroup.add(currentToken); + } + else { + // if this is an unknown token, we need to end the current group + if(currentToken.getType() == DateLexer.UNKNOWN) { + groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + currentGroup = null; + } + // otherwise, the token is known and we're currently collecting for + // a group, so we add it + else { + currentGroup.add(currentToken); + } + } + } + } + if(currentGroup != null) { + groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + } + return groups; } } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 9b60e579..6d8f36b0 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -1,10 +1,8 @@ package com.joestelmach.natty; -import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import java.util.List; import java.util.TimeZone; /** @@ -37,8 +35,7 @@ public class WalkerState { private boolean _firstDateInvocationInGroup = true; private boolean _timeGivenInGroup = false; - private List _dateGroups; - private DateGroup _currentDateGroup; + private DateGroup _dateGroup; /** * Creates a new WalkerState representing the start of @@ -46,8 +43,7 @@ public class WalkerState { */ public WalkerState() { resetCalender(); - _currentDateGroup = new DateGroup(); - _dateGroups = new ArrayList(); + _dateGroup = new DateGroup(); } public void setDefaultTimeZone(final TimeZone zone) { @@ -379,23 +375,15 @@ public void setExplicitTime(String hours, String minutes, String seconds, String */ public void captureDateTime() { Date date = _calendar.getTime(); - _currentDateGroup.addDate(date); + _dateGroup.addDate(date); _firstDateInvocationInGroup = true; } - /** - * - */ - public void captureDateGroup() { - _dateGroups.add(_currentDateGroup); - _currentDateGroup = new DateGroup(); - } - /** * @return the list of date times found */ - public List getDateGroups() { - return _dateGroups; + public DateGroup getDateGroup() { + return _dateGroup; } /** diff --git a/src/test/java/com/natty/parse/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java similarity index 99% rename from src/test/java/com/natty/parse/AbstractTest.java rename to src/test/java/com/joestelmach/natty/AbstractTest.java index d06e7ac0..7f59c1b5 100644 --- a/src/test/java/com/natty/parse/AbstractTest.java +++ b/src/test/java/com/joestelmach/natty/AbstractTest.java @@ -1,4 +1,4 @@ -package com.natty.parse; +package com.joestelmach.natty; import java.util.Calendar; import java.util.Date; diff --git a/src/test/java/com/natty/parse/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java similarity index 95% rename from src/test/java/com/natty/parse/DateTest.java rename to src/test/java/com/joestelmach/natty/DateTest.java index 0f3a8430..c001bdfc 100644 --- a/src/test/java/com/natty/parse/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -1,4 +1,4 @@ -package com.natty.parse; +package com.joestelmach.natty; import java.text.DateFormat; import java.util.Calendar; @@ -9,10 +9,6 @@ import org.junit.Test; -import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.DateGroup; -import com.joestelmach.natty.Parser; - /** * Runs the parser through the various date formats * @@ -187,14 +183,18 @@ public void testRange() throws Exception { } public static void main(String[] args) throws Exception{ - String inputString = "golf tomorrow at 9 AM ssdf pebble beach"; + String value = "golf with friends tomorrow at 10"; + value = "golf tomorrow at 9 AM at pebble beach"; + + String inputString = "1978-01-28"; Parser parser = new Parser(); List groups = parser.parse(inputString); for(DateGroup group:groups) { - System.out.println(group.getDates()); - System.out.println(group.getLine()); - System.out.println(group.getPosition()); + System.out.println(inputString); + System.out.println(group.getSyntaxTree().toStringTree()); + System.out.println("line: " + group.getLine() + ", column: " + group.getPosition()); System.out.println(group.getText()); + System.out.println(group.getDates()); } } } diff --git a/src/test/java/com/natty/parse/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java similarity index 99% rename from src/test/java/com/natty/parse/DateTimeTest.java rename to src/test/java/com/joestelmach/natty/DateTimeTest.java index 74f0ff6f..1cb3e7b1 100644 --- a/src/test/java/com/natty/parse/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -1,4 +1,4 @@ -package com.natty.parse; +package com.joestelmach.natty; import java.text.DateFormat; import java.util.Date; @@ -15,6 +15,7 @@ * @author Joe Stelmach */ public class DateTimeTest extends AbstractTest { + @Test public void testSpecific() { validateDateTime("1st oct in the year '89 1300 hours", 10, 1, 1989, 13, 0, 0); diff --git a/src/test/java/com/natty/parse/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java similarity index 98% rename from src/test/java/com/natty/parse/TimeTest.java rename to src/test/java/com/joestelmach/natty/TimeTest.java index 6777672b..2eb8b1f7 100644 --- a/src/test/java/com/natty/parse/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -1,4 +1,4 @@ -package com.natty.parse; +package com.joestelmach.natty; import java.text.DateFormat; diff --git a/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java b/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java new file mode 100644 index 00000000..473b6525 --- /dev/null +++ b/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java @@ -0,0 +1,70 @@ +package com.natty.joestelmach.grammar; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; + +import junit.framework.Assert; + +import org.antlr.runtime.ANTLRInputStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.ParserRuleReturnScope; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.antlr.runtime.tree.Tree; + +import com.joestelmach.natty.ANTLRNoCaseInputStream; +import com.joestelmach.natty.ParseListener; +import com.joestelmach.natty.generated.DateLexer; +import com.joestelmach.natty.generated.DateParser; +import com.joestelmach.natty.generated.TreeRewrite; + +public abstract class AbstractGrammarTest { + protected String _ruleName; + + /** + * + * @param input + * @param ast + */ + protected void assertAST(String input, String ast) throws Exception { + Assert.assertEquals(ast, buildAST(input)); + } + + /** + * + * @param value + * @return + * @throws Exception + */ + protected String buildAST(String value) throws Exception { + DateParser parser = buildParser(value); + Class klass = Class.forName("com.joestelmach.natty.generated.DateParser"); + Method meth = klass.getMethod(_ruleName, (Class[]) null); + ParserRuleReturnScope ret = (ParserRuleReturnScope) meth.invoke(parser, (Object[]) null); + + Tree tree = (Tree)ret.getTree(); + // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) + CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); + TreeRewrite s = new TreeRewrite(nodes); + tree = (CommonTree)s.downup(tree); + + return tree.toStringTree(); + } + + /** + * + * @param value + * @return + */ + private DateParser buildParser(String value) throws Exception { + // lex + ANTLRInputStream input = new ANTLRNoCaseInputStream( + new ByteArrayInputStream(value.getBytes())); + DateLexer lexer = new DateLexer(input); + CommonTokenStream tokens = new CommonTokenStream(lexer); + + // parse + ParseListener listener = new ParseListener(); + return new DateParser(tokens, listener); + } +} diff --git a/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java b/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java new file mode 100644 index 00000000..98a1cfe5 --- /dev/null +++ b/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java @@ -0,0 +1,632 @@ +package com.natty.joestelmach.grammar; + +import org.junit.Test; + +public class DateGrammarTest extends AbstractGrammarTest { + @Test + public void date() throws Exception { + _ruleName = "date"; + + assertAST("the day before yesterday", "(RELATIVE_DATE (SEEK < by_day 1 (RELATIVE_DATE (SEEK < by_day 1 day))))"); + assertAST("1st oct in the year '89", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89))"); + assertAST("2009-10-10", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2009))"); + assertAST("seven years ago", "(RELATIVE_DATE (SEEK < by_day 7 year))"); + assertAST("next monday", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2)))"); + } + + @Test + public void alternative_day_of_month_list() throws Exception { + _ruleName = "alternative_day_of_month_list"; + + assertAST("mon may 15 or 16", + "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 15))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 16)))"); + + assertAST("mon may 15 to 16", + "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 15))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 16)))"); + + assertAST("mon may 15 and 16", + "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 15))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 16)))"); + + assertAST("mon may 15 through 16", + "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 15))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 16)))"); + + assertAST("mon may 15 or 16", + "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 15))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 16)))"); + + assertAST("first or last day of september", + "(DATE_TIME (RELATIVE_DATE (EXPLICIT_SEEK (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))) (DATE_TIME (RELATIVE_DATE (EXPLICIT_SEEK (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))))"); + + assertAST("first or last day of september at 5pm", + "(DATE_TIME (RELATIVE_DATE (EXPLICIT_SEEK (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (EXPLICIT_SEEK (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))"); + + + assertAST("first or last day of next september at 6am", + "(DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am))"); + + assertAST("first or last day of 2 septembers from now at 5pm", + "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))"); + } + + @Test + public void global_date_prefix() throws Exception { + _ruleName = "global_date_prefix"; + + assertAST("the day after", "> by_day 1"); + assertAST("day after", "> by_day 1"); + assertAST("2 days after", "> by_day 2"); + assertAST("three days before", "< by_day 3"); + assertAST("six months after", "> by_month 6"); + assertAST("3 weeks before", "< by_week 3"); + assertAST("10 years after", "> by_year 10"); + assertAST("the day before", "< by_day 1"); + assertAST("day before", "< by_day 1"); + } + + @Test + public void relaxed_date() throws Exception { + _ruleName = "relaxed_date"; + + assertAST("oct 1, 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); + assertAST("oct. 1, 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); + assertAST("oct 1,1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); + assertAST("1st oct in the year '89", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89))"); + assertAST("thirty first of december 80", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 80))"); + assertAST("the first of december in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); + assertAST("the 2 of february in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); + assertAST("the 2nd of february in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); + assertAST("the second of february in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); + assertAST("jan. 2nd", "(EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2))"); + assertAST("sun, nov 21 2010", "(EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 21) (DAY_OF_WEEK 1) (YEAR_OF 2010))"); + } + + @Test + public void formal_date() throws Exception { + _ruleName = "formal_date"; + + assertAST("2009-10-10", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2009))"); + assertAST("1980-1-2", "(EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); + assertAST("12/12/12", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 12) (YEAR_OF 12))"); + assertAST("3/4", "(EXPLICIT_DATE (MONTH_OF_YEAR 3) (DAY_OF_MONTH 4))"); + assertAST("sun, 11/21/2010", "(EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 21) (DAY_OF_WEEK 1) (YEAR_OF 2010))"); + } + + @Test + public void formal_month_of_year() throws Exception { + _ruleName = "formal_month_of_year"; + + assertAST("01", "(MONTH_OF_YEAR 01)"); + assertAST("1", "(MONTH_OF_YEAR 1)"); + assertAST("02", "(MONTH_OF_YEAR 02)"); + assertAST("2", "(MONTH_OF_YEAR 2)"); + assertAST("03", "(MONTH_OF_YEAR 03)"); + assertAST("3", "(MONTH_OF_YEAR 3)"); + assertAST("04", "(MONTH_OF_YEAR 04)"); + assertAST("4", "(MONTH_OF_YEAR 4)"); + assertAST("05", "(MONTH_OF_YEAR 05)"); + assertAST("5", "(MONTH_OF_YEAR 5)"); + assertAST("06", "(MONTH_OF_YEAR 06)"); + assertAST("6", "(MONTH_OF_YEAR 6)"); + assertAST("07", "(MONTH_OF_YEAR 07)"); + assertAST("7", "(MONTH_OF_YEAR 7)"); + assertAST("08", "(MONTH_OF_YEAR 08)"); + assertAST("8", "(MONTH_OF_YEAR 8)"); + assertAST("09", "(MONTH_OF_YEAR 09)"); + assertAST("9", "(MONTH_OF_YEAR 9)"); + assertAST("10", "(MONTH_OF_YEAR 10)"); + assertAST("11", "(MONTH_OF_YEAR 11)"); + assertAST("12", "(MONTH_OF_YEAR 12)"); + //"00" FAIL + //"0" FAIL + //"13" FAIL + } + + @Test + public void formal_day_of_month() throws Exception { + _ruleName = "formal_day_of_month"; + + assertAST("01", "(DAY_OF_MONTH 01)"); + assertAST("1", "(DAY_OF_MONTH 1)"); + assertAST("02", "(DAY_OF_MONTH 02)"); + assertAST("2", "(DAY_OF_MONTH 2)"); + assertAST("03", "(DAY_OF_MONTH 03)"); + assertAST("3", "(DAY_OF_MONTH 3)"); + assertAST("04", "(DAY_OF_MONTH 04)"); + assertAST("4", "(DAY_OF_MONTH 4)"); + assertAST("05", "(DAY_OF_MONTH 05)"); + assertAST("5", "(DAY_OF_MONTH 5)"); + assertAST("06", "(DAY_OF_MONTH 06)"); + assertAST("6", "(DAY_OF_MONTH 6)"); + assertAST("07", "(DAY_OF_MONTH 07)"); + assertAST("7", "(DAY_OF_MONTH 7)"); + assertAST("08", "(DAY_OF_MONTH 08)"); + assertAST("8", "(DAY_OF_MONTH 8)"); + assertAST("09", "(DAY_OF_MONTH 09)"); + assertAST("9", "(DAY_OF_MONTH 9)"); + assertAST("10", "(DAY_OF_MONTH 10)"); + assertAST("11", "(DAY_OF_MONTH 11)"); + assertAST("12", "(DAY_OF_MONTH 12)"); + assertAST("13", "(DAY_OF_MONTH 13)"); + assertAST("14", "(DAY_OF_MONTH 14)"); + assertAST("15", "(DAY_OF_MONTH 15)"); + assertAST("16", "(DAY_OF_MONTH 16)"); + assertAST("17", "(DAY_OF_MONTH 17)"); + assertAST("18", "(DAY_OF_MONTH 18)"); + assertAST("19", "(DAY_OF_MONTH 19)"); + assertAST("20", "(DAY_OF_MONTH 20)"); + assertAST("21", "(DAY_OF_MONTH 21)"); + assertAST("22", "(DAY_OF_MONTH 22)"); + assertAST("23", "(DAY_OF_MONTH 23)"); + assertAST("24", "(DAY_OF_MONTH 24)"); + assertAST("25", "(DAY_OF_MONTH 25)"); + assertAST("26", "(DAY_OF_MONTH 26)"); + assertAST("27", "(DAY_OF_MONTH 27)"); + assertAST("28", "(DAY_OF_MONTH 28)"); + assertAST("29", "(DAY_OF_MONTH 29)"); + assertAST("30", "(DAY_OF_MONTH 30)"); + assertAST("31", "(DAY_OF_MONTH 31)"); + //"00" FAIL + //"0" FAIL + //"32" FAIL + } + + @Test + public void formal_year() throws Exception { + _ruleName = "formal_year"; + + assertAST("1999", "(YEAR_OF 1999)"); + assertAST("80", "(YEAR_OF 80)"); + assertAST("0000", "(YEAR_OF 0000)"); + assertAST("2010", "(YEAR_OF 2010)"); + assertAST("03", "(YEAR_OF 03)"); + //"037" FAIL + //"0" FAIL + //"03700" FAIL + } + + @Test + public void formal_year_four_digits() throws Exception { + _ruleName = "formal_year_four_digits"; + + assertAST("1999", "(YEAR_OF 1999)"); + assertAST("0000", "(YEAR_OF 0000)"); + assertAST("2010", "(YEAR_OF 2010)"); + //assertAST("80" FAIL + //assertAST("03" FAIL + //assertAST("037" FAIL + //assertAST("0" FAIL + //assertAST("03700" FAIL + } + + @Test + public void formal_date_separator() throws Exception { + _ruleName = "formal_date_separator"; + + assertAST("-", "-"); + assertAST("/", "/"); + } + + @Test + public void relative_date() throws Exception { + _ruleName = "relative_date"; + + assertAST("yesterday", "(RELATIVE_DATE (SEEK < by_day 1 day))"); + assertAST("tomorrow", "(RELATIVE_DATE (SEEK > by_day 1 day))"); + assertAST("in 3 days", "(RELATIVE_DATE (SEEK > by_day 3 day))"); + assertAST("3 days ago", "(RELATIVE_DATE (SEEK < by_day 3 day))"); + assertAST("in 3 weeks", "(RELATIVE_DATE (SEEK > by_day 3 week))"); + assertAST("four weeks ago", "(RELATIVE_DATE (SEEK < by_day 4 week))"); + assertAST("in 3 months", "(RELATIVE_DATE (SEEK > by_day 3 month))"); + assertAST("three months ago", "(RELATIVE_DATE (SEEK < by_day 3 month))"); + assertAST("in 3 years", "(RELATIVE_DATE (SEEK > by_day 3 year))"); + assertAST("seven years ago", "(RELATIVE_DATE (SEEK < by_day 7 year))"); + assertAST("60 years ago", "(RELATIVE_DATE (SEEK < by_day 60 year))"); + assertAST("32 days ago", "(RELATIVE_DATE (SEEK < by_day 32 day))"); + assertAST("next monday", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2)))"); + assertAST("next mon", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2)))"); + assertAST("4 mondays from now", "(RELATIVE_DATE (SEEK > by_day 4 (DAY_OF_WEEK 2)))"); + assertAST("4 mondays from today", "(RELATIVE_DATE (SEEK > by_day 4 (DAY_OF_WEEK 2)))"); + assertAST("next weekend", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 7)))"); + assertAST("six mondays ago", "(RELATIVE_DATE (SEEK < by_day 6 (DAY_OF_WEEK 2)))"); + assertAST("last monday", "(RELATIVE_DATE (SEEK < by_week 1 (DAY_OF_WEEK 2)))"); + assertAST("last mon", "(RELATIVE_DATE (SEEK < by_week 1 (DAY_OF_WEEK 2)))"); + assertAST("this past mon", "(RELATIVE_DATE (SEEK < by_day 1 (DAY_OF_WEEK 2)))"); + assertAST("this coming mon", "(RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 2)))"); + assertAST("this upcoming mon", "(RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 2)))"); + assertAST("next thurs", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5)))"); + assertAST("next month", "(RELATIVE_DATE (SEEK > by_week 1 month))"); + assertAST("last month", "(RELATIVE_DATE (SEEK < by_week 1 month))"); + assertAST("next week", "(RELATIVE_DATE (SEEK > by_week 1 week))"); + assertAST("last week", "(RELATIVE_DATE (SEEK < by_week 1 week))"); + assertAST("next year", "(RELATIVE_DATE (SEEK > by_week 1 year))"); + assertAST("last year", "(RELATIVE_DATE (SEEK < by_week 1 year))"); + } + + @Test + public void explicit_relative_date() throws Exception { + _ruleName = "explicit_relative_date"; + + assertAST("monday of last week", + "(RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("tuesday of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 3)))"); + + assertAST("the monday of 2 weeks ago", + "(RELATIVE_DATE (SEEK < by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("tuesday of 3 weeks from now", + "(RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 3)))"); + + assertAST("monday of 3 weeks from now", + "(RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("1st of three months ago", + "(RELATIVE_DATE (SEEK < by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("10th of next month", + "(RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 10)))"); + + assertAST("28th of last month", + "(RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 28)))"); + + assertAST("10th of next october", + "(RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 10)) (EXPLICIT_SEEK (DAY_OF_MONTH 10)))"); + + assertAST("the 30th of this month", + "(RELATIVE_DATE (SEEK > by_day 0 month) (EXPLICIT_SEEK (DAY_OF_MONTH 30)))"); + + assertAST("10th of the month after next", + "(RELATIVE_DATE (SEEK > by_day 2 month) (EXPLICIT_SEEK (DAY_OF_MONTH 10)))"); + + assertAST("the last thursday in november", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))"); + + assertAST("the last thursday in november 1999", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)) (EXPLICIT_SEEK (YEAR_OF 1999)))"); + + assertAST("3rd wed in next month", + "(RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK 3 (DAY_OF_WEEK 4)))"); + + assertAST("the last sunday in november", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 1)))"); + + assertAST("the first wed. in january", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 1)) (EXPLICIT_SEEK 1 (DAY_OF_WEEK 4)))"); + + assertAST("the last day of february 1999", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 31)) (EXPLICIT_SEEK (YEAR_OF 1999)))"); + + assertAST("the first wed. in january in the year 2004", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 1)) (EXPLICIT_SEEK 1 (DAY_OF_WEEK 4)) (EXPLICIT_SEEK (YEAR_OF 2004)))"); + + assertAST("last monday of last month", + "(RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 2)))"); + + assertAST("the last sunday of next nov", + "(RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 1)))"); + + assertAST("the 3rd sunday of 2 novembers from now", + "(RELATIVE_DATE (SEEK > by_day 2 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 3 (DAY_OF_WEEK 1)))"); + + assertAST("the last monday in 2 novembers ago", + "(RELATIVE_DATE (SEEK < by_day 2 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 2)))"); + + assertAST("the beginning of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("the end of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("the end of this week", + "(RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("the start of this week", + "(RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("start of 3 weeks from now", + "(RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("the end of 3 weeks ago", + "(RELATIVE_DATE (SEEK < by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("the first day of this week", + "(RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("the last day of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("first day of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2)))"); + + assertAST("last day of last week", + "(RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("start of 3 months from now", + "(RELATIVE_DATE (SEEK > by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("beginning of next month", + "(RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("end of next month", + "(RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))"); + + assertAST("last day of next month", + "(RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))"); + + assertAST("first day of 3 months from now", + "(RELATIVE_DATE (SEEK > by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("end of next october", + "(RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 10)) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))"); + + assertAST("first day of feb", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("last day of three februarys from now", + "(RELATIVE_DATE (SEEK > by_day 3 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))"); + + assertAST("in the end of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("at the end of last week", + "(RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("at the end of 2 weeks", + "(RELATIVE_DATE (SEEK > by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("in the start of june", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 6)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))"); + + assertAST("at the end of next week", + "(RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6)))"); + + assertAST("at the end of last month", + "(RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))"); + + assertAST("the second day of april", + "(RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK (DAY_OF_MONTH 2)))"); + + assertAST("the thirtieth day of next april", + "(RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK (DAY_OF_MONTH 30)))"); + } + + @Test + public void relative_occurrence_index() throws Exception { + _ruleName = "relative_occurrence_index"; + + assertAST("1", "1"); + assertAST("2", "2"); + assertAST("3", "3"); + assertAST("4", "4"); + assertAST("5", "5"); + assertAST("first", "1"); + assertAST("second", "2"); + assertAST("third", "3"); + assertAST("fourth", "4"); + assertAST("fifth", "5"); + assertAST("last", "5"); + //assertAST("sixth" FAIL); + //assertAST("6" FAIL); + } + + @Test + public void relative_target() throws Exception { + _ruleName = "relative_target"; + + assertAST("sunday" , "(DAY_OF_WEEK 1)"); + assertAST("sundays" , "(DAY_OF_WEEK 1)"); + assertAST("sun" , "(DAY_OF_WEEK 1)"); + assertAST("monday" , "(DAY_OF_WEEK 2)"); + assertAST("mondays" , "(DAY_OF_WEEK 2)"); + assertAST("mon" , "(DAY_OF_WEEK 2)"); + assertAST("tuesday" , "(DAY_OF_WEEK 3)"); + assertAST("tuesdays" , "(DAY_OF_WEEK 3)"); + assertAST("tues" , "(DAY_OF_WEEK 3)"); + assertAST("tue" , "(DAY_OF_WEEK 3)"); + assertAST("wednesday" , "(DAY_OF_WEEK 4)"); + assertAST("wednesdays", "(DAY_OF_WEEK 4)"); + assertAST("wed" , "(DAY_OF_WEEK 4)"); + assertAST("thursday" , "(DAY_OF_WEEK 5)"); + assertAST("thursdays" , "(DAY_OF_WEEK 5)"); + assertAST("thur" , "(DAY_OF_WEEK 5)"); + assertAST("thu" , "(DAY_OF_WEEK 5)"); + assertAST("friday" , "(DAY_OF_WEEK 6)"); + assertAST("fridays" , "(DAY_OF_WEEK 6)"); + assertAST("fri" , "(DAY_OF_WEEK 6)"); + assertAST("saturday" , "(DAY_OF_WEEK 7)"); + assertAST("saturdays" , "(DAY_OF_WEEK 7)"); + assertAST("sat" , "(DAY_OF_WEEK 7)"); + assertAST("day", "day"); + assertAST("days", "day"); + assertAST("week", "week"); + assertAST("weeks", "week"); + assertAST("month", "month"); + assertAST("months", "month"); + assertAST("year", "year"); + assertAST("years", "year"); + } + + @Test + public void relaxed_day_of_month() throws Exception { + _ruleName = "relaxed_day_of_month"; + + assertAST("three", "(DAY_OF_MONTH 3)"); + assertAST("third", "(DAY_OF_MONTH 3)"); + assertAST("3rd", "(DAY_OF_MONTH 3)"); + assertAST("3", "(DAY_OF_MONTH 3)"); + assertAST("03", "(DAY_OF_MONTH 03)"); + assertAST("21", "(DAY_OF_MONTH 21)"); + assertAST("thirty one", "(DAY_OF_MONTH 31)"); + assertAST("thirty-one", "(DAY_OF_MONTH 31)"); + assertAST("thirty first", "(DAY_OF_MONTH 31)"); + assertAST("thirty-first", "(DAY_OF_MONTH 31)"); + assertAST("31st", "(DAY_OF_MONTH 31)"); + //assertAST("32" FAIL + } + + @Test + public void relaxed_year() throws Exception { + _ruleName = "relaxed_year"; + + assertAST("'69", "(YEAR_OF 69)"); + assertAST("79", "(YEAR_OF 79)"); + assertAST("2079", "(YEAR_OF 2079)"); + assertAST("'80", "(YEAR_OF 80)"); + assertAST("1979", "(YEAR_OF 1979)"); + assertAST("2004", "(YEAR_OF 2004)"); + //assertAST("999" FAIL"); + //assertAST("999" FAIL"); + } + + @Test + public void relaxed_year_prefix() throws Exception { + _ruleName = "relaxed_year_prefix"; + + //assertAST(", in the year ", ", in the year"); + //assertAST(" in the year ", " in the year"); + //assertAST("in the year ", "in the year"); + //assertAST("in the yesr ", "in the year"); + } + + @Test + public void implicit_prefix() throws Exception { + _ruleName = "implicit_prefix"; + + assertAST("this", "> by_day 0"); + } + + @Test + public void relative_date_prefix() throws Exception { + _ruleName = "relative_date_prefix"; + + assertAST("this last" , "< by_week 1"); + assertAST("last" , "< by_week 1"); + assertAST("this past" , "< by_day 1"); + assertAST("past" , "< by_day 1"); + assertAST("this next" , "> by_week 1"); + assertAST("next" , "> by_week 1"); + assertAST("this coming" , "> by_day 1"); + assertAST("coming" , "> by_day 1"); + assertAST("this upcoming", "> by_day 1"); + assertAST("upcoming" , "> by_day 1"); + assertAST("3" , "> by_day 3"); + assertAST("twenty-eight" , "> by_day 28"); + //assertAST("in 3" , "> by_day 3"); + //assertAST("in twenty" , "> by_day 20"); + } + + @Test + public void relative_date_suffix() throws Exception { + _ruleName = "relative_date_suffix"; + + assertAST("from now", "> by_day"); + assertAST("ago", "< by_day"); + } + + @Test + public void relative_date_span() throws Exception { + _ruleName = "relative_date_span"; + + assertAST("day", "day"); + assertAST("days", "day"); + assertAST("week", "week"); + assertAST("weeks", "week"); + assertAST("month", "month"); + assertAST("months", "month"); + assertAST("year", "year"); + assertAST("years", "year"); + } + + @Test + public void relaxed_month() throws Exception { + _ruleName = "relaxed_month"; + + assertAST("january", "(MONTH_OF_YEAR 1)"); + assertAST("jan", "(MONTH_OF_YEAR 1)"); + assertAST("february", "(MONTH_OF_YEAR 2)"); + assertAST("feb", "(MONTH_OF_YEAR 2)"); + assertAST("march", "(MONTH_OF_YEAR 3)"); + assertAST("mar", "(MONTH_OF_YEAR 3)"); + assertAST("april", "(MONTH_OF_YEAR 4)"); + assertAST("apr", "(MONTH_OF_YEAR 4)"); + assertAST("may", "(MONTH_OF_YEAR 5)"); + assertAST("june", "(MONTH_OF_YEAR 6)"); + assertAST("jun", "(MONTH_OF_YEAR 6)"); + assertAST("july", "(MONTH_OF_YEAR 7)"); + assertAST("jul", "(MONTH_OF_YEAR 7)"); + assertAST("august", "(MONTH_OF_YEAR 8)"); + assertAST("aug", "(MONTH_OF_YEAR 8)"); + assertAST("september", "(MONTH_OF_YEAR 9)"); + assertAST("sep", "(MONTH_OF_YEAR 9)"); + assertAST("sept", "(MONTH_OF_YEAR 9)"); + assertAST("october", "(MONTH_OF_YEAR 10)"); + assertAST("oct", "(MONTH_OF_YEAR 10)"); + assertAST("november", "(MONTH_OF_YEAR 11)"); + assertAST("nov", "(MONTH_OF_YEAR 11)"); + assertAST("december", "(MONTH_OF_YEAR 12)"); + assertAST("dec", "(MONTH_OF_YEAR 12)"); + assertAST("jan.", "(MONTH_OF_YEAR 1)"); + assertAST("feb.", "(MONTH_OF_YEAR 2)"); + assertAST("mar.", "(MONTH_OF_YEAR 3)"); + assertAST("apr.", "(MONTH_OF_YEAR 4)"); + assertAST("jun.", "(MONTH_OF_YEAR 6)"); + assertAST("jul.", "(MONTH_OF_YEAR 7)"); + assertAST("aug.", "(MONTH_OF_YEAR 8)"); + assertAST("sep.", "(MONTH_OF_YEAR 9)"); + assertAST("sept.", "(MONTH_OF_YEAR 9)"); + assertAST("oct.", "(MONTH_OF_YEAR 10)"); + assertAST("nov.", "(MONTH_OF_YEAR 11)"); + assertAST("dec.", "(MONTH_OF_YEAR 12)"); + } + + @Test + public void day_of_week() throws Exception { + _ruleName = "day_of_week"; + + assertAST("sunday" , "(DAY_OF_WEEK 1)"); + assertAST("sundays" , "(DAY_OF_WEEK 1)"); + assertAST("sun" , "(DAY_OF_WEEK 1)"); + assertAST("sun." , "(DAY_OF_WEEK 1)"); + assertAST("monday" , "(DAY_OF_WEEK 2)"); + assertAST("mondays" , "(DAY_OF_WEEK 2)"); + assertAST("mon" , "(DAY_OF_WEEK 2)"); + assertAST("mon." , "(DAY_OF_WEEK 2)"); + assertAST("tuesday" , "(DAY_OF_WEEK 3)"); + assertAST("tuesdays" , "(DAY_OF_WEEK 3)"); + assertAST("tues" , "(DAY_OF_WEEK 3)"); + assertAST("tues." , "(DAY_OF_WEEK 3)"); + assertAST("tue" , "(DAY_OF_WEEK 3)"); + assertAST("tue." , "(DAY_OF_WEEK 3)"); + assertAST("wednesday" , "(DAY_OF_WEEK 4)"); + assertAST("wednesdays", "(DAY_OF_WEEK 4)"); + assertAST("wed" , "(DAY_OF_WEEK 4)"); + assertAST("wed." , "(DAY_OF_WEEK 4)"); + assertAST("thursday" , "(DAY_OF_WEEK 5)"); + assertAST("thursdays" , "(DAY_OF_WEEK 5)"); + assertAST("thur" , "(DAY_OF_WEEK 5)"); + assertAST("thur." , "(DAY_OF_WEEK 5)"); + assertAST("thu" , "(DAY_OF_WEEK 5)"); + assertAST("thu." , "(DAY_OF_WEEK 5)"); + assertAST("friday" , "(DAY_OF_WEEK 6)"); + assertAST("fridays" , "(DAY_OF_WEEK 6)"); + assertAST("fri" , "(DAY_OF_WEEK 6)"); + assertAST("fri." , "(DAY_OF_WEEK 6)"); + assertAST("saturday" , "(DAY_OF_WEEK 7)"); + assertAST("saturdays" , "(DAY_OF_WEEK 7)"); + assertAST("sat" , "(DAY_OF_WEEK 7)"); + assertAST("sat." , "(DAY_OF_WEEK 7)"); + } + + @Test + public void named_relative_date() throws Exception { + _ruleName = "named_relative_date"; + + assertAST("today", "(RELATIVE_DATE (SEEK > by_day 0 day))"); + assertAST("now", "(RELATIVE_DATE (SEEK > by_day 0 day))"); + assertAST("tomorow" , "(RELATIVE_DATE (SEEK > by_day 1 day))"); + assertAST("tomorrow" , "(RELATIVE_DATE (SEEK > by_day 1 day))"); + assertAST("tommorow" , "(RELATIVE_DATE (SEEK > by_day 1 day))"); + assertAST("tommorrow", "(RELATIVE_DATE (SEEK > by_day 1 day))"); + assertAST("yesterday", "(RELATIVE_DATE (SEEK < by_day 1 day))"); + } +} diff --git a/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java b/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java new file mode 100644 index 00000000..8a0aeb75 --- /dev/null +++ b/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java @@ -0,0 +1,725 @@ +package com.natty.joestelmach.grammar; + +import org.junit.Test; + +public class NumericGrammarTest extends AbstractGrammarTest { + + @Test + public void int_00_to_23_optional_prefix() throws Exception { + _ruleName = "int_00_to_23_optional_prefix"; + + assertAST("00", "00"); + assertAST("01", "01"); + assertAST("02", "02"); + assertAST("03", "03"); + assertAST("04", "04"); + assertAST("05", "05"); + assertAST("06", "06"); + assertAST("07", "07"); + assertAST("08", "08"); + assertAST("09", "09"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + } + + @Test + public void test_int_00_to_59_mandatory_prefix() throws Exception { + _ruleName = "int_00_to_59_mandatory_prefix"; + + assertAST("00", "00"); + assertAST("01", "01"); + assertAST("02", "02"); + assertAST("03", "03"); + assertAST("04", "04"); + assertAST("05", "05"); + assertAST("06", "06"); + assertAST("07", "07"); + assertAST("08", "08"); + assertAST("09", "09"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + assertAST("24", "24"); + assertAST("25", "25"); + assertAST("26", "26"); + assertAST("27", "27"); + assertAST("28", "28"); + assertAST("29", "29"); + assertAST("30", "30"); + assertAST("31", "31"); + assertAST("32", "32"); + assertAST("33", "33"); + assertAST("34", "34"); + assertAST("35", "35"); + assertAST("36", "36"); + assertAST("37", "37"); + assertAST("38", "38"); + assertAST("39", "39"); + assertAST("40", "40"); + assertAST("41", "41"); + assertAST("42", "42"); + assertAST("43", "43"); + assertAST("44", "44"); + assertAST("45", "45"); + assertAST("46", "46"); + assertAST("47", "47"); + assertAST("48", "48"); + assertAST("49", "49"); + assertAST("50", "50"); + assertAST("51", "51"); + assertAST("52", "52"); + assertAST("53", "53"); + assertAST("54", "54"); + assertAST("55", "55"); + assertAST("56", "56"); + assertAST("57", "57"); + assertAST("58", "58"); + assertAST("59", "59"); + /* + "0" FAIL + "1" FAIL + "2" FAIL + "3" FAIL + "4" FAIL + "5" FAIL + "6" FAIL + "7" FAIL + "8" FAIL + "9" FAIL + "60" FAIL + */ + } + + @Test + public void test_int_00_to_99_mandatory_prefix() throws Exception { + _ruleName = "int_00_to_99_mandatory_prefix"; + + assertAST("00", "00"); + assertAST("01", "01"); + assertAST("02", "02"); + assertAST("03", "03"); + assertAST("04", "04"); + assertAST("05", "05"); + assertAST("06", "06"); + assertAST("07", "07"); + assertAST("08", "08"); + assertAST("09", "09"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + assertAST("24", "24"); + assertAST("25", "25"); + assertAST("26", "26"); + assertAST("27", "27"); + assertAST("28", "28"); + assertAST("29", "29"); + assertAST("30", "30"); + assertAST("31", "31"); + assertAST("32", "32"); + assertAST("33", "33"); + assertAST("34", "34"); + assertAST("35", "35"); + assertAST("36", "36"); + assertAST("37", "37"); + assertAST("38", "38"); + assertAST("39", "39"); + assertAST("40", "40"); + assertAST("41", "41"); + assertAST("42", "42"); + assertAST("43", "43"); + assertAST("44", "44"); + assertAST("45", "45"); + assertAST("46", "46"); + assertAST("47", "47"); + assertAST("48", "48"); + assertAST("49", "49"); + assertAST("50", "50"); + assertAST("51", "51"); + assertAST("52", "52"); + assertAST("53", "53"); + assertAST("54", "54"); + assertAST("55", "55"); + assertAST("56", "56"); + assertAST("57", "57"); + assertAST("58", "58"); + assertAST("59", "59"); + assertAST("60", "60"); + assertAST("61", "61"); + assertAST("62", "62"); + assertAST("63", "63"); + assertAST("64", "64"); + assertAST("65", "65"); + assertAST("66", "66"); + assertAST("67", "67"); + assertAST("68", "68"); + assertAST("69", "69"); + assertAST("70", "70"); + assertAST("71", "71"); + assertAST("72", "72"); + assertAST("73", "73"); + assertAST("74", "74"); + assertAST("75", "75"); + assertAST("76", "76"); + assertAST("77", "77"); + assertAST("78", "78"); + assertAST("79", "79"); + assertAST("80", "80"); + assertAST("81", "81"); + assertAST("82", "82"); + assertAST("83", "83"); + assertAST("84", "84"); + assertAST("85", "85"); + assertAST("86", "86"); + assertAST("87", "87"); + assertAST("88", "88"); + assertAST("89", "89"); + assertAST("90", "90"); + assertAST("91", "91"); + assertAST("92", "92"); + assertAST("93", "93"); + assertAST("94", "94"); + assertAST("95", "95"); + assertAST("96", "96"); + assertAST("97", "97"); + assertAST("98", "98"); + assertAST("99", "99"); + /* + "0" FAIL + "1" FAIL + "2" FAIL + "3" FAIL + "4" FAIL + "5" FAIL + "6" FAIL + "7" FAIL + "8" FAIL + "9" FAIL + "100" FAIL + */ + } + + @Test + public void int_1_to_9() throws Exception { + _ruleName = "int_1_to_9"; + + assertAST("1", "1"); + assertAST("2", "2"); + assertAST("3", "3"); + assertAST("4", "4"); + assertAST("5", "5"); + assertAST("6", "6"); + assertAST("7", "7"); + assertAST("8", "8"); + assertAST("9", "9"); + /* + FAIL: + 0 + 00 + 01 + 02 + 03 + 04 + 05 + 06 + 07 + 08 + 09 + */ + } + + @Test + public void int_01_to_12() throws Exception { + _ruleName = "int_01_to_12"; + + assertAST("01", "01"); + assertAST("02", "02"); + assertAST("03", "03"); + assertAST("04", "04"); + assertAST("05", "05"); + assertAST("06", "06"); + assertAST("07", "07"); + assertAST("08", "08"); + assertAST("09", "09"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + /* + FAIL: + 0 + 00 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + */ + } + + + @Test + public void int_13_to_23() throws Exception { + _ruleName = "int_13_to_23"; + + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + } + + @Test + public void int_24_to_31() throws Exception { + _ruleName = "int_24_to_31"; + + assertAST("24", "24"); + assertAST("25", "25"); + assertAST("26", "26"); + assertAST("27", "27"); + assertAST("28", "28"); + assertAST("29", "29"); + assertAST("30", "30"); + assertAST("31", "31"); + } + + @Test + public void int_32_to_59() throws Exception { + _ruleName = "int_32_to_59"; + + assertAST("32", "32"); + assertAST("33", "33"); + assertAST("34", "34"); + assertAST("35", "35"); + assertAST("36", "36"); + assertAST("37", "37"); + assertAST("38", "38"); + assertAST("39", "39"); + assertAST("40", "40"); + assertAST("41", "41"); + assertAST("42", "42"); + assertAST("43", "43"); + assertAST("44", "44"); + assertAST("45", "45"); + assertAST("46", "46"); + assertAST("47", "47"); + assertAST("48", "48"); + assertAST("49", "49"); + assertAST("50", "50"); + assertAST("51", "51"); + assertAST("52", "52"); + assertAST("53", "53"); + assertAST("54", "54"); + assertAST("55", "55"); + assertAST("56", "56"); + assertAST("57", "57"); + assertAST("58", "58"); + assertAST("59", "59"); + } + + @Test + public void int_60_to_99() throws Exception { + _ruleName = "int_60_to_99"; + + assertAST("60", "60"); + assertAST("61", "61"); + assertAST("62", "62"); + assertAST("63", "63"); + assertAST("64", "64"); + assertAST("65", "65"); + assertAST("66", "66"); + assertAST("67", "67"); + assertAST("68", "68"); + assertAST("69", "69"); + assertAST("70", "70"); + assertAST("71", "71"); + assertAST("72", "72"); + assertAST("73", "73"); + assertAST("74", "74"); + assertAST("75", "75"); + assertAST("76", "76"); + assertAST("77", "77"); + assertAST("78", "78"); + assertAST("79", "79"); + assertAST("80", "80"); + assertAST("81", "81"); + assertAST("82", "82"); + assertAST("83", "83"); + assertAST("84", "84"); + assertAST("85", "85"); + assertAST("86", "86"); + assertAST("87", "87"); + assertAST("88", "88"); + assertAST("89", "89"); + assertAST("90", "90"); + assertAST("91", "91"); + assertAST("92", "92"); + assertAST("93", "93"); + assertAST("94", "94"); + assertAST("95", "95"); + assertAST("96", "96"); + assertAST("97", "97"); + assertAST("98", "98"); + assertAST("99", "99"); + } + + @Test + public void int_01_to_12_optional_prefix() throws Exception { + _ruleName = "int_01_to_12_optional_prefix"; + + assertAST("01", "01"); + assertAST("1", "1"); + assertAST("02", "02"); + assertAST("2", "2"); + assertAST("03", "03"); + assertAST("3", "3"); + assertAST("04", "04"); + assertAST("4", "4"); + assertAST("05", "05"); + assertAST("5", "5"); + assertAST("06", "06"); + assertAST("6", "6"); + assertAST("07", "07"); + assertAST("7", "7"); + assertAST("08", "08"); + assertAST("8", "8"); + assertAST("09", "09"); + assertAST("9", "9"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + } + + @Test + public void int_01_to_31_optional_prefix() throws Exception { + _ruleName = "int_01_to_31_optional_prefix"; + + assertAST("01", "01"); + assertAST("1", "1"); + assertAST("02", "02"); + assertAST("2", "2"); + assertAST("03", "03"); + assertAST("3", "3"); + assertAST("04", "04"); + assertAST("4", "4"); + assertAST("05", "05"); + assertAST("5", "5"); + assertAST("06", "06"); + assertAST("6", "6"); + assertAST("07", "07"); + assertAST("7", "7"); + assertAST("08", "08"); + assertAST("8", "8"); + assertAST("09", "09"); + assertAST("9", "9"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + assertAST("24", "24"); + assertAST("25", "25"); + assertAST("26", "26"); + assertAST("27", "27"); + assertAST("28", "28"); + assertAST("29", "29"); + assertAST("30", "30"); + assertAST("31", "31"); + } + + @Test + public void int_four_digits() throws Exception { + _ruleName = "int_four_digits"; + + assertAST("0000", "0000"); + assertAST("0100", "0100"); + assertAST("0020", "0020"); + assertAST("0003", "0003"); + assertAST("9999", "9999"); + assertAST("5050", "5050"); + //"000" FAIL + //"33" FAIL + //"2" FAIL + } + + @Test + public void spelled_or_int_01_to_31_optional_prefix() throws Exception { + _ruleName = "spelled_or_int_01_to_31_optional_prefix"; + + assertAST("one", "1"); + assertAST("two", "2"); + assertAST("three", "3"); + assertAST("four", "4"); + assertAST("five", "5"); + assertAST("six", "6"); + assertAST("seven", "7"); + assertAST("eight", "8"); + assertAST("nine", "9"); + assertAST("ten", "10"); + assertAST("eleven", "11"); + assertAST("twelve", "12"); + assertAST("thirteen", "13"); + assertAST("fourteen", "14"); + assertAST("fifteen", "15"); + assertAST("sixteen", "16"); + assertAST("seventeen", "17"); + assertAST("eighteen", "18"); + assertAST("nineteen", "19"); + assertAST("twenty", "20"); + assertAST("twenty one", "21"); + assertAST("twenty-one", "21"); + assertAST("twenty two", "22"); + assertAST("twenty-two", "22"); + assertAST("twenty three", "23"); + assertAST("twenty-three", "23"); + assertAST("twenty four", "24"); + assertAST("twenty-four", "24"); + assertAST("twenty five", "25"); + assertAST("twenty-five", "25"); + assertAST("twenty six", "26"); + assertAST("twenty-six", "26"); + assertAST("twenty seven", "27"); + assertAST("twenty-seven", "27"); + assertAST("twenty-eight", "28"); + assertAST("twenty nine", "29"); + assertAST("twenty-nine", "29"); + assertAST("thirty", "30"); + assertAST("thirty one", "31"); + assertAST("thirty-one", "31"); + assertAST("01", "01"); + assertAST("1", "1"); + assertAST("02", "02"); + assertAST("2", "2"); + assertAST("03", "03"); + assertAST("3", "3"); + assertAST("04", "04"); + assertAST("4", "4"); + assertAST("05", "05"); + assertAST("5", "5"); + assertAST("06", "06"); + assertAST("6", "6"); + assertAST("07", "07"); + assertAST("7", "7"); + assertAST("08", "08"); + assertAST("8", "8"); + assertAST("09", "09"); + assertAST("9", "9"); + assertAST("10", "10"); + assertAST("11", "11"); + assertAST("12", "12"); + assertAST("13", "13"); + assertAST("14", "14"); + assertAST("15", "15"); + assertAST("16", "16"); + assertAST("17", "17"); + assertAST("18", "18"); + assertAST("19", "19"); + assertAST("20", "20"); + assertAST("21", "21"); + assertAST("22", "22"); + assertAST("23", "23"); + assertAST("24", "24"); + assertAST("25", "25"); + assertAST("26", "26"); + assertAST("27", "27"); + assertAST("28", "28"); + assertAST("29", "29"); + assertAST("30", "30"); + assertAST("31", "31"); + //assertAST("zero", FAIL + } + + @Test + public void spelled_or_int_optional_prefix() throws Exception { + _ruleName = "spelled_or_int_optional_prefix"; + + assertAST("1", "1"); + assertAST("01", "01"); + assertAST("60", "60"); + assertAST("99", "99"); + assertAST("one", "1"); + assertAST("two", "2"); + //"00" FAIL + //"0" FAIL + //"zero" FAIL + } + + @Test + public void spelled_one_to_thirty_one() throws Exception { + _ruleName = "spelled_one_to_thirty_one"; + + assertAST("one", "1"); + assertAST("two", "2"); + assertAST("three", "3"); + assertAST("four", "4"); + assertAST("five", "5"); + assertAST("six", "6"); + assertAST("seven", "7"); + assertAST("eight", "8"); + assertAST("nine", "9"); + assertAST("ten", "10"); + assertAST("eleven", "11"); + assertAST("twelve", "12"); + assertAST("thirteen", "13"); + assertAST("fourteen", "14"); + assertAST("fifteen", "15"); + assertAST("sixteen", "16"); + assertAST("seventeen", "17"); + assertAST("eighteen", "18"); + assertAST("nineteen", "19"); + assertAST("twenty", "20"); + assertAST("twenty one", "21"); + assertAST("twenty-one", "21"); + assertAST("twenty two", "22"); + assertAST("twenty-two", "22"); + assertAST("twenty three", "23"); + assertAST("twenty-three", "23"); + assertAST("twenty four", "24"); + assertAST("twenty-four", "24"); + assertAST("twenty five", "25"); + assertAST("twenty-five", "25"); + assertAST("twenty six", "26"); + assertAST("twenty-six", "26"); + assertAST("twenty seven", "27"); + assertAST("twenty-seven", "27"); + assertAST("twenty-eight", "28"); + assertAST("twenty nine", "29"); + assertAST("twenty-nine", "29"); + assertAST("thirty", "30"); + assertAST("thirty one", "31"); + assertAST("thirty-one", "31"); + + //"zero" FAIL + //"thirty two" FAIL + //"thirty-two" FAIL + } + + @Test + public void spelled_first_to_thirty_first() throws Exception { + _ruleName = "spelled_first_to_thirty_first"; + + assertAST("first", "1"); + assertAST("1st", "1"); + assertAST("second", "2"); + assertAST("2nd", "2"); + assertAST("third", "3"); + assertAST("3rd", "3"); + assertAST("fourth", "4"); + assertAST("4th", "4"); + assertAST("fifth", "5"); + assertAST("5th", "5"); + assertAST("sixth", "6"); + assertAST("6th", "6"); + assertAST("seventh", "7"); + assertAST("7th", "7"); + assertAST("eighth", "8"); + assertAST("8th", "8"); + assertAST("ninth", "9"); + assertAST("9th", "9"); + assertAST("tenth", "10"); + assertAST("10th", "10"); + assertAST("eleventh", "11"); + assertAST("11th", "11"); + assertAST("twelfth", "12"); + assertAST("12th", "12"); + assertAST("thirteenth", "13"); + assertAST("13th", "13"); + assertAST("fourteenth", "14"); + assertAST("14th", "14"); + assertAST("fifteenth", "15"); + assertAST("15th", "15"); + assertAST("sixteenth", "16"); + assertAST("16th", "16"); + assertAST("seventeenth", "17"); + assertAST("17th", "17"); + assertAST("eighteenth", "18"); + assertAST("18th", "18"); + assertAST("nineteenth", "19"); + assertAST("19th", "19"); + assertAST("twentieth", "20"); + assertAST("20th", "20"); + assertAST("twenty-first", "21"); + assertAST("twenty first", "21"); + assertAST("21st", "21"); + assertAST("twenty-second", "22"); + assertAST("twenty second", "22"); + assertAST("22nd", "22"); + assertAST("twenty-third", "23"); + assertAST("twenty third", "23"); + assertAST("23rd", "23"); + assertAST("twenty-fourth", "24"); + assertAST("twenty fourth", "24"); + assertAST("24th", "24"); + assertAST("twenty-fifth", "25"); + assertAST("twenty fifth", "25"); + assertAST("25th", "25"); + assertAST("twenty-sixth", "26"); + assertAST("twenty sixth", "26"); + assertAST("26th", "26"); + assertAST("twenty-seventh", "27"); + assertAST("twenty seventh", "27"); + assertAST("27th", "27"); + assertAST("twenty-eighth", "28"); + assertAST("twenty eighth", "28"); + assertAST("28th", "28"); + assertAST("twenty-ninth", "29"); + assertAST("twenty ninth", "29"); + assertAST("29th", "29"); + assertAST("thirtieth", "30"); + assertAST("30th", "30"); + assertAST("thirty-first", "31"); + assertAST("thirty first", "31"); + assertAST("31st", "31"); + } +} diff --git a/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java b/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java new file mode 100644 index 00000000..3450c7ed --- /dev/null +++ b/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java @@ -0,0 +1,189 @@ +package com.natty.joestelmach.grammar; + +import org.junit.Test; + +public class TimeGrammarTest extends AbstractGrammarTest { + + @Test + public void time_zone_abbreviation() throws Exception { + _ruleName = "time_zone_abbreviation"; + + assertAST("est", "America/New_York"); + assertAST("edt", "America/New_York"); + assertAST("et", "America/New_York"); + assertAST("pst", "America/Los_Angeles"); + assertAST("pdt", "America/Los_Angeles"); + assertAST("pt", "America/Los_Angeles"); + assertAST("cst", "America/Chicago"); + assertAST("cdt", "America/Chicago"); + assertAST("ct", "America/Chicago"); + assertAST("mst", "America/Denver"); + assertAST("mdt", "America/Denver"); + assertAST("mt", "America/Denver"); + assertAST("akst", "America/Anchorage"); + assertAST("akdt", "America/Anchorage"); + assertAST("akt", "America/Anchorage"); + assertAST("hast", "Pacific/Honolulu"); + assertAST("hadt" , "Pacific/Honolulu"); + assertAST("hat" , "Pacific/Honolulu"); + assertAST("hst", "Pacific/Honolulu"); + } + + @Test + public void meridian_indicator() throws Exception { + _ruleName = "meridian_indicator"; + + assertAST("am", "am"); + assertAST("a.m.", "am"); + assertAST("a", "am"); + assertAST("pm", "pm"); + assertAST("p.m.", "pm"); + assertAST("p", "pm"); + } + + @Test + public void minutes() throws Exception { + _ruleName = "minutes"; + + assertAST("00", "(MINUTES_OF_HOUR 00)"); + assertAST("01", "(MINUTES_OF_HOUR 01)"); + assertAST("02", "(MINUTES_OF_HOUR 02)"); + assertAST("03", "(MINUTES_OF_HOUR 03)"); + assertAST("04", "(MINUTES_OF_HOUR 04)"); + assertAST("05", "(MINUTES_OF_HOUR 05)"); + assertAST("06", "(MINUTES_OF_HOUR 06)"); + assertAST("07", "(MINUTES_OF_HOUR 07)"); + assertAST("08", "(MINUTES_OF_HOUR 08)"); + assertAST("09", "(MINUTES_OF_HOUR 09)"); + assertAST("10", "(MINUTES_OF_HOUR 10)"); + assertAST("11", "(MINUTES_OF_HOUR 11)"); + assertAST("12", "(MINUTES_OF_HOUR 12)"); + assertAST("13", "(MINUTES_OF_HOUR 13)"); + assertAST("14", "(MINUTES_OF_HOUR 14)"); + assertAST("15", "(MINUTES_OF_HOUR 15)"); + assertAST("16", "(MINUTES_OF_HOUR 16)"); + assertAST("17", "(MINUTES_OF_HOUR 17)"); + assertAST("18", "(MINUTES_OF_HOUR 18)"); + assertAST("19", "(MINUTES_OF_HOUR 19)"); + assertAST("20", "(MINUTES_OF_HOUR 20)"); + assertAST("21", "(MINUTES_OF_HOUR 21)"); + assertAST("22", "(MINUTES_OF_HOUR 22)"); + assertAST("23", "(MINUTES_OF_HOUR 23)"); + assertAST("24", "(MINUTES_OF_HOUR 24)"); + assertAST("25", "(MINUTES_OF_HOUR 25)"); + assertAST("26", "(MINUTES_OF_HOUR 26)"); + assertAST("27", "(MINUTES_OF_HOUR 27)"); + assertAST("28", "(MINUTES_OF_HOUR 28)"); + assertAST("29", "(MINUTES_OF_HOUR 29)"); + assertAST("30", "(MINUTES_OF_HOUR 30)"); + assertAST("31", "(MINUTES_OF_HOUR 31)"); + assertAST("32", "(MINUTES_OF_HOUR 32)"); + assertAST("33", "(MINUTES_OF_HOUR 33)"); + assertAST("34", "(MINUTES_OF_HOUR 34)"); + assertAST("35", "(MINUTES_OF_HOUR 35)"); + assertAST("36", "(MINUTES_OF_HOUR 36)"); + assertAST("37", "(MINUTES_OF_HOUR 37)"); + assertAST("38", "(MINUTES_OF_HOUR 38)"); + assertAST("39", "(MINUTES_OF_HOUR 39)"); + assertAST("40", "(MINUTES_OF_HOUR 40)"); + assertAST("41", "(MINUTES_OF_HOUR 41)"); + assertAST("42", "(MINUTES_OF_HOUR 42)"); + assertAST("43", "(MINUTES_OF_HOUR 43)"); + assertAST("44", "(MINUTES_OF_HOUR 44)"); + assertAST("45", "(MINUTES_OF_HOUR 45)"); + assertAST("46", "(MINUTES_OF_HOUR 46)"); + assertAST("47", "(MINUTES_OF_HOUR 47)"); + assertAST("48", "(MINUTES_OF_HOUR 48)"); + assertAST("49", "(MINUTES_OF_HOUR 49)"); + assertAST("50", "(MINUTES_OF_HOUR 50)"); + assertAST("51", "(MINUTES_OF_HOUR 51)"); + assertAST("52", "(MINUTES_OF_HOUR 52)"); + assertAST("53", "(MINUTES_OF_HOUR 53)"); + assertAST("54", "(MINUTES_OF_HOUR 54)"); + assertAST("55", "(MINUTES_OF_HOUR 55)"); + assertAST("56", "(MINUTES_OF_HOUR 56)"); + assertAST("57", "(MINUTES_OF_HOUR 57)"); + assertAST("58", "(MINUTES_OF_HOUR 58)"); + assertAST("59", "(MINUTES_OF_HOUR 59)"); + //assertAST("0", "FAIL "); + //assertAST("1", "FAIL"); + //assertAST("2", "FAIL"); + //assertAST("3", "FAIL "); + //assertAST("4", "FAIL"); + //assertAST("5", "FAIL"); + //assertAST("6", "FAIL"); + //assertAST("7", "FAIL"); + //assertAST("8", "FAIL"); + //assertAST("9", "FAIL"); + //"60" FAIL + } + + @Test + public void hours() throws Exception { + _ruleName = "hours"; + + assertAST("00", "(HOURS_OF_DAY 00)"); + assertAST("01", "(HOURS_OF_DAY 01)"); + assertAST("1" , "(HOURS_OF_DAY 1)"); + assertAST("02", "(HOURS_OF_DAY 02)"); + assertAST("2" , "(HOURS_OF_DAY 2)"); + assertAST("03", "(HOURS_OF_DAY 03)"); + assertAST("3" , "(HOURS_OF_DAY 3)"); + assertAST("04", "(HOURS_OF_DAY 04)"); + assertAST("4" , "(HOURS_OF_DAY 4)"); + assertAST("05", "(HOURS_OF_DAY 05)"); + assertAST("5" , "(HOURS_OF_DAY 5)"); + assertAST("06", "(HOURS_OF_DAY 06)"); + assertAST("6" , "(HOURS_OF_DAY 6)"); + assertAST("07", "(HOURS_OF_DAY 07)"); + assertAST("7" , "(HOURS_OF_DAY 7)"); + assertAST("08", "(HOURS_OF_DAY 08)"); + assertAST("8" , "(HOURS_OF_DAY 8)"); + assertAST("09", "(HOURS_OF_DAY 09)"); + assertAST("9" , "(HOURS_OF_DAY 9)"); + assertAST("10", "(HOURS_OF_DAY 10)"); + assertAST("11", "(HOURS_OF_DAY 11)"); + assertAST("12", "(HOURS_OF_DAY 12)"); + assertAST("13", "(HOURS_OF_DAY 13)"); + assertAST("14", "(HOURS_OF_DAY 14)"); + assertAST("15", "(HOURS_OF_DAY 15)"); + assertAST("16", "(HOURS_OF_DAY 16)"); + assertAST("17", "(HOURS_OF_DAY 17)"); + assertAST("18", "(HOURS_OF_DAY 18)"); + assertAST("19", "(HOURS_OF_DAY 19)"); + assertAST("20", "(HOURS_OF_DAY 20)"); + assertAST("21", "(HOURS_OF_DAY 21)"); + assertAST("22", "(HOURS_OF_DAY 22)"); + assertAST("23", "(HOURS_OF_DAY 23)"); + //"-1" FAIL + //"24" FAIL + } + + @Test + public void explicit_time() throws Exception { + _ruleName = "explicit_time"; + + assertAST("0600h", "(EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00))"); + assertAST("06:00h", "(EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00))"); + assertAST("06:00 hours", "(EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00))"); + assertAST("0000", "(EXPLICIT_TIME (HOURS_OF_DAY 00) (MINUTES_OF_HOUR 00))"); + assertAST("0700h", "(EXPLICIT_TIME (HOURS_OF_DAY 07) (MINUTES_OF_HOUR 00))"); + assertAST("6pm", "(EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)"); + assertAST("5:30 a.m.", "(EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 30) am)"); + assertAST("5", "(EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0))"); + assertAST("12:59", "(EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 59))"); + assertAST("23:59", "(EXPLICIT_TIME (HOURS_OF_DAY 23) (MINUTES_OF_HOUR 59))"); + assertAST("00:00", "(EXPLICIT_TIME (HOURS_OF_DAY 00) (MINUTES_OF_HOUR 00))"); + assertAST("10:00am", "(EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)"); + assertAST("10a", "(EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0) am)"); + assertAST("10am", "(EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0) am)"); + assertAST("10", "(EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0))"); + assertAST("8p", "(EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm)"); + assertAST("8pm", "(EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm)"); + assertAST("8 pm", "(EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm)"); + assertAST("noon", "(EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)"); + assertAST("afternoon", "(EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)"); + assertAST("midnight", "(EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)"); + assertAST("mid-night", "(EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)"); + } +} From 4349567264af6d330bdf97774ed7e6beb0ab374f Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 20 Feb 2011 00:51:43 -0500 Subject: [PATCH 028/134] ignoring sub-token streams that yeild no syntax tree, more unit tests --- pom.xml | 16 + .../java/com/joestelmach/natty/DateGroup.java | 1 - .../com/joestelmach/natty/ParseLocation.java | 5 - .../java/com/joestelmach/natty/Parser.java | 48 +- src/test/DateParser.testsuite | 1796 ----------------- src/test/gunit/DateLexer.testsuite | 437 ++++ .../java/com/joestelmach/natty/DateTest.java | 8 +- .../joestelmach/grammar/DateTimeGrammar.java | 86 + 8 files changed, 570 insertions(+), 1827 deletions(-) delete mode 100644 src/test/DateParser.testsuite create mode 100644 src/test/gunit/DateLexer.testsuite create mode 100644 src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java diff --git a/pom.xml b/pom.xml index 6a38c608..17d38bd7 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,22 @@ + + + + org.antlr + maven-gunit-plugin + 3.2 + + + maven-gunit-plugin + test + + gunit + + + + diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index 9e044ee4..eb9f137b 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -9,7 +9,6 @@ /** * * @author Joe Stelmach - * */ public class DateGroup { private List _dates; diff --git a/src/main/java/com/joestelmach/natty/ParseLocation.java b/src/main/java/com/joestelmach/natty/ParseLocation.java index 35ef524f..d604c338 100644 --- a/src/main/java/com/joestelmach/natty/ParseLocation.java +++ b/src/main/java/com/joestelmach/natty/ParseLocation.java @@ -15,7 +15,6 @@ public class ParseLocation { public String getRuleName() { return _ruleName; } - public void setRuleName(String ruleName) { _ruleName = ruleName; } @@ -23,7 +22,6 @@ public void setRuleName(String ruleName) { public String getText() { return _text; } - public void setText(String text) { _text = text; } @@ -31,7 +29,6 @@ public void setText(String text) { public int getLine() { return _line; } - public void setLine(int line) { _line = line; } @@ -39,7 +36,6 @@ public void setLine(int line) { public int getStart() { return _start; } - public void setStart(int start) { _start = start; } @@ -47,7 +43,6 @@ public void setStart(int start) { public int getEnd() { return _end; } - public void setEnd(int end) { _end = end; } diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index ed329c16..1a695ae5 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -72,7 +72,7 @@ public List parse(String value) { List groups = new ArrayList(); for(TokenStream stream:streams) { DateGroup group = singleParse(stream); - if(group.getDates().size() > 0) { + if(group != null && group.getDates().size() > 0) { groups.add(group); } } @@ -96,26 +96,30 @@ private DateGroup singleParse(TokenStream stream) { DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); - // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) - CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); - TreeRewrite s = new TreeRewrite(nodes); - tree = (CommonTree)s.downup(tree); + // we only coninue if a meaningful syntax tree has been built + if(tree.getChildCount() > 0) { - // and walk it - nodes = new CommonTreeNodeStream(tree); - nodes.setTokenStream(stream); - DateWalker walker = new DateWalker(nodes); - walker.getState().setDefaultTimeZone(_defaultTimeZone); - walker.date_time_alternative(); - - // run through the results and append the parse information - group = walker.getState().getDateGroup(); - ParseLocation location = listener.getDateGroupLocation(); - group.setLine(location.getLine()); - group.setText(location.getText()); - group.setPosition(location.getStart()); - group.setSyntaxTree(tree); - group.setParseLocations(listener.getLocations()); + // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) + CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); + TreeRewrite s = new TreeRewrite(nodes); + tree = (CommonTree)s.downup(tree); + + // and walk it + nodes = new CommonTreeNodeStream(tree); + nodes.setTokenStream(stream); + DateWalker walker = new DateWalker(nodes); + walker.getState().setDefaultTimeZone(_defaultTimeZone); + walker.date_time_alternative(); + + // run through the results and append the parse information + group = walker.getState().getDateGroup(); + ParseLocation location = listener.getDateGroupLocation(); + group.setLine(location.getLine()); + group.setText(location.getText()); + group.setPosition(location.getStart()); + group.setSyntaxTree(tree); + group.setParseLocations(listener.getLocations()); + } } catch(RecognitionException e) { _logger.log(Level.SEVERE, "Could not parse input", e); @@ -157,7 +161,9 @@ private List collectTokenStreams(TokenStream stream) { else { // if this is an unknown token, we need to end the current group if(currentToken.getType() == DateLexer.UNKNOWN) { - groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + if(currentGroup.size() > 0) { + groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + } currentGroup = null; } // otherwise, the token is known and we're currently collecting for diff --git a/src/test/DateParser.testsuite b/src/test/DateParser.testsuite deleted file mode 100644 index 0ea69e24..00000000 --- a/src/test/DateParser.testsuite +++ /dev/null @@ -1,1796 +0,0 @@ -gunit Date; -@header{package com.joestelmach.natty.generated;} - -date_time: -" " FAIL -"seven years ago at 3pm" -> (DATE_TIME (RELATIVE_DATE (SEEK < by_day 7 year)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) pm)) -"1st oct in the year '89 1300 hours" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00))) -"1st oct in the year '89 at 1300 hours" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00))) -"1st oct in the year '89, 13:00" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00))) -"1st oct in the year '89,13:00" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00))) -"1st oct in the year '89, at 13:00" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00))) -"3am on oct 1st 2010" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am)) -"3am, october first 2010" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am)) -"3am,october first 2010" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am)) -"3am, on october first 2010" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am)) -"3am october first 2010" -> (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am)) -"next wed. at 5pm" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) -"3 days after next wed" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 3 (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))))) -"the sunday after next wed" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 1) (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))))) -"two days after today" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day))) -"two days from today" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day))) -"3 sundays after next wed" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 3 (DAY_OF_WEEK 1) (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))))) -"september 11, 2010" OK -"the day after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day))) -"the week after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 week))) -"the month after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 month))) -"the year after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 year))) -"wed of the week after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 4)))) -"the 28th of the month after next" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 month) (EXPLICIT_SEEK (DAY_OF_MONTH 28)))) - -"6 in the morning" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) -"4 in the afternoon" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm)) -"monday 6 in the morning" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) -"monday 4 in the afternoon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm)) -"monday 9 in the evening" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 0) pm)) -"this morning" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) -"this afternoon" -> (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) -"final thursday in april" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) -"final thurs in sep" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))) -"tomorrow @ noon" -> (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) - -"6 hours ago" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day 6 hour))) -"10 hrs before noon" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 10 hour))) -"10 hr before midnight" -> (DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 10 hour))) -"5 hours after noon" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 5 hour))) -"5 hours after midnight" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 5 hour))) -"in 5 seconds" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 second))) -"in 5 minutes" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 minute))) -"in 5 hours" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 hour))) -"4 secs from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second))) -"4 sec from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second))) -"4 minutes from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) -"4 mins from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) -"4 min from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute))) -"4 hours from now" -> (DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 hour))) - -date_time_alternative: -"this wed. or next at 5pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))) -"feb 28th or 2 days after" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28)))))) -"january fourth or the friday after" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 4))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 6) (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 4)))))) -"10/10/2008 or 10/12/2008" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2008))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 12) (YEAR_OF 2008)))) -"next wed or thursday" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 5))))) -"next wed, thurs, fri" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 5)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 6))))) -"next wed, thurs, or fri at 6pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4) (DAY_OF_WEEK 6))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm))) -"10/10 or 12/30 or 10/15 at 5pm" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 30)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 15)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))) - -"monday to friday" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 6))))) -"1999-12-31 to tomorrow" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 1999))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)))) -"now to 2010-01-01" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 01) (DAY_OF_MONTH 01) (YEAR_OF 2010)))) -"2009-03-10 9:00 to 11:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00)))) -"26 oct 10:00 am to 11:00 am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00) am))) -"jan 1 to 2" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 1))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2)))) -"16:00 nov 6 to 17:00" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 16) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 17) (MINUTES_OF_HOUR 00)))) -"may 2nd to 5th" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 2))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 5)))) -"6am dec 5 to 7am" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) am))) -"1/3 to 2/3" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 3))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3)))) -"2/3 to in 1 week" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 week)))) -"first day of may to last day of may" -> (DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))))) - -date_time_separator: -"," OK -", " OK -", at " OK -" " OK -" at " OK - -time_date_separator: -"," OK -", " OK -", on " OK -" " OK -" on " OK - -date: -"the day before yesterday" -> (RELATIVE_DATE (SEEK < by_day 1 (RELATIVE_DATE (SEEK < by_day 1 day)))) -"1st oct in the year '89" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) -"2009-10-10" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2009)) -"seven years ago" ->(RELATIVE_DATE (SEEK < by_day 7 year)) -"next monday" -> (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2))) -//"the day before the day before yesterday" -> - -global_date_prefix: -"the day after" -> "> by_day 1" -"day after" -> "> by_day 1" -"2 days after" -> "> by_day 2" -"three days before" -> "< by_day 3" -"six months after" -> "> by_month 6" -"3 weeks before" -> "< by_week 3" -"10 years after" -> "> by_year 10" -"the day before" -> "< by_day 1" -"day before" -> "< by_day 1" - -// ********** relaxed date tests ********** - -relaxed_date: -"oct 1, 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980)) -"oct. 1, 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980)) -"oct 1,1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980)) -"1st oct in the year '89" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) -"thirty first of december 80" -> (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 80)) -"the first of december in the year 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 1) (YEAR_OF 1980)) -"the 2 of february in the year 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980)) -"the 2nd of february in the year 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980)) -"the second of february in the year 1980" -> (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980)) -"jan. 2nd" -> (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2)) -"sun, nov 21 2010" -> (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 21) (DAY_OF_WEEK 1) (YEAR_OF 2010)) - -relaxed_month: -"january" -> (MONTH_OF_YEAR 1) -"jan" -> (MONTH_OF_YEAR 1) -"february" -> (MONTH_OF_YEAR 2) -"feb" -> (MONTH_OF_YEAR 2) -"march" -> (MONTH_OF_YEAR 3) -"mar" -> (MONTH_OF_YEAR 3) -"april" -> (MONTH_OF_YEAR 4) -"apr" -> (MONTH_OF_YEAR 4) -"may" -> (MONTH_OF_YEAR 5) -"june" -> (MONTH_OF_YEAR 6) -"jun" -> (MONTH_OF_YEAR 6) -"july" -> (MONTH_OF_YEAR 7) -"jul" -> (MONTH_OF_YEAR 7) -"august" -> (MONTH_OF_YEAR 8) -"aug" -> (MONTH_OF_YEAR 8) -"september" -> (MONTH_OF_YEAR 9) -"sep" -> (MONTH_OF_YEAR 9) -"sept" -> (MONTH_OF_YEAR 9) -"october" -> (MONTH_OF_YEAR 10) -"oct" -> (MONTH_OF_YEAR 10) -"november" -> (MONTH_OF_YEAR 11) -"nov" -> (MONTH_OF_YEAR 11) -"december" -> (MONTH_OF_YEAR 12) -"dec" -> (MONTH_OF_YEAR 12) -"jan." -> (MONTH_OF_YEAR 1) -"feb." -> (MONTH_OF_YEAR 2) -"mar." -> (MONTH_OF_YEAR 3) -"apr." -> (MONTH_OF_YEAR 4) -"jun." -> (MONTH_OF_YEAR 6) -"jul." -> (MONTH_OF_YEAR 7) -"aug." -> (MONTH_OF_YEAR 8) -"sep." -> (MONTH_OF_YEAR 9) -"sept." -> (MONTH_OF_YEAR 9) -"oct." -> (MONTH_OF_YEAR 10) -"nov." -> (MONTH_OF_YEAR 11) -"dec." -> (MONTH_OF_YEAR 12) - -relaxed_day_of_month: -"three" -> (DAY_OF_MONTH 3) -"third" -> (DAY_OF_MONTH 3) -"3rd" -> (DAY_OF_MONTH 3) -"3" -> (DAY_OF_MONTH 3) -"03" -> (DAY_OF_MONTH 03) -"21" -> (DAY_OF_MONTH 21) -"thirty one" -> (DAY_OF_MONTH 31) -"thirty-one" -> (DAY_OF_MONTH 31) -"thirty first" -> (DAY_OF_MONTH 31) -"thirty-first" -> (DAY_OF_MONTH 31) -"31st" -> (DAY_OF_MONTH 31) -"32" FAIL - -relaxed_year: -"'69" -> (YEAR_OF 69) -"79" -> (YEAR_OF 79) -"2079" -> (YEAR_OF 2079) -"999" FAIL -"999" FAIL -"'80" -> (YEAR_OF 80) -"1979" -> (YEAR_OF 1979) -"2004" -> (YEAR_OF 2004) - -relaxed_year_prefix: -", in the year " OK -" in the year " OK -"in the year " FAIL - -// ********** formal date tests ********** - -formal_date: -"2009-10-10" -> (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2009)) -"1980-1-2" -> (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2) (YEAR_OF 1980)) -"12/12/12" -> (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 12) (YEAR_OF 12)) -"3/4" -> (EXPLICIT_DATE (MONTH_OF_YEAR 3) (DAY_OF_MONTH 4)) -"sun, 11/21/2010" -> (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 21) (DAY_OF_WEEK 1) (YEAR_OF 2010)) - -formal_month_of_year: -"00" FAIL -"0" FAIL -"01" -> (MONTH_OF_YEAR 01) -"1" -> (MONTH_OF_YEAR 1) -"02" -> (MONTH_OF_YEAR 02) -"2" -> (MONTH_OF_YEAR 2) -"03" -> (MONTH_OF_YEAR 03) -"3" -> (MONTH_OF_YEAR 3) -"04" -> (MONTH_OF_YEAR 04) -"4" -> (MONTH_OF_YEAR 4) -"05" -> (MONTH_OF_YEAR 05) -"5" -> (MONTH_OF_YEAR 5) -"06" -> (MONTH_OF_YEAR 06) -"6" -> (MONTH_OF_YEAR 6) -"07" -> (MONTH_OF_YEAR 07) -"7" -> (MONTH_OF_YEAR 7) -"08" -> (MONTH_OF_YEAR 08) -"8" -> (MONTH_OF_YEAR 8) -"09" -> (MONTH_OF_YEAR 09) -"9" -> (MONTH_OF_YEAR 9) -"10" -> (MONTH_OF_YEAR 10) -"11" -> (MONTH_OF_YEAR 11) -"12" -> (MONTH_OF_YEAR 12) -"13" FAIL - -formal_day_of_month: -"00" FAIL -"0" FAIL -"01" -> (DAY_OF_MONTH 01) -"1" -> (DAY_OF_MONTH 1) -"02" -> (DAY_OF_MONTH 02) -"2" -> (DAY_OF_MONTH 2) -"03" -> (DAY_OF_MONTH 03) -"3" -> (DAY_OF_MONTH 3) -"04" -> (DAY_OF_MONTH 04) -"4" -> (DAY_OF_MONTH 4) -"05" -> (DAY_OF_MONTH 05) -"5" -> (DAY_OF_MONTH 5) -"06" -> (DAY_OF_MONTH 06) -"6" -> (DAY_OF_MONTH 6) -"07" -> (DAY_OF_MONTH 07) -"7" -> (DAY_OF_MONTH 7) -"08" -> (DAY_OF_MONTH 08) -"8" -> (DAY_OF_MONTH 8) -"09" -> (DAY_OF_MONTH 09) -"9" -> (DAY_OF_MONTH 9) -"10" -> (DAY_OF_MONTH 10) -"11" -> (DAY_OF_MONTH 11) -"12" -> (DAY_OF_MONTH 12) -"13" -> (DAY_OF_MONTH 13) -"14" -> (DAY_OF_MONTH 14) -"15" -> (DAY_OF_MONTH 15) -"16" -> (DAY_OF_MONTH 16) -"17" -> (DAY_OF_MONTH 17) -"18" -> (DAY_OF_MONTH 18) -"19" -> (DAY_OF_MONTH 19) -"20" -> (DAY_OF_MONTH 20) -"21" -> (DAY_OF_MONTH 21) -"22" -> (DAY_OF_MONTH 22) -"23" -> (DAY_OF_MONTH 23) -"24" -> (DAY_OF_MONTH 24) -"25" -> (DAY_OF_MONTH 25) -"26" -> (DAY_OF_MONTH 26) -"27" -> (DAY_OF_MONTH 27) -"28" -> (DAY_OF_MONTH 28) -"29" -> (DAY_OF_MONTH 29) -"30" -> (DAY_OF_MONTH 30) -"31" -> (DAY_OF_MONTH 31) -"32" FAIL - -formal_year: -"1999" -> (YEAR_OF 1999) -"80" -> (YEAR_OF 80) -"0000" -> (YEAR_OF 0000) -"2010" -> (YEAR_OF 2010) -"03" -> (YEAR_OF 03) -"037" FAIL -"0" FAIL -"03700" FAIL - -formal_year_four_digits: -"1999" -> (YEAR_OF 1999) -"80" FAIL -"0000" -> (YEAR_OF 0000) -"2010" -> (YEAR_OF 2010) -"03" FAIL -"037" FAIL -"0" FAIL -"03700" FAIL - -formal_date_separator: -"-" OK -"/" OK - -// ********** relative date tests ********** - -relative_date: -"yesterday" -> (RELATIVE_DATE (SEEK < by_day 1 day)) -"tomorrow" ->(RELATIVE_DATE (SEEK > by_day 1 day)) -"in 3 days" ->(RELATIVE_DATE (SEEK > by_day 3 day)) -"3 days ago" ->(RELATIVE_DATE (SEEK < by_day 3 day)) -"in 3 weeks" ->(RELATIVE_DATE (SEEK > by_day 3 week)) -"four weeks ago" ->(RELATIVE_DATE (SEEK < by_day 4 week)) -"in 3 months" ->(RELATIVE_DATE (SEEK > by_day 3 month)) -"three months ago" ->(RELATIVE_DATE (SEEK < by_day 3 month)) -"in 3 years" ->(RELATIVE_DATE (SEEK > by_day 3 year)) -"seven years ago" ->(RELATIVE_DATE (SEEK < by_day 7 year)) -"60 years ago" ->(RELATIVE_DATE (SEEK < by_day 60 year)) -"32 days ago" ->(RELATIVE_DATE (SEEK < by_day 32 day)) -"next monday" -> (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2))) -"next mon" -> (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2))) -"4 mondays from now" -> (RELATIVE_DATE (SEEK > by_day 4 (DAY_OF_WEEK 2))) -"4 mondays from today" -> (RELATIVE_DATE (SEEK > by_day 4 (DAY_OF_WEEK 2))) -"next weekend" -> (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 7))) -"six mondays ago" -> (RELATIVE_DATE (SEEK < by_day 6 (DAY_OF_WEEK 2))) -"last monday" -> (RELATIVE_DATE (SEEK < by_week 1 (DAY_OF_WEEK 2))) -"last mon" -> (RELATIVE_DATE (SEEK < by_week 1 (DAY_OF_WEEK 2))) -"this past mon" -> (RELATIVE_DATE (SEEK < by_day 1 (DAY_OF_WEEK 2))) -"this coming mon" -> (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 2))) -"this upcoming mon" -> (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 2))) -"next thurs" -> (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5))) -"next month" -> (RELATIVE_DATE (SEEK > by_week 1 month)) -"last month" -> (RELATIVE_DATE (SEEK < by_week 1 month)) -"next week" -> (RELATIVE_DATE (SEEK > by_week 1 week)) -"last week" -> (RELATIVE_DATE (SEEK < by_week 1 week)) -"next year" -> (RELATIVE_DATE (SEEK > by_week 1 year)) -"last year" -> (RELATIVE_DATE (SEEK < by_week 1 year)) - -relative_occurrence_index: -"1" -> "1" -"2" -> "2" -"3" -> "3" -"4" -> "4" -"5" -> "5" -"6" FAIL -"first" -> "1" -"second" -> "2" -"third" -> "3" -"fourth" -> "4" -"fifth" -> "5" -"sixth" FAIL -"last" -> "5" - -relative_target: -"sunday" -> (DAY_OF_WEEK 1) -"sundays" -> (DAY_OF_WEEK 1) -"sun" -> (DAY_OF_WEEK 1) -"monday" -> (DAY_OF_WEEK 2) -"mondays" -> (DAY_OF_WEEK 2) -"mon" -> (DAY_OF_WEEK 2) -"tuesday" -> (DAY_OF_WEEK 3) -"tuesdays" -> (DAY_OF_WEEK 3) -"tues" -> (DAY_OF_WEEK 3) -"tue" -> (DAY_OF_WEEK 3) -"wednesday" -> (DAY_OF_WEEK 4) -"wednesdays" -> (DAY_OF_WEEK 4) -"wed" -> (DAY_OF_WEEK 4) -"thursday" -> (DAY_OF_WEEK 5) -"thursdays" -> (DAY_OF_WEEK 5) -"thur" -> (DAY_OF_WEEK 5) -"thu" -> (DAY_OF_WEEK 5) -"friday" -> (DAY_OF_WEEK 6) -"fridays" -> (DAY_OF_WEEK 6) -"fri" -> (DAY_OF_WEEK 6) -"saturday" -> (DAY_OF_WEEK 7) -"saturdays" -> (DAY_OF_WEEK 7) -"sat" -> (DAY_OF_WEEK 7) -"day" -> "day" -"days" -> "day" -"week" -> "week" -"weeks" -> "week" -"month" -> "month" -"months" -> "month" -"year" -> "year" -"years" -> "year" - -implicit_prefix: -"this" -> "> by_day 0" - -relative_date_prefix: -"this last" -> "< by_week 1" -"last" -> "< by_week 1" -"this past" -> "< by_day 1" -"past" -> "< by_day 1" -"this next" -> "> by_week 1" -"next" -> "> by_week 1" -"this coming" -> "> by_day 1" -"coming" -> "> by_day 1" -"this upcoming" -> "> by_day 1" -"upcoming" -> "> by_day 1" -"in 3" -> "> by_day 3" -"in twenty" -> "> by_day 20" -"3" -> "> by_day 3" -"twenty-eight" -> "> by_day 28" - -relative_date_suffix: -"from now" -> "> by_day" -"ago" -> "< by_day" - -relative_date_span: -"day" -> "day" -"days" -> "day" -"week" -> "week" -"weeks" -> "week" -"month" -> "month" -"months" -> "month" -"year" -> "year" -"years" -> "year" - -// ********** explicit_relative date tests ********** -explicit_relative_date: -"1st of three months ago" -> (RELATIVE_DATE (SEEK < by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"10th of next month" -> (RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 10))) -"28th of last month" -> (RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 28))) -"10th of next october" -> (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 10)) (EXPLICIT_SEEK (DAY_OF_MONTH 10))) -"the 30th of this month" -> (RELATIVE_DATE (SEEK > by_day 0 month) (EXPLICIT_SEEK (DAY_OF_MONTH 30))) -"monday of last week" -> (RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"tuesday of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 3))) -"the monday of 2 weeks ago" -> (RELATIVE_DATE (SEEK < by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"tuesday of 3 weeks from now" -> (RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 3))) -"monday of 3 weeks from now" -> (RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"the last sunday in november" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 1))) -"the last day of february 1999" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 31)) (EXPLICIT_SEEK (YEAR_OF 1999))) -"the first wed. in january" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 1)) (EXPLICIT_SEEK 1 (DAY_OF_WEEK 4))) -"the first wed. in january in the year 2004" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 1)) (EXPLICIT_SEEK 1 (DAY_OF_WEEK 4)) (EXPLICIT_SEEK (YEAR_OF 2004))) -"3rd wed in next month" -> (RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK 3 (DAY_OF_WEEK 4))) -"last monday of last month" -> (RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 2))) -"the last sunday of next nov" -> (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 1))) -"the 3rd sunday of 2 novembers from now" -> (RELATIVE_DATE (SEEK > by_day 2 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 3 (DAY_OF_WEEK 1))) -"the last monday in 2 novembers ago" -> (RELATIVE_DATE (SEEK < by_day 2 (MONTH_OF_YEAR 11)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 2))) -"the beginning of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"the end of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"the end of this week" -> (RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"the start of this week" -> (RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"start of 3 weeks from now" -> (RELATIVE_DATE (SEEK > by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"the end of 3 weeks ago" -> (RELATIVE_DATE (SEEK < by_day 3 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"the first day of this week" -> (RELATIVE_DATE (SEEK > by_day 0 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"the last day of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"first day of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 2))) -"last day of last week" -> (RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"start of 3 months from now" -> (RELATIVE_DATE (SEEK > by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"beginning of next month" -> (RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"end of next month" -> (RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) -"last day of next month" -> (RELATIVE_DATE (SEEK > by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) -"first day of 3 months from now" -> (RELATIVE_DATE (SEEK > by_day 3 month) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"end of next october" -> (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 10)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) -"first day of feb" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"last day of three februarys from now" -> (RELATIVE_DATE (SEEK > by_day 3 (MONTH_OF_YEAR 2)) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) -"in the end of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"at the end of last week" -> (RELATIVE_DATE (SEEK < by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"at the end of 2 weeks" -> (RELATIVE_DATE (SEEK > by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"in the start of june" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 6)) (EXPLICIT_SEEK (DAY_OF_MONTH 1))) -"at the end of next week" -> (RELATIVE_DATE (SEEK > by_week 1 week) (EXPLICIT_SEEK (DAY_OF_WEEK 6))) -"at the end of last month" -> (RELATIVE_DATE (SEEK < by_week 1 month) (EXPLICIT_SEEK (DAY_OF_MONTH 31))) -"the second day of april" -> (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK (DAY_OF_MONTH 2))) -"the thirtieth day of next april" -> (RELATIVE_DATE (SEEK > by_week 1 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK (DAY_OF_MONTH 30))) - -day_of_week: -"sunday" -> (DAY_OF_WEEK 1) -"sundays" -> (DAY_OF_WEEK 1) -"sun" -> (DAY_OF_WEEK 1) -"sun." -> (DAY_OF_WEEK 1) -"monday" -> (DAY_OF_WEEK 2) -"mondays" -> (DAY_OF_WEEK 2) -"mon" -> (DAY_OF_WEEK 2) -"mon." -> (DAY_OF_WEEK 2) -"tuesday" -> (DAY_OF_WEEK 3) -"tuesdays" -> (DAY_OF_WEEK 3) -"tues" -> (DAY_OF_WEEK 3) -"tues." -> (DAY_OF_WEEK 3) -"tue" -> (DAY_OF_WEEK 3) -"tue." -> (DAY_OF_WEEK 3) -"wednesday" -> (DAY_OF_WEEK 4) -"wednesdays" -> (DAY_OF_WEEK 4) -"wed" -> (DAY_OF_WEEK 4) -"wed." -> (DAY_OF_WEEK 4) -"thursday" -> (DAY_OF_WEEK 5) -"thursdays" -> (DAY_OF_WEEK 5) -"thur" -> (DAY_OF_WEEK 5) -"thur." -> (DAY_OF_WEEK 5) -"thu" -> (DAY_OF_WEEK 5) -"thu." -> (DAY_OF_WEEK 5) -"friday" -> (DAY_OF_WEEK 6) -"fridays" -> (DAY_OF_WEEK 6) -"fri" -> (DAY_OF_WEEK 6) -"fri." -> (DAY_OF_WEEK 6) -"saturday" -> (DAY_OF_WEEK 7) -"saturdays" -> (DAY_OF_WEEK 7) -"sat" -> (DAY_OF_WEEK 7) -"sat." -> (DAY_OF_WEEK 7) - -named_relative_date: -"today" -> (RELATIVE_DATE (SEEK > by_day 0 day)) -"now" -> (RELATIVE_DATE (SEEK > by_day 0 day)) -"tomorow" -> (RELATIVE_DATE (SEEK > by_day 1 day)) -"tomorrow" -> (RELATIVE_DATE (SEEK > by_day 1 day)) -"tommorow" -> (RELATIVE_DATE (SEEK > by_day 1 day)) -"tommorrow" -> (RELATIVE_DATE (SEEK > by_day 1 day)) -"yesterday" -> (RELATIVE_DATE (SEEK < by_day 1 day)) - -// ********** time tests ********** -explicit_time: -"0600h" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) -"06:00h" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) -"06:00 hours" -> (EXPLICIT_TIME (HOURS_OF_DAY 06) (MINUTES_OF_HOUR 00)) -"0000" -> (EXPLICIT_TIME (HOURS_OF_DAY 00) (MINUTES_OF_HOUR 00)) -"0700h" -> (EXPLICIT_TIME (HOURS_OF_DAY 07) (MINUTES_OF_HOUR 00)) -"6pm" -> (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm) -"5:30 a.m." -> (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 30) am) -"5" -> (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0)) -"12:59" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 59)) -"23:59" -> (EXPLICIT_TIME (HOURS_OF_DAY 23) (MINUTES_OF_HOUR 59)) -"00:00" -> (EXPLICIT_TIME (HOURS_OF_DAY 00) (MINUTES_OF_HOUR 00)) -"10:00am" -> (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am) -"10a" -> (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0) am) -"10am" -> (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0) am) -"10" -> (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 0)) -"8p" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) -"8pm" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) -"8 pm" -> (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) pm) -"noon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm) -"afternoon" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm) -"midnight" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am) -"mid-night" -> (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am) - -hours: -"-1" FAIL -"00" -> (HOURS_OF_DAY 00) -"01" -> (HOURS_OF_DAY 01) -"1" -> (HOURS_OF_DAY 1) -"02" -> (HOURS_OF_DAY 02) -"2" -> (HOURS_OF_DAY 2) -"03" -> (HOURS_OF_DAY 03) -"3" -> (HOURS_OF_DAY 3) -"04" -> (HOURS_OF_DAY 04) -"4" -> (HOURS_OF_DAY 4) -"05" -> (HOURS_OF_DAY 05) -"5" -> (HOURS_OF_DAY 5) -"06" -> (HOURS_OF_DAY 06) -"6" -> (HOURS_OF_DAY 6) -"07" -> (HOURS_OF_DAY 07) -"7" -> (HOURS_OF_DAY 7) -"08" -> (HOURS_OF_DAY 08) -"8" -> (HOURS_OF_DAY 8) -"09" -> (HOURS_OF_DAY 09) -"9" -> (HOURS_OF_DAY 9) -"10" -> (HOURS_OF_DAY 10) -"11" -> (HOURS_OF_DAY 11) -"12" -> (HOURS_OF_DAY 12) -"13" -> (HOURS_OF_DAY 13) -"14" -> (HOURS_OF_DAY 14) -"15" -> (HOURS_OF_DAY 15) -"16" -> (HOURS_OF_DAY 16) -"17" -> (HOURS_OF_DAY 17) -"18" -> (HOURS_OF_DAY 18) -"19" -> (HOURS_OF_DAY 19) -"20" -> (HOURS_OF_DAY 20) -"21" -> (HOURS_OF_DAY 21) -"22" -> (HOURS_OF_DAY 22) -"23" -> (HOURS_OF_DAY 23) -"24" FAIL - -minutes: -"-1" FAIL -"00" -> (MINUTES_OF_HOUR 00) -"0" FAIL -"01" -> (MINUTES_OF_HOUR 01) -"1" FAIL -"02" -> (MINUTES_OF_HOUR 02) -"2" FAIL -"03" -> (MINUTES_OF_HOUR 03) -"3" FAIL -"04" -> (MINUTES_OF_HOUR 04) -"4" FAIL -"05" -> (MINUTES_OF_HOUR 05) -"5" FAIL -"06" -> (MINUTES_OF_HOUR 06) -"6" FAIL -"07" -> (MINUTES_OF_HOUR 07) -"7" FAIL -"08" -> (MINUTES_OF_HOUR 08) -"8" FAIL -"09" -> (MINUTES_OF_HOUR 09) -"9" FAIL -"10" -> (MINUTES_OF_HOUR 10) -"11" -> (MINUTES_OF_HOUR 11) -"12" -> (MINUTES_OF_HOUR 12) -"13" -> (MINUTES_OF_HOUR 13) -"14" -> (MINUTES_OF_HOUR 14) -"15" -> (MINUTES_OF_HOUR 15) -"16" -> (MINUTES_OF_HOUR 16) -"17" -> (MINUTES_OF_HOUR 17) -"18" -> (MINUTES_OF_HOUR 18) -"19" -> (MINUTES_OF_HOUR 19) -"20" -> (MINUTES_OF_HOUR 20) -"21" -> (MINUTES_OF_HOUR 21) -"22" -> (MINUTES_OF_HOUR 22) -"23" -> (MINUTES_OF_HOUR 23) -"24" -> (MINUTES_OF_HOUR 24) -"25" -> (MINUTES_OF_HOUR 25) -"26" -> (MINUTES_OF_HOUR 26) -"27" -> (MINUTES_OF_HOUR 27) -"28" -> (MINUTES_OF_HOUR 28) -"29" -> (MINUTES_OF_HOUR 29) -"30" -> (MINUTES_OF_HOUR 30) -"31" -> (MINUTES_OF_HOUR 31) -"32" -> (MINUTES_OF_HOUR 32) -"33" -> (MINUTES_OF_HOUR 33) -"34" -> (MINUTES_OF_HOUR 34) -"35" -> (MINUTES_OF_HOUR 35) -"36" -> (MINUTES_OF_HOUR 36) -"37" -> (MINUTES_OF_HOUR 37) -"38" -> (MINUTES_OF_HOUR 38) -"39" -> (MINUTES_OF_HOUR 39) -"40" -> (MINUTES_OF_HOUR 40) -"41" -> (MINUTES_OF_HOUR 41) -"42" -> (MINUTES_OF_HOUR 42) -"43" -> (MINUTES_OF_HOUR 43) -"44" -> (MINUTES_OF_HOUR 44) -"45" -> (MINUTES_OF_HOUR 45) -"46" -> (MINUTES_OF_HOUR 46) -"47" -> (MINUTES_OF_HOUR 47) -"48" -> (MINUTES_OF_HOUR 48) -"49" -> (MINUTES_OF_HOUR 49) -"50" -> (MINUTES_OF_HOUR 50) -"51" -> (MINUTES_OF_HOUR 51) -"52" -> (MINUTES_OF_HOUR 52) -"53" -> (MINUTES_OF_HOUR 53) -"54" -> (MINUTES_OF_HOUR 54) -"55" -> (MINUTES_OF_HOUR 55) -"56" -> (MINUTES_OF_HOUR 56) -"57" -> (MINUTES_OF_HOUR 57) -"58" -> (MINUTES_OF_HOUR 58) -"59" -> (MINUTES_OF_HOUR 59) -"60" FAIL - -meridian_indicator: -"am" -> "am" -"a.m." -> "am" -"a" -> "am" -"pm" -> "pm" -"p.m." -> "pm" -"p" -> "pm" - -time_zone_abbreviation: -"est" -> "America/New_York" -"edt" -> "America/New_York" -"et" -> "America/New_York" -"pst" -> "America/Los_Angeles" -"pdt" -> "America/Los_Angeles" -"pt" -> "America/Los_Angeles" -"cst" -> "America/Chicago" -"cdt" -> "America/Chicago" -"ct" -> "America/Chicago" -"mst" -> "America/Denver" -"mdt" -> "America/Denver" -"mt" -> "America/Denver" -"akst" -> "America/Anchorage" -"akdt" -> "America/Anchorage" -"akt" -> "America/Anchorage" -"hast" -> "Pacific/Honolulu" -"hadt" -> "Pacific/Honolulu" -"hat" -> "Pacific/Honolulu" -"hst" -> "Pacific/Honolulu" - -// ********** numeric tests ********** - -int_00_to_23_optional_prefix: -"00" -> "00" -"0" -> "0" -"01" -> "01" -"1" -> "1" -"02" -> "02" -"2" -> "2" -"03" -> "03" -"3" -> "3" -"04" -> "04" -"4" -> "4" -"05" -> "05" -"5" -> "5" -"06" -> "06" -"6" -> "6" -"07" -> "07" -"7" -> "7" -"08" -> "08" -"8" -> "8" -"09" -> "09" -"9" -> "9" -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" -> "13" -"14" -> "14" -"15" -> "15" -"16" -> "16" -"17" -> "17" -"18" -> "18" -"19" -> "19" -"20" -> "20" -"21" -> "21" -"22" -> "22" -"23" -> "23" -"24" FAIL - -int_00_to_59_mandatory_prefix: -"00" -> "00" -"0" FAIL -"01" -> "01" -"1" FAIL -"02" -> "02" -"2" FAIL -"03" -> "03" -"3" FAIL -"04" -> "04" -"4" FAIL -"05" -> "05" -"5" FAIL -"06" -> "06" -"6" FAIL -"07" -> "07" -"7" FAIL -"08" -> "08" -"8" FAIL -"09" -> "09" -"9" FAIL -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" -> "13" -"14" -> "14" -"15" -> "15" -"16" -> "16" -"17" -> "17" -"18" -> "18" -"19" -> "19" -"20" -> "20" -"21" -> "21" -"22" -> "22" -"23" -> "23" -"24" -> "24" -"25" -> "25" -"26" -> "26" -"27" -> "27" -"28" -> "28" -"29" -> "29" -"30" -> "30" -"31" -> "31" -"32" -> "32" -"33" -> "33" -"34" -> "34" -"35" -> "35" -"36" -> "36" -"37" -> "37" -"38" -> "38" -"39" -> "39" -"40" -> "40" -"41" -> "41" -"42" -> "42" -"43" -> "43" -"44" -> "44" -"45" -> "45" -"46" -> "46" -"47" -> "47" -"48" -> "48" -"49" -> "49" -"50" -> "50" -"51" -> "51" -"52" -> "52" -"53" -> "53" -"54" -> "54" -"55" -> "55" -"56" -> "56" -"57" -> "57" -"58" -> "58" -"59" -> "59" -"60" FAIL - -int_00_to_99_mandatory_prefix: -"00" -> "00" -"0" FAIL -"01" -> "01" -"1" FAIL -"02" -> "02" -"2" FAIL -"03" -> "03" -"3" FAIL -"04" -> "04" -"4" FAIL -"05" -> "05" -"5" FAIL -"06" -> "06" -"6" FAIL -"07" -> "07" -"7" FAIL -"08" -> "08" -"8" FAIL -"09" -> "09" -"9" FAIL -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" -> "13" -"14" -> "14" -"15" -> "15" -"16" -> "16" -"17" -> "17" -"18" -> "18" -"19" -> "19" -"20" -> "20" -"21" -> "21" -"22" -> "22" -"23" -> "23" -"24" -> "24" -"25" -> "25" -"26" -> "26" -"27" -> "27" -"28" -> "28" -"29" -> "29" -"30" -> "30" -"31" -> "31" -"32" -> "32" -"33" -> "33" -"34" -> "34" -"35" -> "35" -"36" -> "36" -"37" -> "37" -"38" -> "38" -"39" -> "39" -"40" -> "40" -"41" -> "41" -"42" -> "42" -"43" -> "43" -"44" -> "44" -"45" -> "45" -"46" -> "46" -"47" -> "47" -"48" -> "48" -"49" -> "49" -"50" -> "50" -"51" -> "51" -"52" -> "52" -"53" -> "53" -"54" -> "54" -"55" -> "55" -"56" -> "56" -"57" -> "57" -"58" -> "58" -"59" -> "59" -"60" -> "60" -"61" -> "61" -"62" -> "62" -"63" -> "63" -"64" -> "64" -"65" -> "65" -"66" -> "66" -"67" -> "67" -"68" -> "68" -"69" -> "69" -"70" -> "70" -"71" -> "71" -"72" -> "72" -"73" -> "73" -"74" -> "74" -"75" -> "75" -"76" -> "76" -"77" -> "77" -"78" -> "78" -"79" -> "79" -"80" -> "80" -"81" -> "81" -"82" -> "82" -"83" -> "83" -"84" -> "84" -"85" -> "85" -"86" -> "86" -"87" -> "87" -"88" -> "88" -"89" -> "89" -"90" -> "90" -"91" -> "91" -"92" -> "92" -"93" -> "93" -"94" -> "94" -"95" -> "95" -"96" -> "96" -"97" -> "97" -"98" -> "98" -"99" -> "99" -"100" FAIL - -int_01_to_12_optional_prefix: -"00" FAIL -"0" FAIL -"01" -> "01" -"1" -> "1" -"02" -> "02" -"2" -> "2" -"03" -> "03" -"3" -> "3" -"04" -> "04" -"4" -> "4" -"05" -> "05" -"5" -> "5" -"06" -> "06" -"6" -> "6" -"07" -> "07" -"7" -> "7" -"08" -> "08" -"8" -> "8" -"09" -> "09" -"9" -> "9" -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" FAIL - -int_01_to_31_optional_prefix: -"00" FAIL -"0" FAIL -"01" -> "01" -"1" -> "1" -"02" -> "02" -"2" -> "2" -"03" -> "03" -"3" -> "3" -"04" -> "04" -"4" -> "4" -"05" -> "05" -"5" -> "5" -"06" -> "06" -"6" -> "6" -"07" -> "07" -"7" -> "7" -"08" -> "08" -"8" -> "8" -"09" -> "09" -"9" -> "9" -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" -> "13" -"14" -> "14" -"15" -> "15" -"16" -> "16" -"17" -> "17" -"18" -> "18" -"19" -> "19" -"20" -> "20" -"21" -> "21" -"22" -> "22" -"23" -> "23" -"24" -> "24" -"25" -> "25" -"26" -> "26" -"27" -> "27" -"28" -> "28" -"29" -> "29" -"30" -> "30" -"31" -> "31" -"32" FAIL - -int_four_digits: -"000" FAIL -"33" FAIL -"2" FAIL -"0000" -> "0000" -"0100" -> "0100" -"0020" -> "0020" -"0003" -> "0003" -"9999" -> "9999" -"5050" -> "5050" - -spelled_or_int_01_to_31_optional_prefix: -"zero" FAIL -"one" -> "1" -"two" -> "2" -"three" -> "3" -"four" -> "4" -"five" -> "5" -"six" -> "6" -"seven" -> "7" -"eight" -> "8" -"nine" -> "9" -"ten" -> "10" -"eleven" -> "11" -"twelve" -> "12" -"thirteen" -> "13" -"fourteen" -> "14" -"fifteen" -> "15" -"sixteen" -> "16" -"seventeen" -> "17" -"eighteen" -> "18" -"nineteen" -> "19" -"twenty" -> "20" -"twenty one" -> "21" -"twenty-one" -> "21" -"twenty two" -> "22" -"twenty-two" -> "22" -"twenty three" -> "23" -"twenty-three" -> "23" -"twenty four" -> "24" -"twenty-four" -> "24" -"twenty five" -> "25" -"twenty-five" -> "25" -"twenty six" -> "26" -"twenty-six" -> "26" -"twenty seven" -> "27" -"twenty-seven" -> "27" -"twenty-eight" -> "28" -"twenty nine" -> "29" -"twenty-nine" -> "29" -"thirty" -> "30" -"thirty one" -> "31" -"thirty-one" -> "31" -"00" FAIL -"0" FAIL -"01" -> "01" -"1" -> "1" -"02" -> "02" -"2" -> "2" -"03" -> "03" -"3" -> "3" -"04" -> "04" -"4" -> "4" -"05" -> "05" -"5" -> "5" -"06" -> "06" -"6" -> "6" -"07" -> "07" -"7" -> "7" -"08" -> "08" -"8" -> "8" -"09" -> "09" -"9" -> "9" -"10" -> "10" -"11" -> "11" -"12" -> "12" -"13" -> "13" -"14" -> "14" -"15" -> "15" -"16" -> "16" -"17" -> "17" -"18" -> "18" -"19" -> "19" -"20" -> "20" -"21" -> "21" -"22" -> "22" -"23" -> "23" -"24" -> "24" -"25" -> "25" -"26" -> "26" -"27" -> "27" -"28" -> "28" -"29" -> "29" -"30" -> "30" -"31" -> "31" - -spelled_or_int_optional_prefix: -"00" FAIL -"0" FAIL -"01" -> "01" -"60" -> "60" -"99" -> "99" -"1" -> "1" -"zero" FAIL -"one" -> "1" -"two" -> "2" - -spelled_one_to_thirty_one: -"zero" FAIL -"one" -> "1" -"two" -> "2" -"three" -> "3" -"four" -> "4" -"five" -> "5" -"six" -> "6" -"seven" -> "7" -"eight" -> "8" -"nine" -> "9" -"ten" -> "10" -"eleven" -> "11" -"twelve" -> "12" -"thirteen" -> "13" -"fourteen" -> "14" -"fifteen" -> "15" -"sixteen" -> "16" -"seventeen" -> "17" -"eighteen" -> "18" -"nineteen" -> "19" -"twenty" -> "20" -"twenty one" -> "21" -"twenty-one" -> "21" -"twenty two" -> "22" -"twenty-two" -> "22" -"twenty three" -> "23" -"twenty-three" -> "23" -"twenty four" -> "24" -"twenty-four" -> "24" -"twenty five" -> "25" -"twenty-five" -> "25" -"twenty six" -> "26" -"twenty-six" -> "26" -"twenty seven" -> "27" -"twenty-seven" -> "27" -"twenty-eight" -> "28" -"twenty nine" -> "29" -"twenty-nine" -> "29" -"thirty" -> "30" -"thirty one" -> "31" -"thirty-one" -> "31" -"thirty two" FAIL -"thirty-two" FAIL - -spelled_first_to_thirty_first: -"first" -> "1" -"1st" -> "1" -"second" -> "2" -"2nd" -> "2" -"third" -> "3" -"3rd" -> "3" -"fourth" -> "4" -"4th" -> "4" -"fifth" -> "5" -"5th" -> "5" -"sixth" -> "6" -"6th" -> "6" -"seventh" -> "7" -"7th" -> "7" -"eighth" -> "8" -"8th" -> "8" -"ninth" -> "9" -"9th" -> "9" -"tenth" -> "10" -"10th" -> "10" -"eleventh" -> "11" -"11th" -> "11" -"twelfth" -> "12" -"12th" -> "12" -"thirteenth" -> "13" -"13th" -> "13" -"fourteenth" -> "14" -"14th" -> "14" -"fifteenth" -> "15" -"15th" -> "15" -"sixteenth" -> "16" -"16th" -> "16" -"seventeenth" -> "17" -"17th" -> "17" -"eighteenth" -> "18" -"18th" -> "18" -"nineteenth" -> "19" -"19th" -> "19" -"twentieth" -> "20" -"20th" -> "20" -"twenty-first" -> "21" -"twenty first" -> "21" -"21st" -> "21" -"twenty-second" -> "22" -"twenty second" -> "22" -"22nd" -> "22" -"twenty-third" -> "23" -"twenty third" -> "23" -"23rd" -> "23" -"twenty-fourth" -> "24" -"twenty fourth" -> "24" -"24th" -> "24" -"twenty-fifth" -> "25" -"twenty fifth" -> "25" -"25th" -> "25" -"twenty-sixth" -> "26" -"twenty sixth" -> "26" -"26th" -> "26" -"twenty-seventh" -> "27" -"twenty seventh" -> "27" -"27th" -> "27" -"twenty-eighth" -> "28" -"twenty eighth" -> "28" -"28th" -> "28" -"twenty-ninth" -> "29" -"twenty ninth" -> "29" -"29th" -> "29" -"thirtieth" -> "30" -"30th" -> "30" -"thirty-first" -> "31" -"thirty first" -> "31" -"31st" -> "31" - -int_60_to_99: -"59" FAIL -"60" OK -"61" OK -"62" OK -"63" OK -"64" OK -"65" OK -"66" OK -"67" OK -"68" OK -"69" OK -"70" OK -"71" OK -"72" OK -"73" OK -"74" OK -"75" OK -"76" OK -"77" OK -"78" OK -"79" OK -"80" OK -"81" OK -"82" OK -"83" OK -"84" OK -"85" OK -"86" OK -"87" OK -"88" OK -"89" OK -"90" OK -"91" OK -"92" OK -"93" OK -"94" OK -"95" OK -"96" OK -"97" OK -"98" OK -"99" OK -"100" FAIL - - -int_32_to_59: -"31" FAIL -"32" OK -"33" OK -"34" OK -"35" OK -"36" OK -"37" OK -"38" OK -"39" OK -"40" OK -"41" OK -"42" OK -"43" OK -"44" OK -"45" OK -"46" OK -"47" OK -"48" OK -"49" OK -"50" OK -"51" OK -"52" OK -"53" OK -"54" OK -"55" OK -"56" OK -"57" OK -"58" OK -"59" OK -"60" FAIL - -int_24_to_31: -"23" FAIL -"24" OK -"25" OK -"26" OK -"27" OK -"28" OK -"29" OK -"30" OK -"31" OK -"32" FAIL - -int_13_to_23: -"12" FAIL -"13" OK -"14" OK -"15" OK -"16" OK -"17" OK -"18" OK -"19" OK -"20" OK -"21" OK -"22" OK -"23" OK -"24" FAIL - -int_01_to_12: -"0" FAIL -"00" FAIL -"1" FAIL -"01" OK -"2" FAIL -"02" OK -"3" FAIL -"03" OK -"4" FAIL -"04" OK -"5" FAIL -"05" OK -"6" FAIL -"06" OK -"7" FAIL -"07" OK -"8" FAIL -"08" OK -"9" FAIL -"09" OK -"10" OK -"11" OK -"12" OK -"13" FAIL - -int_1_to_9: -"0" FAIL -"00" FAIL -"1" OK -"01" FAIL -"2" OK -"02" FAIL -"3" OK -"03" FAIL -"4" OK -"04" FAIL -"5" OK -"05" FAIL -"6" OK -"06" FAIL -"7" OK -"07" FAIL -"8" OK -"08" FAIL -"9" OK -"09" FAIL -"10" FAIL - -int_1_to_5: -"0" FAIL -"00" FAIL -"1" OK -"01" FAIL -"2" OK -"02" FAIL -"3" OK -"03" FAIL -"4" OK -"04" FAIL -"5" OK -"05" FAIL -"6" FAIL -"06" FAIL - -// ********** date lexer rules ********** - -JANUARY: -"january" OK -"januarys" OK -"jan" OK -"jan." OK - -FEBRUARY: -"february" OK -"februarys" OK -"feb" OK -"feb." OK - -MARCH: -"march" OK -"marches" OK -"mar" OK -"mar." OK - -APRIL: -"april" OK -"aprils" OK -"apr" OK -"apr." OK - -MAY: -"may" OK -"mays" OK - -JUNE: -"june" OK -"junes" OK -"jun" OK -"jun." OK - -JULY: -"july" OK -"julys" OK -"jul" OK -"jul." OK - -AUGUST: -"august" OK -"augusts" OK -"aug" OK -"aug." OK - -SEPTEMBER: -"september" OK -"septembers" OK -"sep" OK -"sep." OK -"sept" OK -"sept." OK - -OCTOBER: -"october" OK -"octobers" OK -"oct" OK -"oct." OK - -NOVEMBER: -"november" OK -"novembers" OK -"nov" OK -"nov." OK - -DECEMBER: -"december" OK -"decembers" OK -"dec" OK -"dec." OK - -SUNDAY: -"sunday" OK -"sundays" OK -"sun" OK -"sun." OK -"suns" OK -"suns." OK - -MONDAY: -"monday" OK -"mondays" OK -"mon" OK -"mon." OK -"mons" OK -"mons." OK - -TUESDAY: -"tuesday" OK -"tuesdays" OK -"tues" OK -"tues." OK -"tue" OK -"tue." OK - -WEDNESDAY: -"wednesday" OK -"wednesdays" OK -"wed" OK -"wed." OK -"weds" OK -"weds." OK - -THURSDAY: -"thursday" OK -"thursdays" OK -"thur" OK -"thur." OK -"thu" OK -"thu." OK -"thus" OK -"thus." OK -"thurs" OK -"thurs." OK - -FRIDAY: -"friday" OK -"fridays" OK -"fri" OK -"fri." OK -"fris" OK -"fris." OK - -SATURDAY: -"saturday" OK -"saturdays" OK -"sat" OK -"sat." OK -"sats" OK -"sats." OK -"weekend" OK - -HOUR: -"hour" OK -"hours" OK - -DAY: -"day" OK -"days" OK - -WEEK: -"week" OK -"weeks" OK - -MONTH: -"month" OK -"months" OK - -YEAR: -"year" OK -"years" OK - -TODAY:"today" OK - -NOW:"now" OK - -TOMORROW: -"tomorow" OK -"tomorrow" OK -"tommorow" OK -"tommorrow" OK - -YESTERDAY : "yesterday" OK - -// ********** time lexer rules ********** - -AM: -"am" OK -"a.m." OK -"a" OK - -PM: -"pm" OK -"p.m." OK -"p" OK - -T:"t" OK - -MILITARY_HOUR_SUFFIX:"h" OK - -MIDNIGHT: -"midnight" OK -"mid-night" OK - -NOON: -"noon" OK -"afternoon" OK -"after-noon" OK - -UTC: -"utc" OK -"gmt" OK -"z" OK - -EST: -"est" OK -"edt" OK -"et" OK - -PST: -"pst" OK -"pdt" OK -"pt" OK - -CST: -"cst" OK -"cdt" OK -"ct" OK - -MST: -"mst" OK -"mdt" OK -"mt" OK - -AKST: -"akst" OK -"akdt" OK -"akt" OK - -HAST: -"hast" OK -"hadt" OK -"hat" OK -"hst" OK - -// ********* numeric lexer rules ********** - -INT_00: "00" OK -INT_00: "0" FAIL -INT_01: "01" OK -INT_01: "1" FAIL -INT_02: "02" OK -INT_02: "2" FAIL -INT_03: "03" OK -INT_03: "3" FAIL -INT_04: "04" OK -INT_04: "4" FAIL -INT_05: "05" OK -INT_05: "6" FAIL -INT_06: "06" OK -INT_06: "6" FAIL -INT_07: "07" OK -INT_07: "7" FAIL -INT_08: "08" OK -INT_08: "8" FAIL -INT_09: "09" OK -INT_09: "9" FAIL -INT_0: "0" OK -INT_1: "1" OK -INT_2: "2" OK -INT_3: "3" OK -INT_4: "4" OK -INT_5: "5" OK -INT_6: "6" OK -INT_7: "7" OK -INT_8: "8" OK -INT_9: "9" OK -INT_10: "10" OK -INT_11: "11" OK -INT_12: "12" OK -INT_13: "13" OK -INT_14: "14" OK -INT_15: "15" OK -INT_16: "16" OK -INT_17: "17" OK -INT_18: "18" OK -INT_19: "19" OK -INT_20: "20" OK -INT_21: "21" OK -INT_22: "22" OK -INT_23: "23" OK -INT_24: "24" OK -INT_25: "25" OK -INT_26: "26" OK -INT_27: "27" OK -INT_28: "28" OK -INT_29: "29" OK -INT_30: "30" OK -INT_31: "31" OK -INT_32: "32" OK -INT_33: "33" OK -INT_34: "34" OK -INT_35: "35" OK -INT_36: "36" OK -INT_37: "37" OK -INT_38: "38" OK -INT_39: "39" OK -INT_40: "40" OK -INT_41: "41" OK -INT_42: "42" OK -INT_43: "43" OK -INT_44: "44" OK -INT_45: "45" OK -INT_46: "46" OK -INT_47: "47" OK -INT_48: "48" OK -INT_49: "49" OK -INT_50: "50" OK -INT_51: "51" OK -INT_52: "52" OK -INT_53: "53" OK -INT_54: "54" OK -INT_55: "55" OK -INT_56: "56" OK -INT_57: "57" OK -INT_58: "58" OK -INT_59: "59" OK -INT_60: "60" OK -INT_61: "61" OK -INT_62: "62" OK -INT_63: "63" OK -INT_64: "64" OK -INT_65: "65" OK -INT_66: "66" OK -INT_67: "67" OK -INT_68: "68" OK -INT_69: "69" OK -INT_70: "70" OK -INT_71: "71" OK -INT_72: "72" OK -INT_73: "73" OK -INT_74: "74" OK -INT_75: "75" OK -INT_76: "76" OK -INT_77: "77" OK -INT_78: "78" OK -INT_79: "79" OK -INT_80: "80" OK -INT_81: "81" OK -INT_82: "82" OK -INT_83: "83" OK -INT_84: "84" OK -INT_85: "85" OK -INT_86: "86" OK -INT_87: "87" OK -INT_88: "88" OK -INT_89: "89" OK -INT_90: "90" OK -INT_91: "91" OK -INT_92: "92" OK -INT_93: "93" OK -INT_94: "94" OK -INT_95: "95" OK -INT_96: "96" OK -INT_97: "97" OK -INT_98: "98" OK -INT_99: "99" OK - -ST: "st" OK -ND: "nd" OK -RD: "rd" OK -TH: "th" OK - -ONE: "one" OK -TWO: "two" OK -THREE: "three" OK -FOUR: "four" OK -FIVE: "five" OK -SIX: "six" OK -SEVEN: "seven" OK -EIGHT: "eight" OK -NINE: "nine" OK -TEN: "ten" OK -ELEVEN: "eleven" OK -TWELVE: "twelve" OK -THIRTEEN: "thirteen" OK -FOURTEEN: "fourteen" OK -FIFTEEN: "fifteen" OK -SIXTEEN: "sixteen" OK -SEVENTEEN: "seventeen" OK -EIGHTEEN: - "eighteen" OK - "eightteen" OK -NINETEEN: "nineteen" OK -TWENTY: "twenty" OK -THIRTY: "thirty" OK - -FIRST: "first" OK -SECOND: "second" OK -THIRD: "third" OK -FOURTH: "fourth" OK -FIFTH: "fifth" OK -SIXTH: "sixth" OK -SEVENTH: "seventh" OK -EIGHTH: "eighth" OK -NINTH: "ninth" OK -TENTH: "tenth" OK -ELEVENTH: "eleventh" OK -TWELFTH: "twelfth" OK -THIRTEENTH: "thirteenth" OK -FOURTEENTH: "fourteenth" OK -FIFTEENTH: "fifteenth" OK -SIXTEENTH: "sixteenth" OK -SEVENTEENTH: "seventeenth" OK -EIGHTEENTH: "eighteenth" OK -NINETEENTH: "nineteenth" OK -TWENTIETH: "twentieth" OK -THIRTIETH: "thirtieth" OK - -COLON: ":" OK -COMMA: "," OK -DASH: "-" OK -SLASH: "/" OK -DOT: "." OK -PLUS: "+" OK -SINGLE_QUOTE: "'" OK -IN: "in" OK -THE: "the" OK -AT: "at" OK -ON: "on" OK -OF: "of" OK -THIS: "this" OK -LAST: "last" OK -NEXT: "next" OK -PAST: "past" OK -COMING: "coming" OK -UPCOMING: "upcoming" OK -FROM: "from" OK -NOW: "now" OK -AGO: "ago" OK -BEFORE: "before" OK -AFTER: "after" OK - -WHITE_SPACE: -" " OK -" " OK -"\t" OK -"\t\t" OK -"\n" OK -"\n" OK -"\n\n" OK -"\r" OK -"\r\r" OK \ No newline at end of file diff --git a/src/test/gunit/DateLexer.testsuite b/src/test/gunit/DateLexer.testsuite new file mode 100644 index 00000000..7ccea6a7 --- /dev/null +++ b/src/test/gunit/DateLexer.testsuite @@ -0,0 +1,437 @@ +gunit Date; +@header{package com.joestelmach.natty.generated;} + +JANUARY: +"january" OK +"januarys" OK +"jan" OK +"jan." OK + +FEBRUARY: +"february" OK +"februarys" OK +"feb" OK +"feb." OK + +MARCH: +"march" OK +"marches" OK +"mar" OK +"mar." OK + +APRIL: +"april" OK +"aprils" OK +"apr" OK +"apr." OK + +MAY: +"may" OK +"mays" OK + +JUNE: +"june" OK +"junes" OK +"jun" OK +"jun." OK + +JULY: +"july" OK +"julys" OK +"jul" OK +"jul." OK + +AUGUST: +"august" OK +"augusts" OK +"aug" OK +"aug." OK + +SEPTEMBER: +"september" OK +"septembers" OK +"sep" OK +"sep." OK +"sept" OK +"sept." OK + +OCTOBER: +"october" OK +"octobers" OK +"oct" OK +"oct." OK + +NOVEMBER: +"november" OK +"novembers" OK +"nov" OK +"nov." OK + +DECEMBER: +"december" OK +"decembers" OK +"dec" OK +"dec." OK + +SUNDAY: +"sunday" OK +"sundays" OK +"sun" OK +"sun." OK +"suns" OK +"suns." OK + +MONDAY: +"monday" OK +"mondays" OK +"mon" OK +"mon." OK +"mons" OK +"mons." OK + +TUESDAY: +"tuesday" OK +"tuesdays" OK +"tues" OK +"tues." OK +"tue" OK +"tue." OK + +WEDNESDAY: +"wednesday" OK +"wednesdays" OK +"wed" OK +"wed." OK +"weds" OK +"weds." OK + +THURSDAY: +"thursday" OK +"thursdays" OK +"thur" OK +"thur." OK +"thu" OK +"thu." OK +"thus" OK +"thus." OK +"thurs" OK +"thurs." OK + +FRIDAY: +"friday" OK +"fridays" OK +"fri" OK +"fri." OK +"fris" OK +"fris." OK + +SATURDAY: +"saturday" OK +"saturdays" OK +"sat" OK +"sat." OK +"sats" OK +"sats." OK +"weekend" OK + +HOUR: +"hour" OK +"hours" OK + +DAY: +"day" OK +"days" OK + +WEEK: +"week" OK +"weeks" OK + +MONTH: +"month" OK +"months" OK + +YEAR: +"year" OK +"years" OK + +TODAY:"today" OK + +NOW:"now" OK + +TOMORROW: +"tomorow" OK +"tomorrow" OK +"tommorow" OK +"tommorrow" OK + +YESTERDAY : "yesterday" OK + +// ********** time lexer rules ********** + +AM: +"am" OK +"a.m." OK +"a" OK + +PM: +"pm" OK +"p.m." OK +"p" OK + +T:"t" OK + +MILITARY_HOUR_SUFFIX:"h" OK + +MIDNIGHT: +"midnight" OK +"mid-night" OK + +NOON: +"noon" OK +"afternoon" OK +"after-noon" OK + +UTC: +"utc" OK +"gmt" OK +"z" OK + +EST: +"est" OK +"edt" OK +"et" OK + +PST: +"pst" OK +"pdt" OK +"pt" OK + +CST: +"cst" OK +"cdt" OK +"ct" OK + +MST: +"mst" OK +"mdt" OK +"mt" OK + +AKST: +"akst" OK +"akdt" OK +"akt" OK + +HAST: +"hast" OK +"hadt" OK +"hat" OK +"hst" OK + +// ********* numeric lexer rules ********** + +INT_00: "00" OK +INT_00: "0" FAIL +INT_01: "01" OK +INT_01: "1" FAIL +INT_02: "02" OK +INT_02: "2" FAIL +INT_03: "03" OK +INT_03: "3" FAIL +INT_04: "04" OK +INT_04: "4" FAIL +INT_05: "05" OK +INT_05: "6" FAIL +INT_06: "06" OK +INT_06: "6" FAIL +INT_07: "07" OK +INT_07: "7" FAIL +INT_08: "08" OK +INT_08: "8" FAIL +INT_09: "09" OK +INT_09: "9" FAIL +INT_0: "0" OK +INT_1: "1" OK +INT_2: "2" OK +INT_3: "3" OK +INT_4: "4" OK +INT_5: "5" OK +INT_6: "6" OK +INT_7: "7" OK +INT_8: "8" OK +INT_9: "9" OK +INT_10: "10" OK +INT_11: "11" OK +INT_12: "12" OK +INT_13: "13" OK +INT_14: "14" OK +INT_15: "15" OK +INT_16: "16" OK +INT_17: "17" OK +INT_18: "18" OK +INT_19: "19" OK +INT_20: "20" OK +INT_21: "21" OK +INT_22: "22" OK +INT_23: "23" OK +INT_24: "24" OK +INT_25: "25" OK +INT_26: "26" OK +INT_27: "27" OK +INT_28: "28" OK +INT_29: "29" OK +INT_30: "30" OK +INT_31: "31" OK +INT_32: "32" OK +INT_33: "33" OK +INT_34: "34" OK +INT_35: "35" OK +INT_36: "36" OK +INT_37: "37" OK +INT_38: "38" OK +INT_39: "39" OK +INT_40: "40" OK +INT_41: "41" OK +INT_42: "42" OK +INT_43: "43" OK +INT_44: "44" OK +INT_45: "45" OK +INT_46: "46" OK +INT_47: "47" OK +INT_48: "48" OK +INT_49: "49" OK +INT_50: "50" OK +INT_51: "51" OK +INT_52: "52" OK +INT_53: "53" OK +INT_54: "54" OK +INT_55: "55" OK +INT_56: "56" OK +INT_57: "57" OK +INT_58: "58" OK +INT_59: "59" OK +INT_60: "60" OK +INT_61: "61" OK +INT_62: "62" OK +INT_63: "63" OK +INT_64: "64" OK +INT_65: "65" OK +INT_66: "66" OK +INT_67: "67" OK +INT_68: "68" OK +INT_69: "69" OK +INT_70: "70" OK +INT_71: "71" OK +INT_72: "72" OK +INT_73: "73" OK +INT_74: "74" OK +INT_75: "75" OK +INT_76: "76" OK +INT_77: "77" OK +INT_78: "78" OK +INT_79: "79" OK +INT_80: "80" OK +INT_81: "81" OK +INT_82: "82" OK +INT_83: "83" OK +INT_84: "84" OK +INT_85: "85" OK +INT_86: "86" OK +INT_87: "87" OK +INT_88: "88" OK +INT_89: "89" OK +INT_90: "90" OK +INT_91: "91" OK +INT_92: "92" OK +INT_93: "93" OK +INT_94: "94" OK +INT_95: "95" OK +INT_96: "96" OK +INT_97: "97" OK +INT_98: "98" OK +INT_99: "99" OK + +ST: "st" OK +ND: "nd" OK +RD: "rd" OK +TH: "th" OK + +ONE: "one" OK +TWO: "two" OK +THREE: "three" OK +FOUR: "four" OK +FIVE: "five" OK +SIX: "six" OK +SEVEN: "seven" OK +EIGHT: "eight" OK +NINE: "nine" OK +TEN: "ten" OK +ELEVEN: "eleven" OK +TWELVE: "twelve" OK +THIRTEEN: "thirteen" OK +FOURTEEN: "fourteen" OK +FIFTEEN: "fifteen" OK +SIXTEEN: "sixteen" OK +SEVENTEEN: "seventeen" OK +EIGHTEEN: + "eighteen" OK + "eightteen" OK +NINETEEN: "nineteen" OK +TWENTY: "twenty" OK +THIRTY: "thirty" OK + +FIRST: "first" OK +SECOND: "second" OK +THIRD: "third" OK +FOURTH: "fourth" OK +FIFTH: "fifth" OK +SIXTH: "sixth" OK +SEVENTH: "seventh" OK +EIGHTH: "eighth" OK +NINTH: "ninth" OK +TENTH: "tenth" OK +ELEVENTH: "eleventh" OK +TWELFTH: "twelfth" OK +THIRTEENTH: "thirteenth" OK +FOURTEENTH: "fourteenth" OK +FIFTEENTH: "fifteenth" OK +SIXTEENTH: "sixteenth" OK +SEVENTEENTH: "seventeenth" OK +EIGHTEENTH: "eighteenth" OK +NINETEENTH: "nineteenth" OK +TWENTIETH: "twentieth" OK +THIRTIETH: "thirtieth" OK + +COLON: ":" OK +COMMA: "," OK +DASH: "-" OK +SLASH: "/" OK +DOT: "." OK +PLUS: "+" OK +SINGLE_QUOTE: "'" OK +IN: "in" OK +THE: "the" OK +AT: "at" OK +ON: "on" OK +OF: "of" OK +THIS: "this" OK +LAST: "last" OK +NEXT: "next" OK +PAST: "past" OK +COMING: "coming" OK +UPCOMING: "upcoming" OK +FROM: "from" OK +NOW: "now" OK +AGO: "ago" OK +BEFORE: "before" OK +AFTER: "after" OK + +WHITE_SPACE: +" " OK +" " OK +"\t" OK +"\t\t" OK +"\n" OK +"\n" OK +"\n\n" OK +"\r" OK +"\r\r" OK \ No newline at end of file diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index c001bdfc..3df280a4 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -184,13 +184,13 @@ public void testRange() throws Exception { public static void main(String[] args) throws Exception{ String value = "golf with friends tomorrow at 10"; - value = "golf tomorrow at 9 AM at pebble beach"; + //value = "golf tomorrow at 9 AM at pebble beach"; + //value = "next wed, thurs, fri"; - String inputString = "1978-01-28"; Parser parser = new Parser(); - List groups = parser.parse(inputString); + List groups = parser.parse(value); for(DateGroup group:groups) { - System.out.println(inputString); + System.out.println(value); System.out.println(group.getSyntaxTree().toStringTree()); System.out.println("line: " + group.getLine() + ", column: " + group.getPosition()); System.out.println(group.getText()); diff --git a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java b/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java new file mode 100644 index 00000000..7781d869 --- /dev/null +++ b/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java @@ -0,0 +1,86 @@ +package com.natty.joestelmach.grammar; + +import org.junit.Test; + +public class DateTimeGrammar extends AbstractGrammarTest { + + @Test + public void date_time_alternative() throws Exception { + _ruleName = "date_time_alternative"; + + assertAST("this wed. or next at 5pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)))"); + assertAST("feb 28th or 2 days after", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 28))))))"); + assertAST("january fourth or the friday after", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 4))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 6) (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 4))))))"); + assertAST("10/10/2008 or 10/12/2008", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10) (YEAR_OF 2008))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 12) (YEAR_OF 2008))))"); + assertAST("next wed or thursday", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5)))))"); + assertAST("next wed, thurs, fri", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 6)))))"); + assertAST("next wed, thurs, or fri at 6pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 6))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)))"); + assertAST("10/10 or 12/30 or 10/15 at 5pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 30)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 15)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)))"); + assertAST("monday to friday", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 6)))))"); + assertAST("1999-12-31 to tomorrow", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 1999))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day))))"); + assertAST("now to 2010-01-01", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 01) (DAY_OF_MONTH 01) (YEAR_OF 2010))))"); + assertAST("2009-03-10 9:00 to 11:00", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 03) (DAY_OF_MONTH 10) (YEAR_OF 2009)) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00))))"); + assertAST("26 oct 10:00 am to 11:00 am", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 26)) (EXPLICIT_TIME (HOURS_OF_DAY 10) (MINUTES_OF_HOUR 00) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 11) (MINUTES_OF_HOUR 00) am)))"); + assertAST("jan 1 to 2", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 1))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 2))))"); + assertAST("16:00 nov 6 to 17:00", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 11) (DAY_OF_MONTH 6)) (EXPLICIT_TIME (HOURS_OF_DAY 16) (MINUTES_OF_HOUR 00))) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 17) (MINUTES_OF_HOUR 00))))"); + assertAST("may 2nd to 5th", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 2))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 5) (DAY_OF_MONTH 5))))"); + assertAST("6am dec 5 to 7am", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 5)) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am)) (DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 7) (MINUTES_OF_HOUR 0) am)))"); + assertAST("1/3 to 2/3", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 1) (DAY_OF_MONTH 3))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3))))"); + assertAST("2/3 to in 1 week", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 3))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 week))))"); + assertAST("first day of may to last day of may", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 1)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 5)) (EXPLICIT_SEEK (DAY_OF_MONTH 31)))))"); + } + + + @Test + public void date_time() throws Exception { + _ruleName = "date_time"; + + assertAST("seven years ago at 3pm", "(DATE_TIME (RELATIVE_DATE (SEEK < by_day 7 year)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) pm))"); + assertAST("1st oct in the year '89 1300 hours", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00)))"); + assertAST("1st oct in the year '89 at 1300 hours", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00)))"); + assertAST("1st oct in the year '89, 13:00", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00)))"); + assertAST("1st oct in the year '89,13:00", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00)))"); + assertAST("1st oct in the year '89, at 13:00", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89)) (EXPLICIT_TIME (HOURS_OF_DAY 13) (MINUTES_OF_HOUR 00)))"); + assertAST("3am on oct 1st 2010", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am))"); + assertAST("3am, october first 2010", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am))"); + assertAST("3am,october first 2010", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am))"); + assertAST("3am, on october first 2010", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am))"); + assertAST("3am october first 2010", "(DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 2010)) (EXPLICIT_TIME (HOURS_OF_DAY 3) (MINUTES_OF_HOUR 0) am))"); + assertAST("next wed. at 5pm", "(DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm))"); + assertAST("3 days after next wed", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 3 (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))))))"); + assertAST("the sunday after next wed", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 (DAY_OF_WEEK 1) (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))))))"); + assertAST("two days after today", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day)))"); + assertAST("two days from today", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day)))"); + assertAST("3 sundays after next wed", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 3 (DAY_OF_WEEK 1) (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))))))"); + assertAST("the day after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 day)))"); + assertAST("the week after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 week)))"); + assertAST("the month after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 month)))"); + assertAST("the year after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 year)))"); + assertAST("wed of the week after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 week) (EXPLICIT_SEEK (DAY_OF_WEEK 4))))"); + assertAST("the 28th of the month after next", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 2 month) (EXPLICIT_SEEK (DAY_OF_MONTH 28))))"); + assertAST("6 in the morning", "(DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am))"); + assertAST("4 in the afternoon", "(DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm))"); + assertAST("monday 6 in the morning", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) am))"); + assertAST("monday 4 in the afternoon", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 4) (MINUTES_OF_HOUR 0) pm))"); + assertAST("monday 9 in the evening", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2))) (EXPLICIT_TIME (HOURS_OF_DAY 9) (MINUTES_OF_HOUR 0) pm))"); + assertAST("this morning", "(DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 8) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am))"); + assertAST("this afternoon", "(DATE_TIME (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm))"); + assertAST("final thursday in april", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 4)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5))))"); + assertAST("final thurs in sep", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (MONTH_OF_YEAR 9)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5))))"); + assertAST("tomorrow @ noon", "(DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day)) (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm))"); + assertAST("6 hours ago", "(DATE_TIME (RELATIVE_TIME (SEEK < by_day 6 hour)))"); + assertAST("10 hrs before noon", "(DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 10 hour)))"); + assertAST("10 hr before midnight", "(DATE_TIME (RELATIVE_TIME (SEEK < by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 10 hour)))"); + assertAST("5 hours after noon", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) pm)) 5 hour)))"); + assertAST("5 hours after midnight", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (EXPLICIT_SEEK (EXPLICIT_TIME (HOURS_OF_DAY 12) (MINUTES_OF_HOUR 0) (SECONDS_OF_MINUTE 0) am)) 5 hour)))"); + assertAST("in 5 seconds", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 second)))"); + assertAST("in 5 minutes", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 minute)))"); + assertAST("in 5 hours", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day 5 hour)))"); + assertAST("4 secs from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second)))"); + assertAST("4 sec from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 second)))"); + assertAST("4 minutes from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute)))"); + assertAST("4 mins from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute)))"); + assertAST("4 min from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 minute)))"); + assertAST("4 hours from now", "(DATE_TIME (RELATIVE_TIME (SEEK > by_day (RELATIVE_DATE (SEEK > by_day 0 day)) 4 hour)))"); + } +} From 39ba3411182caab757d0c14ee3ce28b9dd35ff56 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 20 Feb 2011 17:10:18 -0500 Subject: [PATCH 029/134] adding some logging, fixing some lexer rules --- .../joestelmach/natty/generated/DateLexer.g | 42 ++++++++++--------- .../joestelmach/natty/generated/DateParser.g | 8 +++- .../joestelmach/natty/NattyTokenSource.java | 4 ++ .../java/com/joestelmach/natty/Parser.java | 27 ++++++++++-- .../java/com/joestelmach/natty/DateTest.java | 16 ++++++- src/test/resources/logging.properties | 1 + 6 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 src/test/resources/logging.properties diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index ba14ab07..25cf8728 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -38,22 +38,13 @@ YESTERDAY : 'yesterday'; // ********** time rules ********** -AT : 'at' | '@'; -AFTER : 'after'; -PAST : 'past'; -AM : 'am' | 'a.m.' | 'a'; -PM : 'pm' | 'p.m.' | 'p'; -T : 't'; - -MILITARY_HOUR_SUFFIX : 'h'; +NOON : 'noon' | 'afternoon' | 'after-noon'; MIDNIGHT : 'midnight' | 'mid-night'; -NOON : 'noon' | 'afternoon' | 'after-noon'; MORNING : 'morning'; EVENING : 'evening'; NIGHT : 'night'; -UTC : 'utc' | 'gmt' | 'z'; EST : 'est' | 'edt' | 'et'; PST : 'pst' | 'pdt' | 'pt'; CST : 'cst' | 'cdt' | 'ct'; @@ -74,15 +65,6 @@ INT_07 : '07'; INT_08 : '08'; INT_09 : '09'; INT_0 : '0'; -INT_1 : '1'; -INT_2 : '2'; -INT_3 : '3'; -INT_4 : '4'; -INT_5 : '5'; -INT_6 : '6'; -INT_7 : '7'; -INT_8 : '8'; -INT_9 : '9'; INT_10 : '10'; INT_11 : '11'; INT_12 : '12'; @@ -173,6 +155,15 @@ INT_96 : '96'; INT_97 : '97'; INT_98 : '98'; INT_99 : '99'; +INT_1 : '1'; +INT_2 : '2'; +INT_3 : '3'; +INT_4 : '4'; +INT_5 : '5'; +INT_6 : '6'; +INT_7 : '7'; +INT_8 : '8'; +INT_9 : '9'; ONE : 'one'; TWO : 'two'; @@ -234,6 +225,9 @@ DOT : '.'; PLUS : '+'; SINGLE_QUOTE : '\''; +AT : 'at' | '@'; +AFTER : 'after'; +PAST : 'past'; IN : 'in'; THE : 'the'; OR : 'or'; @@ -255,13 +249,21 @@ BEFORE : 'before'; BEGINNING : 'beginning' | 'begining'; START : 'start'; END : 'end'; + +// single character rules need to be at the bottom +AM : 'am' | 'a.m.' | 'a'; +PM : 'pm' | 'p.m.' | 'p'; +T : 't'; +UTC : 'utc' | 'gmt' | 'z'; + +MILITARY_HOUR_SUFFIX : 'h'; WHITE_SPACE : (DOT | SPACE)+ ; UNKNOWN - : UNKNOWN_CHAR + : ~(SPACE | DOT) ; fragment UNKNOWN_CHAR diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 8f80f136..4a54bcb1 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -36,9 +36,13 @@ tokens { } parse - : date_time_alternative + : empty date_time_alternative ; - + +empty + : + ; + date_time : ( (date)=>date (date_time_separator explicit_time)? diff --git a/src/main/java/com/joestelmach/natty/NattyTokenSource.java b/src/main/java/com/joestelmach/natty/NattyTokenSource.java index 141e9555..df4095a6 100644 --- a/src/main/java/com/joestelmach/natty/NattyTokenSource.java +++ b/src/main/java/com/joestelmach/natty/NattyTokenSource.java @@ -20,4 +20,8 @@ public Token nextToken() { public String getSourceName() { return "natty"; } + + public List getTokens() { + return _tokens; + } } diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 1a695ae5..4cbaf828 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -29,7 +29,7 @@ public class Parser { private TimeZone _defaultTimeZone; - private static final Logger _logger = Logger.getLogger(Parser.class.getName()); + private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); /** * Creates a new parser using the given time zone as the default @@ -88,6 +88,13 @@ public List parse(String value) { * @return */ private DateGroup singleParse(TokenStream stream) { + StringBuilder tokenString = new StringBuilder(); + for(Token token:((NattyTokenSource) stream.getTokenSource()).getTokens()) { + tokenString.append(DateParser.tokenNames[token.getType()]); + tokenString.append(" "); + } + _logger.fine("sub-token stream: " + tokenString.toString()); + DateGroup group = null; try { // parse @@ -95,6 +102,7 @@ private DateGroup singleParse(TokenStream stream) { DateParser parser = new DateParser(stream, listener); DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); + //System.out.println(tree.toStringTree()); // we only coninue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { @@ -137,19 +145,28 @@ private DateGroup singleParse(TokenStream stream) { * @return */ private List collectTokenStreams(TokenStream stream) { - // now we'll walk through the token stream and build a collection + + // walk through the token stream and build a collection // of sub token streams that represent possible date locations List currentGroup = null; List groups = new ArrayList(); Token currentToken; + StringBuilder tokenString = new StringBuilder(); while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { + if(_logger.getLevel() != null && _logger.getLevel().intValue() <= Level.FINE.intValue()) { + tokenString.append(DateParser.tokenNames[currentToken.getType()]); + tokenString.append(" "); + } + // we're currently NOT collecting for a possible date group if(currentGroup == null) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - currentGroup = new ArrayList(); - currentGroup.add(currentToken); + if(DateParser.FOLLOW_empty_in_parse159.member(currentToken.getType())) { + currentGroup = new ArrayList(); + currentGroup.add(currentToken); + } } } // we're currently collecting @@ -178,6 +195,8 @@ private List collectTokenStreams(TokenStream stream) { groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); } + _logger.fine("global token stream: " + tokenString.toString()); + return groups; } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 3df280a4..e979622c 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -4,6 +4,10 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; import junit.framework.Assert; @@ -19,11 +23,13 @@ public class DateTest extends AbstractTest { @Test public void testFormal() { validateDate("1978-01-28", 1, 28, 1978); + /* validateDate("2009-10-10", 10, 10, 2009); validateDate("1980-1-2", 1, 2, 1980); validateDate("12/12/12", 12, 12, 2012); validateDate("3/4", 3, 4, Calendar.getInstance().get(Calendar.YEAR)); validateDate("sun, 11/21/2010", 11, 21, 2010); + */ } @Test @@ -183,8 +189,16 @@ public void testRange() throws Exception { } public static void main(String[] args) throws Exception{ + ConsoleHandler handler = new ConsoleHandler(); + handler.setLevel(Level.FINEST); + Logger logger = Logger.getLogger("com.joestelmach.natty"); + logger.setLevel(Level.FINEST); + logger.addHandler(handler); + + //String value = "golf with friends tomorrow at 10"; String value = "golf with friends tomorrow at 10"; - //value = "golf tomorrow at 9 AM at pebble beach"; + value = "golf tomorrow at 9 AM at pebble beach"; + value = "beach"; //value = "next wed, thurs, fri"; Parser parser = new Parser(); diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 00000000..90dea603 --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1 @@ +.level=FINE \ No newline at end of file From 81384e11269e4a885c1c7151553b6cd77d77d6fa Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 20 Feb 2011 20:28:29 -0500 Subject: [PATCH 030/134] added the ability to search for parse locations by rule name, allow commas in conjunctions, more tests --- .../joestelmach/natty/generated/DateLexer.g | 52 ++++++----- .../joestelmach/natty/generated/DateParser.g | 18 ++-- .../joestelmach/natty/generated/DateWalker.g | 7 ++ .../java/com/joestelmach/natty/DateGroup.java | 9 +- .../com/joestelmach/natty/ParseListener.java | 86 ++++++------------- .../com/joestelmach/natty/ParseLocation.java | 4 + .../java/com/joestelmach/natty/Parser.java | 2 +- .../java/com/joestelmach/natty/DateTest.java | 14 +-- .../com/joestelmach/natty/SearchTest.java | 37 ++++++++ src/test/resources/logging.properties | 1 - 10 files changed, 131 insertions(+), 99 deletions(-) create mode 100644 src/test/java/com/joestelmach/natty/SearchTest.java delete mode 100644 src/test/resources/logging.properties diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 25cf8728..103fad4f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -2,6 +2,16 @@ lexer grammar DateLexer; @header { package com.joestelmach.natty.generated; } +@members { + private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); + + public void displayRecognitionError(String[] tokenNames, RecognitionException e) { + String hdr = getErrorHeader(e); + String msg = getErrorMessage(e, tokenNames); + _logger.fine(msg); + } +} + // ********** date rules ********** JANUARY : 'january' 's'? | 'jan' DOT?; @@ -38,13 +48,22 @@ YESTERDAY : 'yesterday'; // ********** time rules ********** -NOON : 'noon' | 'afternoon' | 'after-noon'; +AT : 'at' | '@'; +AFTER : 'after'; +PAST : 'past'; +AM : 'am' | 'a.m.' | 'a'; +PM : 'pm' | 'p.m.' | 'p'; +T : 't'; + +MILITARY_HOUR_SUFFIX : 'h'; MIDNIGHT : 'midnight' | 'mid-night'; +NOON : 'noon' | 'afternoon' | 'after-noon'; MORNING : 'morning'; EVENING : 'evening'; NIGHT : 'night'; +UTC : 'utc' | 'gmt' | 'z'; EST : 'est' | 'edt' | 'et'; PST : 'pst' | 'pdt' | 'pt'; CST : 'cst' | 'cdt' | 'ct'; @@ -65,6 +84,15 @@ INT_07 : '07'; INT_08 : '08'; INT_09 : '09'; INT_0 : '0'; +INT_1 : '1'; +INT_2 : '2'; +INT_3 : '3'; +INT_4 : '4'; +INT_5 : '5'; +INT_6 : '6'; +INT_7 : '7'; +INT_8 : '8'; +INT_9 : '9'; INT_10 : '10'; INT_11 : '11'; INT_12 : '12'; @@ -155,15 +183,6 @@ INT_96 : '96'; INT_97 : '97'; INT_98 : '98'; INT_99 : '99'; -INT_1 : '1'; -INT_2 : '2'; -INT_3 : '3'; -INT_4 : '4'; -INT_5 : '5'; -INT_6 : '6'; -INT_7 : '7'; -INT_8 : '8'; -INT_9 : '9'; ONE : 'one'; TWO : 'two'; @@ -225,9 +244,6 @@ DOT : '.'; PLUS : '+'; SINGLE_QUOTE : '\''; -AT : 'at' | '@'; -AFTER : 'after'; -PAST : 'past'; IN : 'in'; THE : 'the'; OR : 'or'; @@ -249,21 +265,13 @@ BEFORE : 'before'; BEGINNING : 'beginning' | 'begining'; START : 'start'; END : 'end'; - -// single character rules need to be at the bottom -AM : 'am' | 'a.m.' | 'a'; -PM : 'pm' | 'p.m.' | 'p'; -T : 't'; -UTC : 'utc' | 'gmt' | 'z'; - -MILITARY_HOUR_SUFFIX : 'h'; WHITE_SPACE : (DOT | SPACE)+ ; UNKNOWN - : ~(SPACE | DOT) + : UNKNOWN_CHAR ; fragment UNKNOWN_CHAR diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 4a54bcb1..ad3e50d7 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -35,6 +35,16 @@ tokens { package com.joestelmach.natty.generated; } +@members { + private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); + + public void displayRecognitionError(String[] tokenNames, RecognitionException e) { + String hdr = getErrorHeader(e); + String msg = getErrorMessage(e, tokenNames); + _logger.fine(msg); + } +} + parse : empty date_time_alternative ; @@ -116,7 +126,7 @@ date_time_alternative ; conjunction - : WHITE_SPACE (AND | OR | TO | THROUGH) WHITE_SPACE + : COMMA? WHITE_SPACE (AND | OR | TO | THROUGH) WHITE_SPACE ; alternative_day_of_month_list @@ -145,14 +155,10 @@ alternative_day_of_month_list ; alternative_day_of_week_list - : alternative_direction WHITE_SPACE day_of_week (day_of_week_list_separator day_of_week)+ (date_time_separator explicit_time)? + : alternative_direction WHITE_SPACE day_of_week (conjunction day_of_week)+ (date_time_separator explicit_time)? -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?)+ ; -day_of_week_list_separator - : COMMA (WHITE_SPACE | conjunction) | conjunction - ; - alternative_direction : NEXT -> DIRECTION[">"] SEEK_BY["by_week"] INT["1"] | LAST -> DIRECTION["<"] SEEK_BY["by_week"] INT["1"] diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 89c76e37..7c73c562 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -9,6 +9,13 @@ options { @members { private com.joestelmach.natty.WalkerState _walkerState = new com.joestelmach.natty.WalkerState(); + private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); + + public void displayRecognitionError(String[] tokenNames, RecognitionException e) { + String hdr = getErrorHeader(e); + String msg = getErrorMessage(e, tokenNames); + _logger.fine(msg); + } public com.joestelmach.natty.WalkerState getState() { return _walkerState; diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index eb9f137b..45c0eaf6 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; import org.antlr.runtime.tree.Tree; @@ -15,7 +16,7 @@ public class DateGroup { private String _text; private int _line; private int _position; - private List _parseLocations; + private Map> _parseLocations; private Tree _syntaxTree; public DateGroup() { @@ -49,12 +50,12 @@ public int getPosition() { public void setPosition(int position) { _position = position; } + - public List getParseLocations() { + public Map> getParseLocations() { return _parseLocations; } - - public void setParseLocations(List parseLocations) { + public void setParseLocations(Map> parseLocations) { _parseLocations = parseLocations; } diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index 26849a01..18d1095b 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Stack; @@ -18,51 +17,17 @@ */ public class ParseListener extends BlankDebugEventListener { - private static final Map INTERESTING_RULES; - - static { - INTERESTING_RULES = new LinkedHashMap(); - INTERESTING_RULES.put("global_date_prefix", "date prefix"); - INTERESTING_RULES.put("relative_date", "relative date"); - INTERESTING_RULES.put("relaxed_date", "relaxed date"); - INTERESTING_RULES.put("formal_date", "formal date"); - INTERESTING_RULES.put("explicit_date", "explicit date"); - INTERESTING_RULES.put("relaxed_day_of_month", "day"); - INTERESTING_RULES.put("relaxed_month", "month"); - INTERESTING_RULES.put("relaxed_year", "year"); - INTERESTING_RULES.put("formal_month_of_year", "month"); - INTERESTING_RULES.put("formal_day_of_month", "day"); - INTERESTING_RULES.put("formal_year", "year"); - INTERESTING_RULES.put("relative_prefix", "relative prefix"); - INTERESTING_RULES.put("implicit_prefix", "implicit prefix"); - INTERESTING_RULES.put("relative_suffix", "relative suffix"); - INTERESTING_RULES.put("relative_target", "relative target"); - INTERESTING_RULES.put("relative_date_span", "span"); - INTERESTING_RULES.put("relative_occurrence_index", "relative occurrence index"); - INTERESTING_RULES.put("named_relative_date", "named relative date"); - INTERESTING_RULES.put("day_of_week", "weekday"); - INTERESTING_RULES.put("date", "date"); - INTERESTING_RULES.put("date_time_alternative", "alternative"); - INTERESTING_RULES.put("date_time", "date_time"); - INTERESTING_RULES.put("alternative_direction", "direction"); - INTERESTING_RULES.put("hours", "hours"); - INTERESTING_RULES.put("minutes", "minutes"); - INTERESTING_RULES.put("meridian_indicator", "am/pm"); - INTERESTING_RULES.put("time_zone", "zone"); - INTERESTING_RULES.put("time", "time"); - } - private int backtracking = 0; private Map>> _ruleMap; - private List _locations; + private Map> _locations; private ParseLocation _dateGroupLocation; public ParseListener() { _ruleMap = new HashMap>>(); - _locations = new ArrayList(); + _locations = new HashMap>(); } - public List getLocations() { + public Map> getLocations() { return _locations; } @@ -98,32 +63,33 @@ public void exitRule(String filename, String ruleName) { if(tokenList.size() > 0) { boolean isAlternative = ruleName.equals("date_time_alternative"); - boolean isInteresting = INTERESTING_RULES.keySet().contains(ruleName); - if(isAlternative || isInteresting) { - StringBuilder builder = new StringBuilder(); - for(Token token:tokenList) { - builder.append(token.getText()); - } - String text = builder.toString(); - int line = tokenList.get(0).getLine(); - int start = tokenList.get(0).getCharPositionInLine(); - int end = start + text.length(); + StringBuilder builder = new StringBuilder(); + for(Token token:tokenList) { + builder.append(token.getText()); + } + String text = builder.toString(); + int line = tokenList.get(0).getLine(); + int start = tokenList.get(0).getCharPositionInLine(); + int end = start + text.length(); - ParseLocation location = new ParseLocation(); - location.setRuleName(ruleName); - location.setText(text); - location.setLine(line); - location.setStart(start); - location.setEnd(end); + ParseLocation location = new ParseLocation(); + location.setRuleName(ruleName); + location.setText(text); + location.setLine(line); + location.setStart(start); + location.setEnd(end); - if(isAlternative) { - _dateGroupLocation = location; - } + if(isAlternative) { + _dateGroupLocation = location; + } - if(INTERESTING_RULES.keySet().contains(ruleName)) { - _locations.add(location); - } + List list = _locations.get(location.getRuleName()); + if(list == null) { + list = new ArrayList(); + _locations.put(location.getRuleName(), list); } + + list.add(location); } } diff --git a/src/main/java/com/joestelmach/natty/ParseLocation.java b/src/main/java/com/joestelmach/natty/ParseLocation.java index d604c338..3636d024 100644 --- a/src/main/java/com/joestelmach/natty/ParseLocation.java +++ b/src/main/java/com/joestelmach/natty/ParseLocation.java @@ -46,4 +46,8 @@ public int getEnd() { public void setEnd(int end) { _end = end; } + + public String toString() { + return _text; + } } diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 4cbaf828..b4be27a3 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -163,7 +163,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse159.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse165.member(currentToken.getType())) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index e979622c..18d121c7 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Level; -import java.util.logging.LogManager; import java.util.logging.Logger; import junit.framework.Assert; @@ -190,16 +189,13 @@ public void testRange() throws Exception { public static void main(String[] args) throws Exception{ ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(Level.FINEST); + handler.setLevel(Level.ALL); Logger logger = Logger.getLogger("com.joestelmach.natty"); logger.setLevel(Level.FINEST); logger.addHandler(handler); - //String value = "golf with friends tomorrow at 10"; String value = "golf with friends tomorrow at 10"; value = "golf tomorrow at 9 AM at pebble beach"; - value = "beach"; - //value = "next wed, thurs, fri"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -209,6 +205,14 @@ public static void main(String[] args) throws Exception{ System.out.println("line: " + group.getLine() + ", column: " + group.getPosition()); System.out.println(group.getText()); System.out.println(group.getDates()); + + System.out.print("conjunctions: "); + List conjunctionLocations = group.getParseLocations().get("conjunction"); + if(conjunctionLocations != null) { + for(ParseLocation location:conjunctionLocations) { + System.out.print(location.getText() + " "); + } + } } } } diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java new file mode 100644 index 00000000..5e8691ed --- /dev/null +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -0,0 +1,37 @@ +package com.joestelmach.natty; + +import java.text.DateFormat; +import java.util.Date; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; + +public class SearchTest extends AbstractTest { + + @Test + public void test() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("2/20/2011"); + CalendarSource.setBaseDate(reference); + + Parser parser = new Parser(); + List groups = parser.parse("golf tomorrow at 9 AM at pebble beach"); + Assert.assertEquals(1, groups.size()); + DateGroup group = groups.get(0); + Assert.assertEquals(1, group.getLine()); + Assert.assertEquals(5, group.getPosition()); + Assert.assertEquals(1, group.getDates().size()); + validateDate(group.getDates().get(0), 2, 21, 2011); + validateTime(group.getDates().get(0), 9, 0, 0); + + groups = parser.parse("golf with friends tomorrow at 10 "); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + Assert.assertEquals(1, group.getLine()); + Assert.assertEquals(18, group.getPosition()); + Assert.assertEquals(1, group.getDates().size()); + validateDate(group.getDates().get(0), 2, 21, 2011); + validateTime(group.getDates().get(0), 10, 0, 0); + } +} diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties deleted file mode 100644 index 90dea603..00000000 --- a/src/test/resources/logging.properties +++ /dev/null @@ -1 +0,0 @@ -.level=FINE \ No newline at end of file From 9db3b8627a4a9dfb8dc71e8c87d6d212255ca4dc Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 21 Feb 2011 22:09:23 -0500 Subject: [PATCH 031/134] split numeric rules out into a separate grammar, more tests --- pom.xml | 2 + .../joestelmach/natty/generated/DateParser.g | 189 +----------------- .../natty/generated/imports/NumericRules.g | 188 +++++++++++++++++ .../java/com/joestelmach/natty/Parser.java | 4 +- .../java/com/joestelmach/natty/DateTest.java | 5 +- .../com/joestelmach/natty/SearchTest.java | 19 ++ ...eGrammar.java => DateTimeGrammarTest.java} | 2 +- 7 files changed, 222 insertions(+), 187 deletions(-) create mode 100644 src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g rename src/test/java/com/natty/joestelmach/grammar/{DateTimeGrammar.java => DateTimeGrammarTest.java} (99%) diff --git a/pom.xml b/pom.xml index 17d38bd7..7c04d779 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,7 @@ com/joestelmach/natty/generated/DateParser.g + com/joestelmach/natty/generated/imports/NumericRules.g @@ -132,6 +133,7 @@ com/joestelmach/natty/generated/DateLexer.g com/joestelmach/natty/generated/DateParser.g + com/joestelmach/natty/generated/imports/NumericRules.g diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index ad3e50d7..6c9efe46 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -5,6 +5,8 @@ options { output=AST; } +import NumericRules; + tokens { INT; MONTH_OF_YEAR; @@ -155,10 +157,14 @@ alternative_day_of_month_list ; alternative_day_of_week_list - : alternative_direction WHITE_SPACE day_of_week (conjunction day_of_week)+ (date_time_separator explicit_time)? + : alternative_direction WHITE_SPACE day_of_week (day_of_week_list_separator day_of_week)+ (date_time_separator explicit_time)? -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?)+ ; +day_of_week_list_separator + : COMMA (WHITE_SPACE | conjunction) | conjunction + ; + alternative_direction : NEXT -> DIRECTION[">"] SEEK_BY["by_week"] INT["1"] | LAST -> DIRECTION["<"] SEEK_BY["by_week"] INT["1"] @@ -654,183 +660,4 @@ time_zone_abbreviation | MST -> ZONE["America/Denver"] | AKST -> ZONE["America/Anchorage"] | HAST -> ZONE["Pacific/Honolulu"] - ; - -// ********** numeric rules ********** - -// a number between 00 and 59 inclusive, with a mandatory 0 prefix before numbers 0-9 -int_00_to_59_mandatory_prefix - : (INT_00 - | int_01_to_12 - | int_13_to_23 - | int_24_to_31 - | int_32_to_59) -> INT[$int_00_to_59_mandatory_prefix.text] - ; - -// a number between 00 and 99 inclusive, with a mandatory 0 prefix before numbers 0-9 -int_00_to_99_mandatory_prefix - : (int_00_to_59_mandatory_prefix | int_60_to_99) - -> INT[$int_00_to_99_mandatory_prefix.text] - ; - -// a number between 1 and 12 inclusive, with an optional 0 prefix before numbers 1-9 -int_01_to_12_optional_prefix - : (int_1_to_9 | int_01_to_12) -> INT[$int_01_to_12_optional_prefix.text] - ; - -// a number between 0 and 23 inclusive, with an optional 0 prefix before numbers 0-9 -int_00_to_23_optional_prefix - : (INT_00 - | INT_0 - | int_1_to_9 - | int_01_to_12 - | int_13_to_23) -> INT[$int_00_to_23_optional_prefix.text] - ; - -// a number between 1 and 31 inclusive, with an optional 0 prefix before numbers 1-9 -int_01_to_31_optional_prefix - : (int_01_to_12 - | int_1_to_9 - | int_13_to_23 - | int_24_to_31) -> INT[$int_01_to_31_optional_prefix.text] - ; - -// a number with exactly four digits -int_four_digits - : int_00_to_99_mandatory_prefix int_00_to_99_mandatory_prefix - -> INT[$int_four_digits.text] - ; - -// a number between one and thirty-one either spelled-out, or as an -// integer with an optional 0 prefix for numbers betwen 1 and 9 -spelled_or_int_01_to_31_optional_prefix - : int_01_to_31_optional_prefix - | spelled_one_to_thirty_one - ; - -// a number between 1 and 9999 either spelled-out, or as an -// integer with an optional 0 prefix for numbers betwen 1 and 9 -spelled_or_int_optional_prefix - : spelled_one_to_thirty_one // TODO expand this spelled range to at least ninety-nine - | ((int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99) - (int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? - -> INT[$spelled_or_int_optional_prefix.text]) - ; - -// a spelled number between one and thirty-one (one, two, etc.) -spelled_one_to_thirty_one - : ONE -> INT["1"] - | TWO -> INT["2"] - | THREE -> INT["3"] - | FOUR -> INT["4"] - | FIVE -> INT["5"] - | SIX -> INT["6"] - | SEVEN -> INT["7"] - | EIGHT -> INT["8"] - | NINE -> INT["9"] - | TEN -> INT["10"] - | ELEVEN -> INT["11"] - | TWELVE -> INT["12"] - | THIRTEEN -> INT["13"] - | FOURTEEN -> INT["14"] - | FIFTEEN -> INT["15"] - | SIXTEEN -> INT["16"] - | SEVENTEEN -> INT["17"] - | EIGHTEEN -> INT["18"] - | NINETEEN -> INT["19"] - | (TWENTY WHITE_SPACE ONE)=> TWENTY WHITE_SPACE ONE -> INT["21"] - | TWENTY DASH? ONE -> INT["21"] - | (TWENTY WHITE_SPACE TWO)=> TWENTY WHITE_SPACE TWO -> INT["22"] - | TWENTY DASH? TWO -> INT["22"] - | (TWENTY WHITE_SPACE THREE)=> TWENTY WHITE_SPACE THREE -> INT["23"] - | TWENTY DASH? THREE -> INT["23"] - | (TWENTY WHITE_SPACE FOUR)=> TWENTY WHITE_SPACE FOUR -> INT["24"] - | TWENTY DASH? FOUR -> INT["24"] - | (TWENTY WHITE_SPACE FIVE)=> TWENTY WHITE_SPACE FIVE -> INT["25"] - | TWENTY DASH? FIVE -> INT["25"] - | (TWENTY WHITE_SPACE SIX)=> TWENTY WHITE_SPACE SIX -> INT["26"] - | TWENTY DASH? SIX -> INT["26"] - | (TWENTY WHITE_SPACE SEVEN)=> TWENTY WHITE_SPACE SEVEN -> INT["27"] - | TWENTY DASH? SEVEN -> INT["27"] - | (TWENTY WHITE_SPACE EIGHT)=> TWENTY WHITE_SPACE EIGHT -> INT["28"] - | TWENTY DASH? EIGHT -> INT["28"] - | (TWENTY WHITE_SPACE NINE)=> TWENTY WHITE_SPACE NINE -> INT["29"] - | TWENTY DASH? NINE -> INT["29"] - | TWENTY -> INT["20"] - | (THIRTY WHITE_SPACE ONE)=> THIRTY WHITE_SPACE ONE -> INT["31"] - | THIRTY DASH? ONE -> INT["31"] - | THIRTY -> INT["30"] - ; - -// a spelled number in sequence between first and thirty-first -spelled_first_to_thirty_first - : (FIRST | INT_1 ST) -> INT["1"] - | (SECOND | INT_2 ND) -> INT["2"] - | (THIRD | INT_3 RD) -> INT["3"] - | (FOURTH | INT_4 TH) -> INT["4"] - | (FIFTH | INT_5 TH) -> INT["5"] - | (SIXTH | INT_6 TH) -> INT["6"] - | (SEVENTH | INT_7 TH) -> INT["7"] - | (EIGHTH | INT_8 TH) -> INT["8"] - | (NINTH | INT_9 TH) -> INT["9"] - | (TENTH | INT_10 TH) -> INT["10"] - | (ELEVENTH | INT_11 TH) -> INT["11"] - | (TWELFTH | INT_12 TH) -> INT["12"] - | (THIRTEENTH | INT_13 TH) -> INT["13"] - | (FOURTEENTH | INT_14 TH) -> INT["14"] - | (FIFTEENTH | INT_15 TH) -> INT["15"] - | (SIXTEENTH | INT_16 TH) -> INT["16"] - | (SEVENTEENTH | INT_17 TH) -> INT["17"] - | (EIGHTEENTH | INT_18 TH) -> INT["18"] - | (NINETEENTH | INT_19 TH) -> INT["19"] - | (TWENTIETH | INT_20 TH) -> INT["20"] - | ((TWENTY (DASH | WHITE_SPACE)? FIRST) | INT_21 ST) -> INT["21"] - | ((TWENTY (DASH | WHITE_SPACE)? SECOND) | INT_22 ND) -> INT["22"] - | ((TWENTY (DASH | WHITE_SPACE)? THIRD) | INT_23 RD) -> INT["23"] - | ((TWENTY (DASH | WHITE_SPACE)? FOURTH) | INT_24 TH) -> INT["24"] - | ((TWENTY (DASH | WHITE_SPACE)? FIFTH) | INT_25 TH) -> INT["25"] - | ((TWENTY (DASH | WHITE_SPACE)? SIXTH) | INT_26 TH) -> INT["26"] - | ((TWENTY (DASH | WHITE_SPACE)? SEVENTH) | INT_27 TH) -> INT["27"] - | ((TWENTY (DASH | WHITE_SPACE)? EIGHTH) | INT_28 TH) -> INT["28"] - | ((TWENTY (DASH | WHITE_SPACE)? NINTH) | INT_29 TH) -> INT["29"] - | (THIRTIETH | INT_30 TH) -> INT["30"] - | ((THIRTY (DASH | WHITE_SPACE)? FIRST) | INT_31 ST) -> INT["31"] - ; - -int_60_to_99 - : INT_60 | INT_61 | INT_62 | INT_63 | INT_64 | INT_65 | INT_66 | INT_67 | INT_68 - | INT_69 | INT_70 | INT_71 | INT_72 | INT_73 | INT_74 | INT_75 | INT_76 | INT_77 - | INT_78 | INT_79 | INT_80 | INT_81 | INT_82 | INT_83 | INT_84 | INT_85 | INT_86 - | INT_87 | INT_88 | INT_89 | INT_90 | INT_91 | INT_92 | INT_93 | INT_94 | INT_95 - | INT_96 | INT_97 | INT_98 | INT_99 - ; - -int_32_to_59 - : INT_32 | INT_33 | INT_34 | INT_35 | INT_36 | INT_37 | INT_38 | INT_39 | INT_40 - | INT_41 | INT_42 | INT_43 | INT_44 | INT_45 | INT_46 | INT_47 | INT_48 | INT_49 - | INT_50 | INT_51 | INT_52 | INT_53 | INT_54 | INT_55 | INT_56 | INT_57 | INT_58 - | INT_59 - ; - -int_24_to_31 - : INT_24 | INT_25 | INT_26 | INT_27 | INT_28 | INT_29 | INT_30 | INT_31 - ; - -int_13_to_23 - : INT_13 | INT_14 | INT_15 | INT_16 | INT_17 | INT_18 | INT_19 | INT_20 | INT_21 - | INT_22 | INT_23 - ; - -int_01_to_12 - : INT_01 | INT_02 | INT_03 | INT_04 | INT_05 | INT_06 | INT_07 | INT_08 | INT_09 - | INT_10 | INT_11 | INT_12 - ; - -int_1_to_9 - : INT_1 | INT_2 | INT_3 | INT_4 | INT_5 | INT_6 | INT_7 | INT_8 | INT_9 - ; - -int_1_to_5 - : INT_1 | INT_2 | INT_3 | INT_4 | INT_5 - ; - + ; \ No newline at end of file diff --git a/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g new file mode 100644 index 00000000..7d64a32d --- /dev/null +++ b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g @@ -0,0 +1,188 @@ +parser grammar NumericRules; + +options { + output=AST; +} + +tokens { + INT; +} + +// ********** numeric rules ********** + +// a number between 00 and 59 inclusive, with a mandatory 0 prefix before numbers 0-9 +int_00_to_59_mandatory_prefix + : (INT_00 + | int_01_to_12 + | int_13_to_23 + | int_24_to_31 + | int_32_to_59) -> INT[$int_00_to_59_mandatory_prefix.text] + ; + +// a number between 00 and 99 inclusive, with a mandatory 0 prefix before numbers 0-9 +int_00_to_99_mandatory_prefix + : (int_00_to_59_mandatory_prefix | int_60_to_99) + -> INT[$int_00_to_99_mandatory_prefix.text] + ; + +// a number between 1 and 12 inclusive, with an optional 0 prefix before numbers 1-9 +int_01_to_12_optional_prefix + : (int_1_to_9 | int_01_to_12) -> INT[$int_01_to_12_optional_prefix.text] + ; + +// a number between 0 and 23 inclusive, with an optional 0 prefix before numbers 0-9 +int_00_to_23_optional_prefix + : (INT_00 + | INT_0 + | int_1_to_9 + | int_01_to_12 + | int_13_to_23) -> INT[$int_00_to_23_optional_prefix.text] + ; + +// a number between 1 and 31 inclusive, with an optional 0 prefix before numbers 1-9 +int_01_to_31_optional_prefix + : (int_01_to_12 + | int_1_to_9 + | int_13_to_23 + | int_24_to_31) -> INT[$int_01_to_31_optional_prefix.text] + ; + +// a number with exactly four digits +int_four_digits + : int_00_to_99_mandatory_prefix int_00_to_99_mandatory_prefix + -> INT[$int_four_digits.text] + ; + +// a number between one and thirty-one either spelled-out, or as an +// integer with an optional 0 prefix for numbers betwen 1 and 9 +spelled_or_int_01_to_31_optional_prefix + : int_01_to_31_optional_prefix + | spelled_one_to_thirty_one + ; + +// a number between 1 and 9999 either spelled-out, or as an +// integer with an optional 0 prefix for numbers betwen 1 and 9 +spelled_or_int_optional_prefix + : spelled_one_to_thirty_one // TODO expand this spelled range to at least ninety-nine + | ((int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99) + (int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? + -> INT[$spelled_or_int_optional_prefix.text]) + ; + +// a spelled number between one and thirty-one (one, two, etc.) +spelled_one_to_thirty_one + : ONE -> INT["1"] + | TWO -> INT["2"] + | THREE -> INT["3"] + | FOUR -> INT["4"] + | FIVE -> INT["5"] + | SIX -> INT["6"] + | SEVEN -> INT["7"] + | EIGHT -> INT["8"] + | NINE -> INT["9"] + | TEN -> INT["10"] + | ELEVEN -> INT["11"] + | TWELVE -> INT["12"] + | THIRTEEN -> INT["13"] + | FOURTEEN -> INT["14"] + | FIFTEEN -> INT["15"] + | SIXTEEN -> INT["16"] + | SEVENTEEN -> INT["17"] + | EIGHTEEN -> INT["18"] + | NINETEEN -> INT["19"] + | (TWENTY WHITE_SPACE ONE)=> TWENTY WHITE_SPACE ONE -> INT["21"] + | TWENTY DASH? ONE -> INT["21"] + | (TWENTY WHITE_SPACE TWO)=> TWENTY WHITE_SPACE TWO -> INT["22"] + | TWENTY DASH? TWO -> INT["22"] + | (TWENTY WHITE_SPACE THREE)=> TWENTY WHITE_SPACE THREE -> INT["23"] + | TWENTY DASH? THREE -> INT["23"] + | (TWENTY WHITE_SPACE FOUR)=> TWENTY WHITE_SPACE FOUR -> INT["24"] + | TWENTY DASH? FOUR -> INT["24"] + | (TWENTY WHITE_SPACE FIVE)=> TWENTY WHITE_SPACE FIVE -> INT["25"] + | TWENTY DASH? FIVE -> INT["25"] + | (TWENTY WHITE_SPACE SIX)=> TWENTY WHITE_SPACE SIX -> INT["26"] + | TWENTY DASH? SIX -> INT["26"] + | (TWENTY WHITE_SPACE SEVEN)=> TWENTY WHITE_SPACE SEVEN -> INT["27"] + | TWENTY DASH? SEVEN -> INT["27"] + | (TWENTY WHITE_SPACE EIGHT)=> TWENTY WHITE_SPACE EIGHT -> INT["28"] + | TWENTY DASH? EIGHT -> INT["28"] + | (TWENTY WHITE_SPACE NINE)=> TWENTY WHITE_SPACE NINE -> INT["29"] + | TWENTY DASH? NINE -> INT["29"] + | TWENTY -> INT["20"] + | (THIRTY WHITE_SPACE ONE)=> THIRTY WHITE_SPACE ONE -> INT["31"] + | THIRTY DASH? ONE -> INT["31"] + | THIRTY -> INT["30"] + ; + +// a spelled number in sequence between first and thirty-first +spelled_first_to_thirty_first + : (FIRST | INT_1 ST) -> INT["1"] + | (SECOND | INT_2 ND) -> INT["2"] + | (THIRD | INT_3 RD) -> INT["3"] + | (FOURTH | INT_4 TH) -> INT["4"] + | (FIFTH | INT_5 TH) -> INT["5"] + | (SIXTH | INT_6 TH) -> INT["6"] + | (SEVENTH | INT_7 TH) -> INT["7"] + | (EIGHTH | INT_8 TH) -> INT["8"] + | (NINTH | INT_9 TH) -> INT["9"] + | (TENTH | INT_10 TH) -> INT["10"] + | (ELEVENTH | INT_11 TH) -> INT["11"] + | (TWELFTH | INT_12 TH) -> INT["12"] + | (THIRTEENTH | INT_13 TH) -> INT["13"] + | (FOURTEENTH | INT_14 TH) -> INT["14"] + | (FIFTEENTH | INT_15 TH) -> INT["15"] + | (SIXTEENTH | INT_16 TH) -> INT["16"] + | (SEVENTEENTH | INT_17 TH) -> INT["17"] + | (EIGHTEENTH | INT_18 TH) -> INT["18"] + | (NINETEENTH | INT_19 TH) -> INT["19"] + | (TWENTIETH | INT_20 TH) -> INT["20"] + | ((TWENTY (DASH | WHITE_SPACE)? FIRST) | INT_21 ST) -> INT["21"] + | ((TWENTY (DASH | WHITE_SPACE)? SECOND) | INT_22 ND) -> INT["22"] + | ((TWENTY (DASH | WHITE_SPACE)? THIRD) | INT_23 RD) -> INT["23"] + | ((TWENTY (DASH | WHITE_SPACE)? FOURTH) | INT_24 TH) -> INT["24"] + | ((TWENTY (DASH | WHITE_SPACE)? FIFTH) | INT_25 TH) -> INT["25"] + | ((TWENTY (DASH | WHITE_SPACE)? SIXTH) | INT_26 TH) -> INT["26"] + | ((TWENTY (DASH | WHITE_SPACE)? SEVENTH) | INT_27 TH) -> INT["27"] + | ((TWENTY (DASH | WHITE_SPACE)? EIGHTH) | INT_28 TH) -> INT["28"] + | ((TWENTY (DASH | WHITE_SPACE)? NINTH) | INT_29 TH) -> INT["29"] + | (THIRTIETH | INT_30 TH) -> INT["30"] + | ((THIRTY (DASH | WHITE_SPACE)? FIRST) | INT_31 ST) -> INT["31"] + ; + +int_60_to_99 + : INT_60 | INT_61 | INT_62 | INT_63 | INT_64 | INT_65 | INT_66 | INT_67 | INT_68 + | INT_69 | INT_70 | INT_71 | INT_72 | INT_73 | INT_74 | INT_75 | INT_76 | INT_77 + | INT_78 | INT_79 | INT_80 | INT_81 | INT_82 | INT_83 | INT_84 | INT_85 | INT_86 + | INT_87 | INT_88 | INT_89 | INT_90 | INT_91 | INT_92 | INT_93 | INT_94 | INT_95 + | INT_96 | INT_97 | INT_98 | INT_99 + ; + +int_32_to_59 + : INT_32 | INT_33 | INT_34 | INT_35 | INT_36 | INT_37 | INT_38 | INT_39 | INT_40 + | INT_41 | INT_42 | INT_43 | INT_44 | INT_45 | INT_46 | INT_47 | INT_48 | INT_49 + | INT_50 | INT_51 | INT_52 | INT_53 | INT_54 | INT_55 | INT_56 | INT_57 | INT_58 + | INT_59 + ; + +int_24_to_31 + : INT_24 | INT_25 | INT_26 | INT_27 | INT_28 | INT_29 | INT_30 | INT_31 + ; + +int_13_to_23 + : INT_13 | INT_14 | INT_15 | INT_16 | INT_17 | INT_18 | INT_19 | INT_20 | INT_21 + | INT_22 | INT_23 + ; + +int_01_to_12 + : INT_01 | INT_02 | INT_03 | INT_04 | INT_05 | INT_06 | INT_07 | INT_08 | INT_09 + | INT_10 | INT_11 | INT_12 + ; + +int_1_to_9 + : INT_1 | INT_2 | INT_3 | INT_4 | INT_5 | INT_6 | INT_7 | INT_8 | INT_9 + ; + +int_1_to_5 + : INT_1 | INT_2 | INT_3 | INT_4 | INT_5 + ; + \ No newline at end of file diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index b4be27a3..ff3af278 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -102,7 +102,7 @@ private DateGroup singleParse(TokenStream stream) { DateParser parser = new DateParser(stream, listener); DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); - //System.out.println(tree.toStringTree()); + _logger.fine("AST: " + tree.toStringTree()); // we only coninue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { @@ -163,7 +163,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse165.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse171.member(currentToken.getType())) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 18d121c7..30eb2b26 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -187,15 +187,14 @@ public void testRange() throws Exception { validateDate(dates.get(1), 9, 30, 2012); } - public static void main(String[] args) throws Exception{ + public static void main(String[] args) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.ALL); Logger logger = Logger.getLogger("com.joestelmach.natty"); logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "golf with friends tomorrow at 10"; - value = "golf tomorrow at 9 AM at pebble beach"; + String value = "golf tomorrow at 9 AM at pebble beach"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 5e8691ed..3424d04c 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -33,5 +33,24 @@ public void test() throws Exception { Assert.assertEquals(1, group.getDates().size()); validateDate(group.getDates().get(0), 2, 21, 2011); validateTime(group.getDates().get(0), 10, 0, 0); + + parser = new Parser(); + groups = parser.parse("golf with freinds tomorrow at 9 or Thursday at 10 am"); + Assert.assertEquals(1, groups.size()); + List dates = groups.get(0).getDates(); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 21, 2011); + validateTime(dates.get(0), 9, 0, 0); + validateDate(dates.get(1), 2, 24, 2011); + validateTime(dates.get(1), 10, 0, 0); + + groups = parser.parse("golf with friends tomorrow at 9 or Thursday at 10"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 21, 2011); + validateTime(dates.get(0), 9, 0, 0); + validateDate(dates.get(1), 2, 24, 2011); + validateTime(dates.get(1), 10, 0, 0); } } diff --git a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java b/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java rename to src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java index 7781d869..06123704 100644 --- a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammar.java +++ b/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java @@ -2,7 +2,7 @@ import org.junit.Test; -public class DateTimeGrammar extends AbstractGrammarTest { +public class DateTimeGrammarTest extends AbstractGrammarTest { @Test public void date_time_alternative() throws Exception { From 2ea15a3770438dfba90ab07aa5ee612b7cdda604 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 23 Feb 2011 23:09:31 -0500 Subject: [PATCH 032/134] guard against antlr indexOutOfBoundsException while logging recognition errors. bailing out of tree walk if an error occurs --- .../com/joestelmach/natty/generated/DateLexer.g | 8 ++++---- .../com/joestelmach/natty/generated/DateParser.g | 8 ++++---- .../com/joestelmach/natty/generated/DateWalker.g | 13 +++++++++---- .../com/joestelmach/natty/generated/TreeRewrite.g | 10 ++++++++++ .../java/com/joestelmach/natty/CalendarSource.java | 2 +- .../java/com/joestelmach/natty/WalkerState.java | 7 +++++++ src/test/java/com/joestelmach/natty/DateTest.java | 3 ++- src/test/java/com/joestelmach/natty/SearchTest.java | 6 ++++++ .../natty}/grammar/AbstractGrammarTest.java | 2 +- .../natty}/grammar/DateGrammarTest.java | 2 +- .../natty}/grammar/DateTimeGrammarTest.java | 2 +- .../natty}/grammar/NumericGrammarTest.java | 2 +- .../natty}/grammar/TimeGrammarTest.java | 2 +- 13 files changed, 48 insertions(+), 19 deletions(-) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/AbstractGrammarTest.java (98%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/DateGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/DateTimeGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/NumericGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/TimeGrammarTest.java (99%) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 103fad4f..08c3653f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -5,10 +5,10 @@ lexer grammar DateLexer; @members { private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - public void displayRecognitionError(String[] tokenNames, RecognitionException e) { - String hdr = getErrorHeader(e); - String msg = getErrorMessage(e, tokenNames); - _logger.fine(msg); + public void displayRecognitionError(String[] tokenNames, RecognitionException re) { + String message = getErrorHeader(re); + try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} + _logger.fine(message); } } diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 6c9efe46..5758b1d6 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -40,10 +40,10 @@ tokens { @members { private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - public void displayRecognitionError(String[] tokenNames, RecognitionException e) { - String hdr = getErrorHeader(e); - String msg = getErrorMessage(e, tokenNames); - _logger.fine(msg); + public void displayRecognitionError(String[] tokenNames, RecognitionException re) { + String message = getErrorHeader(re); + try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} + _logger.fine(message); } } diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 7c73c562..bfad38e0 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -11,10 +11,15 @@ options { private com.joestelmach.natty.WalkerState _walkerState = new com.joestelmach.natty.WalkerState(); private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - public void displayRecognitionError(String[] tokenNames, RecognitionException e) { - String hdr = getErrorHeader(e); - String msg = getErrorMessage(e, tokenNames); - _logger.fine(msg); + public void displayRecognitionError(String[] tokenNames, RecognitionException re) { + String message = getErrorHeader(re); + try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} + _logger.fine(message); + } + + public void recover(IntStream input, RecognitionException re) { + reportError(re); + _walkerState.clearDateGroup(); } public com.joestelmach.natty.WalkerState getState() { diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index 13231900..30dbe5b8 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -9,6 +9,16 @@ options { @header { package com.joestelmach.natty.generated; } +@members { + private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); + + public void displayRecognitionError(String[] tokenNames, RecognitionException re) { + String message = getErrorHeader(re); + try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} + _logger.fine(message); + } +} + topdown : ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK INT) ^(DAY_OF_WEEK dow=INT)) -> ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK $dow)) diff --git a/src/main/java/com/joestelmach/natty/CalendarSource.java b/src/main/java/com/joestelmach/natty/CalendarSource.java index 27768cbe..f27cbaa5 100644 --- a/src/main/java/com/joestelmach/natty/CalendarSource.java +++ b/src/main/java/com/joestelmach/natty/CalendarSource.java @@ -11,7 +11,7 @@ * * @author Joe Stelmach */ -public class CalendarSource { +class CalendarSource { private static Date _baseDate; public static void setBaseDate(Date baseDate) { diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 6d8f36b0..27d400cb 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -386,6 +386,13 @@ public DateGroup getDateGroup() { return _dateGroup; } + /** + * Clears any date/times that have been captured + */ + public void clearDateGroup() { + _dateGroup = new DateGroup(); + } + /** * Resets the calendar */ diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 30eb2b26..cc32b3da 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -194,7 +194,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "golf tomorrow at 9 AM at pebble beach"; + String value = "I want to go to park tomorrow and then email john@aol.com"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -212,6 +212,7 @@ public static void main(String[] args) { System.out.print(location.getText() + " "); } } + System.out.print("\n\n"); } } } diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 3424d04c..585b5c0f 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -52,5 +52,11 @@ public void test() throws Exception { validateTime(dates.get(0), 9, 0, 0); validateDate(dates.get(1), 2, 24, 2011); validateTime(dates.get(1), 10, 0, 0); + + groups = parser.parse("I want to go to park tomorrow and then email john@aol.com"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 21, 2011); } } diff --git a/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java similarity index 98% rename from src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java index 473b6525..1e3a69fc 100644 --- a/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import java.io.ByteArrayInputStream; import java.lang.reflect.Method; diff --git a/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java index 98a1cfe5..ac4f2697 100644 --- a/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java index 06123704..e358c20c 100644 --- a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java index 8a0aeb75..606dd15b 100644 --- a/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java index 3450c7ed..b0eaba2f 100644 --- a/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; From a8f79c3b7f5e4b420bfb67bba43dffa662c6f8e0 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 3 Mar 2011 19:32:04 -0500 Subject: [PATCH 033/134] basic recurrence support --- .../joestelmach/natty/generated/DateLexer.g | 8 ++++- .../joestelmach/natty/generated/DateParser.g | 8 ++++- .../joestelmach/natty/generated/DateWalker.g | 11 ++++++ .../com/joestelmach/natty/CalendarSource.java | 2 +- .../java/com/joestelmach/natty/DateGroup.java | 15 ++++++++ .../java/com/joestelmach/natty/Parser.java | 4 +-- .../com/joestelmach/natty/WalkerState.java | 14 +++++++- .../com/joestelmach/natty/AbstractTest.java | 6 +++- .../java/com/joestelmach/natty/DateTest.java | 11 ++++-- .../com/joestelmach/natty/RecurrenceTest.java | 35 +++++++++++++++++++ .../com/joestelmach/natty/SearchTest.java | 8 +++++ .../natty}/grammar/AbstractGrammarTest.java | 2 +- .../natty}/grammar/DateGrammarTest.java | 2 +- .../natty}/grammar/DateTimeGrammarTest.java | 2 +- .../natty}/grammar/NumericGrammarTest.java | 2 +- .../natty}/grammar/TimeGrammarTest.java | 2 +- 16 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/joestelmach/natty/RecurrenceTest.java rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/AbstractGrammarTest.java (98%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/DateGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/DateTimeGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/NumericGrammarTest.java (99%) rename src/test/java/com/{natty/joestelmach => joestelmach/natty}/grammar/TimeGrammarTest.java (99%) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 103fad4f..c8f30e27 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -46,8 +46,14 @@ TODAY : 'today'; TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow'; YESTERDAY : 'yesterday'; -// ********** time rules ********** +// ********** recurrence rules ********** + +EVERY : 'every'; +UNTIL : 'until'; + +// ********** time rules ********** + AT : 'at' | '@'; AFTER : 'after'; PAST : 'past'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 6c9efe46..b0a854f7 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -31,6 +31,7 @@ tokens { AM_PM; ZONE; ZONE_OFFSET; + RECURRENCE; } @header { @@ -48,7 +49,12 @@ tokens { } parse - : empty date_time_alternative + : empty ((recurrence)=>recurrence | date_time_alternative) + ; + +recurrence + : EVERY WHITE_SPACE date_time_alternative (WHITE_SPACE UNTIL WHITE_SPACE date_time)? + -> date_time_alternative ^(RECURRENCE date_time?) ; empty diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 7c73c562..24d2749f 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -22,6 +22,17 @@ options { } } +parse + : date_time_alternative recurrence? + ; + +recurrence + @init { + _walkerState.setRecurring(); + } + : ^(RECURRENCE date_time?{ _walkerState.captureDateTime(); }) + ; + date_time_alternative : ^(DATE_TIME_ALTERNATIVE date_time+) ; diff --git a/src/main/java/com/joestelmach/natty/CalendarSource.java b/src/main/java/com/joestelmach/natty/CalendarSource.java index 27768cbe..f27cbaa5 100644 --- a/src/main/java/com/joestelmach/natty/CalendarSource.java +++ b/src/main/java/com/joestelmach/natty/CalendarSource.java @@ -11,7 +11,7 @@ * * @author Joe Stelmach */ -public class CalendarSource { +class CalendarSource { private static Date _baseDate; public static void setBaseDate(Date baseDate) { diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index 45c0eaf6..fd8e65d9 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -16,6 +16,8 @@ public class DateGroup { private String _text; private int _line; private int _position; + private boolean _isRecurring; + private Date _recurringUntil; private Map> _parseLocations; private Tree _syntaxTree; @@ -51,6 +53,19 @@ public void setPosition(int position) { _position = position; } + public boolean isRecurring() { + return _isRecurring; + } + public void setRecurring(boolean isRecurring) { + _isRecurring = isRecurring; + } + + public Date getRecursUntil() { + return _recurringUntil; + } + public void setRecurringUntil(Date recurringUntil) { + _recurringUntil = recurringUntil; + } public Map> getParseLocations() { return _parseLocations; diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index ff3af278..c79c45ae 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -117,7 +117,7 @@ private DateGroup singleParse(TokenStream stream) { nodes.setTokenStream(stream); DateWalker walker = new DateWalker(nodes); walker.getState().setDefaultTimeZone(_defaultTimeZone); - walker.date_time_alternative(); + walker.parse(); // run through the results and append the parse information group = walker.getState().getDateGroup(); @@ -163,7 +163,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse171.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse176.member(currentToken.getType())) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 6d8f36b0..f0df56fb 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -370,12 +370,24 @@ public void setExplicitTime(String hours, String minutes, String seconds, String _calendar.set(Calendar.MINUTE, minutesInt); } + /** + * + */ + public void setRecurring() { + _dateGroup.setRecurring(true); + } + /** * */ public void captureDateTime() { Date date = _calendar.getTime(); - _dateGroup.addDate(date); + if(_dateGroup.isRecurring()) { + _dateGroup.setRecurringUntil(date); + } + else { + _dateGroup.addDate(date); + } _firstDateInvocationInGroup = true; } diff --git a/src/test/java/com/joestelmach/natty/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java index 7f59c1b5..cb6237a6 100644 --- a/src/test/java/com/joestelmach/natty/AbstractTest.java +++ b/src/test/java/com/joestelmach/natty/AbstractTest.java @@ -10,9 +10,13 @@ import com.joestelmach.natty.CalendarSource; import com.joestelmach.natty.Parser; +/** + * + * @author Joe Stelmach + */ public abstract class AbstractTest { private static final Calendar _calendar = Calendar.getInstance(); - private static final Parser _parser = new Parser(); + protected static final Parser _parser = new Parser(); /** * Resets the calendar source time before each test diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 30eb2b26..5b5e0c96 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -12,6 +12,11 @@ import org.junit.Test; +import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.DateGroup; +import com.joestelmach.natty.ParseLocation; +import com.joestelmach.natty.Parser; + /** * Runs the parser through the various date formats * @@ -22,13 +27,11 @@ public class DateTest extends AbstractTest { @Test public void testFormal() { validateDate("1978-01-28", 1, 28, 1978); - /* validateDate("2009-10-10", 10, 10, 2009); validateDate("1980-1-2", 1, 2, 1980); validateDate("12/12/12", 12, 12, 2012); validateDate("3/4", 3, 4, Calendar.getInstance().get(Calendar.YEAR)); validateDate("sun, 11/21/2010", 11, 21, 2010); - */ } @Test @@ -194,7 +197,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "golf tomorrow at 9 AM at pebble beach"; + String value = "every monday until three mondays from now"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -204,6 +207,8 @@ public static void main(String[] args) { System.out.println("line: " + group.getLine() + ", column: " + group.getPosition()); System.out.println(group.getText()); System.out.println(group.getDates()); + System.out.println("is recurring: " + group.isRecurring()); + System.out.println("recurs until: " + group.getRecursUntil()); System.out.print("conjunctions: "); List conjunctionLocations = group.getParseLocations().get("conjunction"); diff --git a/src/test/java/com/joestelmach/natty/RecurrenceTest.java b/src/test/java/com/joestelmach/natty/RecurrenceTest.java new file mode 100644 index 00000000..8fe54045 --- /dev/null +++ b/src/test/java/com/joestelmach/natty/RecurrenceTest.java @@ -0,0 +1,35 @@ +package com.joestelmach.natty; + +import java.text.DateFormat; +import java.util.Date; + +import junit.framework.Assert; + +import org.junit.Test; + +/** + * + * @author Joe Stelmach + */ +public class RecurrenceTest extends AbstractTest { + + @Test + public void testRelative() throws Exception { + Date reference = DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.SHORT).parse("3/3/2011 12:00 am"); + CalendarSource.setBaseDate(reference); + + DateGroup group = _parser.parse("every friday until two tuesdays from now").get(0); + Assert.assertEquals(1, group.getDates().size()); + validateDate(group.getDates().get(0), 3, 4, 2011); + Assert.assertTrue(group.isRecurring()); + validateDate(group.getRecursUntil(), 3, 15, 2011); + + group = _parser.parse("every saturday or sunday").get(0); + Assert.assertEquals(2, group.getDates().size()); + validateDate(group.getDates().get(0), 3, 5, 2011); + validateDate(group.getDates().get(1), 3, 6, 2011); + Assert.assertTrue(group.isRecurring()); + Assert.assertNull(group.getRecursUntil()); + } +} diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 3424d04c..0e7d7ec2 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -8,6 +8,14 @@ import org.junit.Test; +import com.joestelmach.natty.CalendarSource; +import com.joestelmach.natty.DateGroup; +import com.joestelmach.natty.Parser; + +/** + * + * @author Joe Stelmach + */ public class SearchTest extends AbstractTest { @Test diff --git a/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java similarity index 98% rename from src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java index 473b6525..1e3a69fc 100644 --- a/src/test/java/com/natty/joestelmach/grammar/AbstractGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import java.io.ByteArrayInputStream; import java.lang.reflect.Method; diff --git a/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java index 98a1cfe5..ac4f2697 100644 --- a/src/test/java/com/natty/joestelmach/grammar/DateGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java index 06123704..e358c20c 100644 --- a/src/test/java/com/natty/joestelmach/grammar/DateTimeGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java index 8a0aeb75..606dd15b 100644 --- a/src/test/java/com/natty/joestelmach/grammar/NumericGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; diff --git a/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java similarity index 99% rename from src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java rename to src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java index 3450c7ed..b0eaba2f 100644 --- a/src/test/java/com/natty/joestelmach/grammar/TimeGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java @@ -1,4 +1,4 @@ -package com.natty.joestelmach.grammar; +package com.joestelmach.natty.grammar; import org.junit.Test; From 1c4c8873e6639f1b143653dfc4e3213e92a4ca78 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 15 Mar 2011 23:04:47 -0400 Subject: [PATCH 034/134] new formats: next day, last hour, next minute, for 5 seconds, for six years, etc. --- .../joestelmach/natty/generated/DateLexer.g | 1 + .../joestelmach/natty/generated/DateParser.g | 21 +++++++++++-- .../java/com/joestelmach/natty/DateTest.java | 17 +++++++++- .../java/com/joestelmach/natty/TimeTest.java | 31 +++++++++++++++++-- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index dc7ef554..a47d644e 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -250,6 +250,7 @@ DOT : '.'; PLUS : '+'; SINGLE_QUOTE : '\''; +FOR : 'for'; IN : 'in'; THE : 'the'; OR : 'or'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index edac5286..db2c38c9 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -118,12 +118,24 @@ date_time_alternative -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) // first or last day of 2009 - | (explicit_day_of_year_part conjunction explicit_day_of_year_part WHITE_SPACE relaxed_year)=> - first=explicit_day_of_year_part conjunction second=explicit_day_of_year_part WHITE_SPACE relaxed_year + | (explicit_day_of_year_part conjunction explicit_day_of_year_part WHITE_SPACE relaxed_year)=> + first=explicit_day_of_year_part conjunction second=explicit_day_of_year_part WHITE_SPACE relaxed_year -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $first)) ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $second))) + // for 3 days, for 7 months, for twenty seconds + | FOR WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE + (relative_date_span -> + ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) + | relative_time_span -> + ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) + ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) + ) + // catch all date_time to date_time range | (date_time conjunction date_time)=> date_time conjunction date_time @@ -598,8 +610,13 @@ relative_time : spelled_or_int_optional_prefix WHITE_SPACE relative_time_target WHITE_SPACE relative_time_suffix -> ^(RELATIVE_TIME ^(SEEK relative_time_suffix spelled_or_int_optional_prefix relative_time_target)) + // in 3 minutes | IN WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE relative_time_target -> ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_target)) + + // next hour, last minute + | prefix WHITE_SPACE relative_time_target + -> ^(RELATIVE_TIME ^(SEEK prefix relative_time_target)) ; // a time with an hour, optional minutes, and optional meridian indicator diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 41a78f20..6a283b96 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -188,6 +188,21 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 9, 1, 2012); validateDate(dates.get(1), 9, 30, 2012); + + dates = parseCollection("for 5 days"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 1, 7, 2011); + + dates = parseCollection("for ten months"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 11, 2, 2011); + + dates = parseCollection("for twenty-five years"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 1, 2, 2036); } public static void main(String[] args) { @@ -197,7 +212,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "every monday until three mondays from now"; + String value = "this evening"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index 2eb8b1f7..8cb73310 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -1,11 +1,12 @@ package com.joestelmach.natty; import java.text.DateFormat; +import java.util.Date; +import java.util.List; +import org.junit.Assert; import org.junit.Test; -import com.joestelmach.natty.CalendarSource; - /** * Runs the parser through the various time formats * @@ -63,5 +64,31 @@ public void testRelative() throws Exception { validateTime("4 seconds from now", 12, 0, 4); validateTime("4 minutes from now", 12, 4, 0); validateTime("4 hours from now", 16, 0, 0); + validateTime("next minute", 12, 1, 0); + validateTime("last minute", 11, 59, 0); + validateTime("next second", 12, 0, 1); + validateTime("this second", 12, 0, 0); + validateTime("this minute", 12, 0, 0); + validateTime("this hour", 12, 0, 0); + } + + @Test + public void testRange() throws Exception { + CalendarSource.setBaseDate(DateFormat.getTimeInstance(DateFormat.SHORT).parse("12:00 pm")); + + List dates = parseCollection("for six hours"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 18, 0, 0); + + dates = parseCollection("for 12 minutes"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 12, 12, 0); + + dates = parseCollection("for 10 seconds"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 12, 0, 10); } } From 4cfa7a017358d0a4cf9c31e25530f425e0552378 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 16 Mar 2011 00:07:12 -0400 Subject: [PATCH 035/134] added cpan test --- .../java/com/joestelmach/natty/Parser.java | 4 +- .../java/com/joestelmach/natty/CPANTest.java | 33 + .../java/com/joestelmach/natty/DateTest.java | 2 +- src/test/resources/cpan.txt | 649 +++++++++--------- 4 files changed, 356 insertions(+), 332 deletions(-) create mode 100644 src/test/java/com/joestelmach/natty/CPANTest.java diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index c79c45ae..3731b042 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -10,7 +10,6 @@ import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.TokenStream; import org.antlr.runtime.tree.CommonTree; @@ -101,6 +100,7 @@ private DateGroup singleParse(TokenStream stream) { ParseListener listener = new ParseListener(); DateParser parser = new DateParser(stream, listener); DateParser.parse_return parseReturn = parser.parse(); + Tree tree = (Tree) parseReturn.getTree(); _logger.fine("AST: " + tree.toStringTree()); @@ -129,7 +129,7 @@ private DateGroup singleParse(TokenStream stream) { group.setParseLocations(listener.getLocations()); } - } catch(RecognitionException e) { + } catch(Exception e) { _logger.log(Level.SEVERE, "Could not parse input", e); } diff --git a/src/test/java/com/joestelmach/natty/CPANTest.java b/src/test/java/com/joestelmach/natty/CPANTest.java new file mode 100644 index 00000000..c1081e5a --- /dev/null +++ b/src/test/java/com/joestelmach/natty/CPANTest.java @@ -0,0 +1,33 @@ +package com.joestelmach.natty; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; + +/** + * + * @author Joe Stelmach + */ +public class CPANTest { + + @Test + public void sanityCheck() throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader( + CPANTest.class.getResourceAsStream("/cpan.txt"))); + String value = null; + while((value = reader.readLine()) != null) { + if(!value.trim().startsWith("#") && value.trim().length() > 0) { + Parser parser = new Parser(); + List groups = parser.parse(value); + Assert.assertEquals(1, groups.size()); + Assert.assertTrue(groups.get(0).getDates().size() > 0); + } + } + + reader.close(); + } +} diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 6a283b96..090c75c3 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -212,7 +212,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "this evening"; + String value = "march 2004"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/resources/cpan.txt b/src/test/resources/cpan.txt index e68509e3..2f46f92f 100644 --- a/src/test/resources/cpan.txt +++ b/src/test/resources/cpan.txt @@ -1,329 +1,320 @@ -#now -#yesterday -#today -#tomorrow -#morning -#afternoon -#noon -#midnight -#yesterday at noon -#yesterday at midnight -#today at noon -#today at midnight -#tomorrow at noon -#tomorrow at midnight -#yesterday morning -#yesterday afternoon -#today morning -#today afternoon -#tomorrow morning -#tomorrow afternoon -#thursday morning -#thursday afternoon -#6:00 yesterday -#6:00 today -#6:00 tomorrow -#5am yesterday -#5am today -#5am tomorrow -#4pm yesterday -#4pm today -#4pm tomorrow -#this day -#next day -#last week -#this week -#next week -#last month -#this month -#next month -#last year -#this year -#next year -#last friday -#this friday -#next friday -#last week wednesday -#this week wednesday -#next week wednesday -#10 days ago -#10 weeks ago -#10 months ago -#10 years ago -#in 5 days -#in 5 weeks -#in 5 months -#in 5 years -#saturday -#sunday 11:00 -#yesterday at 4:00 -#today at 4:00 -#tomorrow at 4:00 -#yesterday at 6:45am -#today at 6:45am -#tomorrow at 6:45am -#yesterday at 6:45pm -#today at 6:45pm -#tomorrow at 6:45pm -#yesterday at 2:32 AM -#today at 2:32 AM -#tomorrow at 2:32 AM -#yesterday at 2:32 PM -#today at 2:32 PM -#tomorrow at 2:32 PM -#yesterday 02:32 -#today 02:32 -#tomorrow 02:32 -#yesterday 2:32am -#today 2:32am -#tomorrow 2:32am -#yesterday 2:32pm -#today 2:32pm -#tomorrow 2:32pm -#wednesday at 14:30 -#wednesday at 02:30am -#wednesday at 02:30pm -#wednesday 14:30 -#wednesday 02:30am -#wednesday 02:30pm -#friday 03:00 am -#friday 03:00 pm -#sunday at 05:00 am -#sunday at 05:00 pm -#november 3rd -#last june -#next october -#6 am -#5am -#5:30am -#8 pm -#4pm -#4:20pm -#06:56:06 am -#06:56:06 pm -#mon 2:35 -#1:00 sun -#1am sun -#1pm sun -#1:00 on sun -#1am on sun -#1pm on sun -#12:14 PM -#12:14 AM -#2 days before now -#2 weeks before now -#2 months before now -#2 years before now -#4 days from now -#4 weeks from now -#4 months from now -#4 years from now -#9 in the evening -#last sunday at 21:45 -#noon last friday -#midnight last friday -#noon this friday -#midnight this friday -#noon next friday -#midnight next friday -#last friday at 20:00 -#this friday at 20:00 -#next friday at 20:00 -#1:00 last friday -#1:00 this friday -#1:00 next friday -#1am last friday -#1am this friday -#1am next friday -#1pm last friday -#1pm this friday -#1pm next friday -#5 am last monday -#5 am this monday -#5 am next monday -#5 pm last monday -#5 pm this monday -#5 pm next monday -#last wednesday 7am -#this wednesday 7am -#next wednesday 7am -#last wednesday 7pm -#this wednesday 7pm -#next wednesday 7pm -#last tuesday 11 am -#this tuesday 11 am -#next tuesday 11 am -#last tuesday 11 pm -#this tuesday 11 pm -#next tuesday 11 pm -#yesterday at 13:00 -#today at 13:00 -#tomorrow at 13 -#2nd friday in august -#3rd wednesday in november -#6 mondays from now -#last thursday in april -#january 11 -#11 january -#18 oct 17:00 -#18 oct 5am -#18 oct 5pm -#18 oct 5 am -#18 oct 5 pm -#dec 25 -#feb 28 3:00 -#feb 28 3am -#feb 28 3pm -#feb 28 3 am -#feb 28 3 pm -#19:00 jul 1 -#7am jul 1 -#7pm jul 1 -#7 am jul 1 -#7 pm jul 1 -#may 27th -#march 1st 2009 -#february 14, 2004 -#jan 3 2010 -#3 jan 2000 -#2010 october 28 -#1/3 -#1/3 16:00 -#4:00 -#17:00 -#3:20:00 -#tues -#thurs - -// token additions -6 in the morning -4 in the afternoon -monday 6 in the morning -monday 4 in the afternoon -monday 9 in the evening -evening -this morning -this afternoon -this evening -yesterday evening -today evening -tomorrow evening -thursday evening -final thursday in april -final thurs in sep -yesterday @ noon - -// relative time -6 hours before yesterday -6 hours before tomorrow -3 hours after yesterday -3 hours after tomorrow -10 hours before noon -10 hours before midnight -5 hours after noon -5 hours after midnight -last second -this second -next second -last minute -this minute -next minute -last hour -this hour -next hour -10 seconds ago -10 minutes ago -10 hours ago -in 5 seconds -in 5 minutes -in 5 hours -yesterday 7 seconds ago -yesterday 7 minutes ago -yesterday 7 hours ago -tomorrow 3 seconds ago -tomorrow 3 minutes ago -tomorrow 3 hours ago -2 seconds before now -2 minutes before now -2 hours before now -4 seconds from now -4 minutes from now -4 hours from now - -monday last week -6th day last week -6th day this week -6th day next week -12th day last month -12th day this month -12th day next month -1st day last year -1st day this year -1st day next year -1st tuesday last november -1st tuesday this november -1st tuesday next november -11 january next year -11 january this year -11 january last year -tomorrow 1 year ago -saturday 3 months ago at 17:00 -saturday 3 months ago at 5:00am -saturday 3 months ago at 5:00pm -11 january 2 years ago -4th day last week -8th month last year -8th month this year -8th month next year -fri 3 months ago at 5am -wednesday 1 month ago at 8pm -march -2005 -October 2006 -27/5/1979 --5min -+2d -5 mins ago -tues this week -last day -tuesday last week -tuesday this week -tuesday next week -2nd monday -100th day -4th february -yesterday 7 days ago -yesterday 7 weeks ago -yesterday 7 months ago -yesterday 7 years ago -tomorrow 3 days ago -tomorrow 3 weeks ago -tomorrow 3 months ago -tomorrow 3 years ago - -// timespans -monday to friday -1 April to 31 August -1999-12-31 to tomorrow -now to 2010-01-01 -2009-03-10 9:00 to 11:00 -26 oct 10:00 am to 11:00 am -jan 1 to 2 -16:00 nov 6 to 17:00 -may 2nd to 5th -100th day to 200th -6am dec 5 to 7am -1/3 to 2/3 -2/3 to in 1 week -3/3 21:00 to in 5 days -first day of 2009 to last day of 2009 -first day of may to last day of may -first to last day of 2008 -first to last day of september -for 4 seconds -for 4 minutes -for 4 hours -for 4 days -for 4 weeks -for 4 months -for 4 years \ No newline at end of file + now + yesterday + today + tomorrow + morning + afternoon + evening + noon + midnight + yesterday at noon + yesterday at midnight + today at noon + today at midnight + tomorrow at noon + tomorrow at midnight + this morning + this afternoon + this evening + yesterday morning + yesterday afternoon + yesterday evening + today morning + today afternoon + today evening + tomorrow morning + tomorrow afternoon + tomorrow evening + thursday morning + thursday afternoon + thursday evening + 6:00 yesterday + 6:00 today + 6:00 tomorrow + 5am yesterday + 5am today + 5am tomorrow + 4pm yesterday + 4pm today + 4pm tomorrow + last second + this second + next second + last minute + this minute + next minute + last hour + this hour + next hour + last day + this day + next day + last week + this week + next week + last month + this month + next month + last year + this year + next year + last friday + this friday + next friday + tuesday last week + tuesday this week + tuesday next week + last week wednesday + this week wednesday + next week wednesday + 10 seconds ago + 10 minutes ago + 10 hours ago + 10 days ago + 10 weeks ago + 10 months ago + 10 years ago + in 5 seconds + in 5 minutes + in 5 hours + in 5 days + in 5 weeks + in 5 months + in 5 years + saturday + sunday 11:00 + yesterday at 4:00 + today at 4:00 + tomorrow at 4:00 + yesterday at 6:45am + today at 6:45am + tomorrow at 6:45am + yesterday at 6:45pm + today at 6:45pm + tomorrow at 6:45pm + yesterday at 2:32 AM + today at 2:32 AM + tomorrow at 2:32 AM + yesterday at 2:32 PM + today at 2:32 PM + tomorrow at 2:32 PM + yesterday 02:32 + today 02:32 + tomorrow 02:32 + yesterday 2:32am + today 2:32am + tomorrow 2:32am + yesterday 2:32pm + today 2:32pm + tomorrow 2:32pm + wednesday at 14:30 + wednesday at 02:30am + wednesday at 02:30pm + wednesday 14:30 + wednesday 02:30am + wednesday 02:30pm + friday 03:00 am + friday 03:00 pm + sunday at 05:00 am + sunday at 05:00 pm + 4th february + november 3rd + last june + next october + 6 am + 5am + 5:30am + 8 pm + 4pm + 4:20pm + 06:56:06 am + 06:56:06 pm + mon 2:35 + 1:00 sun + 1am sun + 1pm sun + 1:00 on sun + 1am on sun + 1pm on sun + 12:14 PM + 12:14 AM + 2 seconds before now + 2 minutes before now + 2 hours before now + 2 days before now + 2 weeks before now + 2 months before now + 2 years before now + 4 seconds from now + 4 minutes from now + 4 hours from now + 4 days from now + 4 weeks from now + 4 months from now + 4 years from now + 6 in the morning + 4 in the afternoon + 9 in the evening + monday 6 in the morning + monday 4 in the afternoon + monday 9 in the evening + last sunday at 21:45 + monday last week + 12th day last month + 12th day this month + 12th day next month + 1st tuesday last november + 1st tuesday this november + 1st tuesday next november + 10 hours before noon + 10 hours before midnight + 5 hours after noon + 5 hours after midnight + noon last friday + midnight last friday + noon this friday + midnight this friday + noon next friday + midnight next friday + last friday at 20:00 + this friday at 20:00 + next friday at 20:00 + 1:00 last friday + 1:00 this friday + 1:00 next friday + 1am last friday + 1am this friday + 1am next friday + 1pm last friday + 1pm this friday + 1pm next friday + 5 am last monday + 5 am this monday + 5 am next monday + 5 pm last monday + 5 pm this monday + 5 pm next monday + last wednesday 7am + this wednesday 7am + next wednesday 7am + last wednesday 7pm + this wednesday 7pm + next wednesday 7pm + last tuesday 11 am + this tuesday 11 am + next tuesday 11 am + last tuesday 11 pm + this tuesday 11 pm + next tuesday 11 pm + yesterday at 13:00 + today at 13:00 + tomorrow at 13 + 2nd friday in august + 3rd wednesday in november + tomorrow 1 year ago + 11 january 2 years ago + 6 mondays from now + final thursday in april + last thursday in april + monday to friday + 1 April to 31 August + 1999-12-31 to tomorrow + now to 2010-01-01 + 2009-03-10 9:00 to 11:00 + 26 oct 10:00 am to 11:00 am + jan 1 to 2 + 16:00 nov 6 to 17:00 + may 2nd to 5th + 6am dec 5 to 7am + 1/3 to 2/3 + 2/3 to in 1 week + 3/3 21:00 to in 5 days + first day of 2009 to last day of 2009 + first day of may to last day of may + first to last day of 2008 + first to last day of september + for 4 seconds + for 4 minutes + for 4 hours + for 4 days + for 4 weeks + for 4 months + for 4 years + january 11 + 11 january + 18 oct 17:00 + 18 oct 5am + 18 oct 5pm + 18 oct 5 am + 18 oct 5 pm + dec 25 + feb 28 3:00 + feb 28 3am + feb 28 3pm + feb 28 3 am + feb 28 3 pm + 19:00 jul 1 + 7am jul 1 + 7pm jul 1 + 7 am jul 1 + 7 pm jul 1 + jan 24, 2011 12:00 + jan 24, 2011 12am + jan 24, 2011 12pm + may 27th + 2005 + march 1st 2009 + february 14, 2004 + jan 3 2010 + 3 jan 2000 + 2010 october 28 + 1/3 + 1/3 16:00 + 4:00 + 17:00 + 3:20:00 + + #1st day last year + #1st day this year + #1st day next year + #6th day last week + #6th day this week + #6th day next week + #yesterday 7 seconds ago + #yesterday 7 minutes ago + #yesterday 7 hours ago + #yesterday 7 days ago + #yesterday 7 weeks ago + #yesterday 7 months ago + #yesterday 7 years ago + #tomorrow 3 seconds ago + #tomorrow 3 minutes ago + #tomorrow 3 hours ago + #tomorrow 3 days ago + #tomorrow 3 weeks ago + #tomorrow 3 months ago + #tomorrow 3 years ago + #2nd monday + #100th day + #11 january next year + #11 january this year + #11 january last year + #6 hours before yesterday + #6 hours before tomorrow + #3 hours after yesterday + #3 hours after tomorrow + #saturday 3 months ago at 17:00 + #saturday 3 months ago at 5:00am + #saturday 3 months ago at 5:00pm + #4th day last week + #8th month last year + #8th month this year + #8th month next year + #fri 3 months ago at 5am + #wednesday 1 month ago at 8pm + #October 2006 + #27/5/1979 + #-5min + #+2d + #100th day to 200th + #march \ No newline at end of file From 6a6219429cc48e4288a2c5cd092cc5f976ad65fd Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 29 Mar 2011 21:58:25 -0400 Subject: [PATCH 036/134] forcing unit tests to use eastern time zone --- src/test/java/com/joestelmach/natty/DateTest.java | 13 +++++++------ .../java/com/joestelmach/natty/DateTimeTest.java | 9 +++++++-- src/test/java/com/joestelmach/natty/TimeTest.java | 6 ++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 090c75c3..8203fed6 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -4,25 +4,26 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.TimeZone; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.Assert; +import org.junit.BeforeClass; import org.junit.Test; -import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.DateGroup; -import com.joestelmach.natty.ParseLocation; -import com.joestelmach.natty.Parser; - /** * Runs the parser through the various date formats * * @author Joe Stelmach */ public class DateTest extends AbstractTest { + @BeforeClass + public static void oneTime() { + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + } @Test public void testFormal() { @@ -212,7 +213,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "march 2004"; + String value = "2011-06-17T07:00:00Z"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 1cb3e7b1..afb79b13 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -3,18 +3,22 @@ import java.text.DateFormat; import java.util.Date; import java.util.List; +import java.util.TimeZone; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; -import com.joestelmach.natty.CalendarSource; - /** * Runs the parser through the various datetime formats * * @author Joe Stelmach */ public class DateTimeTest extends AbstractTest { + @BeforeClass + public static void oneTime() { + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + } @Test public void testSpecific() { @@ -28,6 +32,7 @@ public void testSpecific() { validateDateTime("3am,october first 2010", 10, 1, 2010, 3, 0, 0); validateDateTime("3am, on october first 2010", 10, 1, 2010, 3, 0, 0); validateDateTime("3am october first 2010", 10, 1, 2010, 3, 0, 0); + validateDateTime("2011-06-17T07:00:00Z", 6, 17, 2011, 3, 0, 0); } @Test diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index 8cb73310..afacec59 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -3,8 +3,10 @@ import java.text.DateFormat; import java.util.Date; import java.util.List; +import java.util.TimeZone; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; /** @@ -13,6 +15,10 @@ * @author Joe Stelmach */ public class TimeTest extends AbstractTest { + @BeforeClass + public static void oneTime() { + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + } /** * Runs the parser through the various time formats From f4de7db637aeb13b21b96a2452e84eb3c2a03dbc Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 4 Apr 2011 19:47:50 -0400 Subject: [PATCH 037/134] [maven-release-plugin] prepare release natty-0.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7c04d779..2f462187 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.3-SNAPSHOT + 0.3 Natty Date Parser natural language date parser http://natty.joestelmach.com From 04d06ddae7b73e6fe92ede3ec4e23592d054e96d Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 4 Apr 2011 19:47:57 -0400 Subject: [PATCH 038/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f462187..0881de46 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.3 + 0.4-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From e1a6d6a31ee2004daa347f2ac3d69f20f8232127 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 4 Jun 2011 10:56:18 -0400 Subject: [PATCH 039/134] improved searching --- .../java/com/joestelmach/natty/Parser.java | 33 +++++++++++++- .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/SearchTest.java | 43 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 3731b042..64095cb0 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -3,6 +3,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.TimeZone; import java.util.logging.Level; @@ -70,7 +71,35 @@ public List parse(String value) { // and parse each of them List groups = new ArrayList(); for(TokenStream stream:streams) { + List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); DateGroup group = singleParse(stream); + while((group == null || group.getDates().size() == 0) && tokens.size() > 0) { + if(group == null || group.getDates().size() == 0) { + // if we're down to only two tokens in our token stream, we can't continue + if(tokens.size() <= 2) { + tokens.clear(); + } + + // otherwise, we look at the token stream for anotherpossible starting point, + // and attempt to reparse with that sub-sequence of tokens + else { + tokens = tokens.subList(2, tokens.size()); + Iterator iter = tokens.iterator(); + while(iter.hasNext()) { + Token token = iter.next(); + if(!DateParser.FOLLOW_empty_in_parse176.member(token.getType())) { + iter.remove(); + } + else { + break; + } + } + TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); + group = singleParse(newStream); + } + } + } + // if a group with some date(s) was found, we add it if(group != null && group.getDates().size() > 0) { groups.add(group); } @@ -184,8 +213,8 @@ private List collectTokenStreams(TokenStream stream) { currentGroup = null; } // otherwise, the token is known and we're currently collecting for - // a group, so we add it - else { + // a group, we add it if it's not a dot + else if(currentToken.getType() != DateLexer.DOT) { currentGroup.add(currentToken); } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 8203fed6..7a038212 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -213,7 +213,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "2011-06-17T07:00:00Z"; + String value = "I want to purchase a car in the next month."; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index d5a5dbbc..fcc51214 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -66,5 +66,48 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 2, 21, 2011); + + groups = parser.parse("I want to pay off all my debt in the next two years."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 20, 2013); + + groups = parser.parse("I want to purchase a car in the next month."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 3, 20, 2011); + + groups = parser.parse("I want to plan a get-together with my friends for this Friday."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 25, 2011); + + groups = parser.parse("I want to lose five pounds in the next two months."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 4, 20, 2011); + + groups = parser.parse("I want to finalize my college schedule by next week."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 27, 2011); + + groups = parser.parse("I want to read this weekend."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 26, 2011); + + groups = parser.parse("I want to travel a big chunk of world next year."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 20, 2012); + } } From 701e9dd4ac625c4f2808ed337665759ad97be263 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 9 Jun 2011 23:06:10 -0400 Subject: [PATCH 040/134] in 4 to 5 months, for 6 to 7 years --- .../com/joestelmach/natty/generated/DateParser.g | 11 +++++++++-- src/test/java/com/joestelmach/natty/DateTest.java | 12 +++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index db2c38c9..5ee3827e 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -95,6 +95,13 @@ date_time_alternative : (date conjunction global_date_prefix)=> date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) + + // in 2 to 3 months + | ((IN | FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE TO)=> + (IN | FOR | NEXT) WHITE_SPACE one=spelled_or_int_optional_prefix WHITE_SPACE TO WHITE_SPACE two=spelled_or_int_optional_prefix WHITE_SPACE relative_date_span + -> ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $one relative_date_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $two relative_date_span)))) // "next wed or thurs" , "next wed, thurs, or fri" | (alternative_day_of_week_list)=> alternative_day_of_week_list @@ -128,8 +135,8 @@ date_time_alternative | FOR WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE (relative_date_span -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) | relative_time_span -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 7a038212..7a8f5204 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -204,6 +204,16 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 1, 2, 2011); validateDate(dates.get(1), 1, 2, 2036); + + dates = parseCollection("I want to go shopping in Knoxville, TN in the next five to six months."); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 6, 2, 2011); + validateDate(dates.get(1), 7, 2, 2011); + + dates = parseCollection("I want to watch the fireworks in the next two to three months."); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 3, 2, 2011); + validateDate(dates.get(1), 4, 2, 2011); } public static void main(String[] args) { @@ -213,7 +223,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "I want to purchase a car in the next month."; + String value = "I want to go shopping in Knoxville, TN in the next five to six months."; Parser parser = new Parser(); List groups = parser.parse(value); From 40a0521b8bf34eaa593debe2370f99452bb38304 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 12 Jun 2011 19:52:33 -0400 Subject: [PATCH 041/134] adding support for 'last 5 years', 'past two weeks', etc. Representing things like 'next 5 weeks' as a date range instead of just a single date --- .../joestelmach/natty/generated/DateParser.g | 16 +++++++++- .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/SearchTest.java | 30 ++++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 5ee3827e..05f5df09 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -132,7 +132,8 @@ date_time_alternative ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $second))) // for 3 days, for 7 months, for twenty seconds - | FOR WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE + | ((FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE)=> + (FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE (relative_date_span -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) @@ -142,6 +143,19 @@ date_time_alternative ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) ) + + // last 3 days, last 7 months, past twenty seconds + | ((LAST | PAST) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE)=> + (LAST | PAST) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE + (relative_date_span -> + ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) + | relative_time_span -> + ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) + ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) + ) // catch all date_time to date_time range | (date_time conjunction date_time)=> diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 7a8f5204..2636000e 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -223,7 +223,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "I want to go shopping in Knoxville, TN in the next five to six months."; + String value = "last 2 weeks"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index fcc51214..cfc84989 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -70,8 +70,9 @@ public void test() throws Exception { groups = parser.parse("I want to pay off all my debt in the next two years."); Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); - Assert.assertEquals(1, dates.size()); - validateDate(dates.get(0), 2, 20, 2013); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + validateDate(dates.get(1), 2, 20, 2013); groups = parser.parse("I want to purchase a car in the next month."); Assert.assertEquals(1, groups.size()); @@ -88,8 +89,9 @@ public void test() throws Exception { groups = parser.parse("I want to lose five pounds in the next two months."); Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); - Assert.assertEquals(1, dates.size()); - validateDate(dates.get(0), 4, 20, 2011); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + validateDate(dates.get(1), 4, 20, 2011); groups = parser.parse("I want to finalize my college schedule by next week."); Assert.assertEquals(1, groups.size()); @@ -109,5 +111,25 @@ public void test() throws Exception { Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 2, 20, 2012); + groups = parser.parse("last 2 weeks"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + validateDate(dates.get(1), 2, 6, 2011); + + groups = parser.parse("last 5 years"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + validateDate(dates.get(1), 2, 20, 2006); + + groups = parser.parse("next 5 years"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + validateDate(dates.get(1), 2, 20, 2016); } } From 300ff0e9e8818f19f2909515e3ae4e3132e43b6d Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 14 Sep 2011 23:19:08 -0400 Subject: [PATCH 042/134] clean up trailing white space in sub groups before parsing --- src/main/java/com/joestelmach/natty/Parser.java | 14 ++++++++++++++ src/test/java/com/joestelmach/natty/DateTest.java | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 64095cb0..0047cb72 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -208,6 +208,7 @@ private List collectTokenStreams(TokenStream stream) { // if this is an unknown token, we need to end the current group if(currentToken.getType() == DateLexer.UNKNOWN) { if(currentGroup.size() > 0) { + cleanupGroup(currentGroup); groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); } currentGroup = null; @@ -221,6 +222,7 @@ else if(currentToken.getType() != DateLexer.DOT) { } } if(currentGroup != null) { + cleanupGroup(currentGroup); groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); } @@ -228,4 +230,16 @@ else if(currentToken.getType() != DateLexer.DOT) { return groups; } + + /** + * Removes unwanted tokens from the given token group + * @param group + */ + private void cleanupGroup(List group) { + // remove trailing white space + Token lastToken = group.get(group.size() - 1); + if(lastToken.getType() == DateParser.WHITE_SPACE) { + group.remove(lastToken); + } + } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 2636000e..26c0a547 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -214,6 +214,14 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 3, 2, 2011); validateDate(dates.get(1), 4, 2, 2011); + + dates = parseCollection("september 7th something"); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 7, 2011); + + dates = parseCollection("september 7th something happened here"); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 7, 2011); } public static void main(String[] args) { From 7f35b9e81f62f9be2155890e86856841f7bd1dfd Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 18 Oct 2011 22:12:45 -0400 Subject: [PATCH 043/134] [maven-release-plugin] prepare release natty-0.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0881de46..b7ddbbda 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.4-SNAPSHOT + 0.4 Natty Date Parser natural language date parser http://natty.joestelmach.com From e5123972f71bcf1e1da76c9a924e3392bd10152f Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 18 Oct 2011 22:12:48 -0400 Subject: [PATCH 044/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b7ddbbda..2a3f1b60 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.4 + 0.5-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From afe880d2843ceadaf1bd0917ddf4f9a3aaeb5448 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 5 Nov 2011 13:07:38 -0400 Subject: [PATCH 045/134] initial holiday parsing --- pom.xml | 7 + .../joestelmach/natty/generated/DateLexer.g | 45 +- .../joestelmach/natty/generated/DateParser.g | 108 + .../joestelmach/natty/generated/DateWalker.g | 3 + .../java/com/joestelmach/natty/Holiday.java | 58 + .../java/com/joestelmach/natty/Parser.java | 20 +- .../com/joestelmach/natty/WalkerState.java | 4 + src/main/resources/holidays.ics | 470 +++ .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/HolidayTest.java | 50 + .../natty/grammar/HolidayGrammarTest.java | 131 + src/test/resources/holidays.ics | 3316 +++++++++++++++++ 12 files changed, 4202 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/joestelmach/natty/Holiday.java create mode 100644 src/main/resources/holidays.ics create mode 100644 src/test/java/com/joestelmach/natty/HolidayTest.java create mode 100644 src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java create mode 100644 src/test/resources/holidays.ics diff --git a/pom.xml b/pom.xml index 2a3f1b60..ac4d2653 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ maven-compiler-plugin + 2.3.2 1.5 1.5 @@ -235,6 +236,12 @@ 2.7.7 + + org.mnode.ical4j + ical4j + 1.0.2 + + junit junit diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index a47d644e..5b636fb8 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -35,12 +35,12 @@ THURSDAY : 'thursday' 's'? | 'thur' DOT? | 'thu' DOT? | 'thus' DOT? | 'thur FRIDAY : 'friday' 's'? | 'fri' DOT? | 'fris' DOT?; SATURDAY : 'saturday' 's'? | 'sat' DOT? | 'sats' DOT? | 'weekend'; -HOUR : 'hour' | 'hours' | 'hr' | 'hrs'; -MINUTE : 'minute' | 'minutes' | 'min' | 'mins'; +HOUR : 'hour' | 'hours' | 'hr' DOT? | 'hrs' DOT?; +MINUTE : 'minute' | 'minutes' | 'min' DOT? | 'mins' DOT?; DAY : 'day' | 'days' ; -WEEK : 'week' | 'weeks' | 'wks'; +WEEK : 'week' | 'weeks' | 'wks' DOT?; MONTH : 'month' | 'months'; -YEAR : 'year' | 'years' | 'yrs'; +YEAR : 'year' | 'year' SINGLE_QUOTE? 's' | 'yrs' DOT?; TODAY : 'today'; TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow'; @@ -50,7 +50,6 @@ YESTERDAY : 'yesterday'; EVERY : 'every'; UNTIL : 'until'; - // ********** time rules ********** @@ -66,7 +65,7 @@ MILITARY_HOUR_SUFFIX : 'h'; MIDNIGHT : 'midnight' | 'mid-night'; NOON : 'noon' | 'afternoon' | 'after-noon'; MORNING : 'morning'; -EVENING : 'evening'; +EVENING : 'evening' | 'eve'; NIGHT : 'night'; UTC : 'utc' | 'gmt' | 'z'; @@ -277,6 +276,40 @@ WHITE_SPACE : (DOT | SPACE)+ ; +// ********** holiday specific ********** + +FOOL : 'fool' | 'fools' | 'fool' SINGLE_QUOTE 's'; +BLACK : 'black'; +CHRISTMAS : ('christmas' | 'xmas' | 'x-mas') 'es'?; +COLUMBUS : 'columbus'; +EARTH : 'earth'; +EASTER : 'easter'; +FATHER : 'father' | 'fathers' | 'father' SINGLE_QUOTE 's'; +FLAG : 'flag'; +GOOD : 'good'; +GROUNDHOG : GROUND WHITE_SPACE? HOG SINGLE_QUOTE? 's'?; +HALLOWEEN : ('halloween' | 'haloween') 's'?; +INAUGURATION : 'inauguration' | 'inaugaration'; +INDEPENDENCE : 'independence' | 'independance'; +KWANZAA : ('kwanza' 'a'?) 's'?; +LABOR : 'labor'; +MLK : 'mlk' | 'martin' WHITE_SPACE 'luther' WHITE_SPACE 'king' SINGLE_QUOTE? 's'? ('jr' DOT? SINGLE_QUOTE? 's'?)?; +MEMORIAL : 'memorial'; +MOTHER : 'mother' SINGLE_QUOTE? 's'?; +NEW : 'new'; +PALM : 'palm'; +PATRIOT : 'patriot' SINGLE_QUOTE? 's'?; +PRESIDENT : 'president' SINGLE_QUOTE? 's'?; +PATRICK : 'patrick' SINGLE_QUOTE? 's'? | 'paddy' SINGLE_QUOTE? 's'?; +SAINT : 'saint'; +TAX : 'tax'; +THANKSGIVING : 'thanksgiving' 's'?; +ELECTION : 'election'; +VALENTINE : 'valentine' SINGLE_QUOTE? 's'?; +VETERAN : 'veteran' SINGLE_QUOTE? 's'?; +fragment GROUND : 'ground'; +fragment HOG : 'hog'; + UNKNOWN : UNKNOWN_CHAR ; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 05f5df09..c42f1188 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -32,6 +32,7 @@ tokens { ZONE; ZONE_OFFSET; RECURRENCE; + HOLIDAY; } @header { @@ -85,6 +86,7 @@ date | relaxed_date | relative_date | explicit_relative_date + | holiday | global_date_prefix WHITE_SPACE date -> ^(RELATIVE_DATE ^(SEEK global_date_prefix date)) ; @@ -382,6 +384,9 @@ relative_date // today, tomorrow | named_relative_date + + | holiday + -> ^(RELATIVE_DATE holiday) ; // ********** explicit relative date rules ********** @@ -624,6 +629,109 @@ named_relative_time : NOW -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"])) ; +// ********** holidays ********** + +holiday + : relative_date_prefix WHITE_SPACE holiday_name + -> ^(SEEK relative_date_prefix holiday_name) + + | spelled_or_int_optional_prefix WHITE_SPACE holiday_name WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix holiday_name) + + | holiday_name + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] holiday_name) + ; + +holiday_name + : APRIL WHITE_SPACE FOOL (WHITE_SPACE DAY)? + -> HOLIDAY["APRIL_FOOLS_DAY"] + + | BLACK WHITE_SPACE FRIDAY + -> HOLIDAY["BLACK_FRIDAY"] + + | CHRISTMAS WHITE_SPACE EVENING + -> HOLIDAY["CHRISTMAS_EVE"] + + | CHRISTMAS (WHITE_SPACE DAY)? + -> HOLIDAY["CHRISTMAS"] + + | COLUMBUS WHITE_SPACE DAY + -> HOLIDAY["COLUMBUS_DAY"] + + | EARTH WHITE_SPACE DAY + -> HOLIDAY["EARTH_DAY"] + + | EASTER (WHITE_SPACE (SUNDAY | DAY))? + -> HOLIDAY["EASTER"] + + | FATHER WHITE_SPACE DAY + -> HOLIDAY["FATHERS_DAY"] + + | FLAG WHITE_SPACE DAY + -> HOLIDAY["FLAG_DAY"] + + | GOOD WHITE_SPACE FRIDAY + -> HOLIDAY["GOOD_FRIDAY"] + + | GROUNDHOG WHITE_SPACE? DAY + -> HOLIDAY["GROUNDHOG_DAY"] + + | HALLOWEEN (WHITE_SPACE DAY)? + -> HOLIDAY["HALLOWEEN"] + + | INAUGURATION WHITE_SPACE DAY + -> HOLIDAY["INAUGURATION_DAY"] + + | INDEPENDENCE WHITE_SPACE DAY + -> HOLIDAY["INDEPENDENCE_DAY"] + + | KWANZAA (WHITE_SPACE DAY)? + -> HOLIDAY["KWANZAA"] + + | LABOR WHITE_SPACE DAY + -> HOLIDAY["LABOR_DAY"] + + | MLK WHITE_SPACE DAY + -> HOLIDAY["MLK_DAY"] + + | MEMORIAL WHITE_SPACE DAY + -> HOLIDAY["MEMORIAL_DAY"] + + | MOTHER WHITE_SPACE DAY + -> HOLIDAY["MOTHERS_DAY"] + + | NEW WHITE_SPACE YEAR WHITE_SPACE EVENING + -> HOLIDAY["NEW_YEARS_EVE"] + + | NEW WHITE_SPACE YEAR (WHITE_SPACE DAY)? + -> HOLIDAY["NEW_YEARS_DAY"] + + | PATRIOT WHITE_SPACE DAY + -> HOLIDAY["PATRIOT_DAY"] + + | PRESIDENT WHITE_SPACE DAY + -> HOLIDAY["PRESIDENTS_DAY"] + + | (SAINT | ST DOT?) WHITE_SPACE PATRICK WHITE_SPACE DAY + -> HOLIDAY["ST_PATRICKS_DAY"] + + | TAX WHITE_SPACE DAY + -> HOLIDAY["TAX_DAY"] + + | THANKSGIVING (WHITE_SPACE DAY)? + -> HOLIDAY["THANKSGIVING"] + + | ELECTION WHITE_SPACE DAY + -> HOLIDAY["ELECTION_DAY"] + + | VALENTINE WHITE_SPACE DAY + -> HOLIDAY["VALENTINES_DAY"] + + | VETERAN WHITE_SPACE DAY + -> HOLIDAY["VETERANS_DAY"] + ; + + // ********** time rules ********** relative_time diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 366138b7..8d6085f2 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -96,6 +96,9 @@ seek | ^(SEEK DIRECTION SEEK_BY INT date) {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SEEK_BY.text);} + + | ^(SEEK DIRECTION SEEK_BY INT HOLIDAY) + {_walkerState.seekToHoliday($DIRECTION.text, $INT.text, $HOLIDAY.text);} ; explicit_seek diff --git a/src/main/java/com/joestelmach/natty/Holiday.java b/src/main/java/com/joestelmach/natty/Holiday.java new file mode 100644 index 00000000..99128df8 --- /dev/null +++ b/src/main/java/com/joestelmach/natty/Holiday.java @@ -0,0 +1,58 @@ +package com.joestelmach.natty; + +import java.util.HashMap; +import java.util.Map; + +public enum Holiday { + APRIL_FOOLS_DAY("April Fool's Day"), + BLACK_FRIDAY("Black Friday"), + CHRISTMAS("Christmas Day"), + CHRISTMAS_EVE("Christmas Eve"), + COLUMBUS_DAY("Columbus Day (US-OPM)"), + EARTH_DAY("Earth Day"), + EASTER("Easter Sunday"), + FATHERS_DAY("Father's Day"), + FLAG_DAY("Flag Day"), + GOOD_FRIDAY("Good Friday"), + GROUNDHOG_DAY("Groundhog's Day"), + HALLOWEEN("Halloween"), + INAUGURATION_DAY("Inauguration Day"), + INDEPENDENCE_DAY("Independence Day"), + KWANZAA("Kwanzaa"), + LABOR_DAY("Labor Day"), + MLK_DAY("Martin Luther King Jr.'s Day"), + MEMORIAL_DAY("Memorial Day"), + MOTHERS_DAY("Mother's Day"), + NEW_YEARS_DAY("New Year's Day"), + NEW_YEARS_EVE("New Year's Eve"), + PATRIOT_DAY("Patriot Day"), + PRESIDENTS_DAY("President's Day"), + ST_PATRICKS_DAY("St. Patrick's Day"), + TAX_DAY("Tax Day"), + THANKSGIVING("Thanksgiving Day"), + ELECTION_DAY("US General Election"), + VALENTINES_DAY("Valentine's Day"), + VETERANS_DAY("Veteran's Day"); + + private String summary; + private static final Map lookup; + static { + lookup = new HashMap(); + for(Holiday h:values()) { + lookup.put(h.getSummary(), h); + } + } + + Holiday(String summary) { + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public static Holiday fromSummary(String summary) { + if(summary == null) return null; + return lookup.get(summary); + } +} diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 0047cb72..10eaf261 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -44,6 +44,16 @@ public Parser(TimeZone defaultTimeZone) { */ public Parser() { _defaultTimeZone = TimeZone.getDefault(); + + try { + //FileInputStream fin = new FileInputStream("mycalendar.ics"); + + //CalendarBuilder builder = new CalendarBuilder(); + + //Calendar calendar = builder.build(fin); + } catch(Exception e) { + e.printStackTrace(); + } } /** @@ -80,14 +90,14 @@ public List parse(String value) { tokens.clear(); } - // otherwise, we look at the token stream for anotherpossible starting point, - // and attempt to reparse with that sub-sequence of tokens + // otherwise, we look at the token stream for another possible starting point, + // and attempt to re-parse with that sub-sequence of tokens else { tokens = tokens.subList(2, tokens.size()); Iterator iter = tokens.iterator(); while(iter.hasNext()) { Token token = iter.next(); - if(!DateParser.FOLLOW_empty_in_parse176.member(token.getType())) { + if(!DateParser.FOLLOW_empty_in_parse181.member(token.getType())) { iter.remove(); } else { @@ -133,7 +143,7 @@ private DateGroup singleParse(TokenStream stream) { Tree tree = (Tree) parseReturn.getTree(); _logger.fine("AST: " + tree.toStringTree()); - // we only coninue if a meaningful syntax tree has been built + // we only continue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) @@ -192,7 +202,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse176.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse181.member(currentToken.getType())) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 7af68a52..b402370b 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -370,6 +370,10 @@ public void setExplicitTime(String hours, String minutes, String seconds, String _calendar.set(Calendar.MINUTE, minutesInt); } + public void seekToHoliday(String direction, String amount, String holiday) { + System.out.println("seeking " + direction + " " + amount + " " + holiday); + } + /** * */ diff --git a/src/main/resources/holidays.ics b/src/main/resources/holidays.ics new file mode 100644 index 00000000..fbc4c9fd --- /dev/null +++ b/src/main/resources/holidays.ics @@ -0,0 +1,470 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +VERSION:2.0 +X-WR-CALNAME:US Holidays +PRODID:-//Apple Inc.//iCal 5.0//EN +X-APPLE-CALENDAR-COLOR:#0252D4 +X-WR-TIMEZONE:America/Los_Angeles +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20090721T214849Z +UID:EBDD92C4-0F7D-4E7D-8A2A-C0C80A4A6F4E +DTEND;VALUE=DATE:20110101 +TRANSP:TRANSPARENT +SUMMARY:New Year's Day (observed) +DTSTART;VALUE=DATE:20101231 +DTSTAMP:20090722T145911Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:56159EEC-79B9-4873-89DE-A3FEDE4894CE +DTEND;VALUE=DATE:20090309 +EXDATE;VALUE=DATE:20080309 +EXDATE;VALUE=DATE:20070311 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TRANSP:OPAQUE +SUMMARY:Daylight Savings Time Begins +DTSTART;VALUE=DATE:20090308 +DTSTAMP:20090803T233855Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:BECEEEB7-FAE0-4825-B554-2BA7D156DC2D +DTEND;VALUE=DATE:20090511 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=2SU +TRANSP:OPAQUE +SUMMARY:Mother's Day +DTSTART;VALUE=DATE:20090510 +DTSTAMP:20090729T011715Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090803T233245Z +UID:B7AFC343-46DD-446C-8D6B-A76A5EFE53F3 +DTEND;VALUE=DATE:20091104 +RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20111031;BYMONTH=11;BYDAY=1TU +TRANSP:TRANSPARENT +SUMMARY:Election Day +DTSTART;VALUE=DATE:20091103 +DTSTAMP:20101214T190758Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:31DE2253-AE78-44BA-9833-7BD075158D8B +DTEND;VALUE=DATE:20090203 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +TRANSP:OPAQUE +SUMMARY:Groundhog Day +DTSTART;VALUE=DATE:20090202 +DTSTAMP:20090721T160455Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:A5279A3A-5C32-4774-A415-DE5FCC2C3E02 +DTEND;VALUE=DATE:20090217 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYDAY=3MO +TRANSP:OPAQUE +SUMMARY:President's Day +DTSTART;VALUE=DATE:20090216 +DTSTAMP:20090803T233757Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:FC2E5732-CD51-4569-BA85-19B1F5AB89B2 +DTEND;VALUE=DATE:20090402 +RRULE:FREQ=YEARLY;INTERVAL=1 +TRANSP:OPAQUE +SUMMARY:April Fool's Day +DTSTART;VALUE=DATE:20090401 +DTSTAMP:20090721T161433Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:2D1DC405-2F66-47D5-831F-98CC41657CC4 +DTEND;VALUE=DATE:20090116 +EXDATE;VALUE=DATE:20090115 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=3MO +TRANSP:OPAQUE +SUMMARY:Martin Luther King\, Jr. Day +DTSTART;VALUE=DATE:20090115 +DTSTAMP:20090803T233129Z +SEQUENCE:13 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T215938Z +UID:B45EB281-2E5F-4BD7-AA89-536817EB09C3 +DTEND;VALUE=DATE:20110425 +TRANSP:TRANSPARENT +SUMMARY:Easter +DTSTART;VALUE=DATE:20110424 +DTSTAMP:20090803T234817Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:F023551A-8AC7-42D1-9074-EEDA6D3793F4 +DTEND;VALUE=DATE:20090416 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4 +TRANSP:OPAQUE +SUMMARY:Tax Day +DTSTART;VALUE=DATE:20090415 +DTSTAMP:20090721T165038Z +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:5FAEA417-F999-4910-B815-09D4E77544D4 +DTEND;VALUE=DATE:20090102 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1 +TRANSP:OPAQUE +SUMMARY:New Year's Day +DTSTART;VALUE=DATE:20090101 +DTSTAMP:20090721T172608Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:2E325A12-42A5-4B5C-9BDA-A6034BBD2EDB +DTEND;VALUE=DATE:20090213 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +TRANSP:OPAQUE +SUMMARY:Lincoln's Birthday +DTSTART;VALUE=DATE:20090212 +DTSTAMP:20090721T160539Z +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:49AEB08C-C39B-4253-A83A-741BA443181E +DTEND;VALUE=DATE:20090530 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5 +TRANSP:OPAQUE +SUMMARY:John F. Kennedy's Birthday +DTSTART;VALUE=DATE:20090529 +DTSTAMP:20090721T165324Z +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:F31BC84F-0682-426F-A7A8-497520D80245 +DTEND;VALUE=DATE:20090526 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=-1MO +TRANSP:OPAQUE +SUMMARY:Memorial Day +DTSTART;VALUE=DATE:20090525 +DTSTAMP:20090721T212538Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:D53ABCFC-5BA7-4500-81BD-93FCDD264DF4 +DTEND;VALUE=DATE:20090413 +TRANSP:OPAQUE +SUMMARY:Easter +DTSTART;VALUE=DATE:20090412 +DTSTAMP:20090803T233908Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:A51F6546-E2D2-4BED-AFD0-C728899142CD +DTEND;VALUE=DATE:20090616 +EXDATE;VALUE=DATE:20090615 +RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20090615;BYMONTH=6;BYDAY=3TU +TRANSP:OPAQUE +SUMMARY:Father's Day +DTSTART;VALUE=DATE:20090615 +DTSTAMP:20090729T011415Z +SEQUENCE:2 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:2753AF22-1699-4FFF-91C8-3247F3C132FB +DTEND;VALUE=DATE:20070312 +EXDATE;VALUE=DATE:20080309 +EXDATE;VALUE=DATE:20070311 +RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20090307;BYMONTH=3;BYDAY=2SU +TRANSP:OPAQUE +SUMMARY:Daylight Savings Time Begins +DTSTART;VALUE=DATE:20070311 +DTSTAMP:20090721T215715Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T214600Z +UID:81F3B43D-8342-4D48-906A-04BA8A82F14D +DTEND;VALUE=DATE:20090704 +TRANSP:TRANSPARENT +SUMMARY:Independence Day (observed) +DTSTART;VALUE=DATE:20090703 +DTSTAMP:20090803T234443Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T215448Z +UID:CAD6CCCC-701A-4965-B3B3-0B460F4DA2BC +DTEND;VALUE=DATE:20121113 +TRANSP:TRANSPARENT +SUMMARY:Veteran's Day (observed) +DTSTART;VALUE=DATE:20121112 +DTSTAMP:20090722T145635Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:224FF2DF-00F8-4132-95FF-D780C0686A5F +DTEND;VALUE=DATE:20091226 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12 +TRANSP:OPAQUE +SUMMARY:Christmas +DTSTART;VALUE=DATE:20091225 +DTSTAMP:20090803T233541Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:FC1DAF58-B6B3-444F-B8B8-58D9F9C1FD3C +DTEND;VALUE=DATE:20090615 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6 +TRANSP:OPAQUE +SUMMARY:Flag Day +DTSTART;VALUE=DATE:20090614 +DTSTAMP:20090721T165347Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T215244Z +UID:DE2DDD5A-743E-485F-BE61-D71C796E6E86 +DTEND;VALUE=DATE:20111227 +TRANSP:TRANSPARENT +SUMMARY:Christmas (observed) +DTSTART;VALUE=DATE:20111226 +DTSTAMP:20090803T234900Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:18BAF95A-3960-4D45-A148-6079CF5B4524 +DTEND;VALUE=DATE:20091013 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=2MO +TRANSP:OPAQUE +SUMMARY:Columbus Day +DTSTART;VALUE=DATE:20091012 +DTSTAMP:20090721T214236Z +SEQUENCE:7 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:87F1C3C2-678E-4B5B-8B79-C34CDDF08BEA +DTEND;VALUE=DATE:20090318 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 +TRANSP:OPAQUE +SUMMARY:St. Patrick's Day +DTSTART;VALUE=DATE:20090317 +DTSTAMP:20090721T160611Z +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:0EFABF56-241C-4A88-AC3A-86E584B08F12 +DTEND;VALUE=DATE:20090506 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5 +TRANSP:OPAQUE +SUMMARY:Cinco de Mayo +DTSTART;VALUE=DATE:20090505 +DTSTAMP:20090721T165100Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:B082D697-41C2-4B14-B167-00B0497FF2D9 +DTEND;VALUE=DATE:20090215 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 +TRANSP:OPAQUE +SUMMARY:Valentine's Day +DTSTART;VALUE=DATE:20090214 +DTSTAMP:20090721T160548Z +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:5AC21774-EB84-44F5-AA73-ABD21A50D2B2 +DTEND;VALUE=DATE:20090423 +RRULE:FREQ=YEARLY;INTERVAL=1 +TRANSP:OPAQUE +SUMMARY:Earth Day +DTSTART;VALUE=DATE:20090422 +DTSTAMP:20090721T165049Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:6BF1B371-0F0E-436B-8ACE-7D320022FD12 +DTEND;VALUE=DATE:20091112 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11 +TRANSP:OPAQUE +SUMMARY:Veteran's Day +DTSTART;VALUE=DATE:20091111 +DTSTAMP:20090721T172022Z +SEQUENCE:2 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:DF047E5D-E1BA-4DE8-82AC-FC61D621A008 +DTEND;VALUE=DATE:20100405 +TRANSP:OPAQUE +SUMMARY:Easter +DTSTART;VALUE=DATE:20100404 +DTSTAMP:20090803T234104Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T214326Z +UID:57A98619-B009-46AE-B213-D0AF4FB4E78A +DTEND;VALUE=DATE:20101225 +TRANSP:TRANSPARENT +SUMMARY:Christmas (observed) +DTSTART;VALUE=DATE:20101224 +DTSTAMP:20090803T234719Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T211645Z +UID:1F7D3F06-FD6E-48E2-9808-700F7675D0F1 +DTEND;VALUE=DATE:20090908 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9;BYDAY=1MO +TRANSP:TRANSPARENT +SUMMARY:Labor Day +DTSTART;VALUE=DATE:20090907 +DTSTAMP:20090722T145407Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:B35A624C-6FD5-4C98-B390-AF0FA7AFA997 +DTEND;VALUE=DATE:20091101 +RRULE:FREQ=YEARLY;INTERVAL=1 +TRANSP:OPAQUE +SUMMARY:Halloween +DTSTART;VALUE=DATE:20091031 +DTSTAMP:20090721T172638Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090803T234355Z +UID:B0399DCB-D02F-4758-9273-E4902A723569 +DTEND;VALUE=DATE:20090705 +RRULE:FREQ=YEARLY;INTERVAL=1 +TRANSP:TRANSPARENT +SUMMARY:Independence Day +DTSTART;VALUE=DATE:20090704 +DTSTAMP:20090803T234436Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:AC6D39D2-A6D6-4B50-8586-09346F1A87D3 +DTEND;VALUE=DATE:20090622 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6;BYDAY=3SU +TRANSP:OPAQUE +SUMMARY:Father's Day +DTSTART;VALUE=DATE:20090621 +DTSTAMP:20090729T011415Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090803T234608Z +UID:4162B846-C557-48FD-B635-E70EED88243B +DTEND;VALUE=DATE:20100706 +TRANSP:TRANSPARENT +SUMMARY:Independence Day (observed) +DTSTART;VALUE=DATE:20100705 +DTSTAMP:20090803T234625Z +SEQUENCE:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:7B16E9B7-22B1-43C3-ABE2-88F29ED00000 +DTEND;VALUE=DATE:20091127 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=4TH +TRANSP:OPAQUE +SUMMARY:Thanksgiving +DTSTART;VALUE=DATE:20091126 +DTSTAMP:20090803T233519Z +SEQUENCE:7 +END:VEVENT +BEGIN:VEVENT +CREATED:20090803T233815Z +UID:54BA8F70-FD4C-4BB6-A225-AA1A4D7092BE +DTEND;VALUE=DATE:20090223 +RRULE:FREQ=YEARLY;INTERVAL=1 +TRANSP:TRANSPARENT +SUMMARY:Washington's Birthday +DTSTART;VALUE=DATE:20090222 +DTSTAMP:20090803T234047Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T220008Z +UID:49A489DC-9C11-4714-A2C6-1453FCE3922C +DTEND;VALUE=DATE:20120409 +TRANSP:TRANSPARENT +SUMMARY:Easter +DTSTART;VALUE=DATE:20120408 +DTSTAMP:20090803T235036Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T215312Z +UID:305E36CE-99A5-4C38-A94E-B349E0E673AB +DTEND;VALUE=DATE:20120103 +TRANSP:TRANSPARENT +SUMMARY:New Year's Day (observed) +DTSTART;VALUE=DATE:20120102 +DTSTAMP:20090803T234928Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:8EF5993C-76BD-4235-BA9A-9875E0818B62 +DTEND;VALUE=DATE:20071105 +EXDATE;VALUE=DATE:20081102 +EXDATE;VALUE=DATE:20071104 +RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20091031;BYMONTH=11;BYDAY=1SU +TRANSP:OPAQUE +SUMMARY:Daylight Savings Time Ends +DTSTART;VALUE=DATE:20071104 +DTSTAMP:20090721T215746Z +SEQUENCE:4 +END:VEVENT +BEGIN:VEVENT +CREATED:20090721T010305Z +UID:84AD11F7-8AB7-44C8-8C50-7DDB3CAB6A04 +DTEND;VALUE=DATE:20091102 +EXDATE;VALUE=DATE:20081102 +EXDATE;VALUE=DATE:20071104 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TRANSP:OPAQUE +SUMMARY:Daylight Savings Time Ends +DTSTART;VALUE=DATE:20091101 +DTSTAMP:20090803T233528Z +SEQUENCE:5 +END:VEVENT +BEGIN:VEVENT +CREATED:20090803T233245Z +UID:376326ED-123D-412A-A88F-87F7F34EA9EB +DTEND;VALUE=DATE:20111109 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=2TU +TRANSP:TRANSPARENT +SUMMARY:Election Day +DTSTART;VALUE=DATE:20111108 +DTSTAMP:20101214T190758Z +SEQUENCE:5 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 26c0a547..ba562c6b 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -231,7 +231,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "last 2 weeks"; + String value = "april fool's day"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/HolidayTest.java b/src/test/java/com/joestelmach/natty/HolidayTest.java new file mode 100644 index 00000000..923c8aa0 --- /dev/null +++ b/src/test/java/com/joestelmach/natty/HolidayTest.java @@ -0,0 +1,50 @@ +package com.joestelmach.natty; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Period; +import edu.emory.mathcs.backport.java.util.Collections; + +public class HolidayTest { + + public static void main(String[] args) throws Exception { + InputStream fin = HolidayTest.class.getResourceAsStream("/holidays.ics"); + CalendarBuilder builder = new CalendarBuilder(); + Calendar cal = builder.build(fin); + + // Create the date range which is desired. + DateTime from = new DateTime("20000101T070000Z"); + DateTime to = new DateTime("20301231T070000Z");; + Period period = new Period(from, to); + + // For each VEVENT in the ICS + Set names = new HashSet(); + for (Object o : cal.getComponents("VEVENT")) { + Component c = (Component) o; + names.add(c.getProperty("SUMMARY").getValue()); + + /* + PeriodList list = c.calculateRecurrenceSet(period); + for (Object po : list) { + Period p = (Period) po; + System.out.println(p.getRangeStart()); + //System.out.println((Period) po); + } + */ + } + List list = new ArrayList(names); + Collections.sort(list); + + for(String name:list) { + System.out.println(name); + } + } +} diff --git a/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java new file mode 100644 index 00000000..bed6a4c7 --- /dev/null +++ b/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java @@ -0,0 +1,131 @@ +package com.joestelmach.natty.grammar; + +import org.junit.Test; + +public class HolidayGrammarTest extends AbstractGrammarTest { + + @Test + public void names() throws Exception { + _ruleName = "holiday_name"; + + assertAST("april fool's day", "APRIL_FOOLS_DAY"); + assertAST("april fools day", "APRIL_FOOLS_DAY"); + assertAST("april fool day", "APRIL_FOOLS_DAY"); + + assertAST("black friday", "BLACK_FRIDAY"); + assertAST("black fri", "BLACK_FRIDAY"); + assertAST("black fri.", "BLACK_FRIDAY"); + + assertAST("christmas", "CHRISTMAS"); + assertAST("christmas day", "CHRISTMAS"); + + assertAST("christmas eve", "CHRISTMAS_EVE"); + assertAST("christmas eve.", "CHRISTMAS_EVE"); + assertAST("christmas evening", "CHRISTMAS_EVE"); + + assertAST("columbus day", "COLUMBUS_DAY"); + + assertAST("earth day", "EARTH_DAY"); + + assertAST("easter", "EASTER"); + assertAST("easter day", "EASTER"); + assertAST("easter sunday", "EASTER"); + + assertAST("father's day", "FATHERS_DAY"); + assertAST("fathers day", "FATHERS_DAY"); + + assertAST("flag day", "FLAG_DAY"); + + assertAST("good friday", "GOOD_FRIDAY"); + assertAST("good fri", "GOOD_FRIDAY"); + assertAST("good fri.", "GOOD_FRIDAY"); + + assertAST("groundhog day", "GROUNDHOG_DAY"); + assertAST("groundhogs day", "GROUNDHOG_DAY"); + assertAST("groundhog's day", "GROUNDHOG_DAY"); + + assertAST("halloween", "HALLOWEEN"); + assertAST("haloween", "HALLOWEEN"); + assertAST("halloween day", "HALLOWEEN"); + + assertAST("inauguration day", "INAUGURATION_DAY"); + assertAST("inaugaration day", "INAUGURATION_DAY"); + + assertAST("independence day", "INDEPENDENCE_DAY"); + + assertAST("kwanzaa", "KWANZAA"); + assertAST("kwanza", "KWANZAA"); + assertAST("kwanzaa day", "KWANZAA"); + + assertAST("labor day", "LABOR_DAY"); + + assertAST("martin luther king day", "MLK_DAY"); + assertAST("martin luther king jr.'s day", "MLK_DAY"); + assertAST("martin luther king jr. day", "MLK_DAY"); + assertAST("martin luther king jr day", "MLK_DAY"); + assertAST("mlk day", "MLK_DAY"); + + assertAST("memorial day", "MEMORIAL_DAY"); + + assertAST("memorial day", "MEMORIAL_DAY"); + + assertAST("mothers day", "MOTHERS_DAY"); + assertAST("mother's day", "MOTHERS_DAY"); + + assertAST("new year's day", "NEW_YEARS_DAY"); + assertAST("new years day", "NEW_YEARS_DAY"); + assertAST("new years", "NEW_YEARS_DAY"); + + assertAST("new year's eve", "NEW_YEARS_EVE"); + assertAST("new years eve", "NEW_YEARS_EVE"); + assertAST("new years eve.", "NEW_YEARS_EVE"); + + assertAST("patriot day", "PATRIOT_DAY"); + + assertAST("president's day", "PRESIDENTS_DAY"); + assertAST("presidents day", "PRESIDENTS_DAY"); + assertAST("president day", "PRESIDENTS_DAY"); + + assertAST("st. patricks day", "ST_PATRICKS_DAY"); + assertAST("st patrick's day", "ST_PATRICKS_DAY"); + assertAST("saint patrick's day", "ST_PATRICKS_DAY"); + assertAST("saint paddy's day", "ST_PATRICKS_DAY"); + assertAST("saint paddys day", "ST_PATRICKS_DAY"); + + assertAST("tax day", "TAX_DAY"); + + assertAST("thanksgiving", "THANKSGIVING"); + assertAST("thanksgiving day", "THANKSGIVING"); + + assertAST("election day", "ELECTION_DAY"); + + assertAST("valentine's day", "VALENTINES_DAY"); + assertAST("valentines day", "VALENTINES_DAY"); + assertAST("valentine day", "VALENTINES_DAY"); + + assertAST("veterans day", "VETERANS_DAY"); + assertAST("veteran's day", "VETERANS_DAY"); + assertAST("veteran day", "VETERANS_DAY"); + } + + @Test + public void statments() throws Exception { + _ruleName = "holiday"; + + assertAST("april fool's day", "(SEEK > by_day 1 APRIL_FOOLS_DAY)"); + + assertAST("next christmas", "(SEEK > by_week 1 CHRISTMAS)"); + assertAST("coming christmas", "(SEEK > by_day 1 CHRISTMAS)"); + assertAST("upcoming christmas", "(SEEK > by_day 1 CHRISTMAS)"); + + assertAST("last halloween", "(SEEK < by_week 1 HALLOWEEN)"); + assertAST("past halloween", "(SEEK < by_day 1 HALLOWEEN)"); + + assertAST("in three christmases", "(SEEK > by_day 3 CHRISTMAS)"); + + assertAST("three christmases from now", "(SEEK > by_day 3 CHRISTMAS)"); + assertAST("3 christmases ago", "(SEEK < by_day 3 CHRISTMAS)"); + + assertAST("in 10 thanksgivings", "(SEEK > by_day 10 THANKSGIVING)"); + } +} diff --git a/src/test/resources/holidays.ics b/src/test/resources/holidays.ics new file mode 100644 index 00000000..20b9fc35 --- /dev/null +++ b/src/test/resources/holidays.ics @@ -0,0 +1,3316 @@ +BEGIN:VCALENDAR +COMMENT: http://www.mozilla.org/projects/calendar/caldata/USHolidays.ics +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20080827T223540Z +LAST-MODIFIED:20090611T212419Z +UID:b8388cae-2125-4614-9801-5fec40a07408 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000423 +DTEND;VALUE=DATE:20000424 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223613Z +LAST-MODIFIED:20090611T212419Z +UID:27198947-51d4-475c-9116-6dd475ab4729 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010415 +DTEND;VALUE=DATE:20010416 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223633Z +LAST-MODIFIED:20090611T212419Z +UID:adebaeec-b7da-4d1e-baff-73e54a5bbe5d +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020331 +DTEND;VALUE=DATE:20020401 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223650Z +LAST-MODIFIED:20090611T212419Z +UID:3378f42f-00f3-42be-888e-8368d5e140b2 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030420 +DTEND;VALUE=DATE:20030421 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223704Z +LAST-MODIFIED:20090611T212419Z +UID:18eae8ac-65e1-4cc0-8bf3-0c287882fbd9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040411 +DTEND;VALUE=DATE:20040412 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223726Z +LAST-MODIFIED:20090611T212419Z +UID:d0a68adf-3eca-4585-a7a7-853b5d5b03e4 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050327 +DTEND;VALUE=DATE:20050328 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223741Z +LAST-MODIFIED:20090611T212419Z +UID:daf76dce-468b-4f99-8d42-01487f8fafa9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060416 +DTEND;VALUE=DATE:20060417 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223757Z +LAST-MODIFIED:20090611T212419Z +UID:fa7a1db3-1c5f-472d-9ee4-988d0d0287eb +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070408 +DTEND;VALUE=DATE:20070409 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223818Z +LAST-MODIFIED:20090611T212419Z +UID:69230f5f-f2d2-428d-a90c-a7e1c7deea5e +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090412 +DTEND;VALUE=DATE:20090413 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223828Z +LAST-MODIFIED:20090611T212419Z +UID:0bc494ff-69b5-4dca-8699-c4fe5ea25a13 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100404 +DTEND;VALUE=DATE:20100405 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223934Z +LAST-MODIFIED:20090611T212419Z +UID:44a937c0-f0d8-4458-947c-8a33db7217ef +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120408 +DTEND;VALUE=DATE:20120409 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223946Z +LAST-MODIFIED:20090611T212419Z +UID:734e6d03-ab3c-4693-8c81-f73e3b576c27 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130331 +DTEND;VALUE=DATE:20130401 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224000Z +LAST-MODIFIED:20090611T212419Z +UID:c4d6a9e0-a5d6-41d4-9297-97e4fdcc85a5 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140420 +DTEND;VALUE=DATE:20140421 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224012Z +LAST-MODIFIED:20090611T212419Z +UID:b2cab02a-a164-44a4-af66-ccb1b3c253b9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150405 +DTEND;VALUE=DATE:20150406 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224028Z +LAST-MODIFIED:20090611T212419Z +UID:763686f7-3ec4-4dfc-9a33-e4c0bae652fa +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160327 +DTEND;VALUE=DATE:20160328 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224043Z +LAST-MODIFIED:20090611T212419Z +UID:31edb1e8-a064-4a8b-a041-eecd22a4d419 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170416 +DTEND;VALUE=DATE:20170417 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224101Z +LAST-MODIFIED:20090611T212419Z +UID:d869e442-0b4d-40c4-b34c-6780afce8c46 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180401 +DTEND;VALUE=DATE:20180402 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224121Z +LAST-MODIFIED:20090611T212419Z +UID:135738b2-b8d5-4e65-bf7c-736e22c6b8b1 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190421 +DTEND;VALUE=DATE:20190422 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T224133Z +LAST-MODIFIED:20090611T212419Z +UID:7606494f-0019-46e6-9d47-ab9498569e5a +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200412 +DTEND;VALUE=DATE:20200413 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212419Z +UID:c5c154cc-1dd1-11b2-85c7-e3de2bfad30f +SUMMARY:Easter Sunday +STATUS:CONFIRMED +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080323 +DTEND;VALUE=DATE:20080324 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024422Z +LAST-MODIFIED:20090611T212419Z +UID:5e604c83-cacc-46fe-bc40-b4f50e2412e7 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000308 +DTEND;VALUE=DATE:20000309 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024533Z +LAST-MODIFIED:20090611T212419Z +UID:847021ce-03b6-4f2d-abe5-2ba869066b5d +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010228 +DTEND;VALUE=DATE:20010301 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024626Z +LAST-MODIFIED:20090611T212419Z +UID:0e313bdb-171d-40dd-9ddb-1dfb4a777bb5 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020213 +DTEND;VALUE=DATE:20020214 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024822Z +LAST-MODIFIED:20090611T212419Z +UID:1577ed41-e8c5-45f9-83e5-86079fe2510b +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030305 +DTEND;VALUE=DATE:20030306 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024847Z +LAST-MODIFIED:20090611T212419Z +UID:fcbfe576-82e9-4f68-b1ad-5366ff6968ac +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040225 +DTEND;VALUE=DATE:20040226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024911Z +LAST-MODIFIED:20090611T212419Z +UID:a403894c-bb2c-4d02-8dca-91c359a54bfe +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050209 +DTEND;VALUE=DATE:20050210 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024924Z +LAST-MODIFIED:20090611T212419Z +UID:dcc3c2f4-d214-49d8-bb3e-f13c6acf3295 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060301 +DTEND;VALUE=DATE:20060302 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024939Z +LAST-MODIFIED:20090611T212419Z +UID:b81a6c71-9a3d-49a5-b695-8ac5f1bb013b +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070221 +DTEND;VALUE=DATE:20070222 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T024951Z +LAST-MODIFIED:20090611T212419Z +UID:40090c1d-8669-4d7a-b63a-288006cfac65 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080206 +DTEND;VALUE=DATE:20080207 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025003Z +LAST-MODIFIED:20090611T212419Z +UID:609ce29c-966c-40b4-81c4-8ccac74e98dd +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090225 +DTEND;VALUE=DATE:20090226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025017Z +LAST-MODIFIED:20090611T212419Z +UID:f96c0bf5-1cf2-4d2e-9955-eb89f4e645d3 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100217 +DTEND;VALUE=DATE:20100218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025046Z +LAST-MODIFIED:20090611T212419Z +UID:a0b949a8-2a75-4617-a687-1e7dffc098f5 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120222 +DTEND;VALUE=DATE:20120223 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025058Z +LAST-MODIFIED:20090611T212419Z +UID:8caed879-6158-477c-9901-e1531dc855e8 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130213 +DTEND;VALUE=DATE:20130214 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025131Z +LAST-MODIFIED:20090611T212419Z +UID:69e06d74-9acc-4667-8eb0-7c33299d82c0 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140305 +DTEND;VALUE=DATE:20140306 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025146Z +LAST-MODIFIED:20090611T212419Z +UID:a05d7fa6-4c65-4465-a32a-1b5c2d1189aa +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150218 +DTEND;VALUE=DATE:20150219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025202Z +LAST-MODIFIED:20090611T212419Z +UID:ce5fe814-c4b3-4aab-8298-86f54e34ffe4 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160210 +DTEND;VALUE=DATE:20160211 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025220Z +LAST-MODIFIED:20090611T212419Z +UID:746c3ee3-3439-41bd-9ee1-7944812a8a29 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170301 +DTEND;VALUE=DATE:20170302 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025235Z +LAST-MODIFIED:20090611T212419Z +UID:6eff84ea-6763-46d3-9c64-b847376f3e34 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180214 +DTEND;VALUE=DATE:20180215 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025254Z +LAST-MODIFIED:20090611T212419Z +UID:e5d798cf-474f-42c3-83b8-0d1186c2c3ad +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190306 +DTEND;VALUE=DATE:20190307 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025309Z +LAST-MODIFIED:20090611T212419Z +UID:9d17488f-12b8-474b-8790-d22e6786735e +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200226 +DTEND;VALUE=DATE:20200227 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025421Z +LAST-MODIFIED:20090611T212419Z +UID:1abe0e7a-fa55-42b8-b6cc-bf037ab5c7f4 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000416 +DTEND;VALUE=DATE:20000417 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025457Z +LAST-MODIFIED:20090611T212419Z +UID:ff8dd4a7-0593-4fb6-bcdc-7cd8db41a451 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010408 +DTEND;VALUE=DATE:20010409 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025510Z +LAST-MODIFIED:20090611T212419Z +UID:e403c114-aa09-4dcd-b8cf-b342be833477 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020324 +DTEND;VALUE=DATE:20020325 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025522Z +LAST-MODIFIED:20090611T212419Z +UID:8ebc922a-a220-456f-937b-a943e5df123b +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030413 +DTEND;VALUE=DATE:20030414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025549Z +LAST-MODIFIED:20090611T212419Z +UID:e03d10c1-ce57-426f-9a08-cd49c25920a9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040404 +DTEND;VALUE=DATE:20040405 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025630Z +LAST-MODIFIED:20090611T212419Z +UID:8ed86db1-ea90-453a-a1cd-36878811826e +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070401 +DTEND;VALUE=DATE:20070402 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025646Z +LAST-MODIFIED:20090611T212419Z +UID:b45150e2-da4d-477b-aace-b255186d89b1 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080316 +DTEND;VALUE=DATE:20080317 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025820Z +LAST-MODIFIED:20090611T212419Z +UID:96b2e223-1b4f-44e1-80a3-8b57ab7d2f12 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090405 +DTEND;VALUE=DATE:20090406 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025837Z +LAST-MODIFIED:20090611T212419Z +UID:863b0192-787d-4b4b-8550-c7e2db820b0a +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100328 +DTEND;VALUE=DATE:20100329 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025908Z +LAST-MODIFIED:20090611T212419Z +UID:ac159587-0a97-4f98-b11c-2193fe61265a +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120401 +DTEND;VALUE=DATE:20120402 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025922Z +LAST-MODIFIED:20090611T212419Z +UID:4dcb5128-64b8-488c-b9ec-6b8c6184c0e9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130324 +DTEND;VALUE=DATE:20130325 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025939Z +LAST-MODIFIED:20090611T212419Z +UID:0b3db3ec-fa4c-4e71-bf0b-251fde472ef6 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140413 +DTEND;VALUE=DATE:20140414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025956Z +LAST-MODIFIED:20090611T212419Z +UID:b3ac2edc-39ac-4d09-b45b-9a380ecbb63e +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150329 +DTEND;VALUE=DATE:20150330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030027Z +LAST-MODIFIED:20090611T212419Z +UID:7af33c57-7711-4581-86e5-221ac5c59ecd +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160320 +DTEND;VALUE=DATE:20160321 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030042Z +LAST-MODIFIED:20090611T212419Z +UID:b372c357-0224-493e-a95c-50f69a9ee376 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170409 +DTEND;VALUE=DATE:20170410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030101Z +LAST-MODIFIED:20090611T212419Z +UID:2ed9175e-e119-4569-9371-390d70acc0fa +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180325 +DTEND;VALUE=DATE:20180326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030124Z +LAST-MODIFIED:20090611T212419Z +UID:334646f6-5fef-43a9-83bc-d2dd88c9ebe6 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190414 +DTEND;VALUE=DATE:20190415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030150Z +LAST-MODIFIED:20090611T212419Z +UID:7f9c6e82-610a-4485-b5fd-dde7f0dba4b8 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200405 +DTEND;VALUE=DATE:20200406 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030214Z +LAST-MODIFIED:20090611T212419Z +UID:af35fdb4-b67e-4b43-887f-c23f58bf9847 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000421 +DTEND;VALUE=DATE:20000422 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030234Z +LAST-MODIFIED:20090611T212419Z +UID:116e335b-4cae-40b1-abf5-4e2f7121a4c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010413 +DTEND;VALUE=DATE:20010414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030250Z +LAST-MODIFIED:20090611T212420Z +UID:7700d32c-2728-439f-b3a2-e458ba1052d7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020329 +DTEND;VALUE=DATE:20020330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030320Z +LAST-MODIFIED:20090611T212420Z +UID:2710e06f-c089-40d4-945a-9f8fe125a59c +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040409 +DTEND;VALUE=DATE:20040410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030337Z +LAST-MODIFIED:20090611T212420Z +UID:1dfafc7f-17b6-4a8d-b1ef-492adb9bd9dc +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050325 +DTEND;VALUE=DATE:20050326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030352Z +LAST-MODIFIED:20090611T212420Z +UID:8371454e-8dda-4667-81e5-3680dc8cc542 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060414 +DTEND;VALUE=DATE:20060415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030406Z +LAST-MODIFIED:20090611T212420Z +UID:93b64832-1179-4db9-a8e5-4d3375156294 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070406 +DTEND;VALUE=DATE:20070407 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030423Z +LAST-MODIFIED:20090611T212420Z +UID:ee7bc7d8-d5a8-43f1-b817-54b5458429fa +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080321 +DTEND;VALUE=DATE:20080322 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030436Z +LAST-MODIFIED:20090611T212420Z +UID:98652109-bdda-4451-afd3-9955bfe918c7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090410 +DTEND;VALUE=DATE:20090411 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030446Z +LAST-MODIFIED:20090611T212420Z +UID:7241d18c-6e4a-4a15-b262-a0415d05e5c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100402 +DTEND;VALUE=DATE:20100403 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030513Z +LAST-MODIFIED:20090611T212420Z +UID:be42b228-382d-4add-be00-c36fdb2508c7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120406 +DTEND;VALUE=DATE:20120407 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030526Z +LAST-MODIFIED:20090611T212420Z +UID:db8d6551-0b2d-44c1-8c21-ba96030a445e +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130329 +DTEND;VALUE=DATE:20130330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030543Z +LAST-MODIFIED:20090611T212420Z +UID:c14a8a90-a69d-44fe-9e34-a28feed1a1c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140418 +DTEND;VALUE=DATE:20140419 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030602Z +LAST-MODIFIED:20090611T212420Z +UID:269d06c4-2eed-45de-af47-6668b7e58902 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150403 +DTEND;VALUE=DATE:20150404 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030616Z +LAST-MODIFIED:20090611T212420Z +UID:8e3f7823-a340-4b6e-8b7c-10a539f5db8f +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160325 +DTEND;VALUE=DATE:20160326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030630Z +LAST-MODIFIED:20090611T212420Z +UID:a6d17884-5c3e-4f4b-9383-7b83814ada9d +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170414 +DTEND;VALUE=DATE:20170415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030648Z +LAST-MODIFIED:20090611T212420Z +UID:19fb2499-97ae-457f-8d41-6ec76292c77f +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180330 +DTEND;VALUE=DATE:20180331 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030704Z +LAST-MODIFIED:20090611T212420Z +UID:0a125549-300d-4bd9-af20-3fc28935946e +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190419 +DTEND;VALUE=DATE:20190420 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030718Z +LAST-MODIFIED:20090611T212420Z +UID:50de218e-013d-4739-b8d4-f30f74a57168 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200410 +DTEND;VALUE=DATE:20200411 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030737Z +LAST-MODIFIED:20090611T212420Z +UID:af9df15d-af91-4e19-b7d2-e80bf8dfc378 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000611 +DTEND;VALUE=DATE:20000612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030855Z +LAST-MODIFIED:20090611T212420Z +UID:b5eb3eae-875a-4b50-a10c-b8a487907eac +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010603 +DTEND;VALUE=DATE:20010604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030907Z +LAST-MODIFIED:20090611T212420Z +UID:c5a9ee90-d177-4ca5-917e-245791f15312 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020519 +DTEND;VALUE=DATE:20020520 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030942Z +LAST-MODIFIED:20090611T212420Z +UID:9bbe68fb-4d23-4d27-9171-26177baf984d +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030608 +DTEND;VALUE=DATE:20030609 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030959Z +LAST-MODIFIED:20090611T212420Z +UID:c4f15038-8df9-475b-bc85-9948f97cddc7 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040530 +DTEND;VALUE=DATE:20040531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031013Z +LAST-MODIFIED:20090611T212420Z +UID:d54dd907-ba90-4607-88ee-b15d7b79736f +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050515 +DTEND;VALUE=DATE:20050516 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031027Z +LAST-MODIFIED:20090611T212420Z +UID:810562a0-2467-4a43-860d-db69cbf92fdf +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060604 +DTEND;VALUE=DATE:20060605 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031042Z +LAST-MODIFIED:20090611T212420Z +UID:8212e80d-6efa-4e79-bcd0-44fc831a1ecc +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070527 +DTEND;VALUE=DATE:20070528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031101Z +LAST-MODIFIED:20090611T212420Z +UID:da7b884d-0d0f-48d3-97f7-d13b21cea817 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080511 +DTEND;VALUE=DATE:20080512 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031112Z +LAST-MODIFIED:20090611T212420Z +UID:719c1eff-d26b-4316-86b5-464a4d24bf98 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090531 +DTEND;VALUE=DATE:20090601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031123Z +LAST-MODIFIED:20090611T212420Z +UID:a61992d7-0747-46db-aa4c-49ddef65de55 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100523 +DTEND;VALUE=DATE:20100524 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031210Z +LAST-MODIFIED:20090611T212420Z +UID:09673ae2-e44c-4cac-90de-deaf49b6b875 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120527 +DTEND;VALUE=DATE:20120528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031224Z +LAST-MODIFIED:20090611T212420Z +UID:1919a739-59f5-46c2-8cb0-b86726b1e72d +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130519 +DTEND;VALUE=DATE:20130520 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031235Z +LAST-MODIFIED:20090611T212420Z +UID:0a4fae5e-4698-427f-bbdf-b6c08fd96053 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140608 +DTEND;VALUE=DATE:20140609 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031253Z +LAST-MODIFIED:20090611T212420Z +UID:3d5c4095-4429-4abe-8a7a-4bbf5236eaa4 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150524 +DTEND;VALUE=DATE:20150525 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031311Z +LAST-MODIFIED:20090611T212420Z +UID:1177a296-0723-4fcb-8d35-e79b1c021330 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160515 +DTEND;VALUE=DATE:20160516 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031323Z +LAST-MODIFIED:20090611T212420Z +UID:452c678d-0b42-4a89-a347-a0b8e12c4fd6 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170604 +DTEND;VALUE=DATE:20170605 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031341Z +LAST-MODIFIED:20090611T212420Z +UID:01fec43b-b371-4f56-97cb-ef454132a74a +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180520 +DTEND;VALUE=DATE:20180521 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031401Z +LAST-MODIFIED:20090611T212420Z +UID:697b8004-261c-4c0b-a15e-06c1af95ab9b +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190609 +DTEND;VALUE=DATE:20190610 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031412Z +LAST-MODIFIED:20090611T212420Z +UID:ac723797-16b2-4fd9-9d5e-2f55cc90dc22 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200531 +DTEND;VALUE=DATE:20200601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031600Z +LAST-MODIFIED:20090611T212420Z +UID:bec77dd4-49e7-46b5-9ea4-7409cc5369e1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000618 +DTEND;VALUE=DATE:20000619 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031731Z +LAST-MODIFIED:20090611T212420Z +UID:f395a03d-27e2-4214-88c2-20c638c7f99d +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010610 +DTEND;VALUE=DATE:20010611 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031843Z +LAST-MODIFIED:20090611T212420Z +UID:fb37469f-ba53-4fec-9c65-fb22c59470a8 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020526 +DTEND;VALUE=DATE:20020527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030305Z +LAST-MODIFIED:20090611T212420Z +UID:a2eb4297-f3a3-414b-b53a-e7cdaad20ac9 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030418 +DTEND;VALUE=DATE:20030419 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032035Z +LAST-MODIFIED:20090611T212420Z +UID:078f774d-1113-4bad-b7e5-6f4c48100fc4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030615 +DTEND;VALUE=DATE:20030616 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032128Z +LAST-MODIFIED:20090611T212420Z +UID:c2df343c-bf38-4e86-9129-010bd55de1b1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040606 +DTEND;VALUE=DATE:20040607 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025605Z +LAST-MODIFIED:20090611T212420Z +UID:e4891762-ad19-402e-8195-828fb3386d22 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050320 +DTEND;VALUE=DATE:20050321 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032246Z +LAST-MODIFIED:20090611T212420Z +UID:f14c5274-2112-4470-a3f8-f8a5b4eb8199 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050522 +DTEND;VALUE=DATE:20050523 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025617Z +LAST-MODIFIED:20090611T212420Z +UID:33a3f736-bd5b-4a87-b983-612e01ea4dbe +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060409 +DTEND;VALUE=DATE:20060410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032352Z +LAST-MODIFIED:20090611T212421Z +UID:43eeeab3-5836-41cc-82b4-d91d922871c4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060611 +DTEND;VALUE=DATE:20060612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032435Z +LAST-MODIFIED:20090611T212421Z +UID:72234667-e98f-4532-b4da-a2cdd2e56ce4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070603 +DTEND;VALUE=DATE:20070604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032512Z +LAST-MODIFIED:20090611T212421Z +UID:a289a44f-5eb2-4f14-b458-e55dcbc00e9d +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080518 +DTEND;VALUE=DATE:20080519 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032617Z +LAST-MODIFIED:20090611T212421Z +UID:b5d1e103-7807-4049-ac80-df13ab666b5e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090607 +DTEND;VALUE=DATE:20090608 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032717Z +LAST-MODIFIED:20090611T212421Z +UID:59ef3b5c-a65f-4a5f-81ec-25d201285670 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100530 +DTEND;VALUE=DATE:20100531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033236Z +LAST-MODIFIED:20090611T212421Z +UID:f931f051-1dec-4a6c-b78b-615474143fd1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120603 +DTEND;VALUE=DATE:20120604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033315Z +LAST-MODIFIED:20090611T212421Z +UID:838f906b-f3c8-4b94-9cdb-ef698d0222b8 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130526 +DTEND;VALUE=DATE:20130527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033425Z +LAST-MODIFIED:20090611T212421Z +UID:de98efc2-cd59-41fd-af0d-d2dcb58b23fe +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140615 +DTEND;VALUE=DATE:20140616 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033511Z +LAST-MODIFIED:20090611T212421Z +UID:139b7040-5cfd-4db4-abf2-8251ae17368e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150531 +DTEND;VALUE=DATE:20150601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033614Z +LAST-MODIFIED:20090611T212421Z +UID:78d6d38a-a0ae-4b75-bcbb-0276d69a430e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160522 +DTEND;VALUE=DATE:20160523 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033655Z +LAST-MODIFIED:20090611T212421Z +UID:83608056-f721-4564-88ed-3cc5d12b01fb +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170611 +DTEND;VALUE=DATE:20170612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033759Z +LAST-MODIFIED:20090611T212421Z +UID:cc92b242-3a24-471c-b1be-b8918fb8b6de +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180527 +DTEND;VALUE=DATE:20180528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033848Z +LAST-MODIFIED:20090611T212421Z +UID:006195b6-8021-4814-9635-f9f5bffa7a36 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190616 +DTEND;VALUE=DATE:20190617 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033920Z +LAST-MODIFIED:20090611T212421Z +UID:b60896ca-b1eb-4ec1-a9cd-67c7403c6335 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200607 +DTEND;VALUE=DATE:20200608 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224733Z +LAST-MODIFIED:20090611T212421Z +UID:d41971bd-5c3f-4293-a154-a210d39b6667 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000601 +DTEND;VALUE=DATE:20000602 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224836Z +LAST-MODIFIED:20090611T212421Z +UID:e6166189-6b80-44b8-9902-60e58f0cbe0c +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010524 +DTEND;VALUE=DATE:20010525 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224858Z +LAST-MODIFIED:20090611T212421Z +UID:7f5a197b-441e-40e3-a31a-1c0cb5ffed19 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020509 +DTEND;VALUE=DATE:20020510 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224916Z +LAST-MODIFIED:20090611T212421Z +UID:192d84d7-7a76-4d98-a335-919203fa28e1 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030529 +DTEND;VALUE=DATE:20030530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224929Z +LAST-MODIFIED:20090611T212421Z +UID:52cb0477-1dab-4881-bf56-64099c934453 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040520 +DTEND;VALUE=DATE:20040521 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224944Z +LAST-MODIFIED:20090611T212421Z +UID:17c27008-29e2-4050-adda-5757c94fb673 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050505 +DTEND;VALUE=DATE:20050506 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224959Z +LAST-MODIFIED:20090611T212421Z +UID:03f7a269-b863-4fb9-a02f-135803fa2972 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060525 +DTEND;VALUE=DATE:20060526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225014Z +LAST-MODIFIED:20090611T212421Z +UID:fc9f7abd-c3fc-42b2-bd87-7528470f77f3 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070517 +DTEND;VALUE=DATE:20070518 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225025Z +LAST-MODIFIED:20090611T212421Z +UID:c1649dbc-9ad2-4257-a687-7dc706b08ea2 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080501 +DTEND;VALUE=DATE:20080502 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225039Z +LAST-MODIFIED:20090611T212421Z +UID:82c49077-6f81-42ed-84e1-53ebe66f981d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090521 +DTEND;VALUE=DATE:20090522 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225051Z +LAST-MODIFIED:20090611T212421Z +UID:5203b99c-f304-4d25-ba4e-1e776237186d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100513 +DTEND;VALUE=DATE:20100514 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225111Z +LAST-MODIFIED:20090611T212421Z +UID:18e27f82-2d27-42fa-b04c-315c6fadcd9d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120517 +DTEND;VALUE=DATE:20120518 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225120Z +LAST-MODIFIED:20090611T212421Z +UID:7726ca21-1f4d-4cc5-ade3-8c384f5d72a5 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130509 +DTEND;VALUE=DATE:20130510 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225133Z +LAST-MODIFIED:20090611T212421Z +UID:6d025a60-733c-4af4-a2ee-f1b904090666 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140529 +DTEND;VALUE=DATE:20140530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225144Z +LAST-MODIFIED:20090611T212421Z +UID:cd092661-b37e-4ec9-b798-8f957c4278b4 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150514 +DTEND;VALUE=DATE:20150515 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225154Z +LAST-MODIFIED:20090611T212421Z +UID:ed1358ef-c818-4af3-9f8e-4d965d800a21 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160505 +DTEND;VALUE=DATE:20160506 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225204Z +LAST-MODIFIED:20090611T212421Z +UID:7c8351e3-e3fd-4c0b-a60c-f558aa31df55 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170525 +DTEND;VALUE=DATE:20170526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225214Z +LAST-MODIFIED:20090611T212421Z +UID:db037aff-17bf-430b-b094-52e99879ba4e +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180510 +DTEND;VALUE=DATE:20180511 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225224Z +LAST-MODIFIED:20090611T212421Z +UID:dddeea06-c165-474c-96aa-6c1b705c6b0a +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190530 +DTEND;VALUE=DATE:20190531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225238Z +LAST-MODIFIED:20090611T212421Z +UID:09a3f71c-83df-4649-b7d1-4792ae2fb523 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200521 +DTEND;VALUE=DATE:20200522 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025033Z +LAST-MODIFIED:20100911T002511Z +DTSTAMP:20100911T002511Z +UID:a02038e9-a094-4744-b09d-d7d2ef96bd28 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110309 +DTEND;VALUE=DATE:20110310 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025855Z +LAST-MODIFIED:20100911T002525Z +DTSTAMP:20100911T002525Z +UID:844a3d56-4fab-4df1-92d3-8f4385d8efc9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110417 +DTEND;VALUE=DATE:20110418 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030501Z +LAST-MODIFIED:20100911T002555Z +DTSTAMP:20100911T002555Z +UID:6e05d4a1-8b7f-446d-a7fc-566b6837c927 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110422 +DTEND;VALUE=DATE:20110423 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223849Z +LAST-MODIFIED:20100911T002632Z +DTSTAMP:20100911T002632Z +UID:fa0cda04-283e-4b53-95b0-c9903a3f85e2 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110424 +DTEND;VALUE=DATE:20110425 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:2 +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225101Z +LAST-MODIFIED:20100911T002740Z +DTSTAMP:20100911T002740Z +UID:5250764e-c50c-44ba-b3d6-32ee24ee49e1 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110602 +DTEND;VALUE=DATE:20110603 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031143Z +LAST-MODIFIED:20100911T002756Z +DTSTAMP:20100911T002756Z +UID:5e614422-74fe-47d6-bf89-dca8c4d7df3a +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110612 +DTEND;VALUE=DATE:20110613 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033004Z +LAST-MODIFIED:20100911T002812Z +DTSTAMP:20100911T002812Z +UID:001f88a2-a3b4-4dff-a577-c9860d01dcbd +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110619 +DTEND;VALUE=DATE:20110620 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:9f3df592-1dd1-11b2-8b51-a8c49a575f97 +SUMMARY:President's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=2 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000221 +DTEND;VALUE=DATE:20000222 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:e0d4c156-1dd1-11b2-b6a4-dac750e0163e +SUMMARY:Mother's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000514 +DTEND;VALUE=DATE:20000515 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:c2783248-1dd1-11b2-affa-ee50c39d8083 +SUMMARY:Armed Forces Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3SA;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000520 +DTEND;VALUE=DATE:20000521 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:d9706fa6-1dd1-11b2-a349-e97241bd4740 +SUMMARY:Memorial Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=-1MO;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000529 +DTEND;VALUE=DATE:20000530 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:46e6845c-1dd2-11b2-bd3d-d8755a1a171f +SUMMARY:Parents' Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=4SU;BYMONTH=7 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000723 +DTEND;VALUE=DATE:20000724 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:0726ea42-1dd2-11b2-9fe6-dda3d063fb50 +SUMMARY:Labor Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=1MO;BYMONTH=9 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000904 +DTEND;VALUE=DATE:20000905 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:7d4402f4-1dd2-11b2-9790-b6193cfa4349 +SUMMARY:Halloween +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001031 +DTEND;VALUE=DATE:20001101 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:2c7ba10c-1dd2-11b2-b0b7-96f89f288199 +SUMMARY:Veteran's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001111 +DTEND;VALUE=DATE:20001112 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:4299a358-1dd2-11b2-a228-d062222b6f88 +SUMMARY:Thanksgiving Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=4TH;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001123 +DTEND;VALUE=DATE:20001124 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:54f392fa-1dd2-11b2-8ecb-9ae0a7fcebd1 +SUMMARY:Christmas Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001225 +DTEND;VALUE=DATE:20001226 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:b1f194fc-1dd1-11b2-a973-d219c68b95c4 +SUMMARY:New Year's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000101 +DTEND;VALUE=DATE:20000102 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:a2cbca9c-1dd1-11b2-83e2-abab36f0506d +SUMMARY:New Year's Eve +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001231 +DTEND;VALUE=DATE:20010101 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:13f83ed2-1dd2-11b2-a0de-ee8645423959 +SUMMARY:Daylight Savings Time ends +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20071104 +DTEND;VALUE=DATE:20071105 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:f669a974-1dd1-11b2-9fac-f44d91c657af +SUMMARY:Independence Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000704 +DTEND;VALUE=DATE:20000705 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:cb90487e-1dd1-11b2-9e06-d1a12cc963dc +SUMMARY:Father's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3SU;BYMONTH=6 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000618 +DTEND;VALUE=DATE:20000619 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e8022726-1dd1-11b2-aae6-9f73f16c0f01 +SUMMARY:Flag Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000614 +DTEND;VALUE=DATE:20000615 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:f1561106-1dd1-11b2-955f-ba14e5210f45 +SUMMARY:Earth Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000422 +DTEND;VALUE=DATE:20000423 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e46086c2-1dd1-11b2-8a16-a2c99f1c525a +SUMMARY:Tax Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000415 +DTEND;VALUE=DATE:20000416 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:c981b858-1dd1-11b2-b69b-94d05f8bda66 +SUMMARY:April Fool's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000401 +DTEND;VALUE=DATE:20000402 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:bd6cfe4c-1dd1-11b2-b3dc-ebaab9302c26 +SUMMARY:St. Patrick's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000317 +DTEND;VALUE=DATE:20000318 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:aff9f3c8-1dd1-11b2-8593-c63469762eb1 +SUMMARY:George Washington's Birthday (actual) +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000222 +DTEND;VALUE=DATE:20000223 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:790b86e2-1dd2-11b2-9cb9-c905fa35c6f9 +SUMMARY:Valentine's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000214 +DTEND;VALUE=DATE:20000215 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:4d5b843e-1dd2-11b2-a6c9-8ea942124d1b +SUMMARY:Abraham Lincoln's Birthday +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000212 +DTEND;VALUE=DATE:20000213 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:0c771532-1dd2-11b2-8dd9-8638db3aef63 +SUMMARY:Groundhog's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000202 +DTEND;VALUE=DATE:20000203 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T220831Z +LAST-MODIFIED:20090611T212422Z +UID:8a05b5d3-6b97-4fe1-b26e-b258a073c5e0 +SUMMARY:United Nations Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001024 +DTEND;VALUE=DATE:20001025 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e462881a-20b8-4b70-ab3c-cf38016d3398 +SUMMARY:Daylight Savings Time begins +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070311 +DTEND;VALUE=DATE:20070312 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T221253Z +LAST-MODIFIED:20090611T212422Z +UID:1eb525d5-26cd-43f0-819e-acc3003b35ad +SUMMARY:Daylight Savings Time begins +RRULE:FREQ=YEARLY;UNTIL=20060402T000000;BYDAY=1SU;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000402 +DTEND;VALUE=DATE:20000403 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T221447Z +LAST-MODIFIED:20090611T212422Z +UID:05504c5b-5984-4eb0-a60a-c6d5cf083d82 +SUMMARY:Daylight Savings Time ends +RRULE:FREQ=YEARLY;UNTIL=20061029T000000;BYDAY=-1SU;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001029 +DTEND;VALUE=DATE:20001030 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T214410Z +LAST-MODIFIED:20090611T212422Z +UID:088031d1-abea-47ed-978d-39c6319e8bf2 +SUMMARY:Inauguration Day +RRULE:FREQ=YEARLY;INTERVAL=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010120 +DTEND;VALUE=DATE:20010121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T164802Z +LAST-MODIFIED:20090611T212422Z +UID:d3c521e1-ffab-414c-8cfa-99e76758d8c8 +SUMMARY:Constitution Day\, Citizenship Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000917 +DTEND;VALUE=DATE:20000918 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T165106Z +LAST-MODIFIED:20090611T212422Z +UID:a44b8b6e-7ae4-4120-b7ad-14e91691be2a +SUMMARY:National Day of Prayer +RRULE:FREQ=YEARLY;BYDAY=1TH;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000504 +DTEND;VALUE=DATE:20000505 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T165839Z +LAST-MODIFIED:20090611T212422Z +UID:e20f1b5c-8ac4-4bea-acf4-b4f2bfe95329 +SUMMARY:Arbor Day +RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000428 +DTEND;VALUE=DATE:20000429 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080830T011903Z +LAST-MODIFIED:20090611T212422Z +UID:feae8f6c-bdce-46c8-aa45-e2d1c0fb9cb0 +SUMMARY:US General Election +RRULE:FREQ=YEARLY;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001107 +DTEND;VALUE=DATE:20001108 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080830T021222Z +LAST-MODIFIED:20090611T212422Z +UID:60b5ca68-d0a6-4634-9254-16cc300bb12e +SUMMARY:Black Friday +RRULE:FREQ=YEARLY;BYDAY=FR;BYMONTHDAY=23,24,25,26,27,28,29;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001124 +DTEND;VALUE=DATE:20001125 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:dd9bfdf4-1dd1-11b2-ab6e-8da990a14639 +SUMMARY:Martin Luther King Jr.'s Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=1 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000117 +DTEND;VALUE=DATE:20000118 +CLASS:PUBLIC +TRANSP:TRANSPARENT +DESCRIPTION:Traditionally on January 15th.\nObserved on 3rd Monday of January. +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:175691b0-1dd2-11b2-9596-e9837a6245dd +SUMMARY:Columbus Day (observed) +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2MO;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001009 +DTEND;VALUE=DATE:20001010 +CLASS:PUBLIC +TRANSP:TRANSPARENT +DESCRIPTION:Traditionally on October 12th.\nObserved on 2nd Monday of October. +END:VEVENT +BEGIN:VEVENT +CREATED:20080902T140125Z +LAST-MODIFIED:20090611T212422Z +UID:22896130-ef70-46ca-a071-a15f3a746b04 +SUMMARY:Administrative Professionals Day +RRULE:FREQ=YEARLY;BYDAY=WE;BYMONTHDAY=21,22,23,24,25,26,27;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000426 +DTEND;VALUE=DATE:20000427 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T212909Z +LAST-MODIFIED:20090611T212422Z +UID:a0486410-f87b-4e09-939a-77121b58b535 +SUMMARY:Christmas Eve +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001224 +DTEND;VALUE=DATE:20001225 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081126T134531Z +LAST-MODIFIED:20090611T212422Z +UID:8c320d1a-fc56-4282-962c-4c67763a4c58 +SUMMARY:Grandparents Day +RRULE:FREQ=YEARLY;BYDAY=SU;BYMONTHDAY=7,8,9,10,11,12,13;BYMONTH=9 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000910 +DTEND;VALUE=DATE:20000911 +DESCRIPTION:First Sunday following Labor Day +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T214111Z +LAST-MODIFIED:20090611T212422Z +UID:efab1245-c19c-4693-8391-eb5f40cc3743 +SUMMARY:Epiphany +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000106 +DTEND;VALUE=DATE:20000107 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T220608Z +LAST-MODIFIED:20090611T212422Z +UID:37f09326-5105-4092-8535-1b4cad9812ac +SUMMARY:Reformation Day +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001029 +DTEND;VALUE=DATE:20001030 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T034030Z +LAST-MODIFIED:20090611T212422Z +UID:af808fce-8a77-4d15-ad63-c096737303d2 +SUMMARY:All Saints Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001101 +DTEND;VALUE=DATE:20001102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081118T135517Z +LAST-MODIFIED:20090611T212422Z +UID:6c6e277f-d344-4807-8d91-bea1689eac7c +SUMMARY:Immaculate Conception +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001208 +DTEND;VALUE=DATE:20001209 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081118T141534Z +LAST-MODIFIED:20090611T212422Z +UID:25db674e-6d6a-41ee-848b-2b79552f071a +SUMMARY:Assumption Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000815 +DTEND;VALUE=DATE:20000816 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081126T151107Z +LAST-MODIFIED:20090611T212422Z +UID:c3a78810-ea6a-48bc-8178-56fe3fe61e23 +SUMMARY:Kwanzaa +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:19661226 +DTEND;VALUE=DATE:19670102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20091128T184942Z +LAST-MODIFIED:20091128T185038Z +UID:cebdafa9-6b2a-4869-a751-5554b6da2e45 +SUMMARY:Pearl Harbor Remembrance Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001207 +DTEND;VALUE=DATE:20001208 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100911T004419Z +LAST-MODIFIED:20100911T004455Z +DTSTAMP:20100911T004455Z +UID:1ee4d840-eb70-44f0-8f2a-db778c373ac5 +SUMMARY:Patriot Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010911 +DTEND;VALUE=DATE:20010912 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100911T004608Z +LAST-MODIFIED:20100911T004647Z +DTSTAMP:20100911T004647Z +UID:e8c8b4f6-1e0a-4bbb-b077-0fcce9077e1d +SUMMARY:Women's Equality Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000826 +DTEND;VALUE=DATE:20000827 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225155Z +LAST-MODIFIED:20100625T230014Z +DTSTAMP:20100625T230014Z +UID:4c53aa44-f014-4334-8e39-5b2ff090445a +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100101 +DTEND;VALUE=DATE:20100102 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225222Z +LAST-MODIFIED:20100625T230022Z +DTSTAMP:20100625T230022Z +UID:5705d0cf-01b6-4511-9f36-0fc9739b21fc +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100118 +DTEND;VALUE=DATE:20100119 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225249Z +LAST-MODIFIED:20100625T230034Z +DTSTAMP:20100625T230034Z +UID:26751c39-998f-4d1c-bc77-454f21d94fd1 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100215 +DTEND;VALUE=DATE:20100216 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225312Z +LAST-MODIFIED:20100625T230050Z +DTSTAMP:20100625T230050Z +UID:91cde8ea-009b-4a07-b835-7d33b1b7e4e1 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100531 +DTEND;VALUE=DATE:20100601 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225342Z +LAST-MODIFIED:20100625T230108Z +DTSTAMP:20100625T230108Z +UID:63368446-395a-42ca-8108-f1bb7ee5a851 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100705 +DTEND;VALUE=DATE:20100706 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225406Z +LAST-MODIFIED:20100625T230123Z +DTSTAMP:20100625T230123Z +UID:6cbc2eb9-d818-40f0-8e91-7c7ca5e43e0f +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100906 +DTEND;VALUE=DATE:20100907 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225422Z +LAST-MODIFIED:20100625T230138Z +DTSTAMP:20100625T230138Z +UID:26ab3aae-1f30-4eea-9a30-35aab72d9e67 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101011 +DTEND;VALUE=DATE:20101012 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225442Z +LAST-MODIFIED:20100625T230149Z +DTSTAMP:20100625T230149Z +UID:c08f4482-8591-4940-8838-80381b0cf69b +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101111 +DTEND;VALUE=DATE:20101112 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225456Z +LAST-MODIFIED:20100625T230155Z +DTSTAMP:20100625T230155Z +UID:7262eefe-a1b1-4370-8262-c2f900150c0f +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101125 +DTEND;VALUE=DATE:20101126 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225512Z +LAST-MODIFIED:20100625T230205Z +DTSTAMP:20100625T230205Z +UID:431576fe-17ac-48ea-941e-08d25998c7c2 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101224 +DTEND;VALUE=DATE:20101225 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225649Z +LAST-MODIFIED:20100625T230212Z +DTSTAMP:20100625T230212Z +UID:ff38e9bf-6a04-4140-8e14-17f77bfabbdf +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101231 +DTEND;VALUE=DATE:20110101 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225710Z +LAST-MODIFIED:20100625T230225Z +DTSTAMP:20100625T230225Z +UID:c670bd38-0675-4746-8eca-cd06d5d0fb46 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110117 +DTEND;VALUE=DATE:20110118 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225733Z +LAST-MODIFIED:20100625T230234Z +DTSTAMP:20100625T230234Z +UID:486ce67b-2a71-44ae-87d1-ec8d000a9584 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110221 +DTEND;VALUE=DATE:20110222 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225753Z +LAST-MODIFIED:20100625T230259Z +DTSTAMP:20100625T230259Z +UID:aca86965-d147-47d7-8e88-3990efe7188a +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110530 +DTEND;VALUE=DATE:20110531 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225812Z +LAST-MODIFIED:20100625T230311Z +DTSTAMP:20100625T230311Z +UID:40f9466a-95bd-40fd-a0a5-df005fbee438 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110704 +DTEND;VALUE=DATE:20110705 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225826Z +LAST-MODIFIED:20100625T230320Z +DTSTAMP:20100625T230320Z +UID:86a93453-eb85-4836-8e35-f3d7403eb02c +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110905 +DTEND;VALUE=DATE:20110906 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225845Z +LAST-MODIFIED:20100625T230329Z +DTSTAMP:20100625T230329Z +UID:8aed5642-2ab6-4d47-ae3e-d6bda178c530 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111010 +DTEND;VALUE=DATE:20111011 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225901Z +LAST-MODIFIED:20100625T230342Z +DTSTAMP:20100625T230342Z +UID:095e361c-db8c-4936-80ff-6ac356e3a8af +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111111 +DTEND;VALUE=DATE:20111112 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225917Z +LAST-MODIFIED:20100625T230349Z +DTSTAMP:20100625T230349Z +UID:859d4dfe-1eae-4b3b-9d42-438d500ca455 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111124 +DTEND;VALUE=DATE:20111125 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225933Z +LAST-MODIFIED:20100625T230400Z +DTSTAMP:20100625T230400Z +UID:06a9583b-01fb-4ed4-926a-ac995e5dac6d +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111226 +DTEND;VALUE=DATE:20111227 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230426Z +LAST-MODIFIED:20100625T230432Z +DTSTAMP:20100625T230432Z +UID:ec58e9d8-27fa-4f3a-8cb2-8073b860982f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120102 +DTEND;VALUE=DATE:20120103 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230442Z +LAST-MODIFIED:20100625T230451Z +DTSTAMP:20100625T230451Z +UID:e3e1f541-ad97-4683-ab18-13c9d8620130 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120116 +DTEND;VALUE=DATE:20120117 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230503Z +LAST-MODIFIED:20100625T230510Z +DTSTAMP:20100625T230510Z +UID:b2f98535-66c6-423f-846d-7be00b63d315 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120220 +DTEND;VALUE=DATE:20120221 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230528Z +LAST-MODIFIED:20100625T230537Z +DTSTAMP:20100625T230537Z +UID:6b1d9233-40b7-406b-a98c-769b2681d9ad +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120528 +DTEND;VALUE=DATE:20120529 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230550Z +LAST-MODIFIED:20100625T230558Z +DTSTAMP:20100625T230558Z +UID:9c6685c4-cf34-406d-a52d-457432cd2e72 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120704 +DTEND;VALUE=DATE:20120705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230608Z +LAST-MODIFIED:20100625T230616Z +DTSTAMP:20100625T230616Z +UID:64e8f36b-30ca-4a68-bb68-b6be1bac33e7 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120903 +DTEND;VALUE=DATE:20120904 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230625Z +LAST-MODIFIED:20100625T230633Z +DTSTAMP:20100625T230633Z +UID:0feaf063-8cc3-4dcc-80a4-3006dd3ee395 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121008 +DTEND;VALUE=DATE:20121009 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230640Z +LAST-MODIFIED:20100625T230646Z +DTSTAMP:20100625T230646Z +UID:a3ab943c-efa6-403a-94d6-266351d1bb9a +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121112 +DTEND;VALUE=DATE:20121113 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230652Z +LAST-MODIFIED:20100625T230659Z +DTSTAMP:20100625T230659Z +UID:e29b795e-0f37-4ea4-84e6-0f277cf5a8ba +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121122 +DTEND;VALUE=DATE:20121123 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230711Z +LAST-MODIFIED:20100625T230719Z +DTSTAMP:20100625T230719Z +UID:9986c0ee-983a-4538-ad4a-4688fe5b5636 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121225 +DTEND;VALUE=DATE:20121226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230745Z +LAST-MODIFIED:20100625T230752Z +DTSTAMP:20100625T230752Z +UID:0b3180aa-65a8-4732-bb4c-36cd3c79a650 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130101 +DTEND;VALUE=DATE:20130102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230800Z +LAST-MODIFIED:20100625T230806Z +DTSTAMP:20100625T230806Z +UID:19f0cc25-bec1-4e84-915b-a70bb532fcfb +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130121 +DTEND;VALUE=DATE:20130122 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230816Z +LAST-MODIFIED:20100625T230822Z +DTSTAMP:20100625T230822Z +UID:db225df5-a20c-4889-9511-bcf81acad6e4 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130218 +DTEND;VALUE=DATE:20130219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230833Z +LAST-MODIFIED:20100625T230839Z +DTSTAMP:20100625T230839Z +UID:9b12b9be-0f16-42ff-97a5-53a67cc46f2c +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130527 +DTEND;VALUE=DATE:20130528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230849Z +LAST-MODIFIED:20100625T230856Z +DTSTAMP:20100625T230856Z +UID:9b6b81e1-e4f8-490b-9062-24726dbf27f2 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130704 +DTEND;VALUE=DATE:20130705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230905Z +LAST-MODIFIED:20100625T230910Z +DTSTAMP:20100625T230910Z +UID:5b722439-56b8-4f26-a21d-c2c99898bf47 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130902 +DTEND;VALUE=DATE:20130903 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230922Z +LAST-MODIFIED:20100625T230927Z +DTSTAMP:20100625T230927Z +UID:e44229c1-89cd-4c96-8094-c09a56f1a028 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131014 +DTEND;VALUE=DATE:20131015 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230935Z +LAST-MODIFIED:20100625T230941Z +DTSTAMP:20100625T230941Z +UID:fea08c25-1cbc-4d7b-ab30-87bf5b87c4e0 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131111 +DTEND;VALUE=DATE:20131112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230955Z +LAST-MODIFIED:20100625T231001Z +DTSTAMP:20100625T231001Z +UID:12eb5737-4e7f-4278-9f8f-36202257d434 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131128 +DTEND;VALUE=DATE:20131129 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T231008Z +LAST-MODIFIED:20100625T231016Z +DTSTAMP:20100625T231016Z +UID:a2acacd4-e541-4334-b5ad-e2e215cf32fc +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131225 +DTEND;VALUE=DATE:20131226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232203Z +LAST-MODIFIED:20100625T232211Z +DTSTAMP:20100625T232211Z +UID:784620c2-befa-40af-9aa2-5232ceebf80f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140101 +DTEND;VALUE=DATE:20140102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232227Z +LAST-MODIFIED:20100625T232233Z +DTSTAMP:20100625T232233Z +UID:7d90511b-c99b-4937-af69-1da4781923c8 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140120 +DTEND;VALUE=DATE:20140121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232242Z +LAST-MODIFIED:20100625T232249Z +DTSTAMP:20100625T232249Z +UID:760534b9-7b1c-41b1-8797-6dd6583d7b98 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140217 +DTEND;VALUE=DATE:20140218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232259Z +LAST-MODIFIED:20100625T232307Z +DTSTAMP:20100625T232307Z +UID:3a66f3a1-bce5-4dcf-a815-2d50057ba758 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140526 +DTEND;VALUE=DATE:20140527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232324Z +LAST-MODIFIED:20100625T232333Z +DTSTAMP:20100625T232333Z +UID:8dec8541-cada-48c8-bae1-7b64e5bd96f1 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140704 +DTEND;VALUE=DATE:20140705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232344Z +LAST-MODIFIED:20100625T232352Z +DTSTAMP:20100625T232352Z +UID:f1a94187-1b1a-46eb-9eba-9f2e03efff7e +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140901 +DTEND;VALUE=DATE:20140902 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232358Z +LAST-MODIFIED:20100625T232405Z +DTSTAMP:20100625T232405Z +UID:aa078715-e039-42e8-bfca-ceb787c3eceb +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141013 +DTEND;VALUE=DATE:20141014 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232416Z +LAST-MODIFIED:20100625T232424Z +DTSTAMP:20100625T232424Z +UID:5a0ec2c2-3b70-4440-928d-d3c435205736 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141111 +DTEND;VALUE=DATE:20141112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232432Z +LAST-MODIFIED:20100625T232438Z +DTSTAMP:20100625T232438Z +UID:ec969553-a7c6-4ad1-bbf9-061bd48b91dc +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141127 +DTEND;VALUE=DATE:20141128 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232449Z +LAST-MODIFIED:20100625T232456Z +DTSTAMP:20100625T232456Z +UID:f01417f3-4570-4711-9abc-bf20a01cea0c +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141225 +DTEND;VALUE=DATE:20141226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232517Z +LAST-MODIFIED:20100625T232523Z +DTSTAMP:20100625T232523Z +UID:f37395e6-1056-4175-a457-399cdab4eda2 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150101 +DTEND;VALUE=DATE:20150102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232529Z +LAST-MODIFIED:20100625T232536Z +DTSTAMP:20100625T232536Z +UID:3f07705a-a1fb-4915-8dbd-4e1513ade06b +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150119 +DTEND;VALUE=DATE:20150120 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232547Z +LAST-MODIFIED:20100625T232553Z +DTSTAMP:20100625T232553Z +UID:2dff572d-fc88-4925-8817-960c9f913974 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150216 +DTEND;VALUE=DATE:20150217 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232604Z +LAST-MODIFIED:20100625T232611Z +DTSTAMP:20100625T232611Z +UID:62aa4902-1113-4e89-ab08-9a344ff2709f +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150525 +DTEND;VALUE=DATE:20150526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232621Z +LAST-MODIFIED:20100625T232628Z +DTSTAMP:20100625T232628Z +UID:ed53ac56-5535-4877-acf1-84919821da44 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150703 +DTEND;VALUE=DATE:20150704 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232639Z +LAST-MODIFIED:20100625T232645Z +DTSTAMP:20100625T232645Z +UID:fdb30d77-208e-4b9c-acbb-877549b0c60e +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150907 +DTEND;VALUE=DATE:20150908 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232656Z +LAST-MODIFIED:20100625T232703Z +DTSTAMP:20100625T232703Z +UID:a0add000-7f34-4a12-8cfc-dbf98872b532 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151012 +DTEND;VALUE=DATE:20151013 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232710Z +LAST-MODIFIED:20100625T232716Z +DTSTAMP:20100625T232716Z +UID:57bbf2f3-e92c-4864-9171-80e1aefa9b25 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151111 +DTEND;VALUE=DATE:20151112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232724Z +LAST-MODIFIED:20100625T232730Z +DTSTAMP:20100625T232730Z +UID:17357cbf-822b-4e16-97fe-9212a98262ea +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151126 +DTEND;VALUE=DATE:20151127 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232740Z +LAST-MODIFIED:20100625T232746Z +DTSTAMP:20100625T232746Z +UID:a5ae0bfb-a761-48a7-80b3-85c4dec3459d +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151225 +DTEND;VALUE=DATE:20151226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232806Z +LAST-MODIFIED:20100625T232813Z +DTSTAMP:20100625T232813Z +UID:e84cfee3-d9f8-4088-9709-995258432145 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160101 +DTEND;VALUE=DATE:20160102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232819Z +LAST-MODIFIED:20100625T232826Z +DTSTAMP:20100625T232826Z +UID:2c19281f-8160-4886-ae2b-35f5a83d32ec +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160118 +DTEND;VALUE=DATE:20160119 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232836Z +LAST-MODIFIED:20100625T232842Z +DTSTAMP:20100625T232842Z +UID:ccb48d24-c58f-478b-a941-1706c642cd39 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160215 +DTEND;VALUE=DATE:20160216 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232851Z +LAST-MODIFIED:20100625T232858Z +DTSTAMP:20100625T232858Z +UID:067c379d-cdfa-49f5-9de7-65be7b5b0158 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160530 +DTEND;VALUE=DATE:20160531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232911Z +LAST-MODIFIED:20100625T232917Z +DTSTAMP:20100625T232917Z +UID:dddf90ce-a186-4bfb-aaf8-878e07450fcf +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160704 +DTEND;VALUE=DATE:20160705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232925Z +LAST-MODIFIED:20100625T232931Z +DTSTAMP:20100625T232931Z +UID:c734ef2f-53a9-48f3-9da0-f70b70c0d881 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160905 +DTEND;VALUE=DATE:20160906 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232938Z +LAST-MODIFIED:20100625T232944Z +DTSTAMP:20100625T232944Z +UID:c9d05e3a-bd7d-4a5d-94ba-020335a9b3aa +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161010 +DTEND;VALUE=DATE:20161011 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232953Z +LAST-MODIFIED:20100625T233000Z +DTSTAMP:20100625T233000Z +UID:819d893b-af2b-4423-aaff-c4286ee349ee +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161111 +DTEND;VALUE=DATE:20161112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233007Z +LAST-MODIFIED:20100625T233013Z +DTSTAMP:20100625T233013Z +UID:9af9e892-e3a8-4a2c-a130-4e25c92c2baa +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161124 +DTEND;VALUE=DATE:20161125 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233022Z +LAST-MODIFIED:20100625T233028Z +DTSTAMP:20100625T233028Z +UID:8b5ff666-fec7-487b-99c0-aa621548e9ea +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161226 +DTEND;VALUE=DATE:20161227 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233045Z +LAST-MODIFIED:20100625T233052Z +DTSTAMP:20100625T233052Z +UID:9943b6be-9c4b-4fc3-b690-80114b559f26 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170102 +DTEND;VALUE=DATE:20170103 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233101Z +LAST-MODIFIED:20100625T233109Z +DTSTAMP:20100625T233109Z +UID:4c3b85b1-2144-470e-b986-418c087cb5a0 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170116 +DTEND;VALUE=DATE:20170117 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233117Z +LAST-MODIFIED:20100625T233123Z +DTSTAMP:20100625T233123Z +UID:c09d860f-6464-41c4-b2ef-25011d25cecf +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170220 +DTEND;VALUE=DATE:20170221 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233131Z +LAST-MODIFIED:20100625T233138Z +DTSTAMP:20100625T233138Z +UID:ba4752c9-4032-4385-820d-2c4213e14fa5 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170529 +DTEND;VALUE=DATE:20170530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233145Z +LAST-MODIFIED:20100625T233151Z +DTSTAMP:20100625T233151Z +UID:59e934c5-672e-4ccc-a686-9d9c495b3883 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170704 +DTEND;VALUE=DATE:20170705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233158Z +LAST-MODIFIED:20100625T233205Z +DTSTAMP:20100625T233205Z +UID:90340458-52db-4697-b2d7-8bfd51cfaaf7 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170904 +DTEND;VALUE=DATE:20170905 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233212Z +LAST-MODIFIED:20100625T233218Z +DTSTAMP:20100625T233218Z +UID:70c9c1e3-31a2-4b0a-9712-69a184fbfb7c +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171009 +DTEND;VALUE=DATE:20171010 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233225Z +LAST-MODIFIED:20100625T233232Z +DTSTAMP:20100625T233232Z +UID:e9f86e06-9f1c-44bc-b3db-43a220802fe9 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171110 +DTEND;VALUE=DATE:20171111 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233240Z +LAST-MODIFIED:20100625T233246Z +DTSTAMP:20100625T233246Z +UID:d82d8d11-0793-4a68-a876-83c7e29736d0 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171123 +DTEND;VALUE=DATE:20171124 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233255Z +LAST-MODIFIED:20100625T233302Z +DTSTAMP:20100625T233302Z +UID:4526de1c-b74d-4643-b299-4c4c42ed82bb +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171225 +DTEND;VALUE=DATE:20171226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233318Z +LAST-MODIFIED:20100625T233324Z +DTSTAMP:20100625T233324Z +UID:885fb7d7-ea3f-4938-9e40-f612986566f3 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180101 +DTEND;VALUE=DATE:20180102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233329Z +LAST-MODIFIED:20100625T233335Z +DTSTAMP:20100625T233335Z +UID:89c44a14-aee8-49ce-8528-0e7a8bbe6e06 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180115 +DTEND;VALUE=DATE:20180116 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233342Z +LAST-MODIFIED:20100625T233348Z +DTSTAMP:20100625T233348Z +UID:e65fa03d-c681-458c-a26b-4206e3297280 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180219 +DTEND;VALUE=DATE:20180220 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233358Z +LAST-MODIFIED:20100625T233404Z +DTSTAMP:20100625T233404Z +UID:f0a9e5e8-077c-424a-a668-970951c307d5 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180528 +DTEND;VALUE=DATE:20180529 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233413Z +LAST-MODIFIED:20100625T233419Z +DTSTAMP:20100625T233419Z +UID:f00cd4b3-3f19-4b46-9d69-e33790eda2ea +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180704 +DTEND;VALUE=DATE:20180705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233426Z +LAST-MODIFIED:20100625T233432Z +DTSTAMP:20100625T233432Z +UID:d0cdf502-fcdd-48ee-8cdc-2a6bc947a906 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180903 +DTEND;VALUE=DATE:20180904 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233441Z +LAST-MODIFIED:20100625T233447Z +DTSTAMP:20100625T233447Z +UID:4ca554db-e59d-4de8-b043-d28988ae0b39 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181008 +DTEND;VALUE=DATE:20181009 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233455Z +LAST-MODIFIED:20100625T233502Z +DTSTAMP:20100625T233502Z +UID:876c8ca9-48a3-405d-86a1-432a66fb0ceb +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181112 +DTEND;VALUE=DATE:20181113 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233510Z +LAST-MODIFIED:20100625T233516Z +DTSTAMP:20100625T233516Z +UID:5adaf364-32e7-4f8e-bc69-ad93c1c3aa15 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181122 +DTEND;VALUE=DATE:20181123 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233527Z +LAST-MODIFIED:20100625T233532Z +DTSTAMP:20100625T233532Z +UID:05f95ef4-6098-4337-8d15-8b57ff37f251 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181225 +DTEND;VALUE=DATE:20181226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233551Z +LAST-MODIFIED:20100625T233558Z +DTSTAMP:20100625T233558Z +UID:43fc5a98-b57a-456b-a50b-8e0e1de018ff +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190101 +DTEND;VALUE=DATE:20190102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233603Z +LAST-MODIFIED:20100625T233610Z +DTSTAMP:20100625T233610Z +UID:e1f23ce9-56fe-47e0-8f0e-2d408847c5d6 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190121 +DTEND;VALUE=DATE:20190122 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233618Z +LAST-MODIFIED:20100625T233626Z +DTSTAMP:20100625T233626Z +UID:3e8909b9-74aa-41bb-b8e7-23beff8c362d +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190218 +DTEND;VALUE=DATE:20190219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233635Z +LAST-MODIFIED:20100625T233641Z +DTSTAMP:20100625T233641Z +UID:1aac6453-abbf-47de-bf5d-4bd0dfb9cd44 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190527 +DTEND;VALUE=DATE:20190528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233650Z +LAST-MODIFIED:20100625T233700Z +DTSTAMP:20100625T233700Z +UID:dce26b68-5fd3-4b45-a8a6-cb68c8946935 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190704 +DTEND;VALUE=DATE:20190705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233710Z +LAST-MODIFIED:20100625T233716Z +DTSTAMP:20100625T233716Z +UID:199ec585-69d7-45df-9cf2-c1e83d570e3a +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190902 +DTEND;VALUE=DATE:20190903 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233723Z +LAST-MODIFIED:20100625T233728Z +DTSTAMP:20100625T233728Z +UID:61d39eb7-ae9a-4667-bbfa-6107df1e09e7 +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191014 +DTEND;VALUE=DATE:20191015 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233735Z +LAST-MODIFIED:20100625T233741Z +DTSTAMP:20100625T233741Z +UID:290c9fe0-e39e-425a-95b4-831945c6b8f2 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191111 +DTEND;VALUE=DATE:20191112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233747Z +LAST-MODIFIED:20100625T233754Z +DTSTAMP:20100625T233754Z +UID:b7680e77-a64f-4861-bb35-f2d917a9c437 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191128 +DTEND;VALUE=DATE:20191129 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233801Z +LAST-MODIFIED:20100625T233808Z +DTSTAMP:20100625T233808Z +UID:b234249f-565e-4159-b4e5-b0314c48a191 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191225 +DTEND;VALUE=DATE:20191226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233825Z +LAST-MODIFIED:20100625T233832Z +DTSTAMP:20100625T233832Z +UID:a48665de-2184-4468-9f8a-c21cf582455f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200101 +DTEND;VALUE=DATE:20200102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233838Z +LAST-MODIFIED:20100625T233844Z +DTSTAMP:20100625T233844Z +UID:f03dfc3e-924e-4574-87e7-e30fa1166025 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200120 +DTEND;VALUE=DATE:20200121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233852Z +LAST-MODIFIED:20100625T233858Z +DTSTAMP:20100625T233858Z +UID:95d5c62a-4a58-49cf-9c91-d66a0fae1acd +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200217 +DTEND;VALUE=DATE:20200218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233906Z +LAST-MODIFIED:20100625T233912Z +DTSTAMP:20100625T233912Z +UID:dd42517c-228b-44b3-a3ac-0bd4a7aadb75 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200525 +DTEND;VALUE=DATE:20200526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233921Z +LAST-MODIFIED:20100625T233927Z +DTSTAMP:20100625T233927Z +UID:cb3e2de7-2c55-4882-820a-df01c8c6aae7 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200703 +DTEND;VALUE=DATE:20200704 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233935Z +LAST-MODIFIED:20100625T233941Z +DTSTAMP:20100625T233941Z +UID:65eb7b18-064f-4939-a840-f11ef4bec981 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200907 +DTEND;VALUE=DATE:20200908 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233952Z +LAST-MODIFIED:20100625T233959Z +DTSTAMP:20100625T233959Z +UID:2d13e3c5-8a26-49be-bdbc-9a1e9715ae4b +SUMMARY:Columbus Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201012 +DTEND;VALUE=DATE:20201013 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234006Z +LAST-MODIFIED:20100625T234017Z +DTSTAMP:20100625T234017Z +UID:8ffd1252-87f4-481c-b46a-c2aa4cba74c7 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201111 +DTEND;VALUE=DATE:20201112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234023Z +LAST-MODIFIED:20100625T234029Z +DTSTAMP:20100625T234029Z +UID:557d2349-5c21-45c8-aad7-9c24a6aceb66 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201126 +DTEND;VALUE=DATE:20201127 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234036Z +LAST-MODIFIED:20100625T234041Z +DTSTAMP:20100625T234041Z +UID:1c5f2dc5-7cea-48cd-8c36-905930f8eee9 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201225 +DTEND;VALUE=DATE:20201226 +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR \ No newline at end of file From 4cb682f310724d25373d3d1042455850ced5f997 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 5 Nov 2011 22:19:41 -0400 Subject: [PATCH 046/134] working holiday implementation --- .../joestelmach/natty/generated/DateLexer.g | 2 +- .../joestelmach/natty/generated/DateParser.g | 11 +- .../joestelmach/natty/generated/DateWalker.g | 5 +- .../java/com/joestelmach/natty/Holiday.java | 1 - .../com/joestelmach/natty/WalkerState.java | 164 +- src/main/resources/holidays.ics | 3636 +++++++++++++++-- src/main/resources/ical4j.properties | 0 .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/HolidayTest.java | 137 +- .../natty/grammar/HolidayGrammarTest.java | 7 +- src/test/resources/holidays.ics | 3316 --------------- 11 files changed, 3509 insertions(+), 3772 deletions(-) create mode 100644 src/main/resources/ical4j.properties delete mode 100644 src/test/resources/holidays.ics diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 5b636fb8..976a5ba1 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -300,7 +300,7 @@ NEW : 'new'; PALM : 'palm'; PATRIOT : 'patriot' SINGLE_QUOTE? 's'?; PRESIDENT : 'president' SINGLE_QUOTE? 's'?; -PATRICK : 'patrick' SINGLE_QUOTE? 's'? | 'paddy' SINGLE_QUOTE? 's'?; +PATRICK : ('patrick' | 'patty' | 'paddy') SINGLE_QUOTE? 's'?; SAINT : 'saint'; TAX : 'tax'; THANKSGIVING : 'thanksgiving' 's'?; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index c42f1188..b5706efc 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -86,7 +86,6 @@ date | relaxed_date | relative_date | explicit_relative_date - | holiday | global_date_prefix WHITE_SPACE date -> ^(RELATIVE_DATE ^(SEEK global_date_prefix date)) ; @@ -385,6 +384,7 @@ relative_date // today, tomorrow | named_relative_date + // next christmas, 2 thanksgivings ago | holiday -> ^(RELATIVE_DATE holiday) ; @@ -632,11 +632,14 @@ named_relative_time // ********** holidays ********** holiday - : relative_date_prefix WHITE_SPACE holiday_name + : spelled_or_int_optional_prefix WHITE_SPACE holiday_name WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix holiday_name) + + | relative_date_prefix WHITE_SPACE holiday_name -> ^(SEEK relative_date_prefix holiday_name) - | spelled_or_int_optional_prefix WHITE_SPACE holiday_name WHITE_SPACE relative_date_suffix - -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix holiday_name) + | holiday_name relaxed_year_prefix relaxed_year + -> ^(EXPLICIT_SEEK holiday_name relaxed_year) | holiday_name -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] holiday_name) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 8d6085f2..842d3d4e 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -98,7 +98,7 @@ seek {_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SEEK_BY.text);} | ^(SEEK DIRECTION SEEK_BY INT HOLIDAY) - {_walkerState.seekToHoliday($DIRECTION.text, $INT.text, $HOLIDAY.text);} + {_walkerState.seekToHoliday($HOLIDAY.text, $DIRECTION.text, $INT.text);} ; explicit_seek @@ -117,6 +117,9 @@ explicit_seek | ^(EXPLICIT_SEEK ^(YEAR_OF year=INT)) {_walkerState.seekToYear($year.text);} + | ^(EXPLICIT_SEEK HOLIDAY ^(YEAR_OF year=INT)) + {_walkerState.seekToHolidayYear($HOLIDAY.text, $year.text);} + | ^(EXPLICIT_SEEK index=INT ^(DAY_OF_WEEK day=INT)) {_walkerState.setDayOfWeekIndex($index.text, $day.text);} diff --git a/src/main/java/com/joestelmach/natty/Holiday.java b/src/main/java/com/joestelmach/natty/Holiday.java index 99128df8..fdb5414f 100644 --- a/src/main/java/com/joestelmach/natty/Holiday.java +++ b/src/main/java/com/joestelmach/natty/Holiday.java @@ -16,7 +16,6 @@ public enum Holiday { GOOD_FRIDAY("Good Friday"), GROUNDHOG_DAY("Groundhog's Day"), HALLOWEEN("Halloween"), - INAUGURATION_DAY("Inauguration Day"), INDEPENDENCE_DAY("Independence Day"), KWANZAA("Kwanzaa"), LABOR_DAY("Labor Day"), diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index b402370b..842460bb 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -1,9 +1,23 @@ package com.joestelmach.natty; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Period; +import net.fortuna.ical4j.model.PeriodList; /** * @author Joe Stelmach @@ -28,12 +42,17 @@ public class WalkerState { private static final String PLUS = "+"; private static final String MINUS = "-"; private static final String GMT = "GMT"; + private static final String VEVENT = "VEVENT"; + private static final String SUMMARY = "SUMMARY"; + private static final String HOLIDAY_ICS_FILE = "/holidays.ics"; + private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); private GregorianCalendar _calendar; private TimeZone _defaultTimeZone; private int _currentYear; private boolean _firstDateInvocationInGroup = true; private boolean _timeGivenInGroup = false; + private net.fortuna.ical4j.model.Calendar _holidayCalendar; private DateGroup _dateGroup; @@ -142,13 +161,7 @@ public void seekToYear(String year) { markDateInvocation(); - // two digit years require us to choose a reasonable century. - if(year.length() == 2) { - int century = (yearInt > ((_currentYear - 2000) + TWO_DIGIT_YEAR_CENTURY_THRESHOLD)) ? 1900 : 2000; - yearInt = yearInt + century; - } - - _calendar.set(Calendar.YEAR, yearInt); + _calendar.set(Calendar.YEAR, getFullYear(yearInt)); } /** @@ -370,8 +383,73 @@ public void setExplicitTime(String hours, String minutes, String seconds, String _calendar.set(Calendar.MINUTE, minutesInt); } - public void seekToHoliday(String direction, String amount, String holiday) { - System.out.println("seeking " + direction + " " + amount + " " + holiday); + /** + * Seeks forward or backwards to a particular holiday based on the current date + * + * @param holidayString The holiday to seek to + * @param direction The direction to seek + * @param seekAmount The number of years to seek + */ + public void seekToHoliday(String holidayString, String direction, String seekAmount) { + + Holiday holiday = Holiday.valueOf(holidayString); + int seekAmountInt = Integer.parseInt(seekAmount); + assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); + assert(seekAmountInt >= 0); + assert(holiday != null); + + markDateInvocation(); + + // get the current year + Calendar cal = new GregorianCalendar(); + cal.setTimeZone(_defaultTimeZone); + int currentYear = cal.get(Calendar.YEAR); + + // look up a suitable period of holiday occurrences + boolean forwards = direction.equals(DIR_RIGHT); + int startYear = forwards ? currentYear : currentYear - seekAmountInt - 1; + int endYear = forwards ? currentYear + seekAmountInt + 1 : currentYear; + Map dates = getDatesForHoliday(startYear, endYear, holiday); + + // grab the right one + boolean hasPassed = cal.getTime().after(dates.get(currentYear)); + int targetYear = currentYear + + (forwards ? seekAmountInt + (hasPassed ? 0 : -1) : + (seekAmountInt - (hasPassed ? 1 : 0)) * -1); + + cal.setTimeZone(_calendar.getTimeZone()); + cal.setTime(dates.get(targetYear)); + _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); + _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); + _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); + } + + /** + * Seeks to the given holiday within the given year + * + * @param holidayString + * @param yearString + */ + public void seekToHolidayYear(String holidayString, String yearString) { + Holiday holiday = Holiday.valueOf(holidayString); + int yearInt = Integer.parseInt(yearString); + assert(holiday != null); + assert(yearInt >= 0); + + markDateInvocation(); + + int year = getFullYear(yearInt); + Map dates = getDatesForHoliday(year, year, holiday); + Date date = dates.get(year - (holiday.equals(Holiday.NEW_YEARS_EVE) ? 1 : 0)); + + if(date != null) { + Calendar cal = new GregorianCalendar(); + cal.setTimeZone(_calendar.getTimeZone()); + cal.setTime(date); + _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); + _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); + _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); + } } /** @@ -448,6 +526,74 @@ private void markTimeInvocation() { _timeGivenInGroup = true; } + private Map getDatesForHoliday(int startYear, int endYear, Holiday holiday) { + Map holidays = new HashMap(); + + if(_holidayCalendar == null) { + InputStream fin = WalkerState.class.getResourceAsStream(HOLIDAY_ICS_FILE); + try { + _holidayCalendar = new CalendarBuilder().build(fin); + + } catch (IOException e) { + _logger.severe("Couln't open " + HOLIDAY_ICS_FILE); + return holidays; + + } catch (ParserException e) { + _logger.severe("Couln't parse " + HOLIDAY_ICS_FILE); + return holidays; + } + } + + Period period = null; + try { + DateTime from = new DateTime(startYear + "0101T000000Z"); + DateTime to = new DateTime(endYear + "1231T000000Z");; + period = new Period(from, to); + + } catch (ParseException e) { + _logger.log(Level.SEVERE, "Invalid start or end year: " + startYear + ", " + endYear, e); + return holidays; + } + + for (Object component : _holidayCalendar.getComponents(VEVENT)) { + Component vevent = (Component) component; + String summary = vevent.getProperty(SUMMARY).getValue(); + if(summary.equals(holiday.getSummary())) { + PeriodList list = vevent.calculateRecurrenceSet(period); + for(Object p : list) { + DateTime date = ((Period) p).getStart(); + + // this date is at the date of the holiday at 12 AM UTC + Calendar utcCal = new GregorianCalendar(); + utcCal.setTimeZone(TimeZone.getTimeZone(GMT)); + utcCal.setTime(date); + + // use the year, month and day components of our UTC date to form a new local date + Calendar localCal = new GregorianCalendar(); + localCal.setTimeZone(_defaultTimeZone); + localCal.set(Calendar.YEAR, utcCal.get(Calendar.YEAR)); + localCal.set(Calendar.MONTH, utcCal.get(Calendar.MONTH)); + localCal.set(Calendar.DAY_OF_MONTH, utcCal.get(Calendar.DAY_OF_MONTH)); + + holidays.put(localCal.get(Calendar.YEAR), localCal.getTime()); + } + } + } + + return holidays; + } + + private int getFullYear(Integer year) { + int result = year; + + if(year.toString().length() <= 2) { + int century = (year > ((_currentYear - 2000) + TWO_DIGIT_YEAR_CENTURY_THRESHOLD)) ? 1900 : 2000; + result = year + century; + } + + return result; + } + /** * @return the current calendar */ diff --git a/src/main/resources/holidays.ics b/src/main/resources/holidays.ics index fbc4c9fd..0e50d77f 100644 --- a/src/main/resources/holidays.ics +++ b/src/main/resources/holidays.ics @@ -1,470 +1,3316 @@ BEGIN:VCALENDAR -METHOD:PUBLISH +COMMENT:http://www.mozilla.org/projects/calendar/caldata/USHolidays.ics +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 -X-WR-CALNAME:US Holidays -PRODID:-//Apple Inc.//iCal 5.0//EN -X-APPLE-CALENDAR-COLOR:#0252D4 -X-WR-TIMEZONE:America/Los_Angeles -CALSCALE:GREGORIAN -BEGIN:VEVENT -CREATED:20090721T214849Z -UID:EBDD92C4-0F7D-4E7D-8A2A-C0C80A4A6F4E -DTEND;VALUE=DATE:20110101 +BEGIN:VEVENT +CREATED:20080827T223540Z +LAST-MODIFIED:20090611T212419Z +UID:b8388cae-2125-4614-9801-5fec40a07408 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000423 +DTEND;VALUE=DATE:20000424 TRANSP:TRANSPARENT -SUMMARY:New Year's Day (observed) -DTSTART;VALUE=DATE:20101231 -DTSTAMP:20090722T145911Z -SEQUENCE:3 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:56159EEC-79B9-4873-89DE-A3FEDE4894CE -DTEND;VALUE=DATE:20090309 -EXDATE;VALUE=DATE:20080309 -EXDATE;VALUE=DATE:20070311 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU -TRANSP:OPAQUE -SUMMARY:Daylight Savings Time Begins -DTSTART;VALUE=DATE:20090308 -DTSTAMP:20090803T233855Z -SEQUENCE:5 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:BECEEEB7-FAE0-4825-B554-2BA7D156DC2D -DTEND;VALUE=DATE:20090511 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=2SU -TRANSP:OPAQUE -SUMMARY:Mother's Day -DTSTART;VALUE=DATE:20090510 -DTSTAMP:20090729T011715Z -SEQUENCE:3 -END:VEVENT -BEGIN:VEVENT -CREATED:20090803T233245Z -UID:B7AFC343-46DD-446C-8D6B-A76A5EFE53F3 -DTEND;VALUE=DATE:20091104 -RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20111031;BYMONTH=11;BYDAY=1TU -TRANSP:TRANSPARENT -SUMMARY:Election Day -DTSTART;VALUE=DATE:20091103 -DTSTAMP:20101214T190758Z -SEQUENCE:4 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:31DE2253-AE78-44BA-9833-7BD075158D8B -DTEND;VALUE=DATE:20090203 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 -TRANSP:OPAQUE -SUMMARY:Groundhog Day -DTSTART;VALUE=DATE:20090202 -DTSTAMP:20090721T160455Z -SEQUENCE:4 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:A5279A3A-5C32-4774-A415-DE5FCC2C3E02 -DTEND;VALUE=DATE:20090217 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYDAY=3MO -TRANSP:OPAQUE -SUMMARY:President's Day -DTSTART;VALUE=DATE:20090216 -DTSTAMP:20090803T233757Z -SEQUENCE:5 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:FC2E5732-CD51-4569-BA85-19B1F5AB89B2 -DTEND;VALUE=DATE:20090402 -RRULE:FREQ=YEARLY;INTERVAL=1 -TRANSP:OPAQUE -SUMMARY:April Fool's Day -DTSTART;VALUE=DATE:20090401 -DTSTAMP:20090721T161433Z -SEQUENCE:4 +CREATED:20080827T223613Z +LAST-MODIFIED:20090611T212419Z +UID:27198947-51d4-475c-9116-6dd475ab4729 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010415 +DTEND;VALUE=DATE:20010416 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:2D1DC405-2F66-47D5-831F-98CC41657CC4 -DTEND;VALUE=DATE:20090116 -EXDATE;VALUE=DATE:20090115 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=3MO -TRANSP:OPAQUE -SUMMARY:Martin Luther King\, Jr. Day -DTSTART;VALUE=DATE:20090115 -DTSTAMP:20090803T233129Z -SEQUENCE:13 +CREATED:20080827T223633Z +LAST-MODIFIED:20090611T212419Z +UID:adebaeec-b7da-4d1e-baff-73e54a5bbe5d +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020331 +DTEND;VALUE=DATE:20020401 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T215938Z -UID:B45EB281-2E5F-4BD7-AA89-536817EB09C3 -DTEND;VALUE=DATE:20110425 +CREATED:20080827T223650Z +LAST-MODIFIED:20090611T212419Z +UID:3378f42f-00f3-42be-888e-8368d5e140b2 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030420 +DTEND;VALUE=DATE:20030421 TRANSP:TRANSPARENT -SUMMARY:Easter -DTSTART;VALUE=DATE:20110424 -DTSTAMP:20090803T234817Z -SEQUENCE:5 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:F023551A-8AC7-42D1-9074-EEDA6D3793F4 -DTEND;VALUE=DATE:20090416 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4 -TRANSP:OPAQUE -SUMMARY:Tax Day -DTSTART;VALUE=DATE:20090415 -DTSTAMP:20090721T165038Z -SEQUENCE:1 +CREATED:20080827T223704Z +LAST-MODIFIED:20090611T212419Z +UID:18eae8ac-65e1-4cc0-8bf3-0c287882fbd9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040411 +DTEND;VALUE=DATE:20040412 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:5FAEA417-F999-4910-B815-09D4E77544D4 -DTEND;VALUE=DATE:20090102 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1 -TRANSP:OPAQUE -SUMMARY:New Year's Day -DTSTART;VALUE=DATE:20090101 -DTSTAMP:20090721T172608Z -SEQUENCE:3 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:2E325A12-42A5-4B5C-9BDA-A6034BBD2EDB -DTEND;VALUE=DATE:20090213 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 -TRANSP:OPAQUE -SUMMARY:Lincoln's Birthday -DTSTART;VALUE=DATE:20090212 -DTSTAMP:20090721T160539Z -SEQUENCE:1 +CREATED:20080827T223726Z +LAST-MODIFIED:20090611T212419Z +UID:d0a68adf-3eca-4585-a7a7-853b5d5b03e4 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050327 +DTEND;VALUE=DATE:20050328 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:49AEB08C-C39B-4253-A83A-741BA443181E -DTEND;VALUE=DATE:20090530 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5 -TRANSP:OPAQUE -SUMMARY:John F. Kennedy's Birthday -DTSTART;VALUE=DATE:20090529 -DTSTAMP:20090721T165324Z -SEQUENCE:1 +CREATED:20080827T223741Z +LAST-MODIFIED:20090611T212419Z +UID:daf76dce-468b-4f99-8d42-01487f8fafa9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060416 +DTEND;VALUE=DATE:20060417 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:F31BC84F-0682-426F-A7A8-497520D80245 -DTEND;VALUE=DATE:20090526 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=-1MO -TRANSP:OPAQUE -SUMMARY:Memorial Day -DTSTART;VALUE=DATE:20090525 -DTSTAMP:20090721T212538Z -SEQUENCE:3 +CREATED:20080827T223757Z +LAST-MODIFIED:20090611T212419Z +UID:fa7a1db3-1c5f-472d-9ee4-988d0d0287eb +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070408 +DTEND;VALUE=DATE:20070409 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:D53ABCFC-5BA7-4500-81BD-93FCDD264DF4 -DTEND;VALUE=DATE:20090413 -TRANSP:OPAQUE -SUMMARY:Easter +CREATED:20080827T223818Z +LAST-MODIFIED:20090611T212419Z +UID:69230f5f-f2d2-428d-a90c-a7e1c7deea5e +SUMMARY:Easter Sunday +CATEGORIES:Holidays DTSTART;VALUE=DATE:20090412 -DTSTAMP:20090803T233908Z -SEQUENCE:3 +DTEND;VALUE=DATE:20090413 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:A51F6546-E2D2-4BED-AFD0-C728899142CD -DTEND;VALUE=DATE:20090616 -EXDATE;VALUE=DATE:20090615 -RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20090615;BYMONTH=6;BYDAY=3TU -TRANSP:OPAQUE -SUMMARY:Father's Day -DTSTART;VALUE=DATE:20090615 -DTSTAMP:20090729T011415Z -SEQUENCE:2 +CREATED:20080827T223828Z +LAST-MODIFIED:20090611T212419Z +UID:0bc494ff-69b5-4dca-8699-c4fe5ea25a13 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100404 +DTEND;VALUE=DATE:20100405 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:2753AF22-1699-4FFF-91C8-3247F3C132FB -DTEND;VALUE=DATE:20070312 -EXDATE;VALUE=DATE:20080309 -EXDATE;VALUE=DATE:20070311 -RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20090307;BYMONTH=3;BYDAY=2SU -TRANSP:OPAQUE -SUMMARY:Daylight Savings Time Begins -DTSTART;VALUE=DATE:20070311 -DTSTAMP:20090721T215715Z -SEQUENCE:4 +CREATED:20080827T223934Z +LAST-MODIFIED:20090611T212419Z +UID:44a937c0-f0d8-4458-947c-8a33db7217ef +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120408 +DTEND;VALUE=DATE:20120409 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T214600Z -UID:81F3B43D-8342-4D48-906A-04BA8A82F14D -DTEND;VALUE=DATE:20090704 +CREATED:20080827T223946Z +LAST-MODIFIED:20090611T212419Z +UID:734e6d03-ab3c-4693-8c81-f73e3b576c27 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130331 +DTEND;VALUE=DATE:20130401 TRANSP:TRANSPARENT -SUMMARY:Independence Day (observed) -DTSTART;VALUE=DATE:20090703 -DTSTAMP:20090803T234443Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T215448Z -UID:CAD6CCCC-701A-4965-B3B3-0B460F4DA2BC -DTEND;VALUE=DATE:20121113 +CREATED:20080827T224000Z +LAST-MODIFIED:20090611T212419Z +UID:c4d6a9e0-a5d6-41d4-9297-97e4fdcc85a5 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140420 +DTEND;VALUE=DATE:20140421 TRANSP:TRANSPARENT -SUMMARY:Veteran's Day (observed) -DTSTART;VALUE=DATE:20121112 -DTSTAMP:20090722T145635Z -SEQUENCE:3 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:224FF2DF-00F8-4132-95FF-D780C0686A5F -DTEND;VALUE=DATE:20091226 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12 -TRANSP:OPAQUE -SUMMARY:Christmas -DTSTART;VALUE=DATE:20091225 -DTSTAMP:20090803T233541Z -SEQUENCE:5 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:FC1DAF58-B6B3-444F-B8B8-58D9F9C1FD3C -DTEND;VALUE=DATE:20090615 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6 -TRANSP:OPAQUE -SUMMARY:Flag Day -DTSTART;VALUE=DATE:20090614 -DTSTAMP:20090721T165347Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T215244Z -UID:DE2DDD5A-743E-485F-BE61-D71C796E6E86 -DTEND;VALUE=DATE:20111227 +CREATED:20080827T224012Z +LAST-MODIFIED:20090611T212419Z +UID:b2cab02a-a164-44a4-af66-ccb1b3c253b9 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150405 +DTEND;VALUE=DATE:20150406 TRANSP:TRANSPARENT -SUMMARY:Christmas (observed) -DTSTART;VALUE=DATE:20111226 -DTSTAMP:20090803T234900Z -SEQUENCE:5 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:18BAF95A-3960-4D45-A148-6079CF5B4524 -DTEND;VALUE=DATE:20091013 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=2MO -TRANSP:OPAQUE -SUMMARY:Columbus Day -DTSTART;VALUE=DATE:20091012 -DTSTAMP:20090721T214236Z -SEQUENCE:7 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:87F1C3C2-678E-4B5B-8B79-C34CDDF08BEA -DTEND;VALUE=DATE:20090318 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3 -TRANSP:OPAQUE -SUMMARY:St. Patrick's Day -DTSTART;VALUE=DATE:20090317 -DTSTAMP:20090721T160611Z -SEQUENCE:1 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:0EFABF56-241C-4A88-AC3A-86E584B08F12 -DTEND;VALUE=DATE:20090506 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5 -TRANSP:OPAQUE -SUMMARY:Cinco de Mayo -DTSTART;VALUE=DATE:20090505 -DTSTAMP:20090721T165100Z -SEQUENCE:4 +CREATED:20080827T224028Z +LAST-MODIFIED:20090611T212419Z +UID:763686f7-3ec4-4dfc-9a33-e4c0bae652fa +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160327 +DTEND;VALUE=DATE:20160328 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:B082D697-41C2-4B14-B167-00B0497FF2D9 -DTEND;VALUE=DATE:20090215 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2 -TRANSP:OPAQUE -SUMMARY:Valentine's Day -DTSTART;VALUE=DATE:20090214 -DTSTAMP:20090721T160548Z -SEQUENCE:1 +CREATED:20080827T224043Z +LAST-MODIFIED:20090611T212419Z +UID:31edb1e8-a064-4a8b-a041-eecd22a4d419 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170416 +DTEND;VALUE=DATE:20170417 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:5AC21774-EB84-44F5-AA73-ABD21A50D2B2 -DTEND;VALUE=DATE:20090423 -RRULE:FREQ=YEARLY;INTERVAL=1 -TRANSP:OPAQUE -SUMMARY:Earth Day -DTSTART;VALUE=DATE:20090422 -DTSTAMP:20090721T165049Z -SEQUENCE:5 +CREATED:20080827T224101Z +LAST-MODIFIED:20090611T212419Z +UID:d869e442-0b4d-40c4-b34c-6780afce8c46 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180401 +DTEND;VALUE=DATE:20180402 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:6BF1B371-0F0E-436B-8ACE-7D320022FD12 -DTEND;VALUE=DATE:20091112 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11 -TRANSP:OPAQUE -SUMMARY:Veteran's Day -DTSTART;VALUE=DATE:20091111 -DTSTAMP:20090721T172022Z -SEQUENCE:2 +CREATED:20080827T224121Z +LAST-MODIFIED:20090611T212419Z +UID:135738b2-b8d5-4e65-bf7c-736e22c6b8b1 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190421 +DTEND;VALUE=DATE:20190422 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:DF047E5D-E1BA-4DE8-82AC-FC61D621A008 -DTEND;VALUE=DATE:20100405 -TRANSP:OPAQUE -SUMMARY:Easter -DTSTART;VALUE=DATE:20100404 -DTSTAMP:20090803T234104Z -SEQUENCE:3 +CREATED:20080827T224133Z +LAST-MODIFIED:20090611T212419Z +UID:7606494f-0019-46e6-9d47-ab9498569e5a +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200412 +DTEND;VALUE=DATE:20200413 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090721T214326Z -UID:57A98619-B009-46AE-B213-D0AF4FB4E78A -DTEND;VALUE=DATE:20101225 +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212419Z +UID:c5c154cc-1dd1-11b2-85c7-e3de2bfad30f +SUMMARY:Easter Sunday +STATUS:CONFIRMED +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080323 +DTEND;VALUE=DATE:20080324 +CLASS:PUBLIC TRANSP:TRANSPARENT -SUMMARY:Christmas (observed) -DTSTART;VALUE=DATE:20101224 -DTSTAMP:20090803T234719Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T211645Z -UID:1F7D3F06-FD6E-48E2-9808-700F7675D0F1 -DTEND;VALUE=DATE:20090908 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9;BYDAY=1MO +CREATED:20080829T024422Z +LAST-MODIFIED:20090611T212419Z +UID:5e604c83-cacc-46fe-bc40-b4f50e2412e7 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000308 +DTEND;VALUE=DATE:20000309 TRANSP:TRANSPARENT -SUMMARY:Labor Day -DTSTART;VALUE=DATE:20090907 -DTSTAMP:20090722T145407Z -SEQUENCE:5 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:B35A624C-6FD5-4C98-B390-AF0FA7AFA997 -DTEND;VALUE=DATE:20091101 -RRULE:FREQ=YEARLY;INTERVAL=1 -TRANSP:OPAQUE -SUMMARY:Halloween -DTSTART;VALUE=DATE:20091031 -DTSTAMP:20090721T172638Z -SEQUENCE:5 +CREATED:20080829T024533Z +LAST-MODIFIED:20090611T212419Z +UID:847021ce-03b6-4f2d-abe5-2ba869066b5d +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010228 +DTEND;VALUE=DATE:20010301 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090803T234355Z -UID:B0399DCB-D02F-4758-9273-E4902A723569 -DTEND;VALUE=DATE:20090705 -RRULE:FREQ=YEARLY;INTERVAL=1 +CREATED:20080829T024626Z +LAST-MODIFIED:20090611T212419Z +UID:0e313bdb-171d-40dd-9ddb-1dfb4a777bb5 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020213 +DTEND;VALUE=DATE:20020214 TRANSP:TRANSPARENT -SUMMARY:Independence Day -DTSTART;VALUE=DATE:20090704 -DTSTAMP:20090803T234436Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:AC6D39D2-A6D6-4B50-8586-09346F1A87D3 -DTEND;VALUE=DATE:20090622 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6;BYDAY=3SU -TRANSP:OPAQUE -SUMMARY:Father's Day -DTSTART;VALUE=DATE:20090621 -DTSTAMP:20090729T011415Z -SEQUENCE:3 +CREATED:20080829T024822Z +LAST-MODIFIED:20090611T212419Z +UID:1577ed41-e8c5-45f9-83e5-86079fe2510b +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030305 +DTEND;VALUE=DATE:20030306 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090803T234608Z -UID:4162B846-C557-48FD-B635-E70EED88243B -DTEND;VALUE=DATE:20100706 +CREATED:20080829T024847Z +LAST-MODIFIED:20090611T212419Z +UID:fcbfe576-82e9-4f68-b1ad-5366ff6968ac +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040225 +DTEND;VALUE=DATE:20040226 TRANSP:TRANSPARENT -SUMMARY:Independence Day (observed) -DTSTART;VALUE=DATE:20100705 -DTSTAMP:20090803T234625Z -SEQUENCE:3 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:7B16E9B7-22B1-43C3-ABE2-88F29ED00000 -DTEND;VALUE=DATE:20091127 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=4TH -TRANSP:OPAQUE -SUMMARY:Thanksgiving -DTSTART;VALUE=DATE:20091126 -DTSTAMP:20090803T233519Z -SEQUENCE:7 +CREATED:20080829T024911Z +LAST-MODIFIED:20090611T212419Z +UID:a403894c-bb2c-4d02-8dca-91c359a54bfe +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050209 +DTEND;VALUE=DATE:20050210 +TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT -CREATED:20090803T233815Z -UID:54BA8F70-FD4C-4BB6-A225-AA1A4D7092BE -DTEND;VALUE=DATE:20090223 -RRULE:FREQ=YEARLY;INTERVAL=1 +CREATED:20080829T024924Z +LAST-MODIFIED:20090611T212419Z +UID:dcc3c2f4-d214-49d8-bb3e-f13c6acf3295 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060301 +DTEND;VALUE=DATE:20060302 TRANSP:TRANSPARENT -SUMMARY:Washington's Birthday -DTSTART;VALUE=DATE:20090222 -DTSTAMP:20090803T234047Z -SEQUENCE:5 END:VEVENT BEGIN:VEVENT -CREATED:20090721T220008Z -UID:49A489DC-9C11-4714-A2C6-1453FCE3922C -DTEND;VALUE=DATE:20120409 +CREATED:20080829T024939Z +LAST-MODIFIED:20090611T212419Z +UID:b81a6c71-9a3d-49a5-b695-8ac5f1bb013b +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070221 +DTEND;VALUE=DATE:20070222 TRANSP:TRANSPARENT -SUMMARY:Easter -DTSTART;VALUE=DATE:20120408 -DTSTAMP:20090803T235036Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T215312Z -UID:305E36CE-99A5-4C38-A94E-B349E0E673AB -DTEND;VALUE=DATE:20120103 +CREATED:20080829T024951Z +LAST-MODIFIED:20090611T212419Z +UID:40090c1d-8669-4d7a-b63a-288006cfac65 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080206 +DTEND;VALUE=DATE:20080207 TRANSP:TRANSPARENT -SUMMARY:New Year's Day (observed) -DTSTART;VALUE=DATE:20120102 -DTSTAMP:20090803T234928Z -SEQUENCE:4 END:VEVENT BEGIN:VEVENT -CREATED:20090721T010305Z -UID:8EF5993C-76BD-4235-BA9A-9875E0818B62 -DTEND;VALUE=DATE:20071105 -EXDATE;VALUE=DATE:20081102 -EXDATE;VALUE=DATE:20071104 -RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20091031;BYMONTH=11;BYDAY=1SU -TRANSP:OPAQUE -SUMMARY:Daylight Savings Time Ends -DTSTART;VALUE=DATE:20071104 -DTSTAMP:20090721T215746Z -SEQUENCE:4 -END:VEVENT -BEGIN:VEVENT -CREATED:20090721T010305Z -UID:84AD11F7-8AB7-44C8-8C50-7DDB3CAB6A04 -DTEND;VALUE=DATE:20091102 -EXDATE;VALUE=DATE:20081102 -EXDATE;VALUE=DATE:20071104 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU -TRANSP:OPAQUE -SUMMARY:Daylight Savings Time Ends -DTSTART;VALUE=DATE:20091101 -DTSTAMP:20090803T233528Z -SEQUENCE:5 -END:VEVENT -BEGIN:VEVENT -CREATED:20090803T233245Z -UID:376326ED-123D-412A-A88F-87F7F34EA9EB -DTEND;VALUE=DATE:20111109 -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=2TU -TRANSP:TRANSPARENT -SUMMARY:Election Day -DTSTART;VALUE=DATE:20111108 -DTSTAMP:20101214T190758Z -SEQUENCE:5 +CREATED:20080829T025003Z +LAST-MODIFIED:20090611T212419Z +UID:609ce29c-966c-40b4-81c4-8ccac74e98dd +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090225 +DTEND;VALUE=DATE:20090226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025017Z +LAST-MODIFIED:20090611T212419Z +UID:f96c0bf5-1cf2-4d2e-9955-eb89f4e645d3 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100217 +DTEND;VALUE=DATE:20100218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025046Z +LAST-MODIFIED:20090611T212419Z +UID:a0b949a8-2a75-4617-a687-1e7dffc098f5 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120222 +DTEND;VALUE=DATE:20120223 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025058Z +LAST-MODIFIED:20090611T212419Z +UID:8caed879-6158-477c-9901-e1531dc855e8 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130213 +DTEND;VALUE=DATE:20130214 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025131Z +LAST-MODIFIED:20090611T212419Z +UID:69e06d74-9acc-4667-8eb0-7c33299d82c0 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140305 +DTEND;VALUE=DATE:20140306 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025146Z +LAST-MODIFIED:20090611T212419Z +UID:a05d7fa6-4c65-4465-a32a-1b5c2d1189aa +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150218 +DTEND;VALUE=DATE:20150219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025202Z +LAST-MODIFIED:20090611T212419Z +UID:ce5fe814-c4b3-4aab-8298-86f54e34ffe4 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160210 +DTEND;VALUE=DATE:20160211 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025220Z +LAST-MODIFIED:20090611T212419Z +UID:746c3ee3-3439-41bd-9ee1-7944812a8a29 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170301 +DTEND;VALUE=DATE:20170302 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025235Z +LAST-MODIFIED:20090611T212419Z +UID:6eff84ea-6763-46d3-9c64-b847376f3e34 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180214 +DTEND;VALUE=DATE:20180215 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025254Z +LAST-MODIFIED:20090611T212419Z +UID:e5d798cf-474f-42c3-83b8-0d1186c2c3ad +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190306 +DTEND;VALUE=DATE:20190307 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025309Z +LAST-MODIFIED:20090611T212419Z +UID:9d17488f-12b8-474b-8790-d22e6786735e +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200226 +DTEND;VALUE=DATE:20200227 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025421Z +LAST-MODIFIED:20090611T212419Z +UID:1abe0e7a-fa55-42b8-b6cc-bf037ab5c7f4 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000416 +DTEND;VALUE=DATE:20000417 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025457Z +LAST-MODIFIED:20090611T212419Z +UID:ff8dd4a7-0593-4fb6-bcdc-7cd8db41a451 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010408 +DTEND;VALUE=DATE:20010409 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025510Z +LAST-MODIFIED:20090611T212419Z +UID:e403c114-aa09-4dcd-b8cf-b342be833477 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020324 +DTEND;VALUE=DATE:20020325 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025522Z +LAST-MODIFIED:20090611T212419Z +UID:8ebc922a-a220-456f-937b-a943e5df123b +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030413 +DTEND;VALUE=DATE:20030414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025549Z +LAST-MODIFIED:20090611T212419Z +UID:e03d10c1-ce57-426f-9a08-cd49c25920a9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040404 +DTEND;VALUE=DATE:20040405 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025630Z +LAST-MODIFIED:20090611T212419Z +UID:8ed86db1-ea90-453a-a1cd-36878811826e +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070401 +DTEND;VALUE=DATE:20070402 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025646Z +LAST-MODIFIED:20090611T212419Z +UID:b45150e2-da4d-477b-aace-b255186d89b1 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080316 +DTEND;VALUE=DATE:20080317 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025820Z +LAST-MODIFIED:20090611T212419Z +UID:96b2e223-1b4f-44e1-80a3-8b57ab7d2f12 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090405 +DTEND;VALUE=DATE:20090406 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025837Z +LAST-MODIFIED:20090611T212419Z +UID:863b0192-787d-4b4b-8550-c7e2db820b0a +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100328 +DTEND;VALUE=DATE:20100329 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025908Z +LAST-MODIFIED:20090611T212419Z +UID:ac159587-0a97-4f98-b11c-2193fe61265a +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120401 +DTEND;VALUE=DATE:20120402 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025922Z +LAST-MODIFIED:20090611T212419Z +UID:4dcb5128-64b8-488c-b9ec-6b8c6184c0e9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130324 +DTEND;VALUE=DATE:20130325 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025939Z +LAST-MODIFIED:20090611T212419Z +UID:0b3db3ec-fa4c-4e71-bf0b-251fde472ef6 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140413 +DTEND;VALUE=DATE:20140414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025956Z +LAST-MODIFIED:20090611T212419Z +UID:b3ac2edc-39ac-4d09-b45b-9a380ecbb63e +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150329 +DTEND;VALUE=DATE:20150330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030027Z +LAST-MODIFIED:20090611T212419Z +UID:7af33c57-7711-4581-86e5-221ac5c59ecd +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160320 +DTEND;VALUE=DATE:20160321 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030042Z +LAST-MODIFIED:20090611T212419Z +UID:b372c357-0224-493e-a95c-50f69a9ee376 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170409 +DTEND;VALUE=DATE:20170410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030101Z +LAST-MODIFIED:20090611T212419Z +UID:2ed9175e-e119-4569-9371-390d70acc0fa +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180325 +DTEND;VALUE=DATE:20180326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030124Z +LAST-MODIFIED:20090611T212419Z +UID:334646f6-5fef-43a9-83bc-d2dd88c9ebe6 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190414 +DTEND;VALUE=DATE:20190415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030150Z +LAST-MODIFIED:20090611T212419Z +UID:7f9c6e82-610a-4485-b5fd-dde7f0dba4b8 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200405 +DTEND;VALUE=DATE:20200406 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030214Z +LAST-MODIFIED:20090611T212419Z +UID:af35fdb4-b67e-4b43-887f-c23f58bf9847 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000421 +DTEND;VALUE=DATE:20000422 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030234Z +LAST-MODIFIED:20090611T212419Z +UID:116e335b-4cae-40b1-abf5-4e2f7121a4c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010413 +DTEND;VALUE=DATE:20010414 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030250Z +LAST-MODIFIED:20090611T212420Z +UID:7700d32c-2728-439f-b3a2-e458ba1052d7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020329 +DTEND;VALUE=DATE:20020330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030320Z +LAST-MODIFIED:20090611T212420Z +UID:2710e06f-c089-40d4-945a-9f8fe125a59c +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040409 +DTEND;VALUE=DATE:20040410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030337Z +LAST-MODIFIED:20090611T212420Z +UID:1dfafc7f-17b6-4a8d-b1ef-492adb9bd9dc +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050325 +DTEND;VALUE=DATE:20050326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030352Z +LAST-MODIFIED:20090611T212420Z +UID:8371454e-8dda-4667-81e5-3680dc8cc542 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060414 +DTEND;VALUE=DATE:20060415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030406Z +LAST-MODIFIED:20090611T212420Z +UID:93b64832-1179-4db9-a8e5-4d3375156294 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070406 +DTEND;VALUE=DATE:20070407 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030423Z +LAST-MODIFIED:20090611T212420Z +UID:ee7bc7d8-d5a8-43f1-b817-54b5458429fa +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080321 +DTEND;VALUE=DATE:20080322 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030436Z +LAST-MODIFIED:20090611T212420Z +UID:98652109-bdda-4451-afd3-9955bfe918c7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090410 +DTEND;VALUE=DATE:20090411 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030446Z +LAST-MODIFIED:20090611T212420Z +UID:7241d18c-6e4a-4a15-b262-a0415d05e5c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100402 +DTEND;VALUE=DATE:20100403 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030513Z +LAST-MODIFIED:20090611T212420Z +UID:be42b228-382d-4add-be00-c36fdb2508c7 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120406 +DTEND;VALUE=DATE:20120407 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030526Z +LAST-MODIFIED:20090611T212420Z +UID:db8d6551-0b2d-44c1-8c21-ba96030a445e +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130329 +DTEND;VALUE=DATE:20130330 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030543Z +LAST-MODIFIED:20090611T212420Z +UID:c14a8a90-a69d-44fe-9e34-a28feed1a1c0 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140418 +DTEND;VALUE=DATE:20140419 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030602Z +LAST-MODIFIED:20090611T212420Z +UID:269d06c4-2eed-45de-af47-6668b7e58902 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150403 +DTEND;VALUE=DATE:20150404 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030616Z +LAST-MODIFIED:20090611T212420Z +UID:8e3f7823-a340-4b6e-8b7c-10a539f5db8f +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160325 +DTEND;VALUE=DATE:20160326 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030630Z +LAST-MODIFIED:20090611T212420Z +UID:a6d17884-5c3e-4f4b-9383-7b83814ada9d +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170414 +DTEND;VALUE=DATE:20170415 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030648Z +LAST-MODIFIED:20090611T212420Z +UID:19fb2499-97ae-457f-8d41-6ec76292c77f +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180330 +DTEND;VALUE=DATE:20180331 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030704Z +LAST-MODIFIED:20090611T212420Z +UID:0a125549-300d-4bd9-af20-3fc28935946e +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190419 +DTEND;VALUE=DATE:20190420 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030718Z +LAST-MODIFIED:20090611T212420Z +UID:50de218e-013d-4739-b8d4-f30f74a57168 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200410 +DTEND;VALUE=DATE:20200411 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030737Z +LAST-MODIFIED:20090611T212420Z +UID:af9df15d-af91-4e19-b7d2-e80bf8dfc378 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000611 +DTEND;VALUE=DATE:20000612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030855Z +LAST-MODIFIED:20090611T212420Z +UID:b5eb3eae-875a-4b50-a10c-b8a487907eac +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010603 +DTEND;VALUE=DATE:20010604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030907Z +LAST-MODIFIED:20090611T212420Z +UID:c5a9ee90-d177-4ca5-917e-245791f15312 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020519 +DTEND;VALUE=DATE:20020520 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030942Z +LAST-MODIFIED:20090611T212420Z +UID:9bbe68fb-4d23-4d27-9171-26177baf984d +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030608 +DTEND;VALUE=DATE:20030609 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030959Z +LAST-MODIFIED:20090611T212420Z +UID:c4f15038-8df9-475b-bc85-9948f97cddc7 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040530 +DTEND;VALUE=DATE:20040531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031013Z +LAST-MODIFIED:20090611T212420Z +UID:d54dd907-ba90-4607-88ee-b15d7b79736f +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050515 +DTEND;VALUE=DATE:20050516 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031027Z +LAST-MODIFIED:20090611T212420Z +UID:810562a0-2467-4a43-860d-db69cbf92fdf +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060604 +DTEND;VALUE=DATE:20060605 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031042Z +LAST-MODIFIED:20090611T212420Z +UID:8212e80d-6efa-4e79-bcd0-44fc831a1ecc +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070527 +DTEND;VALUE=DATE:20070528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031101Z +LAST-MODIFIED:20090611T212420Z +UID:da7b884d-0d0f-48d3-97f7-d13b21cea817 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080511 +DTEND;VALUE=DATE:20080512 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031112Z +LAST-MODIFIED:20090611T212420Z +UID:719c1eff-d26b-4316-86b5-464a4d24bf98 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090531 +DTEND;VALUE=DATE:20090601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031123Z +LAST-MODIFIED:20090611T212420Z +UID:a61992d7-0747-46db-aa4c-49ddef65de55 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100523 +DTEND;VALUE=DATE:20100524 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031210Z +LAST-MODIFIED:20090611T212420Z +UID:09673ae2-e44c-4cac-90de-deaf49b6b875 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120527 +DTEND;VALUE=DATE:20120528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031224Z +LAST-MODIFIED:20090611T212420Z +UID:1919a739-59f5-46c2-8cb0-b86726b1e72d +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130519 +DTEND;VALUE=DATE:20130520 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031235Z +LAST-MODIFIED:20090611T212420Z +UID:0a4fae5e-4698-427f-bbdf-b6c08fd96053 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140608 +DTEND;VALUE=DATE:20140609 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031253Z +LAST-MODIFIED:20090611T212420Z +UID:3d5c4095-4429-4abe-8a7a-4bbf5236eaa4 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150524 +DTEND;VALUE=DATE:20150525 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031311Z +LAST-MODIFIED:20090611T212420Z +UID:1177a296-0723-4fcb-8d35-e79b1c021330 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160515 +DTEND;VALUE=DATE:20160516 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031323Z +LAST-MODIFIED:20090611T212420Z +UID:452c678d-0b42-4a89-a347-a0b8e12c4fd6 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170604 +DTEND;VALUE=DATE:20170605 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031341Z +LAST-MODIFIED:20090611T212420Z +UID:01fec43b-b371-4f56-97cb-ef454132a74a +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180520 +DTEND;VALUE=DATE:20180521 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031401Z +LAST-MODIFIED:20090611T212420Z +UID:697b8004-261c-4c0b-a15e-06c1af95ab9b +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190609 +DTEND;VALUE=DATE:20190610 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031412Z +LAST-MODIFIED:20090611T212420Z +UID:ac723797-16b2-4fd9-9d5e-2f55cc90dc22 +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200531 +DTEND;VALUE=DATE:20200601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031600Z +LAST-MODIFIED:20090611T212420Z +UID:bec77dd4-49e7-46b5-9ea4-7409cc5369e1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000618 +DTEND;VALUE=DATE:20000619 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031731Z +LAST-MODIFIED:20090611T212420Z +UID:f395a03d-27e2-4214-88c2-20c638c7f99d +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010610 +DTEND;VALUE=DATE:20010611 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031843Z +LAST-MODIFIED:20090611T212420Z +UID:fb37469f-ba53-4fec-9c65-fb22c59470a8 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020526 +DTEND;VALUE=DATE:20020527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030305Z +LAST-MODIFIED:20090611T212420Z +UID:a2eb4297-f3a3-414b-b53a-e7cdaad20ac9 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030418 +DTEND;VALUE=DATE:20030419 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032035Z +LAST-MODIFIED:20090611T212420Z +UID:078f774d-1113-4bad-b7e5-6f4c48100fc4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030615 +DTEND;VALUE=DATE:20030616 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032128Z +LAST-MODIFIED:20090611T212420Z +UID:c2df343c-bf38-4e86-9129-010bd55de1b1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040606 +DTEND;VALUE=DATE:20040607 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025605Z +LAST-MODIFIED:20090611T212420Z +UID:e4891762-ad19-402e-8195-828fb3386d22 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050320 +DTEND;VALUE=DATE:20050321 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032246Z +LAST-MODIFIED:20090611T212420Z +UID:f14c5274-2112-4470-a3f8-f8a5b4eb8199 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050522 +DTEND;VALUE=DATE:20050523 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025617Z +LAST-MODIFIED:20090611T212420Z +UID:33a3f736-bd5b-4a87-b983-612e01ea4dbe +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060409 +DTEND;VALUE=DATE:20060410 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032352Z +LAST-MODIFIED:20090611T212421Z +UID:43eeeab3-5836-41cc-82b4-d91d922871c4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060611 +DTEND;VALUE=DATE:20060612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032435Z +LAST-MODIFIED:20090611T212421Z +UID:72234667-e98f-4532-b4da-a2cdd2e56ce4 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070603 +DTEND;VALUE=DATE:20070604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032512Z +LAST-MODIFIED:20090611T212421Z +UID:a289a44f-5eb2-4f14-b458-e55dcbc00e9d +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080518 +DTEND;VALUE=DATE:20080519 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032617Z +LAST-MODIFIED:20090611T212421Z +UID:b5d1e103-7807-4049-ac80-df13ab666b5e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090607 +DTEND;VALUE=DATE:20090608 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T032717Z +LAST-MODIFIED:20090611T212421Z +UID:59ef3b5c-a65f-4a5f-81ec-25d201285670 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100530 +DTEND;VALUE=DATE:20100531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033236Z +LAST-MODIFIED:20090611T212421Z +UID:f931f051-1dec-4a6c-b78b-615474143fd1 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120603 +DTEND;VALUE=DATE:20120604 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033315Z +LAST-MODIFIED:20090611T212421Z +UID:838f906b-f3c8-4b94-9cdb-ef698d0222b8 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130526 +DTEND;VALUE=DATE:20130527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033425Z +LAST-MODIFIED:20090611T212421Z +UID:de98efc2-cd59-41fd-af0d-d2dcb58b23fe +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140615 +DTEND;VALUE=DATE:20140616 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033511Z +LAST-MODIFIED:20090611T212421Z +UID:139b7040-5cfd-4db4-abf2-8251ae17368e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150531 +DTEND;VALUE=DATE:20150601 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033614Z +LAST-MODIFIED:20090611T212421Z +UID:78d6d38a-a0ae-4b75-bcbb-0276d69a430e +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160522 +DTEND;VALUE=DATE:20160523 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033655Z +LAST-MODIFIED:20090611T212421Z +UID:83608056-f721-4564-88ed-3cc5d12b01fb +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170611 +DTEND;VALUE=DATE:20170612 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033759Z +LAST-MODIFIED:20090611T212421Z +UID:cc92b242-3a24-471c-b1be-b8918fb8b6de +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180527 +DTEND;VALUE=DATE:20180528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033848Z +LAST-MODIFIED:20090611T212421Z +UID:006195b6-8021-4814-9635-f9f5bffa7a36 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190616 +DTEND;VALUE=DATE:20190617 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033920Z +LAST-MODIFIED:20090611T212421Z +UID:b60896ca-b1eb-4ec1-a9cd-67c7403c6335 +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200607 +DTEND;VALUE=DATE:20200608 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224733Z +LAST-MODIFIED:20090611T212421Z +UID:d41971bd-5c3f-4293-a154-a210d39b6667 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000601 +DTEND;VALUE=DATE:20000602 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224836Z +LAST-MODIFIED:20090611T212421Z +UID:e6166189-6b80-44b8-9902-60e58f0cbe0c +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010524 +DTEND;VALUE=DATE:20010525 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224858Z +LAST-MODIFIED:20090611T212421Z +UID:7f5a197b-441e-40e3-a31a-1c0cb5ffed19 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20020509 +DTEND;VALUE=DATE:20020510 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224916Z +LAST-MODIFIED:20090611T212421Z +UID:192d84d7-7a76-4d98-a335-919203fa28e1 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20030529 +DTEND;VALUE=DATE:20030530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224929Z +LAST-MODIFIED:20090611T212421Z +UID:52cb0477-1dab-4881-bf56-64099c934453 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20040520 +DTEND;VALUE=DATE:20040521 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224944Z +LAST-MODIFIED:20090611T212421Z +UID:17c27008-29e2-4050-adda-5757c94fb673 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20050505 +DTEND;VALUE=DATE:20050506 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T224959Z +LAST-MODIFIED:20090611T212421Z +UID:03f7a269-b863-4fb9-a02f-135803fa2972 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20060525 +DTEND;VALUE=DATE:20060526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225014Z +LAST-MODIFIED:20090611T212421Z +UID:fc9f7abd-c3fc-42b2-bd87-7528470f77f3 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070517 +DTEND;VALUE=DATE:20070518 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225025Z +LAST-MODIFIED:20090611T212421Z +UID:c1649dbc-9ad2-4257-a687-7dc706b08ea2 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20080501 +DTEND;VALUE=DATE:20080502 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225039Z +LAST-MODIFIED:20090611T212421Z +UID:82c49077-6f81-42ed-84e1-53ebe66f981d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20090521 +DTEND;VALUE=DATE:20090522 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225051Z +LAST-MODIFIED:20090611T212421Z +UID:5203b99c-f304-4d25-ba4e-1e776237186d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100513 +DTEND;VALUE=DATE:20100514 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225111Z +LAST-MODIFIED:20090611T212421Z +UID:18e27f82-2d27-42fa-b04c-315c6fadcd9d +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120517 +DTEND;VALUE=DATE:20120518 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225120Z +LAST-MODIFIED:20090611T212421Z +UID:7726ca21-1f4d-4cc5-ade3-8c384f5d72a5 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130509 +DTEND;VALUE=DATE:20130510 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225133Z +LAST-MODIFIED:20090611T212421Z +UID:6d025a60-733c-4af4-a2ee-f1b904090666 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140529 +DTEND;VALUE=DATE:20140530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225144Z +LAST-MODIFIED:20090611T212421Z +UID:cd092661-b37e-4ec9-b798-8f957c4278b4 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150514 +DTEND;VALUE=DATE:20150515 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225154Z +LAST-MODIFIED:20090611T212421Z +UID:ed1358ef-c818-4af3-9f8e-4d965d800a21 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160505 +DTEND;VALUE=DATE:20160506 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225204Z +LAST-MODIFIED:20090611T212421Z +UID:7c8351e3-e3fd-4c0b-a60c-f558aa31df55 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170525 +DTEND;VALUE=DATE:20170526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225214Z +LAST-MODIFIED:20090611T212421Z +UID:db037aff-17bf-430b-b094-52e99879ba4e +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180510 +DTEND;VALUE=DATE:20180511 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225224Z +LAST-MODIFIED:20090611T212421Z +UID:dddeea06-c165-474c-96aa-6c1b705c6b0a +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190530 +DTEND;VALUE=DATE:20190531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225238Z +LAST-MODIFIED:20090611T212421Z +UID:09a3f71c-83df-4649-b7d1-4792ae2fb523 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200521 +DTEND;VALUE=DATE:20200522 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025033Z +LAST-MODIFIED:20100911T002511Z +DTSTAMP:20100911T002511Z +UID:a02038e9-a094-4744-b09d-d7d2ef96bd28 +SUMMARY:Ash Wednesday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110309 +DTEND;VALUE=DATE:20110310 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T025855Z +LAST-MODIFIED:20100911T002525Z +DTSTAMP:20100911T002525Z +UID:844a3d56-4fab-4df1-92d3-8f4385d8efc9 +SUMMARY:Palm Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110417 +DTEND;VALUE=DATE:20110418 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T030501Z +LAST-MODIFIED:20100911T002555Z +DTSTAMP:20100911T002555Z +UID:6e05d4a1-8b7f-446d-a7fc-566b6837c927 +SUMMARY:Good Friday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110422 +DTEND;VALUE=DATE:20110423 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T223849Z +LAST-MODIFIED:20100911T002632Z +DTSTAMP:20100911T002632Z +UID:fa0cda04-283e-4b53-95b0-c9903a3f85e2 +SUMMARY:Easter Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110424 +DTEND;VALUE=DATE:20110425 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:2 +SEQUENCE:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20081125T225101Z +LAST-MODIFIED:20100911T002740Z +DTSTAMP:20100911T002740Z +UID:5250764e-c50c-44ba-b3d6-32ee24ee49e1 +SUMMARY:Ascension Day +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110602 +DTEND;VALUE=DATE:20110603 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T031143Z +LAST-MODIFIED:20100911T002756Z +DTSTAMP:20100911T002756Z +UID:5e614422-74fe-47d6-bf89-dca8c4d7df3a +SUMMARY:Pentecost +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110612 +DTEND;VALUE=DATE:20110613 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T033004Z +LAST-MODIFIED:20100911T002812Z +DTSTAMP:20100911T002812Z +UID:001f88a2-a3b4-4dff-a577-c9860d01dcbd +SUMMARY:Trinity Sunday +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110619 +DTEND;VALUE=DATE:20110620 +TRANSP:TRANSPARENT +SEQUENCE:1 +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:9f3df592-1dd1-11b2-8b51-a8c49a575f97 +SUMMARY:President's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=2 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000221 +DTEND;VALUE=DATE:20000222 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:e0d4c156-1dd1-11b2-b6a4-dac750e0163e +SUMMARY:Mother's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000514 +DTEND;VALUE=DATE:20000515 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:c2783248-1dd1-11b2-affa-ee50c39d8083 +SUMMARY:Armed Forces Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3SA;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000520 +DTEND;VALUE=DATE:20000521 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:d9706fa6-1dd1-11b2-a349-e97241bd4740 +SUMMARY:Memorial Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=-1MO;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000529 +DTEND;VALUE=DATE:20000530 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:46e6845c-1dd2-11b2-bd3d-d8755a1a171f +SUMMARY:Parents' Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=4SU;BYMONTH=7 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000723 +DTEND;VALUE=DATE:20000724 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:0726ea42-1dd2-11b2-9fe6-dda3d063fb50 +SUMMARY:Labor Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=1MO;BYMONTH=9 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000904 +DTEND;VALUE=DATE:20000905 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:7d4402f4-1dd2-11b2-9790-b6193cfa4349 +SUMMARY:Halloween +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001031 +DTEND;VALUE=DATE:20001101 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:2c7ba10c-1dd2-11b2-b0b7-96f89f288199 +SUMMARY:Veteran's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001111 +DTEND;VALUE=DATE:20001112 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:4299a358-1dd2-11b2-a228-d062222b6f88 +SUMMARY:Thanksgiving Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=4TH;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001123 +DTEND;VALUE=DATE:20001124 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:54f392fa-1dd2-11b2-8ecb-9ae0a7fcebd1 +SUMMARY:Christmas Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001225 +DTEND;VALUE=DATE:20001226 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:b1f194fc-1dd1-11b2-a973-d219c68b95c4 +SUMMARY:New Year's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000101 +DTEND;VALUE=DATE:20000102 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:a2cbca9c-1dd1-11b2-83e2-abab36f0506d +SUMMARY:New Year's Eve +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001231 +DTEND;VALUE=DATE:20010101 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212421Z +UID:13f83ed2-1dd2-11b2-a0de-ee8645423959 +SUMMARY:Daylight Savings Time ends +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20071104 +DTEND;VALUE=DATE:20071105 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:f669a974-1dd1-11b2-9fac-f44d91c657af +SUMMARY:Independence Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000704 +DTEND;VALUE=DATE:20000705 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:cb90487e-1dd1-11b2-9e06-d1a12cc963dc +SUMMARY:Father's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3SU;BYMONTH=6 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000618 +DTEND;VALUE=DATE:20000619 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e8022726-1dd1-11b2-aae6-9f73f16c0f01 +SUMMARY:Flag Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000614 +DTEND;VALUE=DATE:20000615 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:f1561106-1dd1-11b2-955f-ba14e5210f45 +SUMMARY:Earth Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000422 +DTEND;VALUE=DATE:20000423 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e46086c2-1dd1-11b2-8a16-a2c99f1c525a +SUMMARY:Tax Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000415 +DTEND;VALUE=DATE:20000416 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:c981b858-1dd1-11b2-b69b-94d05f8bda66 +SUMMARY:April Fool's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000401 +DTEND;VALUE=DATE:20000402 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:bd6cfe4c-1dd1-11b2-b3dc-ebaab9302c26 +SUMMARY:St. Patrick's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000317 +DTEND;VALUE=DATE:20000318 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:aff9f3c8-1dd1-11b2-8593-c63469762eb1 +SUMMARY:George Washington's Birthday (actual) +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000222 +DTEND;VALUE=DATE:20000223 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:790b86e2-1dd2-11b2-9cb9-c905fa35c6f9 +SUMMARY:Valentine's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000214 +DTEND;VALUE=DATE:20000215 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:4d5b843e-1dd2-11b2-a6c9-8ea942124d1b +SUMMARY:Abraham Lincoln's Birthday +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000212 +DTEND;VALUE=DATE:20000213 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:0c771532-1dd2-11b2-8dd9-8638db3aef63 +SUMMARY:Groundhog's Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000202 +DTEND;VALUE=DATE:20000203 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T220831Z +LAST-MODIFIED:20090611T212422Z +UID:8a05b5d3-6b97-4fe1-b26e-b258a073c5e0 +SUMMARY:United Nations Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001024 +DTEND;VALUE=DATE:20001025 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:e462881a-20b8-4b70-ab3c-cf38016d3398 +SUMMARY:Daylight Savings Time begins +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20070311 +DTEND;VALUE=DATE:20070312 +CLASS:PUBLIC +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T221253Z +LAST-MODIFIED:20090611T212422Z +UID:1eb525d5-26cd-43f0-819e-acc3003b35ad +SUMMARY:Daylight Savings Time begins +RRULE:FREQ=YEARLY;UNTIL=20060402T000000;BYDAY=1SU;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000402 +DTEND;VALUE=DATE:20000403 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T221447Z +LAST-MODIFIED:20090611T212422Z +UID:05504c5b-5984-4eb0-a60a-c6d5cf083d82 +SUMMARY:Daylight Savings Time ends +RRULE:FREQ=YEARLY;UNTIL=20061029T000000;BYDAY=-1SU;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001029 +DTEND;VALUE=DATE:20001030 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T214410Z +LAST-MODIFIED:20090611T212422Z +UID:088031d1-abea-47ed-978d-39c6319e8bf2 +SUMMARY:Inauguration Day +RRULE:FREQ=YEARLY;INTERVAL=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010120 +DTEND;VALUE=DATE:20010121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T164802Z +LAST-MODIFIED:20090611T212422Z +UID:d3c521e1-ffab-414c-8cfa-99e76758d8c8 +SUMMARY:Constitution Day\, Citizenship Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000917 +DTEND;VALUE=DATE:20000918 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T165106Z +LAST-MODIFIED:20090611T212422Z +UID:a44b8b6e-7ae4-4120-b7ad-14e91691be2a +SUMMARY:National Day of Prayer +RRULE:FREQ=YEARLY;BYDAY=1TH;BYMONTH=5 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000504 +DTEND;VALUE=DATE:20000505 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080828T165839Z +LAST-MODIFIED:20090611T212422Z +UID:e20f1b5c-8ac4-4bea-acf4-b4f2bfe95329 +SUMMARY:Arbor Day +RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000428 +DTEND;VALUE=DATE:20000429 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080830T011903Z +LAST-MODIFIED:20090611T212422Z +UID:feae8f6c-bdce-46c8-aa45-e2d1c0fb9cb0 +SUMMARY:US General Election +RRULE:FREQ=YEARLY;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001107 +DTEND;VALUE=DATE:20001108 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080830T021222Z +LAST-MODIFIED:20090611T212422Z +UID:60b5ca68-d0a6-4634-9254-16cc300bb12e +SUMMARY:Black Friday +RRULE:FREQ=YEARLY;BYDAY=FR;BYMONTHDAY=23,24,25,26,27,28,29;BYMONTH=11 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001124 +DTEND;VALUE=DATE:20001125 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:dd9bfdf4-1dd1-11b2-ab6e-8da990a14639 +SUMMARY:Martin Luther King Jr.'s Day +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=1 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000117 +DTEND;VALUE=DATE:20000118 +CLASS:PUBLIC +TRANSP:TRANSPARENT +DESCRIPTION:Traditionally on January 15th.\nObserved on 3rd Monday of January. +END:VEVENT +BEGIN:VEVENT +CREATED:20070315T171800Z +LAST-MODIFIED:20090611T212422Z +UID:175691b0-1dd2-11b2-9596-e9837a6245dd +SUMMARY:Columbus Day (observed) +STATUS:CONFIRMED +RRULE:FREQ=YEARLY;BYDAY=2MO;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001009 +DTEND;VALUE=DATE:20001010 +CLASS:PUBLIC +TRANSP:TRANSPARENT +DESCRIPTION:Traditionally on October 12th.\nObserved on 2nd Monday of October. +END:VEVENT +BEGIN:VEVENT +CREATED:20080902T140125Z +LAST-MODIFIED:20090611T212422Z +UID:22896130-ef70-46ca-a071-a15f3a746b04 +SUMMARY:Administrative Professionals Day +RRULE:FREQ=YEARLY;BYDAY=WE;BYMONTHDAY=21,22,23,24,25,26,27;BYMONTH=4 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000426 +DTEND;VALUE=DATE:20000427 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T212909Z +LAST-MODIFIED:20090611T212422Z +UID:a0486410-f87b-4e09-939a-77121b58b535 +SUMMARY:Christmas Eve +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001224 +DTEND;VALUE=DATE:20001225 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081126T134531Z +LAST-MODIFIED:20090611T212422Z +UID:8c320d1a-fc56-4282-962c-4c67763a4c58 +SUMMARY:Grandparents Day +RRULE:FREQ=YEARLY;BYDAY=SU;BYMONTHDAY=7,8,9,10,11,12,13;BYMONTH=9 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000910 +DTEND;VALUE=DATE:20000911 +DESCRIPTION:First Sunday following Labor Day +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T214111Z +LAST-MODIFIED:20090611T212422Z +UID:efab1245-c19c-4693-8391-eb5f40cc3743 +SUMMARY:Epiphany +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000106 +DTEND;VALUE=DATE:20000107 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080827T220608Z +LAST-MODIFIED:20090611T212422Z +UID:37f09326-5105-4092-8535-1b4cad9812ac +SUMMARY:Reformation Day +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001029 +DTEND;VALUE=DATE:20001030 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20080829T034030Z +LAST-MODIFIED:20090611T212422Z +UID:af808fce-8a77-4d15-ad63-c096737303d2 +SUMMARY:All Saints Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001101 +DTEND;VALUE=DATE:20001102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081118T135517Z +LAST-MODIFIED:20090611T212422Z +UID:6c6e277f-d344-4807-8d91-bea1689eac7c +SUMMARY:Immaculate Conception +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001208 +DTEND;VALUE=DATE:20001209 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081118T141534Z +LAST-MODIFIED:20090611T212422Z +UID:25db674e-6d6a-41ee-848b-2b79552f071a +SUMMARY:Assumption Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000815 +DTEND;VALUE=DATE:20000816 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20081126T151107Z +LAST-MODIFIED:20090611T212422Z +UID:c3a78810-ea6a-48bc-8178-56fe3fe61e23 +SUMMARY:Kwanzaa +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:19661226 +DTEND;VALUE=DATE:19670102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20091128T184942Z +LAST-MODIFIED:20091128T185038Z +UID:cebdafa9-6b2a-4869-a751-5554b6da2e45 +SUMMARY:Pearl Harbor Remembrance Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20001207 +DTEND;VALUE=DATE:20001208 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100911T004419Z +LAST-MODIFIED:20100911T004455Z +DTSTAMP:20100911T004455Z +UID:1ee4d840-eb70-44f0-8f2a-db778c373ac5 +SUMMARY:Patriot Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20010911 +DTEND;VALUE=DATE:20010912 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100911T004608Z +LAST-MODIFIED:20100911T004647Z +DTSTAMP:20100911T004647Z +UID:e8c8b4f6-1e0a-4bbb-b077-0fcce9077e1d +SUMMARY:Women's Equality Day +RRULE:FREQ=YEARLY +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20000826 +DTEND;VALUE=DATE:20000827 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225155Z +LAST-MODIFIED:20100625T230014Z +DTSTAMP:20100625T230014Z +UID:4c53aa44-f014-4334-8e39-5b2ff090445a +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100101 +DTEND;VALUE=DATE:20100102 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225222Z +LAST-MODIFIED:20100625T230022Z +DTSTAMP:20100625T230022Z +UID:5705d0cf-01b6-4511-9f36-0fc9739b21fc +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100118 +DTEND;VALUE=DATE:20100119 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225249Z +LAST-MODIFIED:20100625T230034Z +DTSTAMP:20100625T230034Z +UID:26751c39-998f-4d1c-bc77-454f21d94fd1 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100215 +DTEND;VALUE=DATE:20100216 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225312Z +LAST-MODIFIED:20100625T230050Z +DTSTAMP:20100625T230050Z +UID:91cde8ea-009b-4a07-b835-7d33b1b7e4e1 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100531 +DTEND;VALUE=DATE:20100601 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225342Z +LAST-MODIFIED:20100625T230108Z +DTSTAMP:20100625T230108Z +UID:63368446-395a-42ca-8108-f1bb7ee5a851 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100705 +DTEND;VALUE=DATE:20100706 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225406Z +LAST-MODIFIED:20100625T230123Z +DTSTAMP:20100625T230123Z +UID:6cbc2eb9-d818-40f0-8e91-7c7ca5e43e0f +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20100906 +DTEND;VALUE=DATE:20100907 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225422Z +LAST-MODIFIED:20100625T230138Z +DTSTAMP:20100625T230138Z +UID:26ab3aae-1f30-4eea-9a30-35aab72d9e67 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101011 +DTEND;VALUE=DATE:20101012 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225442Z +LAST-MODIFIED:20100625T230149Z +DTSTAMP:20100625T230149Z +UID:c08f4482-8591-4940-8838-80381b0cf69b +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101111 +DTEND;VALUE=DATE:20101112 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225456Z +LAST-MODIFIED:20100625T230155Z +DTSTAMP:20100625T230155Z +UID:7262eefe-a1b1-4370-8262-c2f900150c0f +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101125 +DTEND;VALUE=DATE:20101126 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225512Z +LAST-MODIFIED:20100625T230205Z +DTSTAMP:20100625T230205Z +UID:431576fe-17ac-48ea-941e-08d25998c7c2 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101224 +DTEND;VALUE=DATE:20101225 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225649Z +LAST-MODIFIED:20100625T230212Z +DTSTAMP:20100625T230212Z +UID:ff38e9bf-6a04-4140-8e14-17f77bfabbdf +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20101231 +DTEND;VALUE=DATE:20110101 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225710Z +LAST-MODIFIED:20100625T230225Z +DTSTAMP:20100625T230225Z +UID:c670bd38-0675-4746-8eca-cd06d5d0fb46 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110117 +DTEND;VALUE=DATE:20110118 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225733Z +LAST-MODIFIED:20100625T230234Z +DTSTAMP:20100625T230234Z +UID:486ce67b-2a71-44ae-87d1-ec8d000a9584 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110221 +DTEND;VALUE=DATE:20110222 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225753Z +LAST-MODIFIED:20100625T230259Z +DTSTAMP:20100625T230259Z +UID:aca86965-d147-47d7-8e88-3990efe7188a +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110530 +DTEND;VALUE=DATE:20110531 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225812Z +LAST-MODIFIED:20100625T230311Z +DTSTAMP:20100625T230311Z +UID:40f9466a-95bd-40fd-a0a5-df005fbee438 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110704 +DTEND;VALUE=DATE:20110705 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225826Z +LAST-MODIFIED:20100625T230320Z +DTSTAMP:20100625T230320Z +UID:86a93453-eb85-4836-8e35-f3d7403eb02c +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20110905 +DTEND;VALUE=DATE:20110906 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225845Z +LAST-MODIFIED:20100625T230329Z +DTSTAMP:20100625T230329Z +UID:8aed5642-2ab6-4d47-ae3e-d6bda178c530 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111010 +DTEND;VALUE=DATE:20111011 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225901Z +LAST-MODIFIED:20100625T230342Z +DTSTAMP:20100625T230342Z +UID:095e361c-db8c-4936-80ff-6ac356e3a8af +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111111 +DTEND;VALUE=DATE:20111112 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225917Z +LAST-MODIFIED:20100625T230349Z +DTSTAMP:20100625T230349Z +UID:859d4dfe-1eae-4b3b-9d42-438d500ca455 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111124 +DTEND;VALUE=DATE:20111125 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T225933Z +LAST-MODIFIED:20100625T230400Z +DTSTAMP:20100625T230400Z +UID:06a9583b-01fb-4ed4-926a-ac995e5dac6d +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20111226 +DTEND;VALUE=DATE:20111227 +TRANSP:TRANSPARENT +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230426Z +LAST-MODIFIED:20100625T230432Z +DTSTAMP:20100625T230432Z +UID:ec58e9d8-27fa-4f3a-8cb2-8073b860982f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120102 +DTEND;VALUE=DATE:20120103 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230442Z +LAST-MODIFIED:20100625T230451Z +DTSTAMP:20100625T230451Z +UID:e3e1f541-ad97-4683-ab18-13c9d8620130 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120116 +DTEND;VALUE=DATE:20120117 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230503Z +LAST-MODIFIED:20100625T230510Z +DTSTAMP:20100625T230510Z +UID:b2f98535-66c6-423f-846d-7be00b63d315 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120220 +DTEND;VALUE=DATE:20120221 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230528Z +LAST-MODIFIED:20100625T230537Z +DTSTAMP:20100625T230537Z +UID:6b1d9233-40b7-406b-a98c-769b2681d9ad +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120528 +DTEND;VALUE=DATE:20120529 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230550Z +LAST-MODIFIED:20100625T230558Z +DTSTAMP:20100625T230558Z +UID:9c6685c4-cf34-406d-a52d-457432cd2e72 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120704 +DTEND;VALUE=DATE:20120705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230608Z +LAST-MODIFIED:20100625T230616Z +DTSTAMP:20100625T230616Z +UID:64e8f36b-30ca-4a68-bb68-b6be1bac33e7 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20120903 +DTEND;VALUE=DATE:20120904 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230625Z +LAST-MODIFIED:20100625T230633Z +DTSTAMP:20100625T230633Z +UID:0feaf063-8cc3-4dcc-80a4-3006dd3ee395 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121008 +DTEND;VALUE=DATE:20121009 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230640Z +LAST-MODIFIED:20100625T230646Z +DTSTAMP:20100625T230646Z +UID:a3ab943c-efa6-403a-94d6-266351d1bb9a +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121112 +DTEND;VALUE=DATE:20121113 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230652Z +LAST-MODIFIED:20100625T230659Z +DTSTAMP:20100625T230659Z +UID:e29b795e-0f37-4ea4-84e6-0f277cf5a8ba +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121122 +DTEND;VALUE=DATE:20121123 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230711Z +LAST-MODIFIED:20100625T230719Z +DTSTAMP:20100625T230719Z +UID:9986c0ee-983a-4538-ad4a-4688fe5b5636 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20121225 +DTEND;VALUE=DATE:20121226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230745Z +LAST-MODIFIED:20100625T230752Z +DTSTAMP:20100625T230752Z +UID:0b3180aa-65a8-4732-bb4c-36cd3c79a650 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130101 +DTEND;VALUE=DATE:20130102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230800Z +LAST-MODIFIED:20100625T230806Z +DTSTAMP:20100625T230806Z +UID:19f0cc25-bec1-4e84-915b-a70bb532fcfb +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130121 +DTEND;VALUE=DATE:20130122 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230816Z +LAST-MODIFIED:20100625T230822Z +DTSTAMP:20100625T230822Z +UID:db225df5-a20c-4889-9511-bcf81acad6e4 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130218 +DTEND;VALUE=DATE:20130219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230833Z +LAST-MODIFIED:20100625T230839Z +DTSTAMP:20100625T230839Z +UID:9b12b9be-0f16-42ff-97a5-53a67cc46f2c +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130527 +DTEND;VALUE=DATE:20130528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230849Z +LAST-MODIFIED:20100625T230856Z +DTSTAMP:20100625T230856Z +UID:9b6b81e1-e4f8-490b-9062-24726dbf27f2 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130704 +DTEND;VALUE=DATE:20130705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230905Z +LAST-MODIFIED:20100625T230910Z +DTSTAMP:20100625T230910Z +UID:5b722439-56b8-4f26-a21d-c2c99898bf47 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20130902 +DTEND;VALUE=DATE:20130903 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230922Z +LAST-MODIFIED:20100625T230927Z +DTSTAMP:20100625T230927Z +UID:e44229c1-89cd-4c96-8094-c09a56f1a028 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131014 +DTEND;VALUE=DATE:20131015 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230935Z +LAST-MODIFIED:20100625T230941Z +DTSTAMP:20100625T230941Z +UID:fea08c25-1cbc-4d7b-ab30-87bf5b87c4e0 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131111 +DTEND;VALUE=DATE:20131112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T230955Z +LAST-MODIFIED:20100625T231001Z +DTSTAMP:20100625T231001Z +UID:12eb5737-4e7f-4278-9f8f-36202257d434 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131128 +DTEND;VALUE=DATE:20131129 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T231008Z +LAST-MODIFIED:20100625T231016Z +DTSTAMP:20100625T231016Z +UID:a2acacd4-e541-4334-b5ad-e2e215cf32fc +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20131225 +DTEND;VALUE=DATE:20131226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232203Z +LAST-MODIFIED:20100625T232211Z +DTSTAMP:20100625T232211Z +UID:784620c2-befa-40af-9aa2-5232ceebf80f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140101 +DTEND;VALUE=DATE:20140102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232227Z +LAST-MODIFIED:20100625T232233Z +DTSTAMP:20100625T232233Z +UID:7d90511b-c99b-4937-af69-1da4781923c8 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140120 +DTEND;VALUE=DATE:20140121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232242Z +LAST-MODIFIED:20100625T232249Z +DTSTAMP:20100625T232249Z +UID:760534b9-7b1c-41b1-8797-6dd6583d7b98 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140217 +DTEND;VALUE=DATE:20140218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232259Z +LAST-MODIFIED:20100625T232307Z +DTSTAMP:20100625T232307Z +UID:3a66f3a1-bce5-4dcf-a815-2d50057ba758 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140526 +DTEND;VALUE=DATE:20140527 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232324Z +LAST-MODIFIED:20100625T232333Z +DTSTAMP:20100625T232333Z +UID:8dec8541-cada-48c8-bae1-7b64e5bd96f1 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140704 +DTEND;VALUE=DATE:20140705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232344Z +LAST-MODIFIED:20100625T232352Z +DTSTAMP:20100625T232352Z +UID:f1a94187-1b1a-46eb-9eba-9f2e03efff7e +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20140901 +DTEND;VALUE=DATE:20140902 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232358Z +LAST-MODIFIED:20100625T232405Z +DTSTAMP:20100625T232405Z +UID:aa078715-e039-42e8-bfca-ceb787c3eceb +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141013 +DTEND;VALUE=DATE:20141014 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232416Z +LAST-MODIFIED:20100625T232424Z +DTSTAMP:20100625T232424Z +UID:5a0ec2c2-3b70-4440-928d-d3c435205736 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141111 +DTEND;VALUE=DATE:20141112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232432Z +LAST-MODIFIED:20100625T232438Z +DTSTAMP:20100625T232438Z +UID:ec969553-a7c6-4ad1-bbf9-061bd48b91dc +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141127 +DTEND;VALUE=DATE:20141128 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232449Z +LAST-MODIFIED:20100625T232456Z +DTSTAMP:20100625T232456Z +UID:f01417f3-4570-4711-9abc-bf20a01cea0c +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20141225 +DTEND;VALUE=DATE:20141226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232517Z +LAST-MODIFIED:20100625T232523Z +DTSTAMP:20100625T232523Z +UID:f37395e6-1056-4175-a457-399cdab4eda2 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150101 +DTEND;VALUE=DATE:20150102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232529Z +LAST-MODIFIED:20100625T232536Z +DTSTAMP:20100625T232536Z +UID:3f07705a-a1fb-4915-8dbd-4e1513ade06b +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150119 +DTEND;VALUE=DATE:20150120 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232547Z +LAST-MODIFIED:20100625T232553Z +DTSTAMP:20100625T232553Z +UID:2dff572d-fc88-4925-8817-960c9f913974 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150216 +DTEND;VALUE=DATE:20150217 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232604Z +LAST-MODIFIED:20100625T232611Z +DTSTAMP:20100625T232611Z +UID:62aa4902-1113-4e89-ab08-9a344ff2709f +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150525 +DTEND;VALUE=DATE:20150526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232621Z +LAST-MODIFIED:20100625T232628Z +DTSTAMP:20100625T232628Z +UID:ed53ac56-5535-4877-acf1-84919821da44 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150703 +DTEND;VALUE=DATE:20150704 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232639Z +LAST-MODIFIED:20100625T232645Z +DTSTAMP:20100625T232645Z +UID:fdb30d77-208e-4b9c-acbb-877549b0c60e +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20150907 +DTEND;VALUE=DATE:20150908 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232656Z +LAST-MODIFIED:20100625T232703Z +DTSTAMP:20100625T232703Z +UID:a0add000-7f34-4a12-8cfc-dbf98872b532 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151012 +DTEND;VALUE=DATE:20151013 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232710Z +LAST-MODIFIED:20100625T232716Z +DTSTAMP:20100625T232716Z +UID:57bbf2f3-e92c-4864-9171-80e1aefa9b25 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151111 +DTEND;VALUE=DATE:20151112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232724Z +LAST-MODIFIED:20100625T232730Z +DTSTAMP:20100625T232730Z +UID:17357cbf-822b-4e16-97fe-9212a98262ea +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151126 +DTEND;VALUE=DATE:20151127 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232740Z +LAST-MODIFIED:20100625T232746Z +DTSTAMP:20100625T232746Z +UID:a5ae0bfb-a761-48a7-80b3-85c4dec3459d +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20151225 +DTEND;VALUE=DATE:20151226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232806Z +LAST-MODIFIED:20100625T232813Z +DTSTAMP:20100625T232813Z +UID:e84cfee3-d9f8-4088-9709-995258432145 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160101 +DTEND;VALUE=DATE:20160102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232819Z +LAST-MODIFIED:20100625T232826Z +DTSTAMP:20100625T232826Z +UID:2c19281f-8160-4886-ae2b-35f5a83d32ec +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160118 +DTEND;VALUE=DATE:20160119 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232836Z +LAST-MODIFIED:20100625T232842Z +DTSTAMP:20100625T232842Z +UID:ccb48d24-c58f-478b-a941-1706c642cd39 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160215 +DTEND;VALUE=DATE:20160216 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232851Z +LAST-MODIFIED:20100625T232858Z +DTSTAMP:20100625T232858Z +UID:067c379d-cdfa-49f5-9de7-65be7b5b0158 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160530 +DTEND;VALUE=DATE:20160531 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232911Z +LAST-MODIFIED:20100625T232917Z +DTSTAMP:20100625T232917Z +UID:dddf90ce-a186-4bfb-aaf8-878e07450fcf +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160704 +DTEND;VALUE=DATE:20160705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232925Z +LAST-MODIFIED:20100625T232931Z +DTSTAMP:20100625T232931Z +UID:c734ef2f-53a9-48f3-9da0-f70b70c0d881 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20160905 +DTEND;VALUE=DATE:20160906 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232938Z +LAST-MODIFIED:20100625T232944Z +DTSTAMP:20100625T232944Z +UID:c9d05e3a-bd7d-4a5d-94ba-020335a9b3aa +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161010 +DTEND;VALUE=DATE:20161011 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T232953Z +LAST-MODIFIED:20100625T233000Z +DTSTAMP:20100625T233000Z +UID:819d893b-af2b-4423-aaff-c4286ee349ee +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161111 +DTEND;VALUE=DATE:20161112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233007Z +LAST-MODIFIED:20100625T233013Z +DTSTAMP:20100625T233013Z +UID:9af9e892-e3a8-4a2c-a130-4e25c92c2baa +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161124 +DTEND;VALUE=DATE:20161125 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233022Z +LAST-MODIFIED:20100625T233028Z +DTSTAMP:20100625T233028Z +UID:8b5ff666-fec7-487b-99c0-aa621548e9ea +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20161226 +DTEND;VALUE=DATE:20161227 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233045Z +LAST-MODIFIED:20100625T233052Z +DTSTAMP:20100625T233052Z +UID:9943b6be-9c4b-4fc3-b690-80114b559f26 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170102 +DTEND;VALUE=DATE:20170103 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233101Z +LAST-MODIFIED:20100625T233109Z +DTSTAMP:20100625T233109Z +UID:4c3b85b1-2144-470e-b986-418c087cb5a0 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170116 +DTEND;VALUE=DATE:20170117 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233117Z +LAST-MODIFIED:20100625T233123Z +DTSTAMP:20100625T233123Z +UID:c09d860f-6464-41c4-b2ef-25011d25cecf +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170220 +DTEND;VALUE=DATE:20170221 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233131Z +LAST-MODIFIED:20100625T233138Z +DTSTAMP:20100625T233138Z +UID:ba4752c9-4032-4385-820d-2c4213e14fa5 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170529 +DTEND;VALUE=DATE:20170530 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233145Z +LAST-MODIFIED:20100625T233151Z +DTSTAMP:20100625T233151Z +UID:59e934c5-672e-4ccc-a686-9d9c495b3883 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170704 +DTEND;VALUE=DATE:20170705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233158Z +LAST-MODIFIED:20100625T233205Z +DTSTAMP:20100625T233205Z +UID:90340458-52db-4697-b2d7-8bfd51cfaaf7 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20170904 +DTEND;VALUE=DATE:20170905 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233212Z +LAST-MODIFIED:20100625T233218Z +DTSTAMP:20100625T233218Z +UID:70c9c1e3-31a2-4b0a-9712-69a184fbfb7c +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171009 +DTEND;VALUE=DATE:20171010 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233225Z +LAST-MODIFIED:20100625T233232Z +DTSTAMP:20100625T233232Z +UID:e9f86e06-9f1c-44bc-b3db-43a220802fe9 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171110 +DTEND;VALUE=DATE:20171111 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233240Z +LAST-MODIFIED:20100625T233246Z +DTSTAMP:20100625T233246Z +UID:d82d8d11-0793-4a68-a876-83c7e29736d0 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171123 +DTEND;VALUE=DATE:20171124 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233255Z +LAST-MODIFIED:20100625T233302Z +DTSTAMP:20100625T233302Z +UID:4526de1c-b74d-4643-b299-4c4c42ed82bb +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20171225 +DTEND;VALUE=DATE:20171226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233318Z +LAST-MODIFIED:20100625T233324Z +DTSTAMP:20100625T233324Z +UID:885fb7d7-ea3f-4938-9e40-f612986566f3 +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180101 +DTEND;VALUE=DATE:20180102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233329Z +LAST-MODIFIED:20100625T233335Z +DTSTAMP:20100625T233335Z +UID:89c44a14-aee8-49ce-8528-0e7a8bbe6e06 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180115 +DTEND;VALUE=DATE:20180116 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233342Z +LAST-MODIFIED:20100625T233348Z +DTSTAMP:20100625T233348Z +UID:e65fa03d-c681-458c-a26b-4206e3297280 +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180219 +DTEND;VALUE=DATE:20180220 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233358Z +LAST-MODIFIED:20100625T233404Z +DTSTAMP:20100625T233404Z +UID:f0a9e5e8-077c-424a-a668-970951c307d5 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180528 +DTEND;VALUE=DATE:20180529 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233413Z +LAST-MODIFIED:20100625T233419Z +DTSTAMP:20100625T233419Z +UID:f00cd4b3-3f19-4b46-9d69-e33790eda2ea +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180704 +DTEND;VALUE=DATE:20180705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233426Z +LAST-MODIFIED:20100625T233432Z +DTSTAMP:20100625T233432Z +UID:d0cdf502-fcdd-48ee-8cdc-2a6bc947a906 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20180903 +DTEND;VALUE=DATE:20180904 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233441Z +LAST-MODIFIED:20100625T233447Z +DTSTAMP:20100625T233447Z +UID:4ca554db-e59d-4de8-b043-d28988ae0b39 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181008 +DTEND;VALUE=DATE:20181009 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233455Z +LAST-MODIFIED:20100625T233502Z +DTSTAMP:20100625T233502Z +UID:876c8ca9-48a3-405d-86a1-432a66fb0ceb +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181112 +DTEND;VALUE=DATE:20181113 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233510Z +LAST-MODIFIED:20100625T233516Z +DTSTAMP:20100625T233516Z +UID:5adaf364-32e7-4f8e-bc69-ad93c1c3aa15 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181122 +DTEND;VALUE=DATE:20181123 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233527Z +LAST-MODIFIED:20100625T233532Z +DTSTAMP:20100625T233532Z +UID:05f95ef4-6098-4337-8d15-8b57ff37f251 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20181225 +DTEND;VALUE=DATE:20181226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233551Z +LAST-MODIFIED:20100625T233558Z +DTSTAMP:20100625T233558Z +UID:43fc5a98-b57a-456b-a50b-8e0e1de018ff +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190101 +DTEND;VALUE=DATE:20190102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233603Z +LAST-MODIFIED:20100625T233610Z +DTSTAMP:20100625T233610Z +UID:e1f23ce9-56fe-47e0-8f0e-2d408847c5d6 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190121 +DTEND;VALUE=DATE:20190122 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233618Z +LAST-MODIFIED:20100625T233626Z +DTSTAMP:20100625T233626Z +UID:3e8909b9-74aa-41bb-b8e7-23beff8c362d +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190218 +DTEND;VALUE=DATE:20190219 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233635Z +LAST-MODIFIED:20100625T233641Z +DTSTAMP:20100625T233641Z +UID:1aac6453-abbf-47de-bf5d-4bd0dfb9cd44 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190527 +DTEND;VALUE=DATE:20190528 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233650Z +LAST-MODIFIED:20100625T233700Z +DTSTAMP:20100625T233700Z +UID:dce26b68-5fd3-4b45-a8a6-cb68c8946935 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190704 +DTEND;VALUE=DATE:20190705 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233710Z +LAST-MODIFIED:20100625T233716Z +DTSTAMP:20100625T233716Z +UID:199ec585-69d7-45df-9cf2-c1e83d570e3a +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20190902 +DTEND;VALUE=DATE:20190903 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233723Z +LAST-MODIFIED:20100625T233728Z +DTSTAMP:20100625T233728Z +UID:61d39eb7-ae9a-4667-bbfa-6107df1e09e7 +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191014 +DTEND;VALUE=DATE:20191015 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233735Z +LAST-MODIFIED:20100625T233741Z +DTSTAMP:20100625T233741Z +UID:290c9fe0-e39e-425a-95b4-831945c6b8f2 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191111 +DTEND;VALUE=DATE:20191112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233747Z +LAST-MODIFIED:20100625T233754Z +DTSTAMP:20100625T233754Z +UID:b7680e77-a64f-4861-bb35-f2d917a9c437 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191128 +DTEND;VALUE=DATE:20191129 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233801Z +LAST-MODIFIED:20100625T233808Z +DTSTAMP:20100625T233808Z +UID:b234249f-565e-4159-b4e5-b0314c48a191 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20191225 +DTEND;VALUE=DATE:20191226 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233825Z +LAST-MODIFIED:20100625T233832Z +DTSTAMP:20100625T233832Z +UID:a48665de-2184-4468-9f8a-c21cf582455f +SUMMARY:New Year's Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200101 +DTEND;VALUE=DATE:20200102 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233838Z +LAST-MODIFIED:20100625T233844Z +DTSTAMP:20100625T233844Z +UID:f03dfc3e-924e-4574-87e7-e30fa1166025 +SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200120 +DTEND;VALUE=DATE:20200121 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233852Z +LAST-MODIFIED:20100625T233858Z +DTSTAMP:20100625T233858Z +UID:95d5c62a-4a58-49cf-9c91-d66a0fae1acd +SUMMARY:Washington's Birthday (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200217 +DTEND;VALUE=DATE:20200218 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233906Z +LAST-MODIFIED:20100625T233912Z +DTSTAMP:20100625T233912Z +UID:dd42517c-228b-44b3-a3ac-0bd4a7aadb75 +SUMMARY:Memorial Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200525 +DTEND;VALUE=DATE:20200526 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233921Z +LAST-MODIFIED:20100625T233927Z +DTSTAMP:20100625T233927Z +UID:cb3e2de7-2c55-4882-820a-df01c8c6aae7 +SUMMARY:Independence Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200703 +DTEND;VALUE=DATE:20200704 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233935Z +LAST-MODIFIED:20100625T233941Z +DTSTAMP:20100625T233941Z +UID:65eb7b18-064f-4939-a840-f11ef4bec981 +SUMMARY:Labor Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20200907 +DTEND;VALUE=DATE:20200908 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T233952Z +LAST-MODIFIED:20100625T233959Z +DTSTAMP:20100625T233959Z +UID:2d13e3c5-8a26-49be-bdbc-9a1e9715ae4b +SUMMARY:Columbus Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201012 +DTEND;VALUE=DATE:20201013 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234006Z +LAST-MODIFIED:20100625T234017Z +DTSTAMP:20100625T234017Z +UID:8ffd1252-87f4-481c-b46a-c2aa4cba74c7 +SUMMARY:Veterans Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201111 +DTEND;VALUE=DATE:20201112 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234023Z +LAST-MODIFIED:20100625T234029Z +DTSTAMP:20100625T234029Z +UID:557d2349-5c21-45c8-aad7-9c24a6aceb66 +SUMMARY:Thanksgiving Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201126 +DTEND;VALUE=DATE:20201127 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +CREATED:20100625T234036Z +LAST-MODIFIED:20100625T234041Z +DTSTAMP:20100625T234041Z +UID:1c5f2dc5-7cea-48cd-8c36-905930f8eee9 +SUMMARY:Christmas Day (US-OPM) +CATEGORIES:Holidays +DTSTART;VALUE=DATE:20201225 +DTEND;VALUE=DATE:20201226 +TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR \ No newline at end of file diff --git a/src/main/resources/ical4j.properties b/src/main/resources/ical4j.properties new file mode 100644 index 00000000..e69de29b diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index ba562c6b..4c39fb14 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -231,7 +231,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "april fool's day"; + String value = "2 days after new year's eve 2019"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/HolidayTest.java b/src/test/java/com/joestelmach/natty/HolidayTest.java index 923c8aa0..75523291 100644 --- a/src/test/java/com/joestelmach/natty/HolidayTest.java +++ b/src/test/java/com/joestelmach/natty/HolidayTest.java @@ -1,50 +1,105 @@ package com.joestelmach.natty; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.text.DateFormat; +import java.util.Date; +import java.util.TimeZone; -import net.fortuna.ical4j.data.CalendarBuilder; -import net.fortuna.ical4j.model.Calendar; -import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.Period; -import edu.emory.mathcs.backport.java.util.Collections; +import org.junit.BeforeClass; +import org.junit.Test; -public class HolidayTest { + +public class HolidayTest extends AbstractTest { + + @BeforeClass + public static void oneTime() { + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + } - public static void main(String[] args) throws Exception { - InputStream fin = HolidayTest.class.getResourceAsStream("/holidays.ics"); - CalendarBuilder builder = new CalendarBuilder(); - Calendar cal = builder.build(fin); + @Test + public void testUpcomingHoliday() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); - // Create the date range which is desired. - DateTime from = new DateTime("20000101T070000Z"); - DateTime to = new DateTime("20301231T070000Z");; - Period period = new Period(from, to); - - // For each VEVENT in the ICS - Set names = new HashSet(); - for (Object o : cal.getComponents("VEVENT")) { - Component c = (Component) o; - names.add(c.getProperty("SUMMARY").getValue()); - - /* - PeriodList list = c.calculateRecurrenceSet(period); - for (Object po : list) { - Period p = (Period) po; - System.out.println(p.getRangeStart()); - //System.out.println((Period) po); - } - */ - } - List list = new ArrayList(names); - Collections.sort(list); + validateDate("april fool's day", 4, 1, 2012); + validateDate("black friday", 11, 25, 2011); + validateDate("christmas", 12, 25, 2011); + validateDate("christmas eve", 12, 24, 2011); + validateDate("columbus day", 10, 8, 2012); + validateDate("earth day", 4, 22, 2012); + validateDate("easter", 4, 8, 2012); + validateDate("father's day", 6, 17, 2012); + validateDate("flag day", 6, 14, 2012); + validateDate("good friday", 4, 6, 2012); + validateDate("groundhog day", 2, 2, 2012); + validateDate("halloween", 10, 31, 2012); + validateDate("independence day", 7, 4, 2012); + validateDate("kwanzaa", 12, 26, 2011); + validateDate("labor day", 9, 3, 2012); + validateDate("mlk day", 1, 16, 2012); + validateDate("memorial day", 5, 28, 2012); + validateDate("mother's day", 5, 13, 2012); + validateDate("new year's day", 1, 1, 2012); + validateDate("new year's eve", 12, 31, 2011); + validateDate("patriot day", 9, 11, 2012); + validateDate("president's day", 2, 20, 2012); + validateDate("st patty's day", 3, 17, 2012); + validateDate("tax day", 4, 15, 2012); + validateDate("thanksgiving", 11, 24, 2011); + validateDate("election day", 11, 8, 2011); + validateDate("valentine day", 2, 14, 2012); + validateDate("veterans day", 11, 11, 2011); + } + + @Test + public void testRelativeHolidays() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("2 black fridays from now", 11, 23, 2012); + validateDate("three memorial days ago", 5, 25, 2009); + } + + @Test + public void testHolidaysByYear() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("april fool's day 2000", 4, 1, 2000); + validateDate("black friday 2001", 11, 23, 2001); + validateDate("christmas 2002", 12, 25, 2002); + validateDate("christmas eve 2003", 12, 24, 2003); + validateDate("columbus day 2010", 10, 11, 2010); + validateDate("earth day 2005", 4, 22, 2005); + validateDate("easter '06", 4, 16, 2006); + validateDate("father's day '07", 6, 17, 2007); + validateDate("flag day '08", 6, 14, 2008); + validateDate("good friday '09", 4, 10, 2009); + validateDate("groundhog day '10", 2, 2, 2010); + validateDate("halloween '11", 10, 31, 2011); + validateDate("independence day '12", 7, 4, 2012); + validateDate("kwanzaa '13", 12, 26, 2013); + validateDate("labor day '14", 9, 1, 2014); + validateDate("mlk day '15", 1, 19, 2015); + validateDate("memorial day '16", 5, 30, 2016); + validateDate("mother's day 2017", 5, 14, 2017); + validateDate("new year's day 2018", 1, 1, 2018); + validateDate("new year's eve 2019", 12, 31, 2018); + validateDate("patriot day 2020", 9, 11, 2020); + validateDate("president's day 2019", 2, 18, 2019); + validateDate("st patty's day 2018", 3, 17, 2018); + validateDate("tax day 2017", 4, 15, 2017); + validateDate("thanksgiving 2016", 11, 24, 2016); + validateDate("election day 2015", 11, 3, 2015); + validateDate("valentine day 2014", 2, 14, 2014); + validateDate("veterans day 2013", 11, 11, 2013); + } + + @Test + public void testHolidaysWithModifiers() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); - for(String name:list) { - System.out.println(name); - } + validateDate("four days before veterans day 2013", 11, 7, 2013); + validateDate("two days after two thanksgivings from now", 11, 24, 2012); } } diff --git a/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java index bed6a4c7..fce221b4 100644 --- a/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java @@ -48,9 +48,6 @@ public void names() throws Exception { assertAST("haloween", "HALLOWEEN"); assertAST("halloween day", "HALLOWEEN"); - assertAST("inauguration day", "INAUGURATION_DAY"); - assertAST("inaugaration day", "INAUGURATION_DAY"); - assertAST("independence day", "INDEPENDENCE_DAY"); assertAST("kwanzaa", "KWANZAA"); @@ -125,7 +122,11 @@ public void statments() throws Exception { assertAST("three christmases from now", "(SEEK > by_day 3 CHRISTMAS)"); assertAST("3 christmases ago", "(SEEK < by_day 3 CHRISTMAS)"); + assertAST("2 april fool's days ago", "(SEEK < by_day 2 APRIL_FOOLS_DAY)"); assertAST("in 10 thanksgivings", "(SEEK > by_day 10 THANKSGIVING)"); + + assertAST("thanksgiving 2011", "(EXPLICIT_SEEK THANKSGIVING (YEAR_OF 2011))"); + assertAST("christmas, '95", "(EXPLICIT_SEEK CHRISTMAS (YEAR_OF 95))"); } } diff --git a/src/test/resources/holidays.ics b/src/test/resources/holidays.ics deleted file mode 100644 index 20b9fc35..00000000 --- a/src/test/resources/holidays.ics +++ /dev/null @@ -1,3316 +0,0 @@ -BEGIN:VCALENDAR -COMMENT: http://www.mozilla.org/projects/calendar/caldata/USHolidays.ics -PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN -VERSION:2.0 -BEGIN:VEVENT -CREATED:20080827T223540Z -LAST-MODIFIED:20090611T212419Z -UID:b8388cae-2125-4614-9801-5fec40a07408 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000423 -DTEND;VALUE=DATE:20000424 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223613Z -LAST-MODIFIED:20090611T212419Z -UID:27198947-51d4-475c-9116-6dd475ab4729 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010415 -DTEND;VALUE=DATE:20010416 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223633Z -LAST-MODIFIED:20090611T212419Z -UID:adebaeec-b7da-4d1e-baff-73e54a5bbe5d -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020331 -DTEND;VALUE=DATE:20020401 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223650Z -LAST-MODIFIED:20090611T212419Z -UID:3378f42f-00f3-42be-888e-8368d5e140b2 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030420 -DTEND;VALUE=DATE:20030421 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223704Z -LAST-MODIFIED:20090611T212419Z -UID:18eae8ac-65e1-4cc0-8bf3-0c287882fbd9 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040411 -DTEND;VALUE=DATE:20040412 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223726Z -LAST-MODIFIED:20090611T212419Z -UID:d0a68adf-3eca-4585-a7a7-853b5d5b03e4 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050327 -DTEND;VALUE=DATE:20050328 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223741Z -LAST-MODIFIED:20090611T212419Z -UID:daf76dce-468b-4f99-8d42-01487f8fafa9 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060416 -DTEND;VALUE=DATE:20060417 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223757Z -LAST-MODIFIED:20090611T212419Z -UID:fa7a1db3-1c5f-472d-9ee4-988d0d0287eb -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070408 -DTEND;VALUE=DATE:20070409 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223818Z -LAST-MODIFIED:20090611T212419Z -UID:69230f5f-f2d2-428d-a90c-a7e1c7deea5e -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090412 -DTEND;VALUE=DATE:20090413 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223828Z -LAST-MODIFIED:20090611T212419Z -UID:0bc494ff-69b5-4dca-8699-c4fe5ea25a13 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100404 -DTEND;VALUE=DATE:20100405 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223934Z -LAST-MODIFIED:20090611T212419Z -UID:44a937c0-f0d8-4458-947c-8a33db7217ef -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120408 -DTEND;VALUE=DATE:20120409 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223946Z -LAST-MODIFIED:20090611T212419Z -UID:734e6d03-ab3c-4693-8c81-f73e3b576c27 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130331 -DTEND;VALUE=DATE:20130401 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224000Z -LAST-MODIFIED:20090611T212419Z -UID:c4d6a9e0-a5d6-41d4-9297-97e4fdcc85a5 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140420 -DTEND;VALUE=DATE:20140421 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224012Z -LAST-MODIFIED:20090611T212419Z -UID:b2cab02a-a164-44a4-af66-ccb1b3c253b9 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150405 -DTEND;VALUE=DATE:20150406 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224028Z -LAST-MODIFIED:20090611T212419Z -UID:763686f7-3ec4-4dfc-9a33-e4c0bae652fa -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160327 -DTEND;VALUE=DATE:20160328 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224043Z -LAST-MODIFIED:20090611T212419Z -UID:31edb1e8-a064-4a8b-a041-eecd22a4d419 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170416 -DTEND;VALUE=DATE:20170417 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224101Z -LAST-MODIFIED:20090611T212419Z -UID:d869e442-0b4d-40c4-b34c-6780afce8c46 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180401 -DTEND;VALUE=DATE:20180402 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224121Z -LAST-MODIFIED:20090611T212419Z -UID:135738b2-b8d5-4e65-bf7c-736e22c6b8b1 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190421 -DTEND;VALUE=DATE:20190422 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T224133Z -LAST-MODIFIED:20090611T212419Z -UID:7606494f-0019-46e6-9d47-ab9498569e5a -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200412 -DTEND;VALUE=DATE:20200413 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212419Z -UID:c5c154cc-1dd1-11b2-85c7-e3de2bfad30f -SUMMARY:Easter Sunday -STATUS:CONFIRMED -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080323 -DTEND;VALUE=DATE:20080324 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024422Z -LAST-MODIFIED:20090611T212419Z -UID:5e604c83-cacc-46fe-bc40-b4f50e2412e7 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000308 -DTEND;VALUE=DATE:20000309 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024533Z -LAST-MODIFIED:20090611T212419Z -UID:847021ce-03b6-4f2d-abe5-2ba869066b5d -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010228 -DTEND;VALUE=DATE:20010301 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024626Z -LAST-MODIFIED:20090611T212419Z -UID:0e313bdb-171d-40dd-9ddb-1dfb4a777bb5 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020213 -DTEND;VALUE=DATE:20020214 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024822Z -LAST-MODIFIED:20090611T212419Z -UID:1577ed41-e8c5-45f9-83e5-86079fe2510b -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030305 -DTEND;VALUE=DATE:20030306 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024847Z -LAST-MODIFIED:20090611T212419Z -UID:fcbfe576-82e9-4f68-b1ad-5366ff6968ac -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040225 -DTEND;VALUE=DATE:20040226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024911Z -LAST-MODIFIED:20090611T212419Z -UID:a403894c-bb2c-4d02-8dca-91c359a54bfe -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050209 -DTEND;VALUE=DATE:20050210 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024924Z -LAST-MODIFIED:20090611T212419Z -UID:dcc3c2f4-d214-49d8-bb3e-f13c6acf3295 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060301 -DTEND;VALUE=DATE:20060302 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024939Z -LAST-MODIFIED:20090611T212419Z -UID:b81a6c71-9a3d-49a5-b695-8ac5f1bb013b -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070221 -DTEND;VALUE=DATE:20070222 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T024951Z -LAST-MODIFIED:20090611T212419Z -UID:40090c1d-8669-4d7a-b63a-288006cfac65 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080206 -DTEND;VALUE=DATE:20080207 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025003Z -LAST-MODIFIED:20090611T212419Z -UID:609ce29c-966c-40b4-81c4-8ccac74e98dd -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090225 -DTEND;VALUE=DATE:20090226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025017Z -LAST-MODIFIED:20090611T212419Z -UID:f96c0bf5-1cf2-4d2e-9955-eb89f4e645d3 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100217 -DTEND;VALUE=DATE:20100218 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025046Z -LAST-MODIFIED:20090611T212419Z -UID:a0b949a8-2a75-4617-a687-1e7dffc098f5 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120222 -DTEND;VALUE=DATE:20120223 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025058Z -LAST-MODIFIED:20090611T212419Z -UID:8caed879-6158-477c-9901-e1531dc855e8 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130213 -DTEND;VALUE=DATE:20130214 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025131Z -LAST-MODIFIED:20090611T212419Z -UID:69e06d74-9acc-4667-8eb0-7c33299d82c0 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140305 -DTEND;VALUE=DATE:20140306 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025146Z -LAST-MODIFIED:20090611T212419Z -UID:a05d7fa6-4c65-4465-a32a-1b5c2d1189aa -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150218 -DTEND;VALUE=DATE:20150219 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025202Z -LAST-MODIFIED:20090611T212419Z -UID:ce5fe814-c4b3-4aab-8298-86f54e34ffe4 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160210 -DTEND;VALUE=DATE:20160211 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025220Z -LAST-MODIFIED:20090611T212419Z -UID:746c3ee3-3439-41bd-9ee1-7944812a8a29 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170301 -DTEND;VALUE=DATE:20170302 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025235Z -LAST-MODIFIED:20090611T212419Z -UID:6eff84ea-6763-46d3-9c64-b847376f3e34 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180214 -DTEND;VALUE=DATE:20180215 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025254Z -LAST-MODIFIED:20090611T212419Z -UID:e5d798cf-474f-42c3-83b8-0d1186c2c3ad -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190306 -DTEND;VALUE=DATE:20190307 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025309Z -LAST-MODIFIED:20090611T212419Z -UID:9d17488f-12b8-474b-8790-d22e6786735e -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200226 -DTEND;VALUE=DATE:20200227 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025421Z -LAST-MODIFIED:20090611T212419Z -UID:1abe0e7a-fa55-42b8-b6cc-bf037ab5c7f4 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000416 -DTEND;VALUE=DATE:20000417 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025457Z -LAST-MODIFIED:20090611T212419Z -UID:ff8dd4a7-0593-4fb6-bcdc-7cd8db41a451 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010408 -DTEND;VALUE=DATE:20010409 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025510Z -LAST-MODIFIED:20090611T212419Z -UID:e403c114-aa09-4dcd-b8cf-b342be833477 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020324 -DTEND;VALUE=DATE:20020325 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025522Z -LAST-MODIFIED:20090611T212419Z -UID:8ebc922a-a220-456f-937b-a943e5df123b -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030413 -DTEND;VALUE=DATE:20030414 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025549Z -LAST-MODIFIED:20090611T212419Z -UID:e03d10c1-ce57-426f-9a08-cd49c25920a9 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040404 -DTEND;VALUE=DATE:20040405 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025630Z -LAST-MODIFIED:20090611T212419Z -UID:8ed86db1-ea90-453a-a1cd-36878811826e -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070401 -DTEND;VALUE=DATE:20070402 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025646Z -LAST-MODIFIED:20090611T212419Z -UID:b45150e2-da4d-477b-aace-b255186d89b1 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080316 -DTEND;VALUE=DATE:20080317 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025820Z -LAST-MODIFIED:20090611T212419Z -UID:96b2e223-1b4f-44e1-80a3-8b57ab7d2f12 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090405 -DTEND;VALUE=DATE:20090406 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025837Z -LAST-MODIFIED:20090611T212419Z -UID:863b0192-787d-4b4b-8550-c7e2db820b0a -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100328 -DTEND;VALUE=DATE:20100329 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025908Z -LAST-MODIFIED:20090611T212419Z -UID:ac159587-0a97-4f98-b11c-2193fe61265a -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120401 -DTEND;VALUE=DATE:20120402 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025922Z -LAST-MODIFIED:20090611T212419Z -UID:4dcb5128-64b8-488c-b9ec-6b8c6184c0e9 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130324 -DTEND;VALUE=DATE:20130325 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025939Z -LAST-MODIFIED:20090611T212419Z -UID:0b3db3ec-fa4c-4e71-bf0b-251fde472ef6 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140413 -DTEND;VALUE=DATE:20140414 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025956Z -LAST-MODIFIED:20090611T212419Z -UID:b3ac2edc-39ac-4d09-b45b-9a380ecbb63e -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150329 -DTEND;VALUE=DATE:20150330 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030027Z -LAST-MODIFIED:20090611T212419Z -UID:7af33c57-7711-4581-86e5-221ac5c59ecd -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160320 -DTEND;VALUE=DATE:20160321 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030042Z -LAST-MODIFIED:20090611T212419Z -UID:b372c357-0224-493e-a95c-50f69a9ee376 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170409 -DTEND;VALUE=DATE:20170410 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030101Z -LAST-MODIFIED:20090611T212419Z -UID:2ed9175e-e119-4569-9371-390d70acc0fa -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180325 -DTEND;VALUE=DATE:20180326 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030124Z -LAST-MODIFIED:20090611T212419Z -UID:334646f6-5fef-43a9-83bc-d2dd88c9ebe6 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190414 -DTEND;VALUE=DATE:20190415 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030150Z -LAST-MODIFIED:20090611T212419Z -UID:7f9c6e82-610a-4485-b5fd-dde7f0dba4b8 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200405 -DTEND;VALUE=DATE:20200406 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030214Z -LAST-MODIFIED:20090611T212419Z -UID:af35fdb4-b67e-4b43-887f-c23f58bf9847 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000421 -DTEND;VALUE=DATE:20000422 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030234Z -LAST-MODIFIED:20090611T212419Z -UID:116e335b-4cae-40b1-abf5-4e2f7121a4c0 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010413 -DTEND;VALUE=DATE:20010414 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030250Z -LAST-MODIFIED:20090611T212420Z -UID:7700d32c-2728-439f-b3a2-e458ba1052d7 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020329 -DTEND;VALUE=DATE:20020330 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030320Z -LAST-MODIFIED:20090611T212420Z -UID:2710e06f-c089-40d4-945a-9f8fe125a59c -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040409 -DTEND;VALUE=DATE:20040410 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030337Z -LAST-MODIFIED:20090611T212420Z -UID:1dfafc7f-17b6-4a8d-b1ef-492adb9bd9dc -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050325 -DTEND;VALUE=DATE:20050326 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030352Z -LAST-MODIFIED:20090611T212420Z -UID:8371454e-8dda-4667-81e5-3680dc8cc542 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060414 -DTEND;VALUE=DATE:20060415 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030406Z -LAST-MODIFIED:20090611T212420Z -UID:93b64832-1179-4db9-a8e5-4d3375156294 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070406 -DTEND;VALUE=DATE:20070407 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030423Z -LAST-MODIFIED:20090611T212420Z -UID:ee7bc7d8-d5a8-43f1-b817-54b5458429fa -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080321 -DTEND;VALUE=DATE:20080322 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030436Z -LAST-MODIFIED:20090611T212420Z -UID:98652109-bdda-4451-afd3-9955bfe918c7 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090410 -DTEND;VALUE=DATE:20090411 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030446Z -LAST-MODIFIED:20090611T212420Z -UID:7241d18c-6e4a-4a15-b262-a0415d05e5c0 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100402 -DTEND;VALUE=DATE:20100403 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030513Z -LAST-MODIFIED:20090611T212420Z -UID:be42b228-382d-4add-be00-c36fdb2508c7 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120406 -DTEND;VALUE=DATE:20120407 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030526Z -LAST-MODIFIED:20090611T212420Z -UID:db8d6551-0b2d-44c1-8c21-ba96030a445e -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130329 -DTEND;VALUE=DATE:20130330 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030543Z -LAST-MODIFIED:20090611T212420Z -UID:c14a8a90-a69d-44fe-9e34-a28feed1a1c0 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140418 -DTEND;VALUE=DATE:20140419 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030602Z -LAST-MODIFIED:20090611T212420Z -UID:269d06c4-2eed-45de-af47-6668b7e58902 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150403 -DTEND;VALUE=DATE:20150404 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030616Z -LAST-MODIFIED:20090611T212420Z -UID:8e3f7823-a340-4b6e-8b7c-10a539f5db8f -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160325 -DTEND;VALUE=DATE:20160326 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030630Z -LAST-MODIFIED:20090611T212420Z -UID:a6d17884-5c3e-4f4b-9383-7b83814ada9d -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170414 -DTEND;VALUE=DATE:20170415 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030648Z -LAST-MODIFIED:20090611T212420Z -UID:19fb2499-97ae-457f-8d41-6ec76292c77f -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180330 -DTEND;VALUE=DATE:20180331 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030704Z -LAST-MODIFIED:20090611T212420Z -UID:0a125549-300d-4bd9-af20-3fc28935946e -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190419 -DTEND;VALUE=DATE:20190420 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030718Z -LAST-MODIFIED:20090611T212420Z -UID:50de218e-013d-4739-b8d4-f30f74a57168 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200410 -DTEND;VALUE=DATE:20200411 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030737Z -LAST-MODIFIED:20090611T212420Z -UID:af9df15d-af91-4e19-b7d2-e80bf8dfc378 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000611 -DTEND;VALUE=DATE:20000612 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030855Z -LAST-MODIFIED:20090611T212420Z -UID:b5eb3eae-875a-4b50-a10c-b8a487907eac -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010603 -DTEND;VALUE=DATE:20010604 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030907Z -LAST-MODIFIED:20090611T212420Z -UID:c5a9ee90-d177-4ca5-917e-245791f15312 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020519 -DTEND;VALUE=DATE:20020520 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030942Z -LAST-MODIFIED:20090611T212420Z -UID:9bbe68fb-4d23-4d27-9171-26177baf984d -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030608 -DTEND;VALUE=DATE:20030609 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030959Z -LAST-MODIFIED:20090611T212420Z -UID:c4f15038-8df9-475b-bc85-9948f97cddc7 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040530 -DTEND;VALUE=DATE:20040531 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031013Z -LAST-MODIFIED:20090611T212420Z -UID:d54dd907-ba90-4607-88ee-b15d7b79736f -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050515 -DTEND;VALUE=DATE:20050516 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031027Z -LAST-MODIFIED:20090611T212420Z -UID:810562a0-2467-4a43-860d-db69cbf92fdf -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060604 -DTEND;VALUE=DATE:20060605 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031042Z -LAST-MODIFIED:20090611T212420Z -UID:8212e80d-6efa-4e79-bcd0-44fc831a1ecc -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070527 -DTEND;VALUE=DATE:20070528 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031101Z -LAST-MODIFIED:20090611T212420Z -UID:da7b884d-0d0f-48d3-97f7-d13b21cea817 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080511 -DTEND;VALUE=DATE:20080512 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031112Z -LAST-MODIFIED:20090611T212420Z -UID:719c1eff-d26b-4316-86b5-464a4d24bf98 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090531 -DTEND;VALUE=DATE:20090601 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031123Z -LAST-MODIFIED:20090611T212420Z -UID:a61992d7-0747-46db-aa4c-49ddef65de55 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100523 -DTEND;VALUE=DATE:20100524 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031210Z -LAST-MODIFIED:20090611T212420Z -UID:09673ae2-e44c-4cac-90de-deaf49b6b875 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120527 -DTEND;VALUE=DATE:20120528 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031224Z -LAST-MODIFIED:20090611T212420Z -UID:1919a739-59f5-46c2-8cb0-b86726b1e72d -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130519 -DTEND;VALUE=DATE:20130520 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031235Z -LAST-MODIFIED:20090611T212420Z -UID:0a4fae5e-4698-427f-bbdf-b6c08fd96053 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140608 -DTEND;VALUE=DATE:20140609 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031253Z -LAST-MODIFIED:20090611T212420Z -UID:3d5c4095-4429-4abe-8a7a-4bbf5236eaa4 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150524 -DTEND;VALUE=DATE:20150525 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031311Z -LAST-MODIFIED:20090611T212420Z -UID:1177a296-0723-4fcb-8d35-e79b1c021330 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160515 -DTEND;VALUE=DATE:20160516 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031323Z -LAST-MODIFIED:20090611T212420Z -UID:452c678d-0b42-4a89-a347-a0b8e12c4fd6 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170604 -DTEND;VALUE=DATE:20170605 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031341Z -LAST-MODIFIED:20090611T212420Z -UID:01fec43b-b371-4f56-97cb-ef454132a74a -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180520 -DTEND;VALUE=DATE:20180521 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031401Z -LAST-MODIFIED:20090611T212420Z -UID:697b8004-261c-4c0b-a15e-06c1af95ab9b -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190609 -DTEND;VALUE=DATE:20190610 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031412Z -LAST-MODIFIED:20090611T212420Z -UID:ac723797-16b2-4fd9-9d5e-2f55cc90dc22 -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200531 -DTEND;VALUE=DATE:20200601 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031600Z -LAST-MODIFIED:20090611T212420Z -UID:bec77dd4-49e7-46b5-9ea4-7409cc5369e1 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000618 -DTEND;VALUE=DATE:20000619 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031731Z -LAST-MODIFIED:20090611T212420Z -UID:f395a03d-27e2-4214-88c2-20c638c7f99d -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010610 -DTEND;VALUE=DATE:20010611 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031843Z -LAST-MODIFIED:20090611T212420Z -UID:fb37469f-ba53-4fec-9c65-fb22c59470a8 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020526 -DTEND;VALUE=DATE:20020527 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030305Z -LAST-MODIFIED:20090611T212420Z -UID:a2eb4297-f3a3-414b-b53a-e7cdaad20ac9 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030418 -DTEND;VALUE=DATE:20030419 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032035Z -LAST-MODIFIED:20090611T212420Z -UID:078f774d-1113-4bad-b7e5-6f4c48100fc4 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030615 -DTEND;VALUE=DATE:20030616 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032128Z -LAST-MODIFIED:20090611T212420Z -UID:c2df343c-bf38-4e86-9129-010bd55de1b1 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040606 -DTEND;VALUE=DATE:20040607 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025605Z -LAST-MODIFIED:20090611T212420Z -UID:e4891762-ad19-402e-8195-828fb3386d22 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050320 -DTEND;VALUE=DATE:20050321 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032246Z -LAST-MODIFIED:20090611T212420Z -UID:f14c5274-2112-4470-a3f8-f8a5b4eb8199 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050522 -DTEND;VALUE=DATE:20050523 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025617Z -LAST-MODIFIED:20090611T212420Z -UID:33a3f736-bd5b-4a87-b983-612e01ea4dbe -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060409 -DTEND;VALUE=DATE:20060410 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032352Z -LAST-MODIFIED:20090611T212421Z -UID:43eeeab3-5836-41cc-82b4-d91d922871c4 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060611 -DTEND;VALUE=DATE:20060612 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032435Z -LAST-MODIFIED:20090611T212421Z -UID:72234667-e98f-4532-b4da-a2cdd2e56ce4 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070603 -DTEND;VALUE=DATE:20070604 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032512Z -LAST-MODIFIED:20090611T212421Z -UID:a289a44f-5eb2-4f14-b458-e55dcbc00e9d -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080518 -DTEND;VALUE=DATE:20080519 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032617Z -LAST-MODIFIED:20090611T212421Z -UID:b5d1e103-7807-4049-ac80-df13ab666b5e -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090607 -DTEND;VALUE=DATE:20090608 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T032717Z -LAST-MODIFIED:20090611T212421Z -UID:59ef3b5c-a65f-4a5f-81ec-25d201285670 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100530 -DTEND;VALUE=DATE:20100531 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033236Z -LAST-MODIFIED:20090611T212421Z -UID:f931f051-1dec-4a6c-b78b-615474143fd1 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120603 -DTEND;VALUE=DATE:20120604 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033315Z -LAST-MODIFIED:20090611T212421Z -UID:838f906b-f3c8-4b94-9cdb-ef698d0222b8 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130526 -DTEND;VALUE=DATE:20130527 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033425Z -LAST-MODIFIED:20090611T212421Z -UID:de98efc2-cd59-41fd-af0d-d2dcb58b23fe -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140615 -DTEND;VALUE=DATE:20140616 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033511Z -LAST-MODIFIED:20090611T212421Z -UID:139b7040-5cfd-4db4-abf2-8251ae17368e -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150531 -DTEND;VALUE=DATE:20150601 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033614Z -LAST-MODIFIED:20090611T212421Z -UID:78d6d38a-a0ae-4b75-bcbb-0276d69a430e -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160522 -DTEND;VALUE=DATE:20160523 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033655Z -LAST-MODIFIED:20090611T212421Z -UID:83608056-f721-4564-88ed-3cc5d12b01fb -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170611 -DTEND;VALUE=DATE:20170612 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033759Z -LAST-MODIFIED:20090611T212421Z -UID:cc92b242-3a24-471c-b1be-b8918fb8b6de -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180527 -DTEND;VALUE=DATE:20180528 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033848Z -LAST-MODIFIED:20090611T212421Z -UID:006195b6-8021-4814-9635-f9f5bffa7a36 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190616 -DTEND;VALUE=DATE:20190617 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033920Z -LAST-MODIFIED:20090611T212421Z -UID:b60896ca-b1eb-4ec1-a9cd-67c7403c6335 -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200607 -DTEND;VALUE=DATE:20200608 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224733Z -LAST-MODIFIED:20090611T212421Z -UID:d41971bd-5c3f-4293-a154-a210d39b6667 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000601 -DTEND;VALUE=DATE:20000602 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224836Z -LAST-MODIFIED:20090611T212421Z -UID:e6166189-6b80-44b8-9902-60e58f0cbe0c -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010524 -DTEND;VALUE=DATE:20010525 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224858Z -LAST-MODIFIED:20090611T212421Z -UID:7f5a197b-441e-40e3-a31a-1c0cb5ffed19 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20020509 -DTEND;VALUE=DATE:20020510 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224916Z -LAST-MODIFIED:20090611T212421Z -UID:192d84d7-7a76-4d98-a335-919203fa28e1 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20030529 -DTEND;VALUE=DATE:20030530 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224929Z -LAST-MODIFIED:20090611T212421Z -UID:52cb0477-1dab-4881-bf56-64099c934453 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20040520 -DTEND;VALUE=DATE:20040521 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224944Z -LAST-MODIFIED:20090611T212421Z -UID:17c27008-29e2-4050-adda-5757c94fb673 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20050505 -DTEND;VALUE=DATE:20050506 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T224959Z -LAST-MODIFIED:20090611T212421Z -UID:03f7a269-b863-4fb9-a02f-135803fa2972 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20060525 -DTEND;VALUE=DATE:20060526 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225014Z -LAST-MODIFIED:20090611T212421Z -UID:fc9f7abd-c3fc-42b2-bd87-7528470f77f3 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070517 -DTEND;VALUE=DATE:20070518 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225025Z -LAST-MODIFIED:20090611T212421Z -UID:c1649dbc-9ad2-4257-a687-7dc706b08ea2 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20080501 -DTEND;VALUE=DATE:20080502 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225039Z -LAST-MODIFIED:20090611T212421Z -UID:82c49077-6f81-42ed-84e1-53ebe66f981d -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20090521 -DTEND;VALUE=DATE:20090522 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225051Z -LAST-MODIFIED:20090611T212421Z -UID:5203b99c-f304-4d25-ba4e-1e776237186d -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100513 -DTEND;VALUE=DATE:20100514 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225111Z -LAST-MODIFIED:20090611T212421Z -UID:18e27f82-2d27-42fa-b04c-315c6fadcd9d -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120517 -DTEND;VALUE=DATE:20120518 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225120Z -LAST-MODIFIED:20090611T212421Z -UID:7726ca21-1f4d-4cc5-ade3-8c384f5d72a5 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130509 -DTEND;VALUE=DATE:20130510 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225133Z -LAST-MODIFIED:20090611T212421Z -UID:6d025a60-733c-4af4-a2ee-f1b904090666 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140529 -DTEND;VALUE=DATE:20140530 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225144Z -LAST-MODIFIED:20090611T212421Z -UID:cd092661-b37e-4ec9-b798-8f957c4278b4 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150514 -DTEND;VALUE=DATE:20150515 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225154Z -LAST-MODIFIED:20090611T212421Z -UID:ed1358ef-c818-4af3-9f8e-4d965d800a21 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160505 -DTEND;VALUE=DATE:20160506 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225204Z -LAST-MODIFIED:20090611T212421Z -UID:7c8351e3-e3fd-4c0b-a60c-f558aa31df55 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170525 -DTEND;VALUE=DATE:20170526 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225214Z -LAST-MODIFIED:20090611T212421Z -UID:db037aff-17bf-430b-b094-52e99879ba4e -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180510 -DTEND;VALUE=DATE:20180511 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225224Z -LAST-MODIFIED:20090611T212421Z -UID:dddeea06-c165-474c-96aa-6c1b705c6b0a -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190530 -DTEND;VALUE=DATE:20190531 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225238Z -LAST-MODIFIED:20090611T212421Z -UID:09a3f71c-83df-4649-b7d1-4792ae2fb523 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200521 -DTEND;VALUE=DATE:20200522 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025033Z -LAST-MODIFIED:20100911T002511Z -DTSTAMP:20100911T002511Z -UID:a02038e9-a094-4744-b09d-d7d2ef96bd28 -SUMMARY:Ash Wednesday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110309 -DTEND;VALUE=DATE:20110310 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T025855Z -LAST-MODIFIED:20100911T002525Z -DTSTAMP:20100911T002525Z -UID:844a3d56-4fab-4df1-92d3-8f4385d8efc9 -SUMMARY:Palm Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110417 -DTEND;VALUE=DATE:20110418 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T030501Z -LAST-MODIFIED:20100911T002555Z -DTSTAMP:20100911T002555Z -UID:6e05d4a1-8b7f-446d-a7fc-566b6837c927 -SUMMARY:Good Friday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110422 -DTEND;VALUE=DATE:20110423 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T223849Z -LAST-MODIFIED:20100911T002632Z -DTSTAMP:20100911T002632Z -UID:fa0cda04-283e-4b53-95b0-c9903a3f85e2 -SUMMARY:Easter Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110424 -DTEND;VALUE=DATE:20110425 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:2 -SEQUENCE:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20081125T225101Z -LAST-MODIFIED:20100911T002740Z -DTSTAMP:20100911T002740Z -UID:5250764e-c50c-44ba-b3d6-32ee24ee49e1 -SUMMARY:Ascension Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110602 -DTEND;VALUE=DATE:20110603 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T031143Z -LAST-MODIFIED:20100911T002756Z -DTSTAMP:20100911T002756Z -UID:5e614422-74fe-47d6-bf89-dca8c4d7df3a -SUMMARY:Pentecost -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110612 -DTEND;VALUE=DATE:20110613 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T033004Z -LAST-MODIFIED:20100911T002812Z -DTSTAMP:20100911T002812Z -UID:001f88a2-a3b4-4dff-a577-c9860d01dcbd -SUMMARY:Trinity Sunday -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110619 -DTEND;VALUE=DATE:20110620 -TRANSP:TRANSPARENT -SEQUENCE:1 -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:9f3df592-1dd1-11b2-8b51-a8c49a575f97 -SUMMARY:President's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=2 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000221 -DTEND;VALUE=DATE:20000222 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:e0d4c156-1dd1-11b2-b6a4-dac750e0163e -SUMMARY:Mother's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=5 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000514 -DTEND;VALUE=DATE:20000515 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:c2783248-1dd1-11b2-affa-ee50c39d8083 -SUMMARY:Armed Forces Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=3SA;BYMONTH=5 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000520 -DTEND;VALUE=DATE:20000521 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:d9706fa6-1dd1-11b2-a349-e97241bd4740 -SUMMARY:Memorial Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=-1MO;BYMONTH=5 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000529 -DTEND;VALUE=DATE:20000530 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:46e6845c-1dd2-11b2-bd3d-d8755a1a171f -SUMMARY:Parents' Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=4SU;BYMONTH=7 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000723 -DTEND;VALUE=DATE:20000724 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:0726ea42-1dd2-11b2-9fe6-dda3d063fb50 -SUMMARY:Labor Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=1MO;BYMONTH=9 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000904 -DTEND;VALUE=DATE:20000905 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:7d4402f4-1dd2-11b2-9790-b6193cfa4349 -SUMMARY:Halloween -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001031 -DTEND;VALUE=DATE:20001101 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:2c7ba10c-1dd2-11b2-b0b7-96f89f288199 -SUMMARY:Veteran's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001111 -DTEND;VALUE=DATE:20001112 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:4299a358-1dd2-11b2-a228-d062222b6f88 -SUMMARY:Thanksgiving Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=4TH;BYMONTH=11 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001123 -DTEND;VALUE=DATE:20001124 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:54f392fa-1dd2-11b2-8ecb-9ae0a7fcebd1 -SUMMARY:Christmas Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001225 -DTEND;VALUE=DATE:20001226 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:b1f194fc-1dd1-11b2-a973-d219c68b95c4 -SUMMARY:New Year's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000101 -DTEND;VALUE=DATE:20000102 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:a2cbca9c-1dd1-11b2-83e2-abab36f0506d -SUMMARY:New Year's Eve -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001231 -DTEND;VALUE=DATE:20010101 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212421Z -UID:13f83ed2-1dd2-11b2-a0de-ee8645423959 -SUMMARY:Daylight Savings Time ends -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20071104 -DTEND;VALUE=DATE:20071105 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:f669a974-1dd1-11b2-9fac-f44d91c657af -SUMMARY:Independence Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000704 -DTEND;VALUE=DATE:20000705 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:cb90487e-1dd1-11b2-9e06-d1a12cc963dc -SUMMARY:Father's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=3SU;BYMONTH=6 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000618 -DTEND;VALUE=DATE:20000619 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:e8022726-1dd1-11b2-aae6-9f73f16c0f01 -SUMMARY:Flag Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000614 -DTEND;VALUE=DATE:20000615 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:f1561106-1dd1-11b2-955f-ba14e5210f45 -SUMMARY:Earth Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000422 -DTEND;VALUE=DATE:20000423 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:e46086c2-1dd1-11b2-8a16-a2c99f1c525a -SUMMARY:Tax Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000415 -DTEND;VALUE=DATE:20000416 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:c981b858-1dd1-11b2-b69b-94d05f8bda66 -SUMMARY:April Fool's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000401 -DTEND;VALUE=DATE:20000402 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:bd6cfe4c-1dd1-11b2-b3dc-ebaab9302c26 -SUMMARY:St. Patrick's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000317 -DTEND;VALUE=DATE:20000318 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:aff9f3c8-1dd1-11b2-8593-c63469762eb1 -SUMMARY:George Washington's Birthday (actual) -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000222 -DTEND;VALUE=DATE:20000223 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:790b86e2-1dd2-11b2-9cb9-c905fa35c6f9 -SUMMARY:Valentine's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000214 -DTEND;VALUE=DATE:20000215 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:4d5b843e-1dd2-11b2-a6c9-8ea942124d1b -SUMMARY:Abraham Lincoln's Birthday -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000212 -DTEND;VALUE=DATE:20000213 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:0c771532-1dd2-11b2-8dd9-8638db3aef63 -SUMMARY:Groundhog's Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000202 -DTEND;VALUE=DATE:20000203 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T220831Z -LAST-MODIFIED:20090611T212422Z -UID:8a05b5d3-6b97-4fe1-b26e-b258a073c5e0 -SUMMARY:United Nations Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001024 -DTEND;VALUE=DATE:20001025 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:e462881a-20b8-4b70-ab3c-cf38016d3398 -SUMMARY:Daylight Savings Time begins -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20070311 -DTEND;VALUE=DATE:20070312 -CLASS:PUBLIC -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T221253Z -LAST-MODIFIED:20090611T212422Z -UID:1eb525d5-26cd-43f0-819e-acc3003b35ad -SUMMARY:Daylight Savings Time begins -RRULE:FREQ=YEARLY;UNTIL=20060402T000000;BYDAY=1SU;BYMONTH=4 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000402 -DTEND;VALUE=DATE:20000403 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T221447Z -LAST-MODIFIED:20090611T212422Z -UID:05504c5b-5984-4eb0-a60a-c6d5cf083d82 -SUMMARY:Daylight Savings Time ends -RRULE:FREQ=YEARLY;UNTIL=20061029T000000;BYDAY=-1SU;BYMONTH=10 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001029 -DTEND;VALUE=DATE:20001030 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T214410Z -LAST-MODIFIED:20090611T212422Z -UID:088031d1-abea-47ed-978d-39c6319e8bf2 -SUMMARY:Inauguration Day -RRULE:FREQ=YEARLY;INTERVAL=4 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010120 -DTEND;VALUE=DATE:20010121 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080828T164802Z -LAST-MODIFIED:20090611T212422Z -UID:d3c521e1-ffab-414c-8cfa-99e76758d8c8 -SUMMARY:Constitution Day\, Citizenship Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000917 -DTEND;VALUE=DATE:20000918 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080828T165106Z -LAST-MODIFIED:20090611T212422Z -UID:a44b8b6e-7ae4-4120-b7ad-14e91691be2a -SUMMARY:National Day of Prayer -RRULE:FREQ=YEARLY;BYDAY=1TH;BYMONTH=5 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000504 -DTEND;VALUE=DATE:20000505 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080828T165839Z -LAST-MODIFIED:20090611T212422Z -UID:e20f1b5c-8ac4-4bea-acf4-b4f2bfe95329 -SUMMARY:Arbor Day -RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=4 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000428 -DTEND;VALUE=DATE:20000429 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080830T011903Z -LAST-MODIFIED:20090611T212422Z -UID:feae8f6c-bdce-46c8-aa45-e2d1c0fb9cb0 -SUMMARY:US General Election -RRULE:FREQ=YEARLY;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8;BYMONTH=11 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001107 -DTEND;VALUE=DATE:20001108 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080830T021222Z -LAST-MODIFIED:20090611T212422Z -UID:60b5ca68-d0a6-4634-9254-16cc300bb12e -SUMMARY:Black Friday -RRULE:FREQ=YEARLY;BYDAY=FR;BYMONTHDAY=23,24,25,26,27,28,29;BYMONTH=11 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001124 -DTEND;VALUE=DATE:20001125 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:dd9bfdf4-1dd1-11b2-ab6e-8da990a14639 -SUMMARY:Martin Luther King Jr.'s Day -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=3MO;BYMONTH=1 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000117 -DTEND;VALUE=DATE:20000118 -CLASS:PUBLIC -TRANSP:TRANSPARENT -DESCRIPTION:Traditionally on January 15th.\nObserved on 3rd Monday of January. -END:VEVENT -BEGIN:VEVENT -CREATED:20070315T171800Z -LAST-MODIFIED:20090611T212422Z -UID:175691b0-1dd2-11b2-9596-e9837a6245dd -SUMMARY:Columbus Day (observed) -STATUS:CONFIRMED -RRULE:FREQ=YEARLY;BYDAY=2MO;BYMONTH=10 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001009 -DTEND;VALUE=DATE:20001010 -CLASS:PUBLIC -TRANSP:TRANSPARENT -DESCRIPTION:Traditionally on October 12th.\nObserved on 2nd Monday of October. -END:VEVENT -BEGIN:VEVENT -CREATED:20080902T140125Z -LAST-MODIFIED:20090611T212422Z -UID:22896130-ef70-46ca-a071-a15f3a746b04 -SUMMARY:Administrative Professionals Day -RRULE:FREQ=YEARLY;BYDAY=WE;BYMONTHDAY=21,22,23,24,25,26,27;BYMONTH=4 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000426 -DTEND;VALUE=DATE:20000427 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T212909Z -LAST-MODIFIED:20090611T212422Z -UID:a0486410-f87b-4e09-939a-77121b58b535 -SUMMARY:Christmas Eve -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001224 -DTEND;VALUE=DATE:20001225 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081126T134531Z -LAST-MODIFIED:20090611T212422Z -UID:8c320d1a-fc56-4282-962c-4c67763a4c58 -SUMMARY:Grandparents Day -RRULE:FREQ=YEARLY;BYDAY=SU;BYMONTHDAY=7,8,9,10,11,12,13;BYMONTH=9 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000910 -DTEND;VALUE=DATE:20000911 -DESCRIPTION:First Sunday following Labor Day -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T214111Z -LAST-MODIFIED:20090611T212422Z -UID:efab1245-c19c-4693-8391-eb5f40cc3743 -SUMMARY:Epiphany -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000106 -DTEND;VALUE=DATE:20000107 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080827T220608Z -LAST-MODIFIED:20090611T212422Z -UID:37f09326-5105-4092-8535-1b4cad9812ac -SUMMARY:Reformation Day -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001029 -DTEND;VALUE=DATE:20001030 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20080829T034030Z -LAST-MODIFIED:20090611T212422Z -UID:af808fce-8a77-4d15-ad63-c096737303d2 -SUMMARY:All Saints Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001101 -DTEND;VALUE=DATE:20001102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081118T135517Z -LAST-MODIFIED:20090611T212422Z -UID:6c6e277f-d344-4807-8d91-bea1689eac7c -SUMMARY:Immaculate Conception -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001208 -DTEND;VALUE=DATE:20001209 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081118T141534Z -LAST-MODIFIED:20090611T212422Z -UID:25db674e-6d6a-41ee-848b-2b79552f071a -SUMMARY:Assumption Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000815 -DTEND;VALUE=DATE:20000816 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20081126T151107Z -LAST-MODIFIED:20090611T212422Z -UID:c3a78810-ea6a-48bc-8178-56fe3fe61e23 -SUMMARY:Kwanzaa -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:19661226 -DTEND;VALUE=DATE:19670102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20091128T184942Z -LAST-MODIFIED:20091128T185038Z -UID:cebdafa9-6b2a-4869-a751-5554b6da2e45 -SUMMARY:Pearl Harbor Remembrance Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20001207 -DTEND;VALUE=DATE:20001208 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100911T004419Z -LAST-MODIFIED:20100911T004455Z -DTSTAMP:20100911T004455Z -UID:1ee4d840-eb70-44f0-8f2a-db778c373ac5 -SUMMARY:Patriot Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20010911 -DTEND;VALUE=DATE:20010912 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100911T004608Z -LAST-MODIFIED:20100911T004647Z -DTSTAMP:20100911T004647Z -UID:e8c8b4f6-1e0a-4bbb-b077-0fcce9077e1d -SUMMARY:Women's Equality Day -RRULE:FREQ=YEARLY -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20000826 -DTEND;VALUE=DATE:20000827 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225155Z -LAST-MODIFIED:20100625T230014Z -DTSTAMP:20100625T230014Z -UID:4c53aa44-f014-4334-8e39-5b2ff090445a -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100101 -DTEND;VALUE=DATE:20100102 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225222Z -LAST-MODIFIED:20100625T230022Z -DTSTAMP:20100625T230022Z -UID:5705d0cf-01b6-4511-9f36-0fc9739b21fc -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100118 -DTEND;VALUE=DATE:20100119 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225249Z -LAST-MODIFIED:20100625T230034Z -DTSTAMP:20100625T230034Z -UID:26751c39-998f-4d1c-bc77-454f21d94fd1 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100215 -DTEND;VALUE=DATE:20100216 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225312Z -LAST-MODIFIED:20100625T230050Z -DTSTAMP:20100625T230050Z -UID:91cde8ea-009b-4a07-b835-7d33b1b7e4e1 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100531 -DTEND;VALUE=DATE:20100601 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225342Z -LAST-MODIFIED:20100625T230108Z -DTSTAMP:20100625T230108Z -UID:63368446-395a-42ca-8108-f1bb7ee5a851 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100705 -DTEND;VALUE=DATE:20100706 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225406Z -LAST-MODIFIED:20100625T230123Z -DTSTAMP:20100625T230123Z -UID:6cbc2eb9-d818-40f0-8e91-7c7ca5e43e0f -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20100906 -DTEND;VALUE=DATE:20100907 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225422Z -LAST-MODIFIED:20100625T230138Z -DTSTAMP:20100625T230138Z -UID:26ab3aae-1f30-4eea-9a30-35aab72d9e67 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20101011 -DTEND;VALUE=DATE:20101012 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225442Z -LAST-MODIFIED:20100625T230149Z -DTSTAMP:20100625T230149Z -UID:c08f4482-8591-4940-8838-80381b0cf69b -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20101111 -DTEND;VALUE=DATE:20101112 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225456Z -LAST-MODIFIED:20100625T230155Z -DTSTAMP:20100625T230155Z -UID:7262eefe-a1b1-4370-8262-c2f900150c0f -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20101125 -DTEND;VALUE=DATE:20101126 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225512Z -LAST-MODIFIED:20100625T230205Z -DTSTAMP:20100625T230205Z -UID:431576fe-17ac-48ea-941e-08d25998c7c2 -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20101224 -DTEND;VALUE=DATE:20101225 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225649Z -LAST-MODIFIED:20100625T230212Z -DTSTAMP:20100625T230212Z -UID:ff38e9bf-6a04-4140-8e14-17f77bfabbdf -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20101231 -DTEND;VALUE=DATE:20110101 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225710Z -LAST-MODIFIED:20100625T230225Z -DTSTAMP:20100625T230225Z -UID:c670bd38-0675-4746-8eca-cd06d5d0fb46 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110117 -DTEND;VALUE=DATE:20110118 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225733Z -LAST-MODIFIED:20100625T230234Z -DTSTAMP:20100625T230234Z -UID:486ce67b-2a71-44ae-87d1-ec8d000a9584 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110221 -DTEND;VALUE=DATE:20110222 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225753Z -LAST-MODIFIED:20100625T230259Z -DTSTAMP:20100625T230259Z -UID:aca86965-d147-47d7-8e88-3990efe7188a -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110530 -DTEND;VALUE=DATE:20110531 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225812Z -LAST-MODIFIED:20100625T230311Z -DTSTAMP:20100625T230311Z -UID:40f9466a-95bd-40fd-a0a5-df005fbee438 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110704 -DTEND;VALUE=DATE:20110705 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225826Z -LAST-MODIFIED:20100625T230320Z -DTSTAMP:20100625T230320Z -UID:86a93453-eb85-4836-8e35-f3d7403eb02c -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20110905 -DTEND;VALUE=DATE:20110906 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225845Z -LAST-MODIFIED:20100625T230329Z -DTSTAMP:20100625T230329Z -UID:8aed5642-2ab6-4d47-ae3e-d6bda178c530 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20111010 -DTEND;VALUE=DATE:20111011 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225901Z -LAST-MODIFIED:20100625T230342Z -DTSTAMP:20100625T230342Z -UID:095e361c-db8c-4936-80ff-6ac356e3a8af -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20111111 -DTEND;VALUE=DATE:20111112 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225917Z -LAST-MODIFIED:20100625T230349Z -DTSTAMP:20100625T230349Z -UID:859d4dfe-1eae-4b3b-9d42-438d500ca455 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20111124 -DTEND;VALUE=DATE:20111125 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T225933Z -LAST-MODIFIED:20100625T230400Z -DTSTAMP:20100625T230400Z -UID:06a9583b-01fb-4ed4-926a-ac995e5dac6d -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20111226 -DTEND;VALUE=DATE:20111227 -TRANSP:TRANSPARENT -X-MOZ-GENERATION:1 -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230426Z -LAST-MODIFIED:20100625T230432Z -DTSTAMP:20100625T230432Z -UID:ec58e9d8-27fa-4f3a-8cb2-8073b860982f -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120102 -DTEND;VALUE=DATE:20120103 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230442Z -LAST-MODIFIED:20100625T230451Z -DTSTAMP:20100625T230451Z -UID:e3e1f541-ad97-4683-ab18-13c9d8620130 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120116 -DTEND;VALUE=DATE:20120117 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230503Z -LAST-MODIFIED:20100625T230510Z -DTSTAMP:20100625T230510Z -UID:b2f98535-66c6-423f-846d-7be00b63d315 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120220 -DTEND;VALUE=DATE:20120221 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230528Z -LAST-MODIFIED:20100625T230537Z -DTSTAMP:20100625T230537Z -UID:6b1d9233-40b7-406b-a98c-769b2681d9ad -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120528 -DTEND;VALUE=DATE:20120529 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230550Z -LAST-MODIFIED:20100625T230558Z -DTSTAMP:20100625T230558Z -UID:9c6685c4-cf34-406d-a52d-457432cd2e72 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120704 -DTEND;VALUE=DATE:20120705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230608Z -LAST-MODIFIED:20100625T230616Z -DTSTAMP:20100625T230616Z -UID:64e8f36b-30ca-4a68-bb68-b6be1bac33e7 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20120903 -DTEND;VALUE=DATE:20120904 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230625Z -LAST-MODIFIED:20100625T230633Z -DTSTAMP:20100625T230633Z -UID:0feaf063-8cc3-4dcc-80a4-3006dd3ee395 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20121008 -DTEND;VALUE=DATE:20121009 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230640Z -LAST-MODIFIED:20100625T230646Z -DTSTAMP:20100625T230646Z -UID:a3ab943c-efa6-403a-94d6-266351d1bb9a -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20121112 -DTEND;VALUE=DATE:20121113 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230652Z -LAST-MODIFIED:20100625T230659Z -DTSTAMP:20100625T230659Z -UID:e29b795e-0f37-4ea4-84e6-0f277cf5a8ba -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20121122 -DTEND;VALUE=DATE:20121123 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230711Z -LAST-MODIFIED:20100625T230719Z -DTSTAMP:20100625T230719Z -UID:9986c0ee-983a-4538-ad4a-4688fe5b5636 -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20121225 -DTEND;VALUE=DATE:20121226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230745Z -LAST-MODIFIED:20100625T230752Z -DTSTAMP:20100625T230752Z -UID:0b3180aa-65a8-4732-bb4c-36cd3c79a650 -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130101 -DTEND;VALUE=DATE:20130102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230800Z -LAST-MODIFIED:20100625T230806Z -DTSTAMP:20100625T230806Z -UID:19f0cc25-bec1-4e84-915b-a70bb532fcfb -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130121 -DTEND;VALUE=DATE:20130122 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230816Z -LAST-MODIFIED:20100625T230822Z -DTSTAMP:20100625T230822Z -UID:db225df5-a20c-4889-9511-bcf81acad6e4 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130218 -DTEND;VALUE=DATE:20130219 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230833Z -LAST-MODIFIED:20100625T230839Z -DTSTAMP:20100625T230839Z -UID:9b12b9be-0f16-42ff-97a5-53a67cc46f2c -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130527 -DTEND;VALUE=DATE:20130528 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230849Z -LAST-MODIFIED:20100625T230856Z -DTSTAMP:20100625T230856Z -UID:9b6b81e1-e4f8-490b-9062-24726dbf27f2 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130704 -DTEND;VALUE=DATE:20130705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230905Z -LAST-MODIFIED:20100625T230910Z -DTSTAMP:20100625T230910Z -UID:5b722439-56b8-4f26-a21d-c2c99898bf47 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20130902 -DTEND;VALUE=DATE:20130903 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230922Z -LAST-MODIFIED:20100625T230927Z -DTSTAMP:20100625T230927Z -UID:e44229c1-89cd-4c96-8094-c09a56f1a028 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20131014 -DTEND;VALUE=DATE:20131015 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230935Z -LAST-MODIFIED:20100625T230941Z -DTSTAMP:20100625T230941Z -UID:fea08c25-1cbc-4d7b-ab30-87bf5b87c4e0 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20131111 -DTEND;VALUE=DATE:20131112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T230955Z -LAST-MODIFIED:20100625T231001Z -DTSTAMP:20100625T231001Z -UID:12eb5737-4e7f-4278-9f8f-36202257d434 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20131128 -DTEND;VALUE=DATE:20131129 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T231008Z -LAST-MODIFIED:20100625T231016Z -DTSTAMP:20100625T231016Z -UID:a2acacd4-e541-4334-b5ad-e2e215cf32fc -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20131225 -DTEND;VALUE=DATE:20131226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232203Z -LAST-MODIFIED:20100625T232211Z -DTSTAMP:20100625T232211Z -UID:784620c2-befa-40af-9aa2-5232ceebf80f -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140101 -DTEND;VALUE=DATE:20140102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232227Z -LAST-MODIFIED:20100625T232233Z -DTSTAMP:20100625T232233Z -UID:7d90511b-c99b-4937-af69-1da4781923c8 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140120 -DTEND;VALUE=DATE:20140121 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232242Z -LAST-MODIFIED:20100625T232249Z -DTSTAMP:20100625T232249Z -UID:760534b9-7b1c-41b1-8797-6dd6583d7b98 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140217 -DTEND;VALUE=DATE:20140218 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232259Z -LAST-MODIFIED:20100625T232307Z -DTSTAMP:20100625T232307Z -UID:3a66f3a1-bce5-4dcf-a815-2d50057ba758 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140526 -DTEND;VALUE=DATE:20140527 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232324Z -LAST-MODIFIED:20100625T232333Z -DTSTAMP:20100625T232333Z -UID:8dec8541-cada-48c8-bae1-7b64e5bd96f1 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140704 -DTEND;VALUE=DATE:20140705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232344Z -LAST-MODIFIED:20100625T232352Z -DTSTAMP:20100625T232352Z -UID:f1a94187-1b1a-46eb-9eba-9f2e03efff7e -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20140901 -DTEND;VALUE=DATE:20140902 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232358Z -LAST-MODIFIED:20100625T232405Z -DTSTAMP:20100625T232405Z -UID:aa078715-e039-42e8-bfca-ceb787c3eceb -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20141013 -DTEND;VALUE=DATE:20141014 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232416Z -LAST-MODIFIED:20100625T232424Z -DTSTAMP:20100625T232424Z -UID:5a0ec2c2-3b70-4440-928d-d3c435205736 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20141111 -DTEND;VALUE=DATE:20141112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232432Z -LAST-MODIFIED:20100625T232438Z -DTSTAMP:20100625T232438Z -UID:ec969553-a7c6-4ad1-bbf9-061bd48b91dc -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20141127 -DTEND;VALUE=DATE:20141128 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232449Z -LAST-MODIFIED:20100625T232456Z -DTSTAMP:20100625T232456Z -UID:f01417f3-4570-4711-9abc-bf20a01cea0c -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20141225 -DTEND;VALUE=DATE:20141226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232517Z -LAST-MODIFIED:20100625T232523Z -DTSTAMP:20100625T232523Z -UID:f37395e6-1056-4175-a457-399cdab4eda2 -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150101 -DTEND;VALUE=DATE:20150102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232529Z -LAST-MODIFIED:20100625T232536Z -DTSTAMP:20100625T232536Z -UID:3f07705a-a1fb-4915-8dbd-4e1513ade06b -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150119 -DTEND;VALUE=DATE:20150120 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232547Z -LAST-MODIFIED:20100625T232553Z -DTSTAMP:20100625T232553Z -UID:2dff572d-fc88-4925-8817-960c9f913974 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150216 -DTEND;VALUE=DATE:20150217 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232604Z -LAST-MODIFIED:20100625T232611Z -DTSTAMP:20100625T232611Z -UID:62aa4902-1113-4e89-ab08-9a344ff2709f -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150525 -DTEND;VALUE=DATE:20150526 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232621Z -LAST-MODIFIED:20100625T232628Z -DTSTAMP:20100625T232628Z -UID:ed53ac56-5535-4877-acf1-84919821da44 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150703 -DTEND;VALUE=DATE:20150704 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232639Z -LAST-MODIFIED:20100625T232645Z -DTSTAMP:20100625T232645Z -UID:fdb30d77-208e-4b9c-acbb-877549b0c60e -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20150907 -DTEND;VALUE=DATE:20150908 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232656Z -LAST-MODIFIED:20100625T232703Z -DTSTAMP:20100625T232703Z -UID:a0add000-7f34-4a12-8cfc-dbf98872b532 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20151012 -DTEND;VALUE=DATE:20151013 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232710Z -LAST-MODIFIED:20100625T232716Z -DTSTAMP:20100625T232716Z -UID:57bbf2f3-e92c-4864-9171-80e1aefa9b25 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20151111 -DTEND;VALUE=DATE:20151112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232724Z -LAST-MODIFIED:20100625T232730Z -DTSTAMP:20100625T232730Z -UID:17357cbf-822b-4e16-97fe-9212a98262ea -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20151126 -DTEND;VALUE=DATE:20151127 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232740Z -LAST-MODIFIED:20100625T232746Z -DTSTAMP:20100625T232746Z -UID:a5ae0bfb-a761-48a7-80b3-85c4dec3459d -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20151225 -DTEND;VALUE=DATE:20151226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232806Z -LAST-MODIFIED:20100625T232813Z -DTSTAMP:20100625T232813Z -UID:e84cfee3-d9f8-4088-9709-995258432145 -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160101 -DTEND;VALUE=DATE:20160102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232819Z -LAST-MODIFIED:20100625T232826Z -DTSTAMP:20100625T232826Z -UID:2c19281f-8160-4886-ae2b-35f5a83d32ec -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160118 -DTEND;VALUE=DATE:20160119 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232836Z -LAST-MODIFIED:20100625T232842Z -DTSTAMP:20100625T232842Z -UID:ccb48d24-c58f-478b-a941-1706c642cd39 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160215 -DTEND;VALUE=DATE:20160216 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232851Z -LAST-MODIFIED:20100625T232858Z -DTSTAMP:20100625T232858Z -UID:067c379d-cdfa-49f5-9de7-65be7b5b0158 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160530 -DTEND;VALUE=DATE:20160531 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232911Z -LAST-MODIFIED:20100625T232917Z -DTSTAMP:20100625T232917Z -UID:dddf90ce-a186-4bfb-aaf8-878e07450fcf -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160704 -DTEND;VALUE=DATE:20160705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232925Z -LAST-MODIFIED:20100625T232931Z -DTSTAMP:20100625T232931Z -UID:c734ef2f-53a9-48f3-9da0-f70b70c0d881 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20160905 -DTEND;VALUE=DATE:20160906 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232938Z -LAST-MODIFIED:20100625T232944Z -DTSTAMP:20100625T232944Z -UID:c9d05e3a-bd7d-4a5d-94ba-020335a9b3aa -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20161010 -DTEND;VALUE=DATE:20161011 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T232953Z -LAST-MODIFIED:20100625T233000Z -DTSTAMP:20100625T233000Z -UID:819d893b-af2b-4423-aaff-c4286ee349ee -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20161111 -DTEND;VALUE=DATE:20161112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233007Z -LAST-MODIFIED:20100625T233013Z -DTSTAMP:20100625T233013Z -UID:9af9e892-e3a8-4a2c-a130-4e25c92c2baa -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20161124 -DTEND;VALUE=DATE:20161125 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233022Z -LAST-MODIFIED:20100625T233028Z -DTSTAMP:20100625T233028Z -UID:8b5ff666-fec7-487b-99c0-aa621548e9ea -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20161226 -DTEND;VALUE=DATE:20161227 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233045Z -LAST-MODIFIED:20100625T233052Z -DTSTAMP:20100625T233052Z -UID:9943b6be-9c4b-4fc3-b690-80114b559f26 -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170102 -DTEND;VALUE=DATE:20170103 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233101Z -LAST-MODIFIED:20100625T233109Z -DTSTAMP:20100625T233109Z -UID:4c3b85b1-2144-470e-b986-418c087cb5a0 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170116 -DTEND;VALUE=DATE:20170117 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233117Z -LAST-MODIFIED:20100625T233123Z -DTSTAMP:20100625T233123Z -UID:c09d860f-6464-41c4-b2ef-25011d25cecf -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170220 -DTEND;VALUE=DATE:20170221 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233131Z -LAST-MODIFIED:20100625T233138Z -DTSTAMP:20100625T233138Z -UID:ba4752c9-4032-4385-820d-2c4213e14fa5 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170529 -DTEND;VALUE=DATE:20170530 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233145Z -LAST-MODIFIED:20100625T233151Z -DTSTAMP:20100625T233151Z -UID:59e934c5-672e-4ccc-a686-9d9c495b3883 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170704 -DTEND;VALUE=DATE:20170705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233158Z -LAST-MODIFIED:20100625T233205Z -DTSTAMP:20100625T233205Z -UID:90340458-52db-4697-b2d7-8bfd51cfaaf7 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20170904 -DTEND;VALUE=DATE:20170905 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233212Z -LAST-MODIFIED:20100625T233218Z -DTSTAMP:20100625T233218Z -UID:70c9c1e3-31a2-4b0a-9712-69a184fbfb7c -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20171009 -DTEND;VALUE=DATE:20171010 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233225Z -LAST-MODIFIED:20100625T233232Z -DTSTAMP:20100625T233232Z -UID:e9f86e06-9f1c-44bc-b3db-43a220802fe9 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20171110 -DTEND;VALUE=DATE:20171111 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233240Z -LAST-MODIFIED:20100625T233246Z -DTSTAMP:20100625T233246Z -UID:d82d8d11-0793-4a68-a876-83c7e29736d0 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20171123 -DTEND;VALUE=DATE:20171124 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233255Z -LAST-MODIFIED:20100625T233302Z -DTSTAMP:20100625T233302Z -UID:4526de1c-b74d-4643-b299-4c4c42ed82bb -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20171225 -DTEND;VALUE=DATE:20171226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233318Z -LAST-MODIFIED:20100625T233324Z -DTSTAMP:20100625T233324Z -UID:885fb7d7-ea3f-4938-9e40-f612986566f3 -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180101 -DTEND;VALUE=DATE:20180102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233329Z -LAST-MODIFIED:20100625T233335Z -DTSTAMP:20100625T233335Z -UID:89c44a14-aee8-49ce-8528-0e7a8bbe6e06 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180115 -DTEND;VALUE=DATE:20180116 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233342Z -LAST-MODIFIED:20100625T233348Z -DTSTAMP:20100625T233348Z -UID:e65fa03d-c681-458c-a26b-4206e3297280 -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180219 -DTEND;VALUE=DATE:20180220 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233358Z -LAST-MODIFIED:20100625T233404Z -DTSTAMP:20100625T233404Z -UID:f0a9e5e8-077c-424a-a668-970951c307d5 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180528 -DTEND;VALUE=DATE:20180529 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233413Z -LAST-MODIFIED:20100625T233419Z -DTSTAMP:20100625T233419Z -UID:f00cd4b3-3f19-4b46-9d69-e33790eda2ea -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180704 -DTEND;VALUE=DATE:20180705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233426Z -LAST-MODIFIED:20100625T233432Z -DTSTAMP:20100625T233432Z -UID:d0cdf502-fcdd-48ee-8cdc-2a6bc947a906 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20180903 -DTEND;VALUE=DATE:20180904 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233441Z -LAST-MODIFIED:20100625T233447Z -DTSTAMP:20100625T233447Z -UID:4ca554db-e59d-4de8-b043-d28988ae0b39 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20181008 -DTEND;VALUE=DATE:20181009 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233455Z -LAST-MODIFIED:20100625T233502Z -DTSTAMP:20100625T233502Z -UID:876c8ca9-48a3-405d-86a1-432a66fb0ceb -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20181112 -DTEND;VALUE=DATE:20181113 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233510Z -LAST-MODIFIED:20100625T233516Z -DTSTAMP:20100625T233516Z -UID:5adaf364-32e7-4f8e-bc69-ad93c1c3aa15 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20181122 -DTEND;VALUE=DATE:20181123 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233527Z -LAST-MODIFIED:20100625T233532Z -DTSTAMP:20100625T233532Z -UID:05f95ef4-6098-4337-8d15-8b57ff37f251 -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20181225 -DTEND;VALUE=DATE:20181226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233551Z -LAST-MODIFIED:20100625T233558Z -DTSTAMP:20100625T233558Z -UID:43fc5a98-b57a-456b-a50b-8e0e1de018ff -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190101 -DTEND;VALUE=DATE:20190102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233603Z -LAST-MODIFIED:20100625T233610Z -DTSTAMP:20100625T233610Z -UID:e1f23ce9-56fe-47e0-8f0e-2d408847c5d6 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190121 -DTEND;VALUE=DATE:20190122 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233618Z -LAST-MODIFIED:20100625T233626Z -DTSTAMP:20100625T233626Z -UID:3e8909b9-74aa-41bb-b8e7-23beff8c362d -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190218 -DTEND;VALUE=DATE:20190219 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233635Z -LAST-MODIFIED:20100625T233641Z -DTSTAMP:20100625T233641Z -UID:1aac6453-abbf-47de-bf5d-4bd0dfb9cd44 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190527 -DTEND;VALUE=DATE:20190528 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233650Z -LAST-MODIFIED:20100625T233700Z -DTSTAMP:20100625T233700Z -UID:dce26b68-5fd3-4b45-a8a6-cb68c8946935 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190704 -DTEND;VALUE=DATE:20190705 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233710Z -LAST-MODIFIED:20100625T233716Z -DTSTAMP:20100625T233716Z -UID:199ec585-69d7-45df-9cf2-c1e83d570e3a -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20190902 -DTEND;VALUE=DATE:20190903 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233723Z -LAST-MODIFIED:20100625T233728Z -DTSTAMP:20100625T233728Z -UID:61d39eb7-ae9a-4667-bbfa-6107df1e09e7 -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20191014 -DTEND;VALUE=DATE:20191015 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233735Z -LAST-MODIFIED:20100625T233741Z -DTSTAMP:20100625T233741Z -UID:290c9fe0-e39e-425a-95b4-831945c6b8f2 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20191111 -DTEND;VALUE=DATE:20191112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233747Z -LAST-MODIFIED:20100625T233754Z -DTSTAMP:20100625T233754Z -UID:b7680e77-a64f-4861-bb35-f2d917a9c437 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20191128 -DTEND;VALUE=DATE:20191129 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233801Z -LAST-MODIFIED:20100625T233808Z -DTSTAMP:20100625T233808Z -UID:b234249f-565e-4159-b4e5-b0314c48a191 -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20191225 -DTEND;VALUE=DATE:20191226 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233825Z -LAST-MODIFIED:20100625T233832Z -DTSTAMP:20100625T233832Z -UID:a48665de-2184-4468-9f8a-c21cf582455f -SUMMARY:New Year's Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200101 -DTEND;VALUE=DATE:20200102 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233838Z -LAST-MODIFIED:20100625T233844Z -DTSTAMP:20100625T233844Z -UID:f03dfc3e-924e-4574-87e7-e30fa1166025 -SUMMARY:Birthday of Martin Luther King\, Jr. (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200120 -DTEND;VALUE=DATE:20200121 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233852Z -LAST-MODIFIED:20100625T233858Z -DTSTAMP:20100625T233858Z -UID:95d5c62a-4a58-49cf-9c91-d66a0fae1acd -SUMMARY:Washington's Birthday (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200217 -DTEND;VALUE=DATE:20200218 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233906Z -LAST-MODIFIED:20100625T233912Z -DTSTAMP:20100625T233912Z -UID:dd42517c-228b-44b3-a3ac-0bd4a7aadb75 -SUMMARY:Memorial Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200525 -DTEND;VALUE=DATE:20200526 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233921Z -LAST-MODIFIED:20100625T233927Z -DTSTAMP:20100625T233927Z -UID:cb3e2de7-2c55-4882-820a-df01c8c6aae7 -SUMMARY:Independence Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200703 -DTEND;VALUE=DATE:20200704 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233935Z -LAST-MODIFIED:20100625T233941Z -DTSTAMP:20100625T233941Z -UID:65eb7b18-064f-4939-a840-f11ef4bec981 -SUMMARY:Labor Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20200907 -DTEND;VALUE=DATE:20200908 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T233952Z -LAST-MODIFIED:20100625T233959Z -DTSTAMP:20100625T233959Z -UID:2d13e3c5-8a26-49be-bdbc-9a1e9715ae4b -SUMMARY:Columbus Day -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20201012 -DTEND;VALUE=DATE:20201013 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T234006Z -LAST-MODIFIED:20100625T234017Z -DTSTAMP:20100625T234017Z -UID:8ffd1252-87f4-481c-b46a-c2aa4cba74c7 -SUMMARY:Veterans Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20201111 -DTEND;VALUE=DATE:20201112 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T234023Z -LAST-MODIFIED:20100625T234029Z -DTSTAMP:20100625T234029Z -UID:557d2349-5c21-45c8-aad7-9c24a6aceb66 -SUMMARY:Thanksgiving Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20201126 -DTEND;VALUE=DATE:20201127 -TRANSP:TRANSPARENT -END:VEVENT -BEGIN:VEVENT -CREATED:20100625T234036Z -LAST-MODIFIED:20100625T234041Z -DTSTAMP:20100625T234041Z -UID:1c5f2dc5-7cea-48cd-8c36-905930f8eee9 -SUMMARY:Christmas Day (US-OPM) -CATEGORIES:Holidays -DTSTART;VALUE=DATE:20201225 -DTEND;VALUE=DATE:20201226 -TRANSP:TRANSPARENT -END:VEVENT -END:VCALENDAR \ No newline at end of file From 6ff962661b25be5bbbb88b3f6702822b49d8df70 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 6 Nov 2011 19:47:57 -0500 Subject: [PATCH 047/134] test was failing when invoked on dst switch date --- src/test/java/com/joestelmach/natty/DateTest.java | 2 +- src/test/java/com/joestelmach/natty/TimeTest.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 4c39fb14..7409af78 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -231,7 +231,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "2 days after new year's eve 2019"; + String value = "5 hours after midnight"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index afacec59..1be29c17 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -26,6 +26,8 @@ public static void oneTime() { */ @Test public void testFormal() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("1/02/2011"); + CalendarSource.setBaseDate(reference); validateTime("0600h", 6, 0, 0); validateTime("06:00h", 6, 0, 0); validateTime("06:00 hours", 6, 0, 0); @@ -48,6 +50,8 @@ public void testFormal() throws Exception { @Test public void testRelaxed() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("1/02/2011"); + CalendarSource.setBaseDate(reference); validateTime("noon", 12, 0, 0); validateTime("afternoon", 12, 0, 0); validateTime("midnight", 0, 0, 0); From bf45382e459f12712ae31b4d933e6216f9917f11 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 17 Nov 2011 00:26:23 -0500 Subject: [PATCH 048/134] allow dashes for conjunctions, allow missing dots in meridian indicators --- .../antlr3/com/joestelmach/natty/generated/DateLexer.g | 4 ++-- .../com/joestelmach/natty/generated/DateParser.g | 2 +- src/main/java/com/joestelmach/natty/WalkerState.java | 8 ++++---- src/test/java/com/joestelmach/natty/DateTest.java | 2 +- src/test/java/com/joestelmach/natty/DateTimeTest.java | 10 ++++++++++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 976a5ba1..a39eec4a 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -56,8 +56,8 @@ UNTIL : 'until'; AT : 'at' | '@'; AFTER : 'after'; PAST : 'past'; -AM : 'am' | 'a.m.' | 'a'; -PM : 'pm' | 'p.m.' | 'p'; +AM : 'am' | 'a.m' DOT? | 'a'; +PM : 'pm' | 'p.m' DOT? | 'p'; T : 't'; MILITARY_HOUR_SUFFIX : 'h'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index b5706efc..913a3253 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -168,7 +168,7 @@ date_time_alternative ; conjunction - : COMMA? WHITE_SPACE (AND | OR | TO | THROUGH) WHITE_SPACE + : COMMA? WHITE_SPACE (AND | OR | TO | THROUGH | DASH) WHITE_SPACE ; alternative_day_of_month_list diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 842460bb..f74bce9a 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -401,7 +401,7 @@ public void seekToHoliday(String holidayString, String direction, String seekAmo markDateInvocation(); // get the current year - Calendar cal = new GregorianCalendar(); + Calendar cal = getCalendar(); cal.setTimeZone(_defaultTimeZone); int currentYear = cal.get(Calendar.YEAR); @@ -443,7 +443,7 @@ public void seekToHolidayYear(String holidayString, String yearString) { Date date = dates.get(year - (holiday.equals(Holiday.NEW_YEARS_EVE) ? 1 : 0)); if(date != null) { - Calendar cal = new GregorianCalendar(); + Calendar cal = getCalendar(); cal.setTimeZone(_calendar.getTimeZone()); cal.setTime(date); _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); @@ -564,12 +564,12 @@ private Map getDatesForHoliday(int startYear, int endYear, Holida DateTime date = ((Period) p).getStart(); // this date is at the date of the holiday at 12 AM UTC - Calendar utcCal = new GregorianCalendar(); + Calendar utcCal = getCalendar(); utcCal.setTimeZone(TimeZone.getTimeZone(GMT)); utcCal.setTime(date); // use the year, month and day components of our UTC date to form a new local date - Calendar localCal = new GregorianCalendar(); + Calendar localCal = getCalendar(); localCal.setTimeZone(_defaultTimeZone); localCal.set(Calendar.YEAR, utcCal.get(Calendar.YEAR)); localCal.set(Calendar.MONTH, utcCal.get(Calendar.MONTH)); diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 7409af78..31a5a23d 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -231,7 +231,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "5 hours after midnight"; + String value = "Sunday, November 20 2 p.m. - 3 p.m"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index afb79b13..987cbb62 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -95,5 +95,15 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDateTime(dates.get(0), 3, 3, 2010, 21, 0, 0); validateDateTime(dates.get(1), 6, 17, 2010, 21, 0, 0); + + dates = parseCollection("November 20 2 p.m. to 3 p.m"); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 11, 20, 2010, 14, 0, 0); + validateDateTime(dates.get(1), 11, 20, 2010, 15, 0, 0); + + dates = parseCollection("November 20 2 p.m. - 3 p.m."); + Assert.assertEquals(2, dates.size()); + validateDateTime(dates.get(0), 11, 20, 2010, 14, 0, 0); + validateDateTime(dates.get(1), 11, 20, 2010, 15, 0, 0); } } From b46974f3e00bab33b32ebcf916c8a1521d6d97d6 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 29 Nov 2011 21:18:17 -0500 Subject: [PATCH 049/134] september, in a january, in an october, in a week, etc. --- .../joestelmach/natty/generated/DateLexer.g | 1 + .../joestelmach/natty/generated/DateParser.g | 5 +++++ .../java/com/joestelmach/natty/DateTest.java | 20 +++++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index a39eec4a..24505e10 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -251,6 +251,7 @@ SINGLE_QUOTE : '\''; FOR : 'for'; IN : 'in'; +AN : 'an'; THE : 'the'; OR : 'or'; AND : 'and'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 913a3253..7934756a 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -373,6 +373,10 @@ relative_date // relative target with no prefix has an implicit seek of 0 -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) + // january, february + | relaxed_month + -> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] relaxed_month)) + // one month from now | spelled_or_int_optional_prefix WHITE_SPACE relative_target WHITE_SPACE relative_date_suffix -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix relative_target)) @@ -568,6 +572,7 @@ relative_date_prefix | (THIS WHITE_SPACE)? PAST -> DIRECTION["<"] SEEK_BY["by_day"] INT["1"] | (THIS WHITE_SPACE)? COMING -> DIRECTION[">"] SEEK_BY["by_day"] INT["1"] | (THIS WHITE_SPACE)? UPCOMING -> DIRECTION[">"] SEEK_BY["by_day"] INT["1"] + | IN WHITE_SPACE (AM | AN) -> DIRECTION[">"] SEEK_BY["by_day"] INT["1"] | (IN WHITE_SPACE)? spelled_or_int_optional_prefix -> DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix ; diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 31a5a23d..578b72a6 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -4,6 +4,7 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.Map.Entry; import java.util.TimeZone; import java.util.logging.ConsoleHandler; import java.util.logging.Level; @@ -98,6 +99,14 @@ public void testRelative() throws Exception { validateDate("tues this week", 3, 1, 2011); validateDate("tuesday this week", 3, 1, 2011); validateDate("tuesday next week", 3, 8, 2011); + validateDate("this september", 9, 1, 2011); + validateDate("in a september", 9, 1, 2011); + validateDate("in an october", 10, 1, 2011); + validateDate("september", 9, 1, 2011); + validateDate("last september", 9, 1, 2010); + validateDate("next september", 9, 1, 2011); + validateDate("in a year", 2, 28, 2012); + validateDate("in a week", 3, 7, 2011); } @Test @@ -231,7 +240,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Sunday, November 20 2 p.m. - 3 p.m"; + String value = "in a week"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -244,9 +253,16 @@ public static void main(String[] args) { System.out.println("is recurring: " + group.isRecurring()); System.out.println("recurs until: " + group.getRecursUntil()); - System.out.print("conjunctions: "); + System.out.println("\n** Parse Locations **"); + for(Entry> entry:group.getParseLocations().entrySet()) { + for(ParseLocation loc:entry.getValue()) { + System.out.println(loc.getRuleName()); + } + } + List conjunctionLocations = group.getParseLocations().get("conjunction"); if(conjunctionLocations != null) { + System.out.print("\nconjunctions: "); for(ParseLocation location:conjunctionLocations) { System.out.print(location.getText() + " "); } From a808a52683eb199cc7ba642647e28d901011c35c Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 30 Nov 2011 23:10:04 -0500 Subject: [PATCH 050/134] search fixes --- .../joestelmach/natty/generated/DateParser.g | 2 +- .../java/com/joestelmach/natty/Parser.java | 29 +++---- .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/SearchTest.java | 81 +++++++++++++++++++ 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 7934756a..f7a2eff1 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -267,7 +267,7 @@ relaxed_date WHITE_SPACE (OF WHITE_SPACE)? relaxed_month // Jan 21, 1997 Sun, Nov 21 - | relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (relaxed_year_prefix relaxed_year)? + | relaxed_day_of_week? relaxed_month COMMA? WHITE_SPACE relaxed_day_of_month (relaxed_year_prefix relaxed_year)? ) -> ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month relaxed_day_of_week? relaxed_year?) ; diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 10eaf261..333796f6 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -44,16 +44,6 @@ public Parser(TimeZone defaultTimeZone) { */ public Parser() { _defaultTimeZone = TimeZone.getDefault(); - - try { - //FileInputStream fin = new FileInputStream("mycalendar.ics"); - - //CalendarBuilder builder = new CalendarBuilder(); - - //Calendar calendar = builder.build(fin); - } catch(Exception e) { - e.printStackTrace(); - } } /** @@ -104,6 +94,7 @@ public List parse(String value) { break; } } + cleanupGroup(tokens); TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); group = singleParse(newStream); } @@ -246,10 +237,20 @@ else if(currentToken.getType() != DateLexer.DOT) { * @param group */ private void cleanupGroup(List group) { - // remove trailing white space - Token lastToken = group.get(group.size() - 1); - if(lastToken.getType() == DateParser.WHITE_SPACE) { - group.remove(lastToken); + // remove leading white space + if(group.size() > 0) { + Token firstToken = group.get(0); + if(firstToken.getType() == DateParser.WHITE_SPACE) { + group.remove(firstToken); + } + } + + // and trailing white space + if(group.size() > 0) { + Token lastToken = group.get(group.size() - 1); + if(lastToken.getType() == DateParser.WHITE_SPACE) { + group.remove(lastToken); + } } } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 578b72a6..8e0e861c 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -240,7 +240,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "in a week"; + String value = "i want to eat chinese tonight"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index cfc84989..21077036 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -131,5 +131,86 @@ public void test() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 2, 20, 2011); validateDate(dates.get(1), 2, 20, 2016); + + groups = parser.parse("I want to go to my doctors appointment on May 15, 2011."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 5, 15, 2011); + + groups = parser.parse("I intend to become a zombie on December, 21st 2012."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 12, 21, 2012); + + groups = parser.parse("I want to hire a virtual assistant to do research for me on March 15, 2011"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 3, 15, 2011); + + groups = parser.parse("I want to see my mother on sunday."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 27, 2011); + + groups = parser.parse("I want to be able to jog 3 miles non-stop by September."); + Assert.assertEquals(2, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 3, 0, 0); + dates = groups.get(1).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 1, 2011); + + groups = parser.parse("I want to lose 10 lbs in 10 days"); + Assert.assertEquals(2, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 10, 0, 0); + dates = groups.get(1).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 3, 2, 2011); + + groups = parser.parse("I want to visit my grandfathers grave on December 30 2011"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 12, 30, 2011); + + groups = parser.parse("i want to have 1 kid this year"); + Assert.assertEquals(2, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 1, 0, 0); + dates = groups.get(1).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); + + groups = parser.parse("save $1000 by September"); + Assert.assertEquals(2, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 10, 0, 0); + dates = groups.get(1).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 1, 2011); + + groups = parser.parse("have my son play at muse music in provo UT at the 3 band cause they always have fog on the third band at 7:30"); + Assert.assertEquals(2, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 3, 0, 0); + dates = groups.get(1).getDates(); + Assert.assertEquals(1, dates.size()); + validateDateTime(dates.get(0), 2, 20, 2011, 7, 30, 0); + + groups = parser.parse("i want to eat chinese tonight"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 20, 2011); } } From 804fa619ae9bd09c88bc8adddc4e1eff9f7d6ece Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 4 Dec 2011 22:52:30 -0500 Subject: [PATCH 051/134] merging contiguous white space, adding explicit 'tonight' token --- .../com/joestelmach/natty/generated/DateLexer.g | 1 + .../com/joestelmach/natty/generated/DateParser.g | 1 + src/main/java/com/joestelmach/natty/Parser.java | 14 ++++++++++++++ .../java/com/joestelmach/natty/SearchTest.java | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 24505e10..e41ffcca 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -44,6 +44,7 @@ YEAR : 'year' | 'year' SINGLE_QUOTE? 's' | 'yrs' DOT?; TODAY : 'today'; TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow'; +TONIGHT : 'tonight'; YESTERDAY : 'yesterday'; // ********** recurrence rules ********** diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index f7a2eff1..8d1d6f4a 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -798,6 +798,7 @@ named_time : (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | TONIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] | (AT WHITE_SPACE)? MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] ; diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 333796f6..813b5259 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -237,6 +237,20 @@ else if(currentToken.getType() != DateLexer.DOT) { * @param group */ private void cleanupGroup(List group) { + + // remove contiguous white space + Iterator iter = group.iterator(); + Token previousToken = null; + while(iter.hasNext()) { + Token token = iter.next(); + if(previousToken != null && previousToken.getType() == DateParser.WHITE_SPACE) { + if(token.getType() == DateParser.WHITE_SPACE) { + iter.remove(); + } + } + previousToken = token; + } + // remove leading white space if(group.size() > 0) { Token firstToken = group.get(0); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 21077036..3f610b1b 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -211,6 +211,7 @@ public void test() throws Exception { Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDate(dates.get(0), 2, 20, 2011); + validateDateTime(dates.get(0), 2, 20, 2011, 20, 0, 0); + } } From ab1dc650f94311d0035df4870a3521ceb913aa43 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:22:39 -0500 Subject: [PATCH 052/134] [maven-release-plugin] prepare release natty-0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac4d2653..fcfa0d42 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5-SNAPSHOT + 0.5 Natty Date Parser natural language date parser http://natty.joestelmach.com From 27b3c139dbe1fa92e374cc2298e92a1141ae48a9 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:22:42 -0500 Subject: [PATCH 053/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fcfa0d42..2f25a683 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5 + 0.6-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From b6bb46498457f67566ec47f9ab55bf4f310579d0 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:25:39 -0500 Subject: [PATCH 054/134] reverting release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f25a683..ac4d2653 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.6-SNAPSHOT + 0.5-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 391c341e466ce7d49e185c621cb7433e49ae41c7 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:26:14 -0500 Subject: [PATCH 055/134] [maven-release-plugin] prepare release natty-0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac4d2653..fcfa0d42 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5-SNAPSHOT + 0.5 Natty Date Parser natural language date parser http://natty.joestelmach.com From cee50497a63646606fcb4fadd7235a394b803b18 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:27:16 -0500 Subject: [PATCH 056/134] reverting release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fcfa0d42..ac4d2653 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5 + 0.5-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 0495cb320cbe661accafd4a3ab68bf3dff8e8f5b Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:27:52 -0500 Subject: [PATCH 057/134] [maven-release-plugin] prepare release natty-0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac4d2653..fcfa0d42 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5-SNAPSHOT + 0.5 Natty Date Parser natural language date parser http://natty.joestelmach.com From baa79691d124112c403b2c5370c768eb288f3e0a Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 26 Jan 2012 22:27:54 -0500 Subject: [PATCH 058/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fcfa0d42..2f25a683 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.5 + 0.6-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 74e7f77b05ce998e6db1b51a9864cf449da39ce7 Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Sun, 4 Mar 2012 12:32:07 +0300 Subject: [PATCH 059/134] Make tests locale independe.t --- .gitignore | 2 ++ .../com/joestelmach/natty/AbstractTest.java | 17 +++++++++++++---- .../java/com/joestelmach/natty/DateTest.java | 3 +++ .../com/joestelmach/natty/DateTimeTest.java | 3 +++ .../java/com/joestelmach/natty/HolidayTest.java | 3 +++ .../java/com/joestelmach/natty/TimeTest.java | 3 +++ 6 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index eba5e86e..cc1435b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .classpath .settings .project +.idea +*.iml bin target src/main/java/com/joestelmach/natty/generated/* diff --git a/src/test/java/com/joestelmach/natty/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java index cb6237a6..234284a2 100644 --- a/src/test/java/com/joestelmach/natty/AbstractTest.java +++ b/src/test/java/com/joestelmach/natty/AbstractTest.java @@ -2,22 +2,31 @@ import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; +import java.util.TimeZone; import org.junit.Assert; import org.junit.Before; import com.joestelmach.natty.CalendarSource; import com.joestelmach.natty.Parser; +import org.junit.BeforeClass; /** * * @author Joe Stelmach */ public abstract class AbstractTest { - private static final Calendar _calendar = Calendar.getInstance(); - protected static final Parser _parser = new Parser(); - + private static Calendar _calendar; + protected static Parser _parser; + + + public static void initCalendarAndParser() { + _calendar = Calendar.getInstance(); + _parser = new Parser(); + } + /** * Resets the calendar source time before each test */ @@ -141,7 +150,7 @@ protected void validateDateTime(String value, int month, int day, int year, */ protected void validateDateTime(Date date, int month, int day, int year, int hours, int minutes, int seconds) { - + _calendar.setTime(date); Assert.assertEquals(month -1, _calendar.get(Calendar.MONTH)); Assert.assertEquals(day, _calendar.get(Calendar.DAY_OF_MONTH)); diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 8e0e861c..56c6829e 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -4,6 +4,7 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map.Entry; import java.util.TimeZone; import java.util.logging.ConsoleHandler; @@ -23,7 +24,9 @@ public class DateTest extends AbstractTest { @BeforeClass public static void oneTime() { + Locale.setDefault(Locale.US); TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); } @Test diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 987cbb62..7be819f0 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -3,6 +3,7 @@ import java.text.DateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.TimeZone; import org.junit.Assert; @@ -17,7 +18,9 @@ public class DateTimeTest extends AbstractTest { @BeforeClass public static void oneTime() { + Locale.setDefault(Locale.US); TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); } @Test diff --git a/src/test/java/com/joestelmach/natty/HolidayTest.java b/src/test/java/com/joestelmach/natty/HolidayTest.java index 75523291..f792016f 100644 --- a/src/test/java/com/joestelmach/natty/HolidayTest.java +++ b/src/test/java/com/joestelmach/natty/HolidayTest.java @@ -2,6 +2,7 @@ import java.text.DateFormat; import java.util.Date; +import java.util.Locale; import java.util.TimeZone; import org.junit.BeforeClass; @@ -12,7 +13,9 @@ public class HolidayTest extends AbstractTest { @BeforeClass public static void oneTime() { + Locale.setDefault(Locale.US); TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); } @Test diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index 1be29c17..1752b93d 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -3,6 +3,7 @@ import java.text.DateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.TimeZone; import org.junit.Assert; @@ -17,7 +18,9 @@ public class TimeTest extends AbstractTest { @BeforeClass public static void oneTime() { + Locale.setDefault(Locale.US); TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); } /** From 7bfb22c4c6e44fb5c8efe3fa33fafae11d0fe707 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 6 Mar 2012 23:00:21 -0500 Subject: [PATCH 060/134] fixes #28 --- .../joestelmach/natty/generated/DateParser.g | 24 +++++++++---------- .../java/com/joestelmach/natty/DateTest.java | 7 +++++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 8d1d6f4a..ded89ae4 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -92,17 +92,17 @@ date date_time_alternative - // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after - : (date conjunction global_date_prefix)=> - date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) - - // in 2 to 3 months - | ((IN | FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE TO)=> - (IN | FOR | NEXT) WHITE_SPACE one=spelled_or_int_optional_prefix WHITE_SPACE TO WHITE_SPACE two=spelled_or_int_optional_prefix WHITE_SPACE relative_date_span + // in 2 to 3 months, 4 and 7 months + : (((IN | FOR | NEXT) WHITE_SPACE)? spelled_or_int_optional_prefix conjunction)=> + ((IN | FOR | NEXT) WHITE_SPACE)? one=spelled_or_int_optional_prefix conjunction two=spelled_or_int_optional_prefix WHITE_SPACE relative_date_span -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $one relative_date_span))) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $two relative_date_span)))) + + // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after + | (date conjunction global_date_prefix)=> + date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? + -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) // "next wed or thurs" , "next wed, thurs, or fri" | (alternative_day_of_week_list)=> alternative_day_of_week_list @@ -177,10 +177,10 @@ alternative_day_of_month_list -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ // first or last day of september - | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE explicit_relative_month)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE explicit_relative_month (date_time_separator explicit_time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $first) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK explicit_relative_month) $second) explicit_time?) + | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE relaxed_month)=> + first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE relaxed_month (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_month) $first) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_month) $second) explicit_time?) // first or last day of next september | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month)=> diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 56c6829e..ff009138 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -234,6 +234,11 @@ public void testRange() throws Exception { dates = parseCollection("september 7th something happened here"); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 7, 2011); + + dates = parseCollection("bla bla bla 2 and 4 month"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 3, 2, 2011); + validateDate(dates.get(1), 5, 2, 2011); } public static void main(String[] args) { @@ -243,7 +248,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "i want to eat chinese tonight"; + String value = "bla bla bla 2 and 4 month"; Parser parser = new Parser(); List groups = parser.parse(value); From ebe3feaf748b1cf09a34c79899dba11e03ba24f1 Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Wed, 14 Mar 2012 23:28:20 +0300 Subject: [PATCH 061/134] Make parser thread safe. --- .../com/joestelmach/natty/CalendarSource.java | 16 ++-- .../joestelmach/natty/ThreadSafetyTest.java | 74 +++++++++++++++++++ 2 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/joestelmach/natty/ThreadSafetyTest.java diff --git a/src/main/java/com/joestelmach/natty/CalendarSource.java b/src/main/java/com/joestelmach/natty/CalendarSource.java index f27cbaa5..1ffd1a9a 100644 --- a/src/main/java/com/joestelmach/natty/CalendarSource.java +++ b/src/main/java/com/joestelmach/natty/CalendarSource.java @@ -6,22 +6,22 @@ /** * Responsible for generating new Calendars that represent * the current point in time. This is neccessary so we can - * manipulate what the software thinks is the 'current' + * manipulate what the software thinks is the 'current' * time, which may be different than the system time - * + * * @author Joe Stelmach */ -class CalendarSource { - private static Date _baseDate; - +public class CalendarSource { + private static ThreadLocal _baseDate = new ThreadLocal(); + public static void setBaseDate(Date baseDate) { - _baseDate = baseDate; + _baseDate.set(baseDate); } public static GregorianCalendar getCurrentCalendar() { GregorianCalendar calendar = new GregorianCalendar(); - if(_baseDate != null) { - calendar.setTime(_baseDate); + if(_baseDate.get() != null) { + calendar.setTime(_baseDate.get()); } return calendar; } diff --git a/src/test/java/com/joestelmach/natty/ThreadSafetyTest.java b/src/test/java/com/joestelmach/natty/ThreadSafetyTest.java new file mode 100644 index 00000000..5acbcb8b --- /dev/null +++ b/src/test/java/com/joestelmach/natty/ThreadSafetyTest.java @@ -0,0 +1,74 @@ +package com.joestelmach.natty; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.TimeZone; + +import org.junit.Assert; + +import org.junit.Test; +import org.junit.BeforeClass; + +public class ThreadSafetyTest extends AbstractTest { + + private final static int NUM_OF_THREADS = 10; + private final static int JOIN_TIMEOUT = 2000; // 2 seconds + private static DateFormat dateFormat; + + private AtomicInteger numOfCorrectResults = new AtomicInteger(); + + @BeforeClass + public static void oneTime() { + Locale.setDefault(Locale.US); + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US); + initCalendarAndParser(); + } + + @Test + public void testManyThreads() throws Exception { + Thread[] threads = new Thread[NUM_OF_THREADS]; + for (int i = 0; i < NUM_OF_THREADS; i++) { + threads[i] = new Thread(new Tester(i)); + } + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + thread.join(JOIN_TIMEOUT); + } + Assert.assertEquals(NUM_OF_THREADS, numOfCorrectResults.get()); + } + + private class Tester implements Runnable { + + private Date baseDate; + private int baseMinute; + + public Tester(int baseMinute) throws Exception { + String date = String.format("3/3/2011 1:%02d am", baseMinute); + this.baseDate = dateFormat.parse(date); + this.baseMinute = baseMinute; + } + + public void run() { + CalendarSource.setBaseDate(baseDate); + try { + // Immitate some long running task. + Thread.sleep(100); + } catch (Exception e) { } + String newDate = "4/4/2012"; + Date parsed = _parser.parse(newDate).get(0).getDates().get(0); + validateThread(parsed, baseMinute); + numOfCorrectResults.incrementAndGet(); + } + } + + // We need this method, because validateDate and validateTime are not thread safe. + private synchronized void validateThread(Date date, int baseMinute) { + validateDate(date, 4, 4, 2012); + validateTime(date, 1, baseMinute, 0); + } +} From 7226841b18af4bca598723298b20a9d4e5a03c2e Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Tue, 3 Apr 2012 00:51:24 +0300 Subject: [PATCH 062/134] Fix tests with uninitialised calendar and parser. --- src/test/java/com/joestelmach/natty/RecurrenceTest.java | 9 +++++++++ src/test/java/com/joestelmach/natty/SearchTest.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/test/java/com/joestelmach/natty/RecurrenceTest.java b/src/test/java/com/joestelmach/natty/RecurrenceTest.java index 8fe54045..187ac292 100644 --- a/src/test/java/com/joestelmach/natty/RecurrenceTest.java +++ b/src/test/java/com/joestelmach/natty/RecurrenceTest.java @@ -2,8 +2,11 @@ import java.text.DateFormat; import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; import junit.framework.Assert; +import org.junit.BeforeClass; import org.junit.Test; @@ -12,6 +15,12 @@ * @author Joe Stelmach */ public class RecurrenceTest extends AbstractTest { + @BeforeClass + public static void oneTime() { + Locale.setDefault(Locale.US); + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); + } @Test public void testRelative() throws Exception { diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 3f610b1b..0f3475e9 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -3,10 +3,13 @@ import java.text.DateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import junit.framework.Assert; import org.junit.Test; +import org.junit.BeforeClass; import com.joestelmach.natty.CalendarSource; import com.joestelmach.natty.DateGroup; @@ -17,6 +20,12 @@ * @author Joe Stelmach */ public class SearchTest extends AbstractTest { + @BeforeClass + public static void oneTime() { + Locale.setDefault(Locale.US); + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + initCalendarAndParser(); + } @Test public void test() throws Exception { From 65b79cdd26f929441162b2cbf971bba75d89fac5 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 8 May 2012 20:12:05 -0400 Subject: [PATCH 063/134] [maven-release-plugin] prepare release natty-0.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f25a683..75890b25 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.6-SNAPSHOT + 0.6 Natty Date Parser natural language date parser http://natty.joestelmach.com From c359b0dd0bd99e8591405b395c12ff306f17f4e2 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 8 May 2012 20:12:09 -0400 Subject: [PATCH 064/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 75890b25..de1789e4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.6 + 0.7-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 5920db8ec3a36bb0024243ac673f61178242bbaf Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 19 May 2012 23:33:21 -0400 Subject: [PATCH 065/134] Prefer 2 digit time instead of 2 digit year without prefix. fixes #35 --- .../antlr3/com/joestelmach/natty/generated/DateParser.g | 2 +- src/test/java/com/joestelmach/natty/DateTest.java | 4 ++-- src/test/java/com/joestelmach/natty/DateTimeTest.java | 9 ++++++++- .../com/joestelmach/natty/grammar/DateGrammarTest.java | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index ded89ae4..f7966419 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -312,7 +312,7 @@ relaxed_day_of_year ; relaxed_year - : SINGLE_QUOTE? int_00_to_99_mandatory_prefix + : SINGLE_QUOTE int_00_to_99_mandatory_prefix -> ^(YEAR_OF int_00_to_99_mandatory_prefix) | int_four_digits diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index ff009138..001fac4b 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -45,7 +45,7 @@ public void testRelaxed() { validateDate("oct. 1, 1980", 10, 1, 1980); validateDate("oct 1,1980", 10, 1, 1980); validateDate("1st oct in the year '89", 10, 1, 1989); - validateDate("thirty first of december 80", 12, 31, 1980); + validateDate("thirty first of december '80", 12, 31, 1980); validateDate("the first of december in the year 1980", 12, 1, 1980); validateDate("the 2 of february in the year 1980", 2, 2, 1980); validateDate("the 2nd of february in the year 1980", 2, 2, 1980); @@ -248,7 +248,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "bla bla bla 2 and 4 month"; + String value = "april 20th, 10am"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 7be819f0..937f0728 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -24,7 +24,11 @@ public static void oneTime() { } @Test - public void testSpecific() { + public void testSpecific() throws Exception { + Date reference = DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.SHORT).parse("5/19/2012 12:00 am"); + CalendarSource.setBaseDate(reference); + validateDateTime("1st oct in the year '89 1300 hours", 10, 1, 1989, 13, 0, 0); validateDateTime("1st oct in the year '89 at 1300 hours", 10, 1, 1989, 13, 0, 0); validateDateTime("1st oct in the year '89, 13:00", 10, 1, 1989, 13, 0, 0); @@ -36,6 +40,9 @@ public void testSpecific() { validateDateTime("3am, on october first 2010", 10, 1, 2010, 3, 0, 0); validateDateTime("3am october first 2010", 10, 1, 2010, 3, 0, 0); validateDateTime("2011-06-17T07:00:00Z", 6, 17, 2011, 3, 0, 0); + validateDateTime("April 20, 10am", 4, 20, 2012, 10, 0, 0); + validateDateTime("April 20 10", 4, 20, 2012, 10, 0, 0); + validateDateTime("April 20 at 10 am", 4, 20, 2012, 10, 0, 0); } @Test diff --git a/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java index ac4f2697..889de24c 100644 --- a/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java @@ -70,7 +70,7 @@ public void relaxed_date() throws Exception { assertAST("oct. 1, 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); assertAST("oct 1,1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); assertAST("1st oct in the year '89", "(EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 1) (YEAR_OF 89))"); - assertAST("thirty first of december 80", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 80))"); + assertAST("thirty first of december '80", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 80))"); assertAST("the first of december in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 1) (YEAR_OF 1980))"); assertAST("the 2 of february in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); assertAST("the 2nd of february in the year 1980", "(EXPLICIT_DATE (MONTH_OF_YEAR 2) (DAY_OF_MONTH 2) (YEAR_OF 1980))"); From 9e97edc36d6ea83b1e74746318198f1d4d5489b6 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 20 May 2012 00:04:09 -0400 Subject: [PATCH 066/134] fixes #36 --- .../joestelmach/natty/generated/DateParser.g | 12 +++---- .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/DateTimeTest.java | 31 +++++++++++++++++++ .../natty/grammar/DateTimeGrammarTest.java | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index f7966419..681563ca 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -121,9 +121,10 @@ date_time_alternative ) // 1/2 or 1/4 or 1/6 at 6pm - | (date (conjunction date)+ (date_time_separator explicit_time)?)=> - date (conjunction date)+ (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?)+) + // Aug 16 at 10am or Sept 28th at 11am + | (date_time conjunction date_time)=> + date_time (conjunction date_time)+ + -> ^(DATE_TIME_ALTERNATIVE date_time+) // first or last day of 2009 | (explicit_day_of_year_part conjunction explicit_day_of_year_part WHITE_SPACE relaxed_year)=> @@ -158,11 +159,6 @@ date_time_alternative ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) ) - // catch all date_time to date_time range - | (date_time conjunction date_time)=> - date_time conjunction date_time - -> ^(DATE_TIME_ALTERNATIVE date_time date_time) - // single date_time | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) ; diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 001fac4b..b9c6adf6 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -248,7 +248,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "april 20th, 10am"; + String value = "June 25th and July 2nd at 10am and August 16th"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 937f0728..869311e9 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -116,4 +116,35 @@ public void testRange() throws Exception { validateDateTime(dates.get(0), 11, 20, 2010, 14, 0, 0); validateDateTime(dates.get(1), 11, 20, 2010, 15, 0, 0); } + + @Test + public void testList() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("05/19/2012"); + CalendarSource.setBaseDate(reference); + + List dates = parseCollection("June 25th at 9am and July 2nd at 10am and August 16th at 11am"); + Assert.assertEquals(3, dates.size()); + validateDateTime(dates.get(0), 6, 25, 2012, 9, 0, 0); + validateDateTime(dates.get(1), 7, 2, 2012, 10, 0, 0); + validateDateTime(dates.get(2), 8, 16, 2012, 11, 0, 0); + + dates = parseCollection("June 25th at 10am and July 2nd and August 16th"); + Assert.assertEquals(3, dates.size()); + validateDateTime(dates.get(0), 6, 25, 2012, 10, 0, 0); + validateDateTime(dates.get(1), 7, 2, 2012, 10, 0, 0); + validateDateTime(dates.get(2), 8, 16, 2012, 10, 0, 0); + + dates = parseCollection("June 25th and July 2nd at 10am and August 16th"); + Assert.assertEquals(3, dates.size()); + validateDateTime(dates.get(0), 6, 25, 2012, 0, 0, 0); + validateDateTime(dates.get(1), 7, 2, 2012, 10, 0, 0); + validateDateTime(dates.get(2), 8, 16, 2012, 10, 0, 0); + + dates = parseCollection("June 25th and July 2nd and August 16th at 10am"); + Assert.assertEquals(3, dates.size()); + validateDateTime(dates.get(0), 6, 25, 2012, 0, 0, 0); + validateDateTime(dates.get(1), 7, 2, 2012, 0, 0, 0); + validateDateTime(dates.get(2), 8, 16, 2012, 10, 0, 0); + + } } diff --git a/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java index e358c20c..49d09f97 100644 --- a/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java @@ -15,7 +15,7 @@ public void date_time_alternative() throws Exception { assertAST("next wed or thursday", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5)))))"); assertAST("next wed, thurs, fri", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 6)))))"); assertAST("next wed, thurs, or fri at 6pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 4))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 5))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 6))) (EXPLICIT_TIME (HOURS_OF_DAY 6) (MINUTES_OF_HOUR 0) pm)))"); - assertAST("10/10 or 12/30 or 10/15 at 5pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 30)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 15)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)))"); + assertAST("10/10 or 12/30 or 10/15 at 5pm", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 10))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 30))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 10) (DAY_OF_MONTH 15)) (EXPLICIT_TIME (HOURS_OF_DAY 5) (MINUTES_OF_HOUR 0) pm)))"); assertAST("monday to friday", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 2)))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 (DAY_OF_WEEK 6)))))"); assertAST("1999-12-31 to tomorrow", "(DATE_TIME_ALTERNATIVE (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 12) (DAY_OF_MONTH 31) (YEAR_OF 1999))) (DATE_TIME (RELATIVE_DATE (SEEK > by_day 1 day))))"); assertAST("now to 2010-01-01", "(DATE_TIME_ALTERNATIVE (DATE_TIME (RELATIVE_DATE (SEEK > by_day 0 day))) (DATE_TIME (EXPLICIT_DATE (MONTH_OF_YEAR 01) (DAY_OF_MONTH 01) (YEAR_OF 2010))))"); From dda2fbdfdddc6288161b7540a42c0d540135d879 Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Fri, 1 Jun 2012 18:22:28 +0300 Subject: [PATCH 067/134] Always use timezone passed to parser in WalkerState. See https://github.com/joestelmach/natty/issues/38 --- .../com/joestelmach/natty/WalkerState.java | 4 ++++ .../java/com/joestelmach/natty/DateTest.java | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index f74bce9a..246eb8f8 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -67,6 +67,7 @@ public WalkerState() { public void setDefaultTimeZone(final TimeZone zone) { _defaultTimeZone = zone; + resetCalender(); } /** @@ -492,6 +493,9 @@ public void clearDateGroup() { */ private void resetCalender() { _calendar = getCalendar(); + if (_defaultTimeZone != null) { + _calendar.setTimeZone(_defaultTimeZone); + } _currentYear = _calendar.get(Calendar.YEAR); } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index b9c6adf6..67d2fe80 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -3,6 +3,7 @@ import java.text.DateFormat; import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import java.util.Map.Entry; @@ -240,6 +241,28 @@ public void testRange() throws Exception { validateDate(dates.get(0), 3, 2, 2011); validateDate(dates.get(1), 5, 2, 2011); } + + // https://github.com/joestelmach/natty/issues/38 + @Test + public void testRelativeDateDifferentTimezone() { + // Prepare + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + Parser parser = new Parser(TimeZone.getTimeZone("US/Pacific")); + // 2012, June 3, Sunday, 1 a.m. in US/Eastern GMT -4 + // Same time as + // 2012, June 2, Saturday, 10 p.m. in US/Mountaint GMT -7 + Calendar earlySunday = new GregorianCalendar(2012, 5, 3, 1, 0); + CalendarSource.setBaseDate(earlySunday.getTime()); + + // Run + Date result = parser.parse("Sunday at 10am").get(0).getDates().get(0); + + // Validate + // Result should be June 3, 2012 + validateDate(result, 6, 3, 2012); + + TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); + } public static void main(String[] args) { ConsoleHandler handler = new ConsoleHandler(); From df8365379d13832a97c8cb0a1771a83f58e031bb Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Fri, 1 Jun 2012 18:23:06 +0300 Subject: [PATCH 068/134] Fix misspelling and javadocs. --- .../com/joestelmach/natty/WalkerState.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 246eb8f8..7d67e001 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -61,13 +61,13 @@ public class WalkerState { * the next hour from the current time */ public WalkerState() { - resetCalender(); + resetCalendar(); _dateGroup = new DateGroup(); } public void setDefaultTimeZone(final TimeZone zone) { _defaultTimeZone = zone; - resetCalender(); + resetCalendar(); } /** @@ -83,7 +83,7 @@ public void setDefaultTimeZone(final TimeZone zone) { * next (or previous,) week (or multiple of next or previous week depending * on the seek amount.) * - * @param amount the amount to seek. Must be guaranteed to parse as an integer + * @param seekAmount the amount to seek. Must be guaranteed to parse as an integer * * @param dayOfWeek the day of the week to seek to, represented as an integer from * 1 to 7 (1 being Sunday, 7 being Saturday.) Must be guaranteed to parse as an Integer @@ -172,7 +172,7 @@ public void seekToYear(String year) { * '<' go backward * '>' go forward * - * @param amount the amount to seek. Must be guaranteed to parse as an integer + * @param seekAmount the amount to seek. Must be guaranteed to parse as an integer * * @param month the month to seek to. Must be guaranteed to parse as an integer * between 1 and 12 @@ -210,7 +210,7 @@ public void seekToMonth(String direction, String seekAmount, String month) { * '<' go backward * '>' go forward * - * @param amount the amount to seek. Must be guaranteed to parse as an integer + * @param seekAmount the amount to seek. Must be guaranteed to parse as an integer * * @param span */ @@ -243,7 +243,6 @@ public void seekBySpan(String direction, String seekAmount, String span) { * * @param index * @param dayOfWeek - * @param month */ public void setDayOfWeekIndex(String index, String dayOfWeek) { int indexInt = Integer.parseInt(index); @@ -332,7 +331,7 @@ else if(dayOfWeek != null) { * * @param amPm the meridian indicator to use. Must be either 'am' or 'pm' * - * @param zone the time zone to use in one of two formats: + * @param zoneString the time zone to use in one of two formats: * - zoneinfo format (America/New_York, America/Los_Angeles, etc) * - GMT offset (+05:00, -0500, +5, etc) */ @@ -491,7 +490,7 @@ public void clearDateGroup() { /** * Resets the calendar */ - private void resetCalender() { + private void resetCalendar() { _calendar = getCalendar(); if (_defaultTimeZone != null) { _calendar.setTimeZone(_defaultTimeZone); @@ -511,13 +510,13 @@ private void markDateInvocation() { int hours = _calendar.get(Calendar.HOUR_OF_DAY); int minutes = _calendar.get(Calendar.MINUTE); int seconds = _calendar.get(Calendar.SECOND); - resetCalender(); + resetCalendar(); _calendar.set(Calendar.HOUR_OF_DAY, hours); _calendar.set(Calendar.MINUTE, minutes); _calendar.set(Calendar.SECOND, seconds); } else { - resetCalender(); + resetCalendar(); } _firstDateInvocationInGroup = false; } From e4b2fd611d3001612e7ab5343a5f59c059528c28 Mon Sep 17 00:00:00 2001 From: Nikita Beloglazov Date: Fri, 1 Jun 2012 18:34:49 +0300 Subject: [PATCH 069/134] Fix misspelling. --- src/test/java/com/joestelmach/natty/DateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 67d2fe80..57fd9ef2 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -250,7 +250,7 @@ public void testRelativeDateDifferentTimezone() { Parser parser = new Parser(TimeZone.getTimeZone("US/Pacific")); // 2012, June 3, Sunday, 1 a.m. in US/Eastern GMT -4 // Same time as - // 2012, June 2, Saturday, 10 p.m. in US/Mountaint GMT -7 + // 2012, June 2, Saturday, 10 p.m. in US/Pacific GMT -7 Calendar earlySunday = new GregorianCalendar(2012, 5, 3, 1, 0); CalendarSource.setBaseDate(earlySunday.getTime()); From f52c1a418d19d4f813cc7a95c55aa0d31c82a7e2 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 14 Oct 2012 23:13:24 -0400 Subject: [PATCH 070/134] [maven-release-plugin] prepare release natty-0.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index de1789e4..193cabb1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.7-SNAPSHOT + 0.7 Natty Date Parser natural language date parser http://natty.joestelmach.com From 216504672b656286b22a1573a4de60e508f3c0ae Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 14 Oct 2012 23:13:28 -0400 Subject: [PATCH 071/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 193cabb1..91d07626 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.7 + 0.8-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From ed132c6619ef13860a08928ffdc10b9b0f6c9ddb Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 20 Oct 2012 11:59:57 -0400 Subject: [PATCH 072/134] Adding support for formal dates with no day of month 'oct 2006'. fixes #24 --- src/main/antlr3/com/joestelmach/natty/generated/DateParser.g | 3 +++ src/test/java/com/joestelmach/natty/DateTest.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 681563ca..2e65f90e 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -329,6 +329,9 @@ formal_date // year last: 1/02/1980, 2/28/79. 2 or 4 digit year is acceptable | relaxed_day_of_week? formal_month_of_year formal_date_separator formal_day_of_month (formal_date_separator formal_year)? -> ^(EXPLICIT_DATE formal_month_of_year formal_day_of_month relaxed_day_of_week? formal_year?) + + | relaxed_month WHITE_SPACE relaxed_year + -> ^(EXPLICIT_DATE relaxed_month ^(DAY_OF_MONTH INT["1"]) relaxed_year?) ; formal_month_of_year diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 57fd9ef2..910d7edf 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -38,6 +38,9 @@ public void testFormal() { validateDate("12/12/12", 12, 12, 2012); validateDate("3/4", 3, 4, Calendar.getInstance().get(Calendar.YEAR)); validateDate("sun, 11/21/2010", 11, 21, 2010); + validateDate("in october 2006", 10, 1, 2006); + validateDate("feb 1979", 2, 1, 1979); + validateDate("jan '80", 1, 1, 1980); } @Test From 57ec8686dfa21d60bbeb750a7abf98010d47d274 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 20 Oct 2012 12:14:53 -0400 Subject: [PATCH 073/134] adding inferred time information to date groups. fixes #31 --- .../java/com/joestelmach/natty/DateGroup.java | 16 +++++++++++++++- .../java/com/joestelmach/natty/WalkerState.java | 1 + .../java/com/joestelmach/natty/DateTest.java | 3 ++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index fd8e65d9..ba43e926 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -1,5 +1,6 @@ package com.joestelmach.natty; +import java.io.ObjectInputStream.GetField; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -17,14 +18,16 @@ public class DateGroup { private int _line; private int _position; private boolean _isRecurring; + private boolean _isTimeInferred; private Date _recurringUntil; private Map> _parseLocations; private Tree _syntaxTree; public DateGroup() { _dates = new ArrayList(); + _isTimeInferred = true; } - + public List getDates() { return _dates; } @@ -60,6 +63,17 @@ public void setRecurring(boolean isRecurring) { _isRecurring = isRecurring; } + /** + * @return true if the time information in this date group has been inferred + * as opposed to being explicity defined in the _text input. + */ + public boolean isTimeInferred() { + return _isTimeInferred; + } + public void setIsTimeInferred(boolean isTimeInferred) { + this._isTimeInferred = isTimeInferred; + } + public Date getRecursUntil() { return _recurringUntil; } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 7d67e001..f2cbe15e 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -527,6 +527,7 @@ private void markDateInvocation() { */ private void markTimeInvocation() { _timeGivenInGroup = true; + _dateGroup.setIsTimeInferred(false); } private Map getDatesForHoliday(int startYear, int endYear, Holiday holiday) { diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 910d7edf..43708e08 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -274,7 +274,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "June 25th and July 2nd at 10am and August 16th"; + String value = "Jan 5th at 10pm to Jan 7th"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -284,6 +284,7 @@ public static void main(String[] args) { System.out.println("line: " + group.getLine() + ", column: " + group.getPosition()); System.out.println(group.getText()); System.out.println(group.getDates()); + System.out.println("is time inferred: " + group.isTimeInferred()); System.out.println("is recurring: " + group.isRecurring()); System.out.println("recurs until: " + group.getRecursUntil()); From 76215d1677a138baa7722258314bfdc6ecb3f198 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 20 Oct 2012 20:07:11 -0400 Subject: [PATCH 074/134] adding a new sub-parse strategy to resolve token streams that include a dangling known token at the end of the stream. fixes #40 --- .../java/com/joestelmach/natty/Parser.java | 44 +++++++++++++------ .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/SearchTest.java | 18 ++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 813b5259..008aa18d 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -75,28 +75,44 @@ public List parse(String value) { DateGroup group = singleParse(stream); while((group == null || group.getDates().size() == 0) && tokens.size() > 0) { if(group == null || group.getDates().size() == 0) { + // if we're down to only two tokens in our token stream, we can't continue if(tokens.size() <= 2) { tokens.clear(); } - // otherwise, we look at the token stream for another possible starting point, - // and attempt to re-parse with that sub-sequence of tokens + // otherwise, we have two options: else { - tokens = tokens.subList(2, tokens.size()); - Iterator iter = tokens.iterator(); - while(iter.hasNext()) { - Token token = iter.next(); - if(!DateParser.FOLLOW_empty_in_parse181.member(token.getType())) { - iter.remove(); - } - else { - break; + + // 1. Continuously remove tokens from the end of the stream and re-parse. This will + // recover from the case of an extaneous token at the end of the token stream. + // For example: 'june 20th on' + List endRemovedTokens = new ArrayList(tokens); + while((group == null || group.getDates().isEmpty()) && !endRemovedTokens.isEmpty()) { + endRemovedTokens = endRemovedTokens.subList(0, endRemovedTokens.size() - 1); + cleanupGroup(endRemovedTokens); + TokenStream newStream = new CommonTokenStream(new NattyTokenSource(endRemovedTokens)); + group = singleParse(newStream); + } + + // 2. Continuously look for another possible starting point in the token + // stream and re-parse. + if(group == null || group.getDates().isEmpty()) { + tokens = tokens.subList(2, tokens.size()); + Iterator iter = tokens.iterator(); + while(iter.hasNext()) { + Token token = iter.next(); + if(!DateParser.FOLLOW_empty_in_parse181.member(token.getType())) { + iter.remove(); + } + else { + break; + } } + cleanupGroup(tokens); + TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); + group = singleParse(newStream); } - cleanupGroup(tokens); - TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); - group = singleParse(newStream); } } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 43708e08..c4ebe335 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -274,7 +274,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Jan 5th at 10pm to Jan 7th"; + String value = "Watch School Spirits on June 20 on syfy channel"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 0f3475e9..fb3f2860 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -222,5 +222,23 @@ public void test() throws Exception { Assert.assertEquals(1, dates.size()); validateDateTime(dates.get(0), 2, 20, 2011, 20, 0, 0); + groups = parser.parse("Watch School Spirits on June 20 on syfy channel"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 6, 20, 2011); + + groups = parser.parse("Watch School Spirits on June 20 on"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 6, 20, 2011); + + groups = parser.parse("Watch School Spirits on June 20"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 6, 20, 2011); + } } From 7073eac53a24032dfdf7fe92506111f9b4766499 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 28 Oct 2012 18:57:38 -0400 Subject: [PATCH 075/134] adding basic season support. closes #22 --- .../joestelmach/natty/generated/DateLexer.g | 8 + .../joestelmach/natty/generated/DateParser.g | 29 + .../joestelmach/natty/generated/DateWalker.g | 6 + .../com/joestelmach/natty/IcsSearcher.java | 92 +++ .../java/com/joestelmach/natty/Parser.java | 4 +- .../java/com/joestelmach/natty/Season.java | 33 + .../com/joestelmach/natty/WalkerState.java | 198 +++--- src/main/resources/seasons.ics | 600 ++++++++++++++++++ .../java/com/joestelmach/natty/DateTest.java | 2 +- .../natty/{HolidayTest.java => IcsTest.java} | 43 +- 10 files changed, 899 insertions(+), 116 deletions(-) create mode 100644 src/main/java/com/joestelmach/natty/IcsSearcher.java create mode 100644 src/main/java/com/joestelmach/natty/Season.java create mode 100644 src/main/resources/seasons.ics rename src/test/java/com/joestelmach/natty/{HolidayTest.java => IcsTest.java} (73%) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index e41ffcca..78cab73d 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -311,6 +311,14 @@ VALENTINE : 'valentine' SINGLE_QUOTE? 's'?; VETERAN : 'veteran' SINGLE_QUOTE? 's'?; fragment GROUND : 'ground'; fragment HOG : 'hog'; + +// ********** season specific ********** + +WINTER : 'winter' 's'?; +FALL : 'fall' 's'?; +AUTUMN : 'autumn' 's'?; +SPRING : 'spring' 's'?; +SUMMER : 'summer' 's'?; UNKNOWN : UNKNOWN_CHAR diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 2e65f90e..519d80da 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -33,6 +33,7 @@ tokens { ZONE_OFFSET; RECURRENCE; HOLIDAY; + SEASON; } @header { @@ -390,6 +391,10 @@ relative_date // next christmas, 2 thanksgivings ago | holiday -> ^(RELATIVE_DATE holiday) + + // next fall, 2 summers from now + | season + -> ^(RELATIVE_DATE season) ; // ********** explicit relative date rules ********** @@ -738,6 +743,30 @@ holiday_name -> HOLIDAY["VETERANS_DAY"] ; +season + : spelled_or_int_optional_prefix WHITE_SPACE season_name WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix season_name) + + | relative_date_prefix WHITE_SPACE season_name + -> ^(SEEK relative_date_prefix season_name) + + | season_name relaxed_year_prefix relaxed_year + -> ^(EXPLICIT_SEEK season_name relaxed_year) + + | season_name + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] season_name) + ; + +season_name + :WINTER + -> SEASON["WINTER"] + | SPRING + -> SEASON["SPRING"] + | SUMMER + -> SEASON["SUMMER"] + | (FALL | AUTUMN) + -> SEASON["FALL"] + ; // ********** time rules ********** diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 842d3d4e..ca9c904a 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -99,6 +99,9 @@ seek | ^(SEEK DIRECTION SEEK_BY INT HOLIDAY) {_walkerState.seekToHoliday($HOLIDAY.text, $DIRECTION.text, $INT.text);} + + | ^(SEEK DIRECTION SEEK_BY INT SEASON) + {_walkerState.seekToSeason($SEASON.text, $DIRECTION.text, $INT.text);} ; explicit_seek @@ -120,6 +123,9 @@ explicit_seek | ^(EXPLICIT_SEEK HOLIDAY ^(YEAR_OF year=INT)) {_walkerState.seekToHolidayYear($HOLIDAY.text, $year.text);} + | ^(EXPLICIT_SEEK SEASON ^(YEAR_OF year=INT)) + {_walkerState.seekToSeasonYear($SEASON.text, $year.text);} + | ^(EXPLICIT_SEEK index=INT ^(DAY_OF_WEEK day=INT)) {_walkerState.setDayOfWeekIndex($index.text, $day.text);} diff --git a/src/main/java/com/joestelmach/natty/IcsSearcher.java b/src/main/java/com/joestelmach/natty/IcsSearcher.java new file mode 100644 index 00000000..b74d9805 --- /dev/null +++ b/src/main/java/com/joestelmach/natty/IcsSearcher.java @@ -0,0 +1,92 @@ +package com.joestelmach.natty; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Period; +import net.fortuna.ical4j.model.PeriodList; + +public class IcsSearcher { + private static final String GMT = "GMT"; + private static final String VEVENT = "VEVENT"; + private static final String SUMMARY = "SUMMARY"; + private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); + private net.fortuna.ical4j.model.Calendar _holidayCalendar; + private String _calendarFileName; + private TimeZone _timeZone; + + public IcsSearcher(String calendarFileName, TimeZone timeZone) { + _calendarFileName = calendarFileName; + _timeZone = timeZone; + } + + public Map findDates(int startYear, int endYear, String eventSummary) { + Map holidays = new HashMap(); + + if(_holidayCalendar == null) { + InputStream fin = WalkerState.class.getResourceAsStream(_calendarFileName); + try { + _holidayCalendar = new CalendarBuilder().build(fin); + + } catch (IOException e) { + _logger.severe("Couln't open " + _calendarFileName); + return holidays; + + } catch (ParserException e) { + _logger.severe("Couln't parse " + _calendarFileName); + return holidays; + } + } + + Period period = null; + try { + DateTime from = new DateTime(startYear + "0101T000000Z"); + DateTime to = new DateTime(endYear + "1231T000000Z");; + period = new Period(from, to); + + } catch (ParseException e) { + _logger.log(Level.SEVERE, "Invalid start or end year: " + startYear + ", " + endYear, e); + return holidays; + } + + for (Object component : _holidayCalendar.getComponents(VEVENT)) { + Component vevent = (Component) component; + String summary = vevent.getProperty(SUMMARY).getValue(); + if(summary.equals(eventSummary)) { + PeriodList list = vevent.calculateRecurrenceSet(period); + for(Object p : list) { + DateTime date = ((Period) p).getStart(); + + // this date is at the date of the holiday at 12 AM UTC + Calendar utcCal = CalendarSource.getCurrentCalendar(); + utcCal.setTimeZone(TimeZone.getTimeZone(GMT)); + utcCal.setTime(date); + + // use the year, month and day components of our UTC date to form a new local date + Calendar localCal = CalendarSource.getCurrentCalendar(); + localCal.setTimeZone(_timeZone); + localCal.set(Calendar.YEAR, utcCal.get(Calendar.YEAR)); + localCal.set(Calendar.MONTH, utcCal.get(Calendar.MONTH)); + localCal.set(Calendar.DAY_OF_MONTH, utcCal.get(Calendar.DAY_OF_MONTH)); + + holidays.put(localCal.get(Calendar.YEAR), localCal.getTime()); + } + } + } + + return holidays; + } + +} diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 008aa18d..d448218a 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -102,7 +102,7 @@ public List parse(String value) { Iterator iter = tokens.iterator(); while(iter.hasNext()) { Token token = iter.next(); - if(!DateParser.FOLLOW_empty_in_parse181.member(token.getType())) { + if(!DateParser.FOLLOW_empty_in_parse186.member(token.getType())) { iter.remove(); } else { @@ -209,7 +209,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse181.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } diff --git a/src/main/java/com/joestelmach/natty/Season.java b/src/main/java/com/joestelmach/natty/Season.java new file mode 100644 index 00000000..b0f5f8a9 --- /dev/null +++ b/src/main/java/com/joestelmach/natty/Season.java @@ -0,0 +1,33 @@ +package com.joestelmach.natty; + +import java.util.HashMap; +import java.util.Map; + +public enum Season { + WINTER("Winter Solstice"), + SPRING("Vernal Equinox"), + SUMMER("Summer Solstice"), + FALL("Autumnal Equinox"); + + private String summary; + private static final Map lookup; + static { + lookup = new HashMap(); + for(Season h:values()) { + lookup.put(h.getSummary(), h); + } + } + + Season(String summary) { + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public static Season fromSummary(String summary) { + if(summary == null) return null; + return lookup.get(summary); + } +} diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index f2cbe15e..08388afe 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -1,24 +1,12 @@ package com.joestelmach.natty; -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.Map; import java.util.TimeZone; -import java.util.logging.Level; import java.util.logging.Logger; -import net.fortuna.ical4j.data.CalendarBuilder; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.Period; -import net.fortuna.ical4j.model.PeriodList; - /** * @author Joe Stelmach */ @@ -45,6 +33,7 @@ public class WalkerState { private static final String VEVENT = "VEVENT"; private static final String SUMMARY = "SUMMARY"; private static final String HOLIDAY_ICS_FILE = "/holidays.ics"; + private static final String SEASON_ICS_FILE = "/seasons.ics"; private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); private GregorianCalendar _calendar; @@ -52,7 +41,6 @@ public class WalkerState { private int _currentYear; private boolean _firstDateInvocationInGroup = true; private boolean _timeGivenInGroup = false; - private net.fortuna.ical4j.model.Calendar _holidayCalendar; private DateGroup _dateGroup; @@ -391,39 +379,12 @@ public void setExplicitTime(String hours, String minutes, String seconds, String * @param seekAmount The number of years to seek */ public void seekToHoliday(String holidayString, String direction, String seekAmount) { - Holiday holiday = Holiday.valueOf(holidayString); - int seekAmountInt = Integer.parseInt(seekAmount); - assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); - assert(seekAmountInt >= 0); assert(holiday != null); - markDateInvocation(); - - // get the current year - Calendar cal = getCalendar(); - cal.setTimeZone(_defaultTimeZone); - int currentYear = cal.get(Calendar.YEAR); - - // look up a suitable period of holiday occurrences - boolean forwards = direction.equals(DIR_RIGHT); - int startYear = forwards ? currentYear : currentYear - seekAmountInt - 1; - int endYear = forwards ? currentYear + seekAmountInt + 1 : currentYear; - Map dates = getDatesForHoliday(startYear, endYear, holiday); - - // grab the right one - boolean hasPassed = cal.getTime().after(dates.get(currentYear)); - int targetYear = currentYear + - (forwards ? seekAmountInt + (hasPassed ? 0 : -1) : - (seekAmountInt - (hasPassed ? 1 : 0)) * -1); - - cal.setTimeZone(_calendar.getTimeZone()); - cal.setTime(dates.get(targetYear)); - _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); - _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); - _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); + seekToIcsEvent(HOLIDAY_ICS_FILE, holiday.getSummary(), direction, seekAmount); } - + /** * Seeks to the given holiday within the given year * @@ -432,26 +393,38 @@ public void seekToHoliday(String holidayString, String direction, String seekAmo */ public void seekToHolidayYear(String holidayString, String yearString) { Holiday holiday = Holiday.valueOf(holidayString); - int yearInt = Integer.parseInt(yearString); assert(holiday != null); - assert(yearInt >= 0); - markDateInvocation(); + seekToIcsEventYear(HOLIDAY_ICS_FILE, yearString, holiday.getSummary()); + } + + /** + * Seeks forward or backwards to a particular season based on the current date + * + * @param seasonString The season to seek to + * @param direction The direction to seek + * @param seekAmount The number of years to seek + */ + public void seekToSeason(String seasonString, String direction, String seekAmount) { + Season season = Season.valueOf(seasonString); + assert(season!= null); - int year = getFullYear(yearInt); - Map dates = getDatesForHoliday(year, year, holiday); - Date date = dates.get(year - (holiday.equals(Holiday.NEW_YEARS_EVE) ? 1 : 0)); + seekToIcsEvent(SEASON_ICS_FILE, season.getSummary(), direction, seekAmount); + } + + /** + * Seeks to the given season within the given year + * + * @param seasonString + * @param yearString + */ + public void seekToSeasonYear(String seasonString, String yearString) { + Season season = Season.valueOf(seasonString); + assert(season != null); - if(date != null) { - Calendar cal = getCalendar(); - cal.setTimeZone(_calendar.getTimeZone()); - cal.setTime(date); - _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); - _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); - _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); - } + seekToIcsEventYear(SEASON_ICS_FILE, yearString, season.getSummary()); } - + /** * */ @@ -498,6 +471,58 @@ private void resetCalendar() { _currentYear = _calendar.get(Calendar.YEAR); } + private void seekToIcsEvent(String icsFileName, String eventSummary, String direction, String seekAmount) { + int seekAmountInt = Integer.parseInt(seekAmount); + assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT)); + assert(seekAmountInt >= 0); + + markDateInvocation(); + + // get the current year + Calendar cal = getCalendar(); + cal.setTimeZone(_defaultTimeZone); + int currentYear = cal.get(Calendar.YEAR); + + // look up a suitable period of occurrences + boolean forwards = direction.equals(DIR_RIGHT); + int startYear = forwards ? currentYear : currentYear - seekAmountInt - 1; + int endYear = forwards ? currentYear + seekAmountInt + 1 : currentYear; + Map dates = getDatesFromIcs(icsFileName, eventSummary, + startYear, endYear); + + // grab the right one + boolean hasPassed = cal.getTime().after(dates.get(currentYear)); + int targetYear = currentYear + + (forwards ? seekAmountInt + (hasPassed ? 0 : -1) : + (seekAmountInt - (hasPassed ? 1 : 0)) * -1); + + cal.setTimeZone(_calendar.getTimeZone()); + cal.setTime(dates.get(targetYear)); + _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); + _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); + _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); + } + + private void seekToIcsEventYear(String icsFileName, String yearString, String eventSummary) { + int yearInt = Integer.parseInt(yearString); + assert(yearInt >= 0); + + markDateInvocation(); + + int year = getFullYear(yearInt); + Map dates = getDatesFromIcs(icsFileName, eventSummary, year, year); + Date date = dates.get(year - (eventSummary.equals(Holiday.NEW_YEARS_EVE.getSummary()) ? 1 : 0)); + + if(date != null) { + Calendar cal = getCalendar(); + cal.setTimeZone(_calendar.getTimeZone()); + cal.setTime(date); + _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR)); + _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH)); + _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH)); + } + } + /** * ensures that the first invocation of a date seeking * rule is captured @@ -530,61 +555,10 @@ private void markTimeInvocation() { _dateGroup.setIsTimeInferred(false); } - private Map getDatesForHoliday(int startYear, int endYear, Holiday holiday) { - Map holidays = new HashMap(); - - if(_holidayCalendar == null) { - InputStream fin = WalkerState.class.getResourceAsStream(HOLIDAY_ICS_FILE); - try { - _holidayCalendar = new CalendarBuilder().build(fin); - - } catch (IOException e) { - _logger.severe("Couln't open " + HOLIDAY_ICS_FILE); - return holidays; - - } catch (ParserException e) { - _logger.severe("Couln't parse " + HOLIDAY_ICS_FILE); - return holidays; - } - } - - Period period = null; - try { - DateTime from = new DateTime(startYear + "0101T000000Z"); - DateTime to = new DateTime(endYear + "1231T000000Z");; - period = new Period(from, to); - - } catch (ParseException e) { - _logger.log(Level.SEVERE, "Invalid start or end year: " + startYear + ", " + endYear, e); - return holidays; - } - - for (Object component : _holidayCalendar.getComponents(VEVENT)) { - Component vevent = (Component) component; - String summary = vevent.getProperty(SUMMARY).getValue(); - if(summary.equals(holiday.getSummary())) { - PeriodList list = vevent.calculateRecurrenceSet(period); - for(Object p : list) { - DateTime date = ((Period) p).getStart(); - - // this date is at the date of the holiday at 12 AM UTC - Calendar utcCal = getCalendar(); - utcCal.setTimeZone(TimeZone.getTimeZone(GMT)); - utcCal.setTime(date); - - // use the year, month and day components of our UTC date to form a new local date - Calendar localCal = getCalendar(); - localCal.setTimeZone(_defaultTimeZone); - localCal.set(Calendar.YEAR, utcCal.get(Calendar.YEAR)); - localCal.set(Calendar.MONTH, utcCal.get(Calendar.MONTH)); - localCal.set(Calendar.DAY_OF_MONTH, utcCal.get(Calendar.DAY_OF_MONTH)); - - holidays.put(localCal.get(Calendar.YEAR), localCal.getTime()); - } - } - } - - return holidays; + private Map getDatesFromIcs(String icsFileName, + String eventSummary, int startYear, int endYear) { + IcsSearcher searcher = new IcsSearcher(icsFileName, _defaultTimeZone); + return searcher.findDates(startYear, endYear, eventSummary); } private int getFullYear(Integer year) { diff --git a/src/main/resources/seasons.ics b/src/main/resources/seasons.ics new file mode 100644 index 00000000..d4671905 --- /dev/null +++ b/src/main/resources/seasons.ics @@ -0,0 +1,600 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:-//Apple Inc.//iCal 3.0//EN +CALSCALE:GREGORIAN +X-WR-CALNAME:EarthSeasons +X-WR-CALDESC:This calendar contains all of the Vernal Equinox, Summer Solstice, Autumnal Equinox, and Winter Solstice times for the Northern Hemisphere through the year 2020. +X-WR-RELCALID:B35600F3-5ED9-4984-8182-D4BD6C1DF452 +VERSION:2.0 +X-WR-TIMEZONE:US/Central +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:E0E8F3C4-6877-11D7-AB01-000393AF7662 +DTSTART:20111222T053000Z +DTSTAMP:20070410T001744Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20111222T053000Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:D5FCDAAB-6875-11D7-AB01-000393AF7662 +DTSTART:20070923T095100Z +DTSTAMP:20070409T234742Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20070923T095100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:8710C20F-6875-11D7-AB01-000393AF7662 +DTSTART:20061222T002200Z +DTSTAMP:20070409T234409Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20061222T002200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:BA88E98A-6878-11D7-AB01-000393AF7662 +DTSTART:20130922T204400Z +DTSTAMP:20070410T002413Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20130922T204400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:47FF98BF-687D-11D7-AB01-000393AF7662 +DTSTART:20200620T214300Z +DTSTAMP:20070410T012436Z +SUMMARY:Summer Solstice +CREATED:20091115T181128Z +DTEND:20200620T214300Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:5 +TRANSP:OPAQUE +UID:64625F50-6879-11D7-AB01-000393AF7662 +DTSTART:20141221T230300Z +DTSTAMP:20070410T002809Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20141221T230300Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:FA0F80CE-687B-11D7-AB01-000393AF7662 +DTSTART:20190621T155400Z +DTSTAMP:20070410T012152Z +SUMMARY:Summer Solstice +CREATED:20091115T181130Z +DTEND:20190621T155400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:9D605B9C-6876-11D7-AB01-000393AF7662 +DTSTART:20090621T054500Z +DTSTAMP:20070410T000512Z +SUMMARY:Summer Solstice +CREATED:20091115T181128Z +DTEND:20090621T054500Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:589557A2-687B-11D7-AB01-000393AF7662 +DTSTART:20180320T161500Z +DTSTAMP:20070410T003950Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20180320T161500Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:425B72D2-6876-11D7-AB01-000393AF7662 +DTSTART:20080922T154400Z +DTSTAMP:20070409T235326Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20080922T154400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:A3E8121C-6879-11D7-AB01-000393AF7662 +DTSTART:20150621T163800Z +DTSTAMP:20070410T003013Z +SUMMARY:Summer Solstice +CREATED:20091115T181129Z +DTEND:20150621T163800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:85CBA533-687D-11D7-AB01-000393AF7662 +DTSTART:20201221T100200Z +DTSTAMP:20070410T012543Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20201221T100200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:1D64B1E2-687B-11D7-AB01-000393AF7662 +DTSTART:20170922T200200Z +DTSTAMP:20070410T003748Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181127Z +DTEND:20170922T200200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:133688F3-6878-11D7-AB01-000393AF7662 +DTSTART:20120620T230900Z +DTSTAMP:20070410T001906Z +SUMMARY:Summer Solstice +CREATED:20091115T181127Z +DTEND:20120620T230900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:36E26948-6877-11D7-AB01-000393AF7662 +DTSTART:20100923T030900Z +DTSTAMP:20070410T000928Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181127Z +DTEND:20100923T030900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:9954D2D2-687A-11D7-AB01-000393AF7662 +DTSTART:20160922T142100Z +DTSTAMP:20070410T003352Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20160922T142100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:FC378A9C-6876-11D7-AB01-000393AF7662 +DTSTART:20100320T173200Z +DTSTAMP:20070410T000719Z +SUMMARY:Vernal Equinox +CREATED:20091115T181131Z +DTEND:20100320T173200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:C5377646-687A-11D7-AB01-000393AF7662 +DTSTART:20161221T104400Z +DTSTAMP:20070410T003431Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20161221T104400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:6DDE0568-6878-11D7-AB01-000393AF7662 +DTSTART:20121221T111100Z +DTSTAMP:20070410T002007Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20121221T111100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:E17A7B6A-687A-11D7-AB01-000393AF7662 +DTSTART:20170320T102800Z +DTSTAMP:20070410T003505Z +SUMMARY:Vernal Equinox +CREATED:20091115T181129Z +DTEND:20170320T102800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:FC332B42-6877-11D7-AB01-000393AF7662 +DTSTART:20120320T051400Z +DTSTAMP:20070410T001831Z +SUMMARY:Vernal Equinox +CREATED:20091115T181131Z +DTEND:20120320T051400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:4B2C4C29-6878-11D7-AB01-000393AF7662 +DTSTART:20120922T144900Z +DTSTAMP:20070410T001937Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20120922T144900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:06A51795-687D-11D7-AB01-000393AF7662 +DTSTART:20191222T041900Z +DTSTAMP:20070410T012319Z +SUMMARY:Winter Solstice +CREATED:20091115T181127Z +DTEND:20191222T041900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:BD792A22-687B-11D7-AB01-000393AF7662 +DTSTART:20181221T222200Z +DTSTAMP:20070410T012026Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20181221T222200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:30C535A1-6879-11D7-AB01-000393AF7662 +DTSTART:20140923T022900Z +DTSTAMP:20070410T002747Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181127Z +DTEND:20140923T022900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:6CE97CB7-6875-11D7-AB01-000393AF7662 +DTSTART:20060923T040300Z +DTSTAMP:20070409T234337Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20060923T040300Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:5 +TRANSP:OPAQUE +UID:78C7D1E4-6877-11D7-AB01-000393AF7662 +DTSTART:20110320T232100Z +DTSTAMP:20070410T001344Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20110320T232100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:26176C76-687D-11D7-AB01-000393AF7662 +DTSTART:20200320T034900Z +DTSTAMP:20070410T012402Z +SUMMARY:Vernal Equinox +CREATED:20091115T181127Z +DTEND:20200320T034900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:E3D02AE3-6879-11D7-AB01-000393AF7662 +DTSTART:20151222T044800Z +DTSTAMP:20070410T003123Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20151222T044800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:107525BC-6876-11D7-AB01-000393AF7662 +DTSTART:20080320T054800Z +DTSTAMP:20070409T235141Z +SUMMARY:Vernal Equinox +CREATED:20091115T181127Z +DTEND:20080320T054800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:A3AA0842-6875-11D7-AB01-000393AF7662 +DTSTART:20070321T000700Z +DTSTAMP:20070409T234521Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20070321T000700Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:8 +TRANSP:OPAQUE +UID:F0810C5B-6875-11D7-AB01-000393AF7662 +DTSTART:20071222T060800Z +DTSTAMP:20070409T235017Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20071222T060800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:87FB4C04-6878-11D7-AB01-000393AF7662 +DTSTART:20130320T110200Z +DTSTAMP:20070410T002203Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20130320T110200Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:19F07040-6877-11D7-AB01-000393AF7662 +DTSTART:20100621T112800Z +DTSTAMP:20070410T000828Z +SUMMARY:Summer Solstice +CREATED:20091115T181127Z +DTEND:20100621T112800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:5 +TRANSP:OPAQUE +UID:7FA978FD-687A-11D7-AB01-000393AF7662 +DTSTART:20160620T223400Z +DTSTAMP:20070410T003330Z +SUMMARY:Summer Solstice +CREATED:20091115T181128Z +DTEND:20160620T223400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:BC01DF70-6875-11D7-AB01-000393AF7662 +DTSTART:20070621T180600Z +DTSTAMP:20070409T234609Z +SUMMARY:Summer Solstice +CREATED:20091115T181129Z +DTEND:20070621T180600Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:C8B0CD4C-6877-11D7-AB01-000393AF7662 +DTSTART:20110923T090400Z +DTSTAMP:20070410T001522Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20110923T090400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:10 +TRANSP:OPAQUE +UID:308BD832-6875-11D7-AB01-000393AF7662 +DTSTART:20060621T122600Z +DTSTAMP:20070409T233509Z +SUMMARY:Summer Solstice +CREATED:20091115T181127Z +DTEND:20060621T122600Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:A6298608-6878-11D7-AB01-000393AF7662 +DTSTART:20130621T050400Z +DTSTAMP:20070410T002339Z +SUMMARY:Summer Solstice +CREATED:20091115T181129Z +DTEND:20130621T050400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:677A3ED8-687D-11D7-AB01-000393AF7662 +DTSTART:20200922T133000Z +DTSTAMP:20070410T012509Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20200922T133000Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:E148E3E4-687B-11D7-AB01-000393AF7662 +DTSTART:20190320T215800Z +DTSTAMP:20070410T012131Z +SUMMARY:Vernal Equinox +CREATED:20091115T181129Z +DTEND:20190320T215800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:F925736E-687A-11D7-AB01-000393AF7662 +DTSTART:20170621T042400Z +DTSTAMP:20070410T003541Z +SUMMARY:Summer Solstice +CREATED:20091115T181130Z +DTEND:20170621T042400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:C1D76BDB-6876-11D7-AB01-000393AF7662 +DTSTART:20090922T211800Z +DTSTAMP:20070410T000601Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20090922T211800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:F9E3A9BC-6878-11D7-AB01-000393AF7662 +DTSTART:20140320T165700Z +DTSTAMP:20070410T002627Z +SUMMARY:Vernal Equinox +CREATED:20091115T181130Z +DTEND:20140320T165700Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:1719D691-6879-11D7-AB01-000393AF7662 +DTSTART:20140621T105100Z +DTSTAMP:20070410T002651Z +SUMMARY:Summer Solstice +CREATED:20091115T181127Z +DTEND:20140621T105100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:D72CDF90-6876-11D7-AB01-000393AF7662 +DTSTART:20091221T174700Z +DTSTAMP:20070410T000630Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20091221T174700Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:3BED6989-687B-11D7-AB01-000393AF7662 +DTSTART:20171221T162800Z +DTSTAMP:20070410T003822Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20171221T162800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:16 +TRANSP:OPAQUE +UID:990A4B78-687C-11D7-AB01-000393AF7662 +DTSTART:20190923T075000Z +DTSTAMP:20070410T012227Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181128Z +DTEND:20190923T075000Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:A1932616-6877-11D7-AB01-000393AF7662 +DTSTART:20110621T171600Z +DTSTAMP:20070410T001444Z +SUMMARY:Summer Solstice +CREATED:20091115T181128Z +DTEND:20110621T171600Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:D858FB09-6878-11D7-AB01-000393AF7662 +DTSTART:20131221T171100Z +DTSTAMP:20070410T002508Z +SUMMARY:Winter Solstice +CREATED:20091115T181129Z +DTEND:20131221T171100Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:8 +TRANSP:OPAQUE +UID:C138E162-6879-11D7-AB01-000393AF7662 +DTSTART:20150923T082000Z +DTSTAMP:20070410T003042Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20150923T082000Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:5 +TRANSP:OPAQUE +UID:4DA9C3D2-6877-11D7-AB01-000393AF7662 +DTSTART:20101221T233800Z +DTSTAMP:20070410T001021Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20101221T233800Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:88E0847A-687B-11D7-AB01-000393AF7662 +DTSTART:20180621T100700Z +DTSTAMP:20070410T004035Z +SUMMARY:Summer Solstice +CREATED:20091115T181128Z +DTEND:20180621T100700Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:6270E19C-687A-11D7-AB01-000393AF7662 +DTSTART:20160320T043000Z +DTSTAMP:20070410T003259Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20160320T043000Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:65915B26-6876-11D7-AB01-000393AF7662 +DTSTART:20081221T120400Z +DTSTAMP:20070409T235630Z +SUMMARY:Winter Solstice +CREATED:20091115T181128Z +DTEND:20081221T120400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:87836E42-6876-11D7-AB01-000393AF7662 +DTSTART:20090320T114400Z +DTSTAMP:20070410T000434Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20090320T114400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:7 +TRANSP:OPAQUE +UID:A5647333-687B-11D7-AB01-000393AF7662 +DTSTART:20180923T015400Z +DTSTAMP:20070410T004117Z +SUMMARY:Autumnal Equinox +CREATED:20091115T181129Z +DTEND:20180923T015400Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:2B838FE8-6876-11D7-AB01-000393AF7662 +DTSTART:20080620T235900Z +DTSTAMP:20070409T235226Z +SUMMARY:Summer Solstice +CREATED:20091115T181127Z +DTEND:20080620T235900Z +END:VEVENT +BEGIN:VEVENT +SEQUENCE:6 +TRANSP:OPAQUE +UID:8758D02E-6879-11D7-AB01-000393AF7662 +DTSTART:20150320T224500Z +DTSTAMP:20070410T002935Z +SUMMARY:Vernal Equinox +CREATED:20091115T181128Z +DTEND:20150320T224500Z +END:VEVENT +END:VCALENDAR diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index c4ebe335..251ba664 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -274,7 +274,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Watch School Spirits on June 20 on syfy channel"; + String value = "next fall"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/HolidayTest.java b/src/test/java/com/joestelmach/natty/IcsTest.java similarity index 73% rename from src/test/java/com/joestelmach/natty/HolidayTest.java rename to src/test/java/com/joestelmach/natty/IcsTest.java index f792016f..f344a8e0 100644 --- a/src/test/java/com/joestelmach/natty/HolidayTest.java +++ b/src/test/java/com/joestelmach/natty/IcsTest.java @@ -9,7 +9,7 @@ import org.junit.Test; -public class HolidayTest extends AbstractTest { +public class IcsTest extends AbstractTest { @BeforeClass public static void oneTime() { @@ -18,6 +18,18 @@ public static void oneTime() { initCalendarAndParser(); } + @Test + public void testUpcomingSeason() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("5/05/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("spring", 3, 20, 2012); + validateDate("summer", 6, 21, 2011); + validateDate("fall", 9, 23, 2011); + validateDate("autumn", 9, 23, 2011); + validateDate("winter", 12, 22, 2011); + } + @Test public void testUpcomingHoliday() throws Exception { Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); @@ -62,6 +74,25 @@ public void testRelativeHolidays() throws Exception { validateDate("three memorial days ago", 5, 25, 2009); } + @Test + public void testSeasonsByYear() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("spring 2010", 3, 20, 2010); + validateDate("spring 2018", 3, 20, 2018); + + validateDate("summer 2012", 6, 20, 2012); + validateDate("summer 2015", 6, 21, 2015); + + validateDate("fall 2011", 9, 23, 2011); + validateDate("fall 2012", 9, 22, 2012); + validateDate("autumn 2016", 9, 22, 2016); + + validateDate("winter 2016", 12, 21, 2016); + validateDate("winter 2011", 12, 22, 2011); + } + @Test public void testHolidaysByYear() throws Exception { Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); @@ -105,4 +136,14 @@ public void testHolidaysWithModifiers() throws Exception { validateDate("four days before veterans day 2013", 11, 7, 2013); validateDate("two days after two thanksgivings from now", 11, 24, 2012); } + + @Test + public void testSeasonsWithModifiers() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011"); + CalendarSource.setBaseDate(reference); + + validateDate("four days before fall 2013", 9, 18, 2013); + validateDate("two days after two summers from now", 6, 23, 2013); + validateDate("three summers ago", 6, 21, 2009); + } } From f8810dd1264a42fbfe62315dd1a22ed4af5d4f64 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 8 Oct 2013 22:11:21 -0400 Subject: [PATCH 076/134] organizing imports --- src/main/java/com/joestelmach/natty/DateGroup.java | 1 - src/test/java/com/joestelmach/natty/AbstractTest.java | 6 ------ src/test/java/com/joestelmach/natty/DateTest.java | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index ba43e926..b0d03cf1 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -1,6 +1,5 @@ package com.joestelmach.natty; -import java.io.ObjectInputStream.GetField; import java.util.ArrayList; import java.util.Date; import java.util.List; diff --git a/src/test/java/com/joestelmach/natty/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java index 234284a2..f2f499cd 100644 --- a/src/test/java/com/joestelmach/natty/AbstractTest.java +++ b/src/test/java/com/joestelmach/natty/AbstractTest.java @@ -2,17 +2,11 @@ import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; -import java.util.TimeZone; import org.junit.Assert; import org.junit.Before; -import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.Parser; -import org.junit.BeforeClass; - /** * * @author Joe Stelmach diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 251ba664..987949d3 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -274,7 +274,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "next fall"; + String value = "clinton"; Parser parser = new Parser(); List groups = parser.parse(value); From 45bef5625678d430f43bc268c1cdd026d5e1d4b9 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 8 Oct 2013 22:54:59 -0400 Subject: [PATCH 077/134] sub-token stream fence post error --- .../java/com/joestelmach/natty/Parser.java | 2 +- .../com/joestelmach/natty/SearchTest.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index d448218a..c8a87444 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -98,7 +98,7 @@ public List parse(String value) { // 2. Continuously look for another possible starting point in the token // stream and re-parse. if(group == null || group.getDates().isEmpty()) { - tokens = tokens.subList(2, tokens.size()); + tokens = tokens.subList(1, tokens.size()); Iterator iter = tokens.iterator(); while(iter.hasNext()) { Token token = iter.next(); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index fb3f2860..87e2a744 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -240,5 +240,36 @@ public void test() throws Exception { Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 6, 20, 2011); + groups = parser.parse("hillary clinton sep 13, 2013"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 13, 2013); + Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals("sep 13, 2013", groups.get(0).getText()); + + groups = parser.parse("hillary clinton 9/13/2013"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 13, 2013); + Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals("9/13/2013", groups.get(0).getText()); + + groups = parser.parse("hillary clintoo sep 13, 2013"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 13, 2013); + Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals("sep 13, 2013", groups.get(0).getText()); + + groups = parser.parse("clinton sep 13 2013"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 13, 2013); + Assert.assertEquals(8, groups.get(0).getPosition()); + Assert.assertEquals("sep 13 2013", groups.get(0).getText()); } } From ad0875cf38315f2840e4d8a3c61adc2c12f119f8 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 9 Oct 2013 21:07:29 -0400 Subject: [PATCH 078/134] [maven-release-plugin] prepare release natty-0.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 91d07626..ad63c0c4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.8-SNAPSHOT + 0.8 Natty Date Parser natural language date parser http://natty.joestelmach.com From 596b3d3bae26b29483867b28043f8ddc3eb8dd11 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 9 Oct 2013 21:07:32 -0400 Subject: [PATCH 079/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ad63c0c4..8c4da715 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.8 + 0.9-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 6e2ad85589d9ba153571bdffaa270e0bd1b39788 Mon Sep 17 00:00:00 2001 From: Katya Androshina Date: Fri, 6 Dec 2013 00:00:19 +0100 Subject: [PATCH 080/134] Fix for issue 46: Parsing error - Fri (.)* In general, tokens that are substrings should be ignored. Known issues: afri, zfri, afriday, zfriday are still parsed for the following reasons: - 'afri' or 'afriday' are lexed as FRIDAY, with AM dissapearing somewhere - 'zfri' or 'zfriday' are lexed as UTC FRIDAY and are then parsed into a valid date --- .../java/com/joestelmach/natty/Parser.java | 54 ++++++++++++++----- .../com/joestelmach/natty/SearchTest.java | 20 +++++-- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index c8a87444..76c0ff81 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -6,8 +6,10 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.CommonTokenStream; @@ -71,7 +73,7 @@ public List parse(String value) { // and parse each of them List groups = new ArrayList(); for(TokenStream stream:streams) { - List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); + List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); DateGroup group = singleParse(stream); while((group == null || group.getDates().size() == 0) && tokens.size() > 0) { if(group == null || group.getDates().size() == 0) { @@ -88,7 +90,7 @@ public List parse(String value) { // recover from the case of an extaneous token at the end of the token stream. // For example: 'june 20th on' List endRemovedTokens = new ArrayList(tokens); - while((group == null || group.getDates().isEmpty()) && !endRemovedTokens.isEmpty()) { + while((group == null || group.getDates().isEmpty()) && endRemovedTokens.size() > 2) { endRemovedTokens = endRemovedTokens.subList(0, endRemovedTokens.size() - 1); cleanupGroup(endRemovedTokens); TokenStream newStream = new CommonTokenStream(new NattyTokenSource(endRemovedTokens)); @@ -133,14 +135,17 @@ public List parse(String value) { * @return */ private DateGroup singleParse(TokenStream stream) { + DateGroup group = null; + List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); + if(tokens.isEmpty()) return group; + StringBuilder tokenString = new StringBuilder(); - for(Token token:((NattyTokenSource) stream.getTokenSource()).getTokens()) { + for(Token token:tokens) { tokenString.append(DateParser.tokenNames[token.getType()]); tokenString.append(" "); } _logger.fine("sub-token stream: " + tokenString.toString()); - DateGroup group = null; try { // parse ParseListener listener = new ParseListener(); @@ -209,7 +214,7 @@ private List collectTokenStreams(TokenStream stream) { // ignore white space in-between possible rules if(currentToken.getType() != DateLexer.WHITE_SPACE) { // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) { + if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType()) || currentToken.getType() == DateLexer.UNKNOWN) { currentGroup = new ArrayList(); currentGroup.add(currentToken); } @@ -225,6 +230,7 @@ private List collectTokenStreams(TokenStream stream) { // if this is an unknown token, we need to end the current group if(currentToken.getType() == DateLexer.UNKNOWN) { if(currentGroup.size() > 0) { + currentGroup.add(currentToken); cleanupGroup(currentGroup); groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); } @@ -254,7 +260,7 @@ else if(currentToken.getType() != DateLexer.DOT) { */ private void cleanupGroup(List group) { - // remove contiguous white space + // remove contiguous white space Iterator iter = group.iterator(); Token previousToken = null; while(iter.hasNext()) { @@ -269,17 +275,41 @@ private void cleanupGroup(List group) { // remove leading white space if(group.size() > 0) { - Token firstToken = group.get(0); - if(firstToken.getType() == DateParser.WHITE_SPACE) { - group.remove(firstToken); + boolean skip = false; + Iterator it1 = group.iterator(); + while(it1.hasNext()) { + Token tk = it1.next(); + if(tk.getType() == DateParser.WHITE_SPACE) { + it1.remove(); + skip = false; + } else if(tk.getType() == DateParser.UNKNOWN) { + it1.remove(); + skip = true; + } else if(skip) { + it1.remove(); + } else if(!DateParser.FOLLOW_empty_in_parse186.member(tk.getType())) { + it1.remove(); + } else break; } } // and trailing white space if(group.size() > 0) { - Token lastToken = group.get(group.size() - 1); - if(lastToken.getType() == DateParser.WHITE_SPACE) { - group.remove(lastToken); + boolean skip = false; + while(group.size() > 0) { + Token lastToken = group.get(group.size() - 1); + if(lastToken.getType() == DateParser.WHITE_SPACE) { + group.remove(lastToken); + skip = false; + } + else if(lastToken.getType() == DateParser.UNKNOWN) { + group.remove(lastToken); + skip = true; + } + else if(skip) { + group.remove(lastToken); + } + else break; } } } diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 87e2a744..f5787d89 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -199,12 +199,9 @@ public void test() throws Exception { validateDate(dates.get(0), 2, 20, 2011); groups = parser.parse("save $1000 by September"); - Assert.assertEquals(2, groups.size()); + Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDateTime(dates.get(0), 2, 20, 2011, 10, 0, 0); - dates = groups.get(1).getDates(); - Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 1, 2011); groups = parser.parse("have my son play at muse music in provo UT at the 3 band cause they always have fog on the third band at 7:30"); @@ -271,5 +268,20 @@ public void test() throws Exception { validateDate(dates.get(0), 9, 13, 2013); Assert.assertEquals(8, groups.get(0).getPosition()); Assert.assertEquals("sep 13 2013", groups.get(0).getText()); + + groups = parser.parse("wedding dinner with Pam"); + Assert.assertEquals(0, groups.size()); + + groups = parser.parse("yummy fried chicken"); + Assert.assertEquals(0, groups.size()); + + groups = parser.parse("I am friend with Pam"); + Assert.assertEquals(0, groups.size()); + + groups = parser.parse("bfriday blah blah"); + Assert.assertEquals(0, groups.size()); + + groups = parser.parse("dinner bmong friends"); + Assert.assertEquals(0, groups.size()); } } From 7a710ccf18a0e7efe707a6baa495bc5e9a2aaeb1 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 21 Dec 2013 09:14:42 -0500 Subject: [PATCH 081/134] intellij --- .gitignore | 2 - .idea/.name | 1 + .idea/compiler.xml | 33 + .idea/copyright/profiles_settings.xml | 5 + .idea/encodings.xml | 7 + .idea/libraries/Maven__antlr_antlr_2_7_7.xml | 13 + ...oncurrent_backport_util_concurrent_3_1.xml | 13 + ...Maven__commons_codec_commons_codec_1_5.xml | 13 + .../Maven__commons_lang_commons_lang_2_6.xml | 13 + ..._commons_logging_commons_logging_1_1_1.xml | 13 + .idea/libraries/Maven__junit_junit_4_1.xml | 13 + .../libraries/Maven__org_antlr_antlr_3_2.xml | 13 + .../Maven__org_antlr_antlr_runtime_3_2.xml | 13 + .../libraries/Maven__org_antlr_gunit_3_2.xml | 13 + .../Maven__org_antlr_stringtemplate_3_2.xml | 13 + .../Maven__org_mnode_ical4j_ical4j_1_0_2.xml | 13 + .idea/misc.xml | 24 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + .idea/workspace.xml | 648 ++++++++++++++++++ natty.iml | 38 + pom.xml | 13 +- 23 files changed, 925 insertions(+), 10 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/libraries/Maven__antlr_antlr_2_7_7.xml create mode 100644 .idea/libraries/Maven__backport_util_concurrent_backport_util_concurrent_3_1.xml create mode 100644 .idea/libraries/Maven__commons_codec_commons_codec_1_5.xml create mode 100644 .idea/libraries/Maven__commons_lang_commons_lang_2_6.xml create mode 100644 .idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml create mode 100644 .idea/libraries/Maven__junit_junit_4_1.xml create mode 100644 .idea/libraries/Maven__org_antlr_antlr_3_2.xml create mode 100644 .idea/libraries/Maven__org_antlr_antlr_runtime_3_2.xml create mode 100644 .idea/libraries/Maven__org_antlr_gunit_3_2.xml create mode 100644 .idea/libraries/Maven__org_antlr_stringtemplate_3_2.xml create mode 100644 .idea/libraries/Maven__org_mnode_ical4j_ical4j_1_0_2.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 natty.iml diff --git a/.gitignore b/.gitignore index cc1435b7..eba5e86e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ .classpath .settings .project -.idea -*.iml bin target src/main/java/com/joestelmach/natty/generated/* diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..7fbdae22 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +natty \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..959b8a6b --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..3572571a --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..74c0d0a5 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/libraries/Maven__antlr_antlr_2_7_7.xml b/.idea/libraries/Maven__antlr_antlr_2_7_7.xml new file mode 100644 index 00000000..b8d93d8e --- /dev/null +++ b/.idea/libraries/Maven__antlr_antlr_2_7_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__backport_util_concurrent_backport_util_concurrent_3_1.xml b/.idea/libraries/Maven__backport_util_concurrent_backport_util_concurrent_3_1.xml new file mode 100644 index 00000000..c5ed7930 --- /dev/null +++ b/.idea/libraries/Maven__backport_util_concurrent_backport_util_concurrent_3_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__commons_codec_commons_codec_1_5.xml b/.idea/libraries/Maven__commons_codec_commons_codec_1_5.xml new file mode 100644 index 00000000..d0e20adb --- /dev/null +++ b/.idea/libraries/Maven__commons_codec_commons_codec_1_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml b/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml new file mode 100644 index 00000000..2ec83767 --- /dev/null +++ b/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml b/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml new file mode 100644 index 00000000..b770f56a --- /dev/null +++ b/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__junit_junit_4_1.xml b/.idea/libraries/Maven__junit_junit_4_1.xml new file mode 100644 index 00000000..fd926eb3 --- /dev/null +++ b/.idea/libraries/Maven__junit_junit_4_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_antlr_antlr_3_2.xml b/.idea/libraries/Maven__org_antlr_antlr_3_2.xml new file mode 100644 index 00000000..7bafdd32 --- /dev/null +++ b/.idea/libraries/Maven__org_antlr_antlr_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_antlr_antlr_runtime_3_2.xml b/.idea/libraries/Maven__org_antlr_antlr_runtime_3_2.xml new file mode 100644 index 00000000..899879a7 --- /dev/null +++ b/.idea/libraries/Maven__org_antlr_antlr_runtime_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_antlr_gunit_3_2.xml b/.idea/libraries/Maven__org_antlr_gunit_3_2.xml new file mode 100644 index 00000000..7b126b33 --- /dev/null +++ b/.idea/libraries/Maven__org_antlr_gunit_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_antlr_stringtemplate_3_2.xml b/.idea/libraries/Maven__org_antlr_stringtemplate_3_2.xml new file mode 100644 index 00000000..d3e1ac9f --- /dev/null +++ b/.idea/libraries/Maven__org_antlr_stringtemplate_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_mnode_ical4j_ical4j_1_0_2.xml b/.idea/libraries/Maven__org_mnode_ical4j_ical4j_1_0_2.xml new file mode 100644 index 00000000..37391498 --- /dev/null +++ b/.idea/libraries/Maven__org_mnode_ical4j_ical4j_1_0_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..c0595dbf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..e228c765 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 00000000..922003b8 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..275077f8 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..40a85c17 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No facets are configured + + + + + + + + + + + + + + + 1.6 + + + + + + + + natty + + + + + + + + + + + + + + + Maven: antlr:antlr:2.7.7 + + + + + + + + + + + diff --git a/natty.iml b/natty.iml new file mode 100644 index 00000000..a02b06a8 --- /dev/null +++ b/natty.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 8c4da715..1d1fbd30 100644 --- a/pom.xml +++ b/pom.xml @@ -72,8 +72,8 @@ maven-compiler-plugin 2.3.2 - 1.5 - 1.5 + 1.6 + 1.6 @@ -177,6 +177,7 @@ org.apache.maven.plugins maven-gpg-plugin + 1.4 sign-artifacts @@ -192,6 +193,7 @@ org.apache.maven.plugins maven-javadoc-plugin + 2.9.1 attach-javadocs @@ -208,6 +210,7 @@ org.apache.maven.plugins maven-source-plugin + 2.2.1 attach-sources @@ -230,12 +233,6 @@ 3.2 - - antlr - antlr - 2.7.7 - - org.mnode.ical4j ical4j From 2dea23e94c0b93113bc9c0b19a89b2613a42a527 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sat, 21 Dec 2013 10:22:06 -0500 Subject: [PATCH 082/134] intellij settings --- .idea/workspace.xml | 157 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 21 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 40a85c17..ed5892f4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,7 +2,9 @@ - + + + @@ -78,7 +80,27 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -98,6 +120,8 @@ @@ -327,7 +351,7 @@ - + @@ -337,8 +361,20 @@ + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + localhost 5050 @@ -464,32 +541,57 @@ - + - + - + + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index e228c765..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 275077f8..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ed5892f4..4c6a50d9 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,8 +2,15 @@ + + + + + + + @@ -22,7 +29,7 @@ - + - - - + + @@ -71,7 +77,8 @@ - + + @@ -80,7 +87,18 @@ - + + + + + + + + + + + + @@ -89,18 +107,68 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + @@ -116,10 +184,20 @@ + + + - @@ -198,7 +276,7 @@ - + @@ -210,6 +288,21 @@ + + + + + + + + + + + @@ -225,7 +318,25 @@ + + + + + + + + + + @@ -235,15 +346,41 @@ + + + + + + + + + + + + + + @@ -253,15 +390,19 @@ + + @@ -271,19 +412,23 @@ + + @@ -293,54 +438,81 @@ - - - - + + + + + + + + + + + + + + + + - @@ -365,6 +537,7 @@ + @@ -374,25 +547,151 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + + + + + + + + + + + + + + + + - + + + + + + + + + + - - - - + + + - + + @@ -708,7 +1239,7 @@ - 1.6 + 1.7 - - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e6e7e472..9456dfc2 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ org.antlr maven-gunit-plugin - 3.2 + 3.2 maven-gunit-plugin diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index fdf484d8..1e80d1d1 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -92,112 +92,124 @@ date ; date_time_alternative - - // in 2 to 3 months, 4 and 7 months - : (((IN | FOR | NEXT) WHITE_SPACE)? spelled_or_int_optional_prefix conjunction)=> - ((IN | FOR | NEXT) WHITE_SPACE)? one=spelled_or_int_optional_prefix conjunction two=spelled_or_int_optional_prefix WHITE_SPACE relative_date_span - -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $one relative_date_span))) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $two relative_date_span)))) - + // for 3 days, for 7 months, for twenty seconds + : (date_time_alternative_range)=> date_time_alternative_range + -> ^(DATE_TIME_ALTERNATIVE date_time_alternative_range) + // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after | (date conjunction global_date_prefix)=> date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) - - // "next wed or thurs" , "next wed, thurs, or fri" - | (alternative_day_of_week_list)=> alternative_day_of_week_list - -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list) - + + // first or last day of 2009 + | (alternative_day_of_year_list)=> alternative_day_of_year_list + -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_year_list) + // feb 16, 17, or 18 | (alternative_day_of_month_list)=> alternative_day_of_month_list -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_month_list) - - // this wed. or next - | ((THIS WHITE_SPACE)? day_of_week conjunction alternative_direction)=> - (THIS WHITE_SPACE)? day_of_week conjunction alternative_direction (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?) - ) - - // 1/2 or 1/4 or 1/6 at 6pm - // Aug 16 at 10am or Sept 28th at 11am - | (date_time conjunction date_time)=> - date_time (conjunction date_time)+ + + // "next wed or thurs" , "next wed, thurs, or fri", "this wed or next" + | (alternative_day_of_week_list)=> alternative_day_of_week_list + -> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list) + + // 1/2 or 1/4 or 1/6 at 6pm, Aug 16 at 10am or Sept 28th at 11am, Feb 28th + | date_time (conjunction date_time)* -> ^(DATE_TIME_ALTERNATIVE date_time+) - - // first or last day of 2009 - | (explicit_day_of_year_part conjunction explicit_day_of_year_part WHITE_SPACE relaxed_year)=> - first=explicit_day_of_year_part conjunction second=explicit_day_of_year_part WHITE_SPACE relaxed_year - -> ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $first)) - ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $second))) - - // for 3 days, for 7 months, for twenty seconds - | ((FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE)=> - (FOR | NEXT) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE - (relative_date_span -> - ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) - | relative_time_span -> - ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) - ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) - ) - - // last 3 days, last 7 months, past twenty seconds - | ((LAST | PAST) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE)=> - (LAST | PAST) WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE - (relative_date_span -> - ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_date_span)))) - | relative_time_span -> - ^(DATE_TIME_ALTERNATIVE - ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["0"] SPAN["day"]))) - ^(DATE_TIME ^(RELATIVE_TIME ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_span)))) - ) - - // single date_time - | date_time -> ^(DATE_TIME_ALTERNATIVE date_time) ; - + +date_time_alternative_range + : ( + // in two days, in 3 or 4 days + range_direction one=spelled_or_int_optional_prefix + (conjunction two=spelled_or_int_optional_prefix)? WHITE_SPACE range_span + + // two to 7 days, 4 to 5 days ago + | one=spelled_or_int_optional_prefix conjunction two=spelled_or_int_optional_prefix WHITE_SPACE + range_span (WHITE_SPACE relative_date_suffix)? + ) + -> {$range_direction.text != null && $range_direction.inclusive && $two.text != null}? + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK range_direction INT["0"] range_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK range_direction $one range_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK range_direction $two range_span))) + + -> {$range_direction.text != null && $range_direction.inclusive}? + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK range_direction INT["0"] range_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK range_direction $one range_span))) + + -> {$relative_date_suffix.text != null && $two.text != null}? + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix $one range_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix $two range_span))) + + -> {$relative_date_suffix.text != null}? + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix $one range_span))) + + -> {$two.text != null}? + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $one range_span))) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $two range_span))) + + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] $one range_span))) + ; + +range_direction returns [Boolean inclusive] + : (FOR | NEXT) WHITE_SPACE {$inclusive=true;} -> DIRECTION[">"] SEEK_BY["by_day"] + | (LAST | PAST) WHITE_SPACE {$inclusive=true;} -> DIRECTION["<"] SEEK_BY["by_day"] + | IN WHITE_SPACE {$inclusive=false;} -> DIRECTION[">"] SEEK_BY["by_day"] + ; + conjunction : COMMA? WHITE_SPACE (AND | OR | TO | THROUGH | DASH) WHITE_SPACE ; - + +range_span + : relative_date_span + | relative_time_span + ; + +alternative_day_of_year_list + : first=explicit_day_of_year_part conjunction second=explicit_day_of_year_part WHITE_SPACE relaxed_year + -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $first)) + ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) $second)) + ; + alternative_day_of_month_list // mon may 15 or tues may 16 : ((relaxed_day_of_week? relaxed_month WHITE_SPACE relaxed_day_of_month (conjunction relaxed_day_of_month)+) (date_time_separator explicit_time)?) -> ^(DATE_TIME ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month) explicit_time?)+ - - // first or last day of september - | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE relaxed_month)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE relaxed_month (date_time_separator explicit_time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_month) $first) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_month) $second) explicit_time?) - - // first or last day of next september - | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month (date_time_separator explicit_time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $first) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK prefix explicit_relative_month) $second) explicit_time?) - - // first or last day of 2 septembers from now - | (explicit_day_of_month_part conjunction explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix)=> - first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE - spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix (date_time_separator explicit_time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $first) explicit_time?) - ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) $second) explicit_time?) + + | first=explicit_day_of_month_part conjunction second=explicit_day_of_month_part WHITE_SPACE alternative_day_seek (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE alternative_day_seek $first) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE alternative_day_seek $second) explicit_time?) ; - + +alternative_day_seek + // next september + : prefix WHITE_SPACE explicit_relative_month + -> ^(SEEK prefix explicit_relative_month) + + // 2 septembers from now + | spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) + + // september + | relaxed_month + -> ^(EXPLICIT_SEEK relaxed_month) + ; + alternative_day_of_week_list - : alternative_direction WHITE_SPACE day_of_week (day_of_week_list_separator day_of_week)+ (date_time_separator explicit_time)? - -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK alternative_direction day_of_week)) explicit_time?)+ + : first_direction=alternative_direction WHITE_SPACE day_of_week + + // this wed or thursday or friday + ((day_of_week_list_separator day_of_week)+ (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK $first_direction day_of_week)) explicit_time?)+ + + // this wed. or next + | conjunction second_direction=alternative_direction (date_time_separator explicit_time)? + -> ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] day_of_week)) explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK $second_direction day_of_week)) explicit_time?) + ) ; - + day_of_week_list_separator : COMMA (WHITE_SPACE | conjunction) | conjunction ; @@ -213,38 +225,33 @@ global_date_prefix // the day after : (THE WHITE_SPACE)? DAY WHITE_SPACE prefix_direction -> prefix_direction SEEK_BY["by_day"] INT["1"] - - // 3 days before - | spelled_or_int_optional_prefix WHITE_SPACE DAY WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_day"] spelled_or_int_optional_prefix - - // 2 weeks before - | spelled_or_int_optional_prefix WHITE_SPACE WEEK WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_week"] spelled_or_int_optional_prefix - | WEEK WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_week"] INT["1"] + // 2 weeks from now + | (amt=spelled_or_int_optional_prefix WHITE_SPACE)? global_date_prefix_seek prefix_direction + -> {$amt.text != null}? + prefix_direction global_date_prefix_seek spelled_or_int_optional_prefix + + -> prefix_direction global_date_prefix_seek INT["1"] - // 6 months before - | spelled_or_int_optional_prefix WHITE_SPACE MONTH WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_month"] spelled_or_int_optional_prefix - - // 6 years before - | spelled_or_int_optional_prefix WHITE_SPACE YEAR WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_year"] spelled_or_int_optional_prefix - - // the friday after - | (THE WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_day"] INT["1"] day_of_week - // 3 fridays before - | spelled_or_int_optional_prefix WHITE_SPACE day_of_week WHITE_SPACE prefix_direction + | (spelled_or_int_optional_prefix WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction -> prefix_direction SEEK_BY["by_day"] spelled_or_int_optional_prefix day_of_week - - | (THE WHITE_SPACE)? spelled_first_to_thirty_first WHITE_SPACE day_of_week WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_day"] spelled_first_to_thirty_first day_of_week - ; - + + // the friday after, 2 fridays from now + | (THE WHITE_SPACE)? (spelled_first_to_thirty_first WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction + -> {$spelled_first_to_thirty_first.text != null}? + prefix_direction SEEK_BY["by_day"] spelled_first_to_thirty_first day_of_week + + -> prefix_direction SEEK_BY["by_day"] INT["1"] day_of_week + ; + +global_date_prefix_seek + : DAY WHITE_SPACE -> SEEK_BY["by_day"] + | WEEK WHITE_SPACE -> SEEK_BY["by_week"] + | MONTH WHITE_SPACE -> SEEK_BY["by_month"] + | YEAR WHITE_SPACE -> SEEK_BY["by_year"] + ; + prefix_direction : (AFTER | FROM | ON) -> DIRECTION[">"] | BEFORE -> DIRECTION["<"] @@ -254,15 +261,19 @@ prefix_direction // relaxed date with a spelled-out or abbreviated month relaxed_date - : ( - // The 31st of April in the year 2008 - // RFC822 style: Fri, 21 Nov 1997 - relaxed_day_of_week? relaxed_day_of_month_prefix? relaxed_day_of_month - WHITE_SPACE (OF WHITE_SPACE)? relaxed_month (relaxed_year_prefix relaxed_year)? + : relaxed_date_month_first + | relaxed_date_month_last + ; + +relaxed_date_month_first + : relaxed_day_of_week? relaxed_month COMMA? WHITE_SPACE relaxed_day_of_month (relaxed_year_prefix relaxed_year)? + -> ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month relaxed_day_of_week? relaxed_year?) + ; - // Jan 21, 1997 Sun, Nov 21 - | relaxed_day_of_week? relaxed_month COMMA? WHITE_SPACE relaxed_day_of_month (relaxed_year_prefix relaxed_year)? - ) -> ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month relaxed_day_of_week? relaxed_year?) +relaxed_date_month_last + : relaxed_day_of_week? relaxed_day_of_month_prefix? relaxed_day_of_month + WHITE_SPACE (OF WHITE_SPACE)? relaxed_month (relaxed_year_prefix relaxed_year)? + -> ^(EXPLICIT_DATE relaxed_month relaxed_day_of_month relaxed_day_of_week? relaxed_year?) ; relaxed_day_of_week @@ -291,7 +302,7 @@ relaxed_month relaxed_day_of_month : spelled_or_int_01_to_31_optional_prefix -> ^(DAY_OF_MONTH spelled_or_int_01_to_31_optional_prefix) - + | spelled_first_to_thirty_first -> ^(DAY_OF_MONTH spelled_first_to_thirty_first) ; @@ -320,8 +331,13 @@ relaxed_year_prefix // ********** formal date rules ********** formal_date + // march 2009 + : (relaxed_month WHITE_SPACE relaxed_year)=> + relaxed_month WHITE_SPACE relaxed_year + -> ^(EXPLICIT_DATE relaxed_month ^(DAY_OF_MONTH INT["1"]) relaxed_year?) + // year first: 1979-02-28, 1980/01/02, etc. full 4 digit year required to match - : relaxed_day_of_week? formal_year_four_digits formal_date_separator (formal_month_of_year | relaxed_month) formal_date_separator formal_day_of_month + | relaxed_day_of_week? formal_year_four_digits formal_date_separator (formal_month_of_year | relaxed_month) formal_date_separator formal_day_of_month -> ^(EXPLICIT_DATE formal_month_of_year? relaxed_month? formal_day_of_month relaxed_day_of_week? formal_year_four_digits) // year last: 1/02/1980, 2/28/79. 2 or 4 digit year is acceptable @@ -331,9 +347,6 @@ formal_date // 15-Apr-2014 | formal_day_of_month formal_date_separator relaxed_month (formal_date_separator formal_year_four_digits)? -> ^(EXPLICIT_DATE relaxed_month formal_day_of_month formal_year_four_digits?) - - | relaxed_month WHITE_SPACE relaxed_year - -> ^(EXPLICIT_DATE relaxed_month ^(DAY_OF_MONTH INT["1"]) relaxed_year?) ; formal_month_of_year @@ -406,92 +419,79 @@ relative_date // these represent explicit points within a relative range explicit_relative_date + // the first day of 2009 + : explicit_day_of_year_part WHITE_SPACE relaxed_year + -> ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) explicit_day_of_year_part) + + | explicit_day_of_month_part WHITE_SPACE explicit_relative_month_seek (relaxed_year_prefix relaxed_year)? + -> {$relaxed_year.text != null}? + ^(RELATIVE_DATE explicit_relative_month_seek explicit_day_of_month_part ^(EXPLICIT_SEEK relaxed_year)) + + -> ^(RELATIVE_DATE explicit_relative_month_seek explicit_day_of_month_part) + + | explicit_day_of_week_part WHITE_SPACE explicit_relative_week_seek + -> ^(RELATIVE_DATE explicit_relative_week_seek explicit_day_of_week_part) + ; + +explicit_relative_month_seek // 1st of three months ago, 10th of 3 octobers from now, the last monday in 2 novembers ago - : (explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix)=> - explicit_day_of_month_part WHITE_SPACE spelled_or_int_optional_prefix - WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix - -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) explicit_day_of_month_part) - + : spelled_or_int_optional_prefix WHITE_SPACE explicit_relative_month WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix explicit_relative_month) + // 10th of next month, 31st of last month, 10th of next october, 30th of this month, the last thursday of last november - | (explicit_day_of_month_part WHITE_SPACE prefix)=> - explicit_day_of_month_part WHITE_SPACE prefix WHITE_SPACE explicit_relative_month - -> ^(RELATIVE_DATE - ^(SEEK prefix explicit_relative_month) - explicit_day_of_month_part) - + | prefix WHITE_SPACE explicit_relative_month + -> ^(SEEK prefix explicit_relative_month) + // 10th of the month after next - | (explicit_day_of_month_part WHITE_SPACE THE WHITE_SPACE MONTH WHITE_SPACE AFTER WHITE_SPACE NEXT)=> - explicit_day_of_month_part WHITE_SPACE THE WHITE_SPACE MONTH WHITE_SPACE AFTER WHITE_SPACE NEXT - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["month"]) - explicit_day_of_month_part) - - // monday after next - | (explicit_day_of_week_part WHITE_SPACE AFTER WHITE_SPACE NEXT) - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) - explicit_day_of_week_part) - - // saturday before last - | (explicit_day_of_week_part WHITE_SPACE BEFORE WHITE_SPACE LAST) - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) - explicit_day_of_week_part) - - // monday of last week, tuesday of next week - | (explicit_day_of_week_part WHITE_SPACE prefix WHITE_SPACE WEEK)=> - explicit_day_of_week_part WHITE_SPACE prefix WHITE_SPACE WEEK - -> ^(RELATIVE_DATE - ^(SEEK prefix SPAN["week"]) - explicit_day_of_week_part) - - // monday of 2 weeks ago, tuesday of 3 weeks from now - | (explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix)=> - explicit_day_of_week_part WHITE_SPACE spelled_or_int_optional_prefix - WHITE_SPACE WEEK WHITE_SPACE relative_date_suffix - -> ^(RELATIVE_DATE - ^(SEEK relative_date_suffix spelled_or_int_optional_prefix SPAN["week"]) - explicit_day_of_week_part) - - // monday of the week after next - | explicit_day_of_week_part WHITE_SPACE THE WHITE_SPACE WEEK WHITE_SPACE AFTER WHITE_SPACE NEXT - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) - explicit_day_of_week_part) - - // the last thursday in november 1999 - | (explicit_day_of_month_part WHITE_SPACE relaxed_month relaxed_year_prefix relaxed_year)=> - explicit_day_of_month_part WHITE_SPACE relaxed_month relaxed_year_prefix relaxed_year - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] relaxed_month) - explicit_day_of_month_part - ^(EXPLICIT_SEEK relaxed_year)) - - // above without the year restriction - | explicit_day_of_month_part WHITE_SPACE relaxed_month - -> ^(RELATIVE_DATE - ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] relaxed_month) - explicit_day_of_month_part) - - // the first day of 2009 - | explicit_day_of_year_part WHITE_SPACE relaxed_year - -> ^(RELATIVE_DATE ^(EXPLICIT_SEEK relaxed_year) explicit_day_of_year_part) + | THE WHITE_SPACE MONTH WHITE_SPACE AFTER WHITE_SPACE NEXT + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["month"]) + + // september + | relaxed_month + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["0"] relaxed_month) ; - + +explicit_relative_week_seek + // after next + : AFTER WHITE_SPACE NEXT + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) + + // before last + | BEFORE WHITE_SPACE LAST + -> ^(SEEK DIRECTION["<"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) + + // last week, tuesday of next week + | prefix WHITE_SPACE WEEK + -> ^(SEEK prefix SPAN["week"]) + + // 2 weeks ago, tuesday of 3 weeks from now + | spelled_or_int_optional_prefix WHITE_SPACE WEEK WHITE_SPACE relative_date_suffix + -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix SPAN["week"]) + + // the week after next + | THE WHITE_SPACE WEEK WHITE_SPACE AFTER WHITE_SPACE NEXT + -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] SPAN["week"]) + ; + explicit_day_of_month_part // first of, 10th of, 31st of, - : (THE WHITE_SPACE)? relaxed_day_of_month (WHITE_SPACE (IN | OF))? + : (THE WHITE_SPACE)? relaxed_day_of_month day_of_month_suffix? -> ^(EXPLICIT_SEEK relaxed_day_of_month) // the last thursday - | (THE WHITE_SPACE)? relative_occurrence_index WHITE_SPACE day_of_week (WHITE_SPACE (IN | OF))? + | (THE WHITE_SPACE)? relative_occurrence_index WHITE_SPACE day_of_week day_of_month_suffix -> ^(EXPLICIT_SEEK relative_occurrence_index day_of_week) // in the start of, at the beginning of, the end of, last day of, first day of - | (((IN | AT) WHITE_SPACE)? THE WHITE_SPACE)? explicit_day_of_month_bound (WHITE_SPACE (OF | IN))? + | (((IN | AT) WHITE_SPACE)? THE WHITE_SPACE)? explicit_day_of_month_bound day_of_month_suffix? -> explicit_day_of_month_bound ; +day_of_month_suffix + : (WHITE_SPACE (IN | OF))=> + WHITE_SPACE (IN | OF) + ; + explicit_day_of_week_part // monday of, tuesday of : (THE WHITE_SPACE)? relaxed_day_of_week (IN | OF)? @@ -664,8 +664,9 @@ holiday | relative_date_prefix WHITE_SPACE holiday_name -> ^(SEEK relative_date_prefix holiday_name) - | holiday_name relaxed_year_prefix relaxed_year - -> ^(EXPLICIT_SEEK holiday_name relaxed_year) + | (holiday_name relaxed_year_prefix relaxed_year)=> + holiday_name relaxed_year_prefix relaxed_year + -> ^(EXPLICIT_SEEK holiday_name relaxed_year) | holiday_name -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] holiday_name) @@ -678,8 +679,9 @@ holiday_name | BLACK WHITE_SPACE FRIDAY -> HOLIDAY["BLACK_FRIDAY"] - | CHRISTMAS WHITE_SPACE EVENING - -> HOLIDAY["CHRISTMAS_EVE"] + | (CHRISTMAS WHITE_SPACE EVENING)=> + CHRISTMAS WHITE_SPACE EVENING + -> HOLIDAY["CHRISTMAS_EVE"] | CHRISTMAS (WHITE_SPACE DAY)? -> HOLIDAY["CHRISTMAS"] @@ -729,8 +731,9 @@ holiday_name | MOTHER WHITE_SPACE DAY -> HOLIDAY["MOTHERS_DAY"] - | NEW WHITE_SPACE YEAR WHITE_SPACE EVENING - -> HOLIDAY["NEW_YEARS_EVE"] + | (NEW WHITE_SPACE YEAR WHITE_SPACE EVENING)=> + NEW WHITE_SPACE YEAR WHITE_SPACE EVENING + -> HOLIDAY["NEW_YEARS_EVE"] | NEW WHITE_SPACE YEAR (WHITE_SPACE DAY)? -> HOLIDAY["NEW_YEARS_DAY"] @@ -767,8 +770,9 @@ season | relative_date_prefix WHITE_SPACE season_name -> ^(SEEK relative_date_prefix season_name) - | season_name relaxed_year_prefix relaxed_year - -> ^(EXPLICIT_SEEK season_name relaxed_year) + | (season_name relaxed_year_prefix)=> + season_name relaxed_year_prefix relaxed_year + -> ^(EXPLICIT_SEEK season_name relaxed_year) | season_name -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] season_name) @@ -791,11 +795,7 @@ relative_time // 10 hours ago, 20 minutes before noon : spelled_or_int_optional_prefix WHITE_SPACE relative_time_target WHITE_SPACE relative_time_suffix -> ^(RELATIVE_TIME ^(SEEK relative_time_suffix spelled_or_int_optional_prefix relative_time_target)) - - // in 3 minutes - | IN WHITE_SPACE spelled_or_int_optional_prefix WHITE_SPACE relative_time_target - -> ^(RELATIVE_TIME ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] spelled_or_int_optional_prefix relative_time_target)) - + // next hour, last minute | prefix WHITE_SPACE relative_time_target -> ^(RELATIVE_TIME ^(SEEK prefix relative_time_target)) @@ -803,16 +803,23 @@ relative_time // a time with an hour, optional minutes, and optional meridian indicator explicit_time - : hours COLON? minutes (COLON? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? (WHITE_SPACE? time_zone)? - -> ^(EXPLICIT_TIME hours minutes seconds? meridian_indicator? time_zone?) - - | hours (WHITE_SPACE? meridian_indicator)? (WHITE_SPACE? time_zone)? - -> ^(EXPLICIT_TIME hours ^(MINUTES_OF_HOUR INT["0"]) meridian_indicator? time_zone?) - - | (THIS WHITE_SPACE)? named_time (WHITE_SPACE time_zone)? + : explicit_time_hours_minutes (WHITE_SPACE? time_zone)? + -> ^(EXPLICIT_TIME explicit_time_hours_minutes time_zone?) + + | named_time (WHITE_SPACE time_zone)? -> ^(EXPLICIT_TIME named_time time_zone?) ; - + +explicit_time_hours_minutes returns [String hours, String minutes, String ampm] + : hours COLON? minutes (COLON? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? + {$hours=$hours.text; $minutes=$minutes.text; $ampm=$meridian_indicator.text;} + -> hours minutes seconds? meridian_indicator? + + | hours (WHITE_SPACE? meridian_indicator)? + {$hours=$hours.text; $ampm=$meridian_indicator.text;} + -> hours ^(MINUTES_OF_HOUR INT["0"]) meridian_indicator? + ; + // hour of the day hours : int_00_to_23_optional_prefix -> ^(HOURS_OF_DAY int_00_to_23_optional_prefix) @@ -840,33 +847,43 @@ simple_meridian_indicator ; friendly_meridian_indicator - : (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> AM_PM["am"] - | (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> AM_PM["pm"] - | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> AM_PM["pm"] - | (AT WHITE_SPACE)? NIGHT -> AM_PM["pm"] + : (((IN WHITE_SPACE THE) | AT) WHITE_SPACE)? + ( + MORNING -> AM_PM["am"] + | (NOON | EVENING | NIGHT) -> AM_PM["pm"] + ) ; named_time - : (IN WHITE_SPACE THE WHITE_SPACE)? NOON -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] - | (IN WHITE_SPACE THE WHITE_SPACE)? MORNING -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] - | (IN WHITE_SPACE THE WHITE_SPACE)? NIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + : named_time_prefix? named_hour ((WHITE_SPACE AT)? WHITE_SPACE hm=explicit_time_hours_minutes)? - // tonight at 7, this evening at 6:30 - | ((TONIGHT | EVENING) WHITE_SPACE AT WHITE_SPACE int_01_to_31_optional_prefix)=> - (TONIGHT | EVENING) WHITE_SPACE AT WHITE_SPACE hour=int_01_to_31_optional_prefix (COLON? minutes)? + // If the named time is at night, but the hour given is before 5, we'll assume tomorrow morning + -> {$hm.text != null && $named_hour.ampm != null && $named_hour.ampm.equals("pm") && Integer.parseInt($hm.hours) < 5}? + ^(HOURS_OF_DAY INT[Integer.toString(Integer.parseInt($hm.hours) + 24)]) + ^(MINUTES_OF_HOUR INT[$hm.minutes]) + ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] - // If the hour given is before 5, we'll assume tomorrow morning - -> {Integer.parseInt($hour.text) < 5}? - ^(HOURS_OF_DAY INT[Integer.toString(Integer.parseInt($hour.text) + 24)]) - minutes? ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + -> {$hm.hours != null}? + ^(HOURS_OF_DAY INT[$hm.hours]) + ^(MINUTES_OF_HOUR INT[$hm.minutes]) + ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] - -> ^(HOURS_OF_DAY $hour) minutes? ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + -> named_hour + ; - | TONIGHT -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] - | (AT WHITE_SPACE)? MIDNIGHT -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] - | (IN WHITE_SPACE THE WHITE_SPACE)? EVENING -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] +named_time_prefix + : ((IN WHITE_SPACE THE) | AT | THIS) WHITE_SPACE ; - + +named_hour returns [String ampm] + : MORNING {$ampm="am";} -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | MIDNIGHT {$ampm="am";} -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["am"] + | NOON {$ampm="pm";} -> ^(HOURS_OF_DAY INT["12"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | NIGHT {$ampm="pm";} -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | TONIGHT {$ampm="pm";} -> ^(HOURS_OF_DAY INT["8"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + | EVENING {$ampm="pm";} -> ^(HOURS_OF_DAY INT["7"]) ^(MINUTES_OF_HOUR INT["0"]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM["pm"] + ; + time_zone : time_zone_plus_offset | time_zone_abbreviation @@ -876,7 +893,6 @@ time_zone_plus_offset : UTC? time_zone_offset -> ZONE_OFFSET[$time_zone_offset.text] ; - time_zone_offset : (PLUS | DASH) hours (COLON? minutes)? ; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index 1e460e03..d939b18c 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -32,6 +32,9 @@ topdown | ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR INT) amount=INT ^(MONTH_OF_YEAR dow=INT)) -> ^(SEEK DIRECTION SEEK_BY $amount ^(MONTH_OF_YEAR $dow)) + | ^(SEEK DIRECTION SEEK_BY INT SPAN amt=INT span=SPAN) + -> ^(SEEK DIRECTION SEEK_BY $amt $span) + // ensure year seek happens before day of week seek | ^(RELATIVE_DATE ^(SEEK dir=DIRECTION seekby=SEEK_BY day=INT ^(MONTH_OF_YEAR month=INT)) diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index b0d03cf1..2bddef30 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -1,12 +1,12 @@ package com.joestelmach.natty; +import org.antlr.runtime.tree.Tree; + import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; -import org.antlr.runtime.tree.Tree; - /** * * @author Joe Stelmach @@ -64,7 +64,7 @@ public void setRecurring(boolean isRecurring) { /** * @return true if the time information in this date group has been inferred - * as opposed to being explicity defined in the _text input. + * as opposed to being explicity defined in the input. */ public boolean isTimeInferred() { return _isTimeInferred; diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 0bb1fcb9..2ccab5d3 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -234,17 +234,68 @@ public void testRange() throws Exception { Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 1, 2, 2011); validateDate(dates.get(1), 1, 2, 2036); - - dates = parseCollection("I want to go shopping in Knoxville, TN in the next five to six months."); + + dates = parseCollection("2 and 4 months"); Assert.assertEquals(2, dates.size()); - validateDate(dates.get(0), 6, 2, 2011); - validateDate(dates.get(1), 7, 2, 2011); - - dates = parseCollection("I want to watch the fireworks in the next two to three months."); + validateDate(dates.get(0), 3, 2, 2011); + validateDate(dates.get(1), 5, 2, 2011); + + dates = parseCollection("in 2 to 4 months"); Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 3, 2, 2011); - validateDate(dates.get(1), 4, 2, 2011); - + validateDate(dates.get(1), 5, 2, 2011); + + dates = parseCollection("for 2 to 4 months"); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 3, 2, 2011); + validateDate(dates.get(2), 5, 2, 2011); + + dates = parseCollection("next 2 to 4 months"); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 3, 2, 2011); + validateDate(dates.get(2), 5, 2, 2011); + + dates = parseCollection("2 to 4 months from now"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 3, 2, 2011); + validateDate(dates.get(1), 5, 2, 2011); + + dates = parseCollection("last 2 to 4 months"); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 11, 2, 2010); + validateDate(dates.get(2), 9, 2, 2010); + + dates = parseCollection("past 2 to 4 months"); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 11, 2, 2010); + validateDate(dates.get(2), 9, 2, 2010); + + dates = parseCollection("2 to 4 months ago"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 11, 2, 2010); + validateDate(dates.get(1), 9, 2, 2010); + + dates = parseCollection("1 to 2 days"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 1, 3, 2011); + validateDate(dates.get(1), 1, 4, 2011); + + dates = parseCollection("I want to go shopping in Knoxville, TN in the next five to six months."); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 6, 2, 2011); + validateDate(dates.get(2), 7, 2, 2011); + + dates = parseCollection("I want to watch the fireworks in the next two to three months."); + Assert.assertEquals(3, dates.size()); + validateDate(dates.get(0), 1, 2, 2011); + validateDate(dates.get(1), 3, 2, 2011); + validateDate(dates.get(2), 4, 2, 2011); + dates = parseCollection("september 7th something"); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 7, 2011); @@ -288,7 +339,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "thursday evening at 6:30"; + String value = "Second Monday in October 2017"; Parser parser = new Parser(); List groups = parser.parse(value); @@ -305,7 +356,7 @@ public static void main(String[] args) { System.out.println("\n** Parse Locations **"); for(Entry> entry:group.getParseLocations().entrySet()) { for(ParseLocation loc:entry.getValue()) { - System.out.println(loc.getRuleName()); + System.out.println(loc.getRuleName() + ": " + loc.getText()); } } diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index ee96904f..99f3df35 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -58,6 +58,7 @@ public void testRelaxed() throws Exception { Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("1/02/2011"); CalendarSource.setBaseDate(reference); validateTime("noon", 12, 0, 0); + validateTime("at noon", 12, 0, 0); validateTime("afternoon", 12, 0, 0); validateTime("midnight", 0, 0, 0); validateTime("mid-night", 0, 0, 0); From 741ac15ec60b4ee5b76a8a39f6564c917bbaf069 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 8 Feb 2015 00:25:25 -0500 Subject: [PATCH 105/134] small rule cleanup --- .../antlr3/com/joestelmach/natty/generated/DateParser.g | 7 ++++--- src/test/java/com/joestelmach/natty/DateTest.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 1e80d1d1..f0d28d77 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -99,7 +99,9 @@ date_time_alternative // today or the day after that, feb 16th or 2 days after that, january fourth or the friday after | (date conjunction global_date_prefix)=> date conjunction global_date_prefix (WHITE_SPACE THAT)? (date_time_separator explicit_time)? - -> ^(DATE_TIME_ALTERNATIVE ^(DATE_TIME date explicit_time?) ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) + -> ^(DATE_TIME_ALTERNATIVE + ^(DATE_TIME date explicit_time?) + ^(DATE_TIME ^(RELATIVE_DATE ^(SEEK global_date_prefix date) explicit_time?))) // first or last day of 2009 | (alternative_day_of_year_list)=> alternative_day_of_year_list @@ -488,8 +490,7 @@ explicit_day_of_month_part ; day_of_month_suffix - : (WHITE_SPACE (IN | OF))=> - WHITE_SPACE (IN | OF) + : WHITE_SPACE (IN | OF) ; explicit_day_of_week_part diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 2ccab5d3..bd48b265 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -339,7 +339,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Second Monday in October 2017"; + String value = "this year"; Parser parser = new Parser(); List groups = parser.parse(value); From 555a14a3546e6880b0a3737ced1f098d817dfde2 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 8 Feb 2015 00:29:07 -0500 Subject: [PATCH 106/134] adding 'current' as a prefix. Fixes #103 --- src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g | 1 + src/main/antlr3/com/joestelmach/natty/generated/DateParser.g | 2 +- src/test/java/com/joestelmach/natty/DateTest.java | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 3da62c95..4fc84e47 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -250,6 +250,7 @@ DOT : '.'; PLUS : '+'; SINGLE_QUOTE : '\''; +CURRENT : 'current'; FOR : 'for'; IN : 'in'; AN : 'an'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index f0d28d77..6eec7aea 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -585,7 +585,7 @@ relative_time_span ; implicit_prefix - : THIS -> DIRECTION[">"] SEEK_BY["by_day"] INT["0"] + : (THIS | CURRENT) -> DIRECTION[">"] SEEK_BY["by_day"] INT["0"] ; relative_date_prefix diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index bd48b265..7bcc2748 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -128,6 +128,9 @@ public void testRelative() throws Exception { validateDate("A month ago", 1, 28, 2011); validateDate("A week ago", 2, 21, 2011); validateDate("A year ago", 2, 28, 2010); + validateDate("this month", 2, 28, 2011); + validateDate("current month", 2, 28, 2011); + validateDate("current year", 2, 28, 2011); } @Test @@ -339,7 +342,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "this year"; + String value = "current year"; Parser parser = new Parser(); List groups = parser.parse(value); From 5f9d799d500e620fb5dbace8652e92cccdbd175b Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 8 Feb 2015 12:19:27 -0500 Subject: [PATCH 107/134] antlr 3.3 --- natty.iml | 8 ++++---- pom.xml | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/natty.iml b/natty.iml index 45e1a08f..894caa3e 100644 --- a/natty.iml +++ b/natty.iml @@ -22,8 +22,8 @@ - - + + @@ -31,7 +31,7 @@ - - + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9456dfc2..348f382b 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ org.antlr antlr3-maven-plugin - 3.2 + 3.3 @@ -110,7 +110,6 @@ true src/main/antlr3/com/joestelmach/natty/generated/imports src/main/java - 10000 com/joestelmach/natty/generated/DateLexer.g com/joestelmach/natty/generated/DateParser.g @@ -130,7 +129,6 @@ false src/main/java src/main/antlr3/com/joestelmach/natty/generated/imports - 10000 com/joestelmach/natty/generated/DateLexer.g com/joestelmach/natty/generated/DateParser.g @@ -146,7 +144,7 @@ org.antlr maven-gunit-plugin - 3.2 + 3.3 maven-gunit-plugin @@ -230,7 +228,7 @@ org.antlr antlr-runtime - 3.2 + 3.3 @@ -249,7 +247,7 @@ org.antlr gunit - 3.2 + 3.3 test From af230b9e9c239dcf7e48e02ecfdf41d04429e9b3 Mon Sep 17 00:00:00 2001 From: azhemoytuk Date: Thu, 7 Aug 2014 11:56:19 +0300 Subject: [PATCH 108/134] Return EOF token when no more tokens available. Required for antlr 3.3+. --- src/main/java/com/joestelmach/natty/NattyTokenSource.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/joestelmach/natty/NattyTokenSource.java b/src/main/java/com/joestelmach/natty/NattyTokenSource.java index df4095a6..cc69972f 100644 --- a/src/main/java/com/joestelmach/natty/NattyTokenSource.java +++ b/src/main/java/com/joestelmach/natty/NattyTokenSource.java @@ -2,10 +2,13 @@ import java.util.List; +import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import org.antlr.runtime.TokenSource; public class NattyTokenSource implements TokenSource { + private static final Token EOF_TOKEN = new CommonToken(Token.EOF); + private List _tokens; private int _index = 0; @@ -14,7 +17,7 @@ public NattyTokenSource(List tokens) { } public Token nextToken() { - return _tokens.size() > _index ? _tokens.get(_index++) : null; + return _tokens.size() > _index ? _tokens.get(_index++) : EOF_TOKEN; } public String getSourceName() { From 47d328f7c9ca7c98c78fe14f3f66079fc09a265b Mon Sep 17 00:00:00 2001 From: clementcm Date: Mon, 9 Feb 2015 22:41:55 -0500 Subject: [PATCH 109/134] antlr 3.3 ParseListener api change --- src/main/java/com/joestelmach/natty/ParseListener.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index 18d1095b..88bcadca 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -1,15 +1,11 @@ package com.joestelmach.natty; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; - import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.debug.BlankDebugEventListener; +import java.util.*; + /** * Responsible for collecting parse information from the debug parser * @@ -36,7 +32,7 @@ public ParseLocation getDateGroupLocation() { } // don't add backtracking or cyclic DFA nodes - public void enterDecision(int d) { + public void enterDecision(int d, boolean couldBacktrack) { backtracking++; } From 2964683c51e3e524265f53600006fd1fd7b3707d Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 9 Feb 2015 23:17:48 -0500 Subject: [PATCH 110/134] upgrading to antlr 3.5.2. fixes #60 --- natty.iml | 11 ++++++----- pom.xml | 8 ++++---- .../com/joestelmach/natty/generated/DateParser.g | 8 +++++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/natty.iml b/natty.iml index 894caa3e..b97fc75c 100644 --- a/natty.iml +++ b/natty.iml @@ -22,16 +22,17 @@ - - - + - - + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 348f382b..4950dda3 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ org.antlr antlr3-maven-plugin - 3.3 + 3.5.2 @@ -144,7 +144,7 @@ org.antlr maven-gunit-plugin - 3.3 + 3.5.2 maven-gunit-plugin @@ -228,7 +228,7 @@ org.antlr antlr-runtime - 3.3 + 3.5.2 @@ -247,7 +247,7 @@ org.antlr gunit - 3.3 + 3.5.2 test diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 6eec7aea..2af9402d 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -856,19 +856,21 @@ friendly_meridian_indicator ; named_time - : named_time_prefix? named_hour ((WHITE_SPACE AT)? WHITE_SPACE hm=explicit_time_hours_minutes)? + : (named_time_prefix? named_hour (WHITE_SPACE AT)? WHITE_SPACE hm=explicit_time_hours_minutes)=> + named_time_prefix? named_hour (WHITE_SPACE AT)? WHITE_SPACE hm=explicit_time_hours_minutes // If the named time is at night, but the hour given is before 5, we'll assume tomorrow morning - -> {$hm.text != null && $named_hour.ampm != null && $named_hour.ampm.equals("pm") && Integer.parseInt($hm.hours) < 5}? + -> {$named_hour.ampm != null && $named_hour.ampm.equals("pm") && Integer.parseInt($hm.hours) < 5}? ^(HOURS_OF_DAY INT[Integer.toString(Integer.parseInt($hm.hours) + 24)]) ^(MINUTES_OF_HOUR INT[$hm.minutes]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] - -> {$hm.hours != null}? + -> ^(HOURS_OF_DAY INT[$hm.hours]) ^(MINUTES_OF_HOUR INT[$hm.minutes]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] + | named_time_prefix? named_hour -> named_hour ; From 74b569a5d3fb3164af2d86ad6892bd5b023fdc94 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 9 Feb 2015 23:47:36 -0500 Subject: [PATCH 111/134] [maven-release-plugin] prepare release natty-0.10 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4950dda3..d5dd5f64 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.10-SNAPSHOT + 0.10 Natty Date Parser natural language date parser http://natty.joestelmach.com From 416b53d3a3e4466249367f97ea1f878a4f774ab8 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 9 Feb 2015 23:47:37 -0500 Subject: [PATCH 112/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5dd5f64..008e7576 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.10 + 0.11-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From 31bd9738c8c127445edd2865f5b92068bb1aeab9 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 10 Feb 2015 23:29:20 -0500 Subject: [PATCH 113/134] re-enable global date prefixes with no amount. fixes #108 --- .../joestelmach/natty/generated/DateParser.g | 26 +++++++++---------- .../joestelmach/natty/generated/TreeRewrite.g | 6 ++--- .../natty/generated/imports/NumericRules.g | 2 +- .../java/com/joestelmach/natty/DateTest.java | 6 ++++- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 2af9402d..02d72e23 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -229,24 +229,25 @@ global_date_prefix -> prefix_direction SEEK_BY["by_day"] INT["1"] // 2 weeks from now - | (amt=spelled_or_int_optional_prefix WHITE_SPACE)? global_date_prefix_seek prefix_direction - -> {$amt.text != null}? - prefix_direction global_date_prefix_seek spelled_or_int_optional_prefix + | (global_date_prefix_amount WHITE_SPACE)? global_date_prefix_seek prefix_direction + -> {$global_date_prefix_amount.text != null}? + prefix_direction global_date_prefix_seek global_date_prefix_amount -> prefix_direction global_date_prefix_seek INT["1"] - // 3 fridays before - | (spelled_or_int_optional_prefix WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction - -> prefix_direction SEEK_BY["by_day"] spelled_or_int_optional_prefix day_of_week - - // the friday after, 2 fridays from now - | (THE WHITE_SPACE)? (spelled_first_to_thirty_first WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction - -> {$spelled_first_to_thirty_first.text != null}? - prefix_direction SEEK_BY["by_day"] spelled_first_to_thirty_first day_of_week + // 3 fridays before, the friday after, 2 fridays from now + | (THE WHITE_SPACE)? (global_date_prefix_amount WHITE_SPACE)? day_of_week WHITE_SPACE prefix_direction + -> {$global_date_prefix_amount.text != null}? + prefix_direction SEEK_BY["by_day"] global_date_prefix_amount day_of_week -> prefix_direction SEEK_BY["by_day"] INT["1"] day_of_week ; +global_date_prefix_amount + : spelled_first_to_thirty_first + | spelled_or_int_optional_prefix + ; + global_date_prefix_seek : DAY WHITE_SPACE -> SEEK_BY["by_day"] | WEEK WHITE_SPACE -> SEEK_BY["by_week"] @@ -865,8 +866,7 @@ named_time ^(MINUTES_OF_HOUR INT[$hm.minutes]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] - -> - ^(HOURS_OF_DAY INT[$hm.hours]) + -> ^(HOURS_OF_DAY INT[$hm.hours]) ^(MINUTES_OF_HOUR INT[$hm.minutes]) ^(SECONDS_OF_MINUTE INT["0"]) AM_PM[$named_hour.ampm] diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index d939b18c..21a724c4 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -22,13 +22,13 @@ options { topdown : ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK INT) ^(DAY_OF_WEEK dow=INT)) -> ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_WEEK $dow)) - + | ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_MONTH INT) ^(DAY_OF_MONTH dow=INT)) -> ^(SEEK DIRECTION SEEK_BY INT ^(DAY_OF_MONTH $dow)) - + | ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR INT) ^(MONTH_OF_YEAR dow=INT)) -> ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR $dow)) - + | ^(SEEK DIRECTION SEEK_BY INT ^(MONTH_OF_YEAR INT) amount=INT ^(MONTH_OF_YEAR dow=INT)) -> ^(SEEK DIRECTION SEEK_BY $amount ^(MONTH_OF_YEAR $dow)) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g index 7d64a32d..7b631565 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g @@ -65,7 +65,7 @@ spelled_or_int_01_to_31_optional_prefix spelled_or_int_optional_prefix : spelled_one_to_thirty_one // TODO expand this spelled range to at least ninety-nine | ((int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99) - (int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? + (int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? -> INT[$spelled_or_int_optional_prefix.text]) ; diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 7bcc2748..843db997 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -117,12 +117,16 @@ public void testRelative() throws Exception { validateDate("the saturday after next", 3, 19, 2011); validateDate("the monday after next", 3, 14, 2011); validateDate("the monday after next monday", 3, 14, 2011); + validateDate("the monday before May 25", 5, 23, 2011); + validateDate("the 2nd monday before May 25", 5, 16, 2011); + validateDate("3 mondays after May 25", 6, 13, 2011); validateDate("tuesday before last", 2, 15, 2011); validateDate("a week from now", 3, 7, 2011); validateDate("a month from today", 3, 28, 2011); validateDate("a week after this friday", 3, 11, 2011); validateDate("a week from this friday", 3, 11, 2011); validateDate("two weeks from this friday", 3, 18, 2011); + validateDate("the second week after this friday", 3, 18, 2011); validateDate("It's gonna snow! How about skiing tomorrow", 3, 1, 2011); validateDate("A week on tuesday", 3, 8, 2011); validateDate("A month ago", 1, 28, 2011); @@ -342,7 +346,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "current year"; + String value = "Monday before May 25"; Parser parser = new Parser(); List groups = parser.parse(value); From 9a6e4c93137ab79d60a69df6756bee52aa4ab4ef Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Tue, 10 Feb 2015 23:44:53 -0500 Subject: [PATCH 114/134] fixes #100 --- .../com/joestelmach/natty/generated/DateParser.g | 2 +- src/test/java/com/joestelmach/natty/DateTest.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 02d72e23..15a0c048 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -491,7 +491,7 @@ explicit_day_of_month_part ; day_of_month_suffix - : WHITE_SPACE (IN | OF) + : WHITE_SPACE (IN | OF) (WHITE_SPACE MONTH)? ; explicit_day_of_week_part diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 843db997..ab4ebf19 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -135,6 +135,16 @@ public void testRelative() throws Exception { validateDate("this month", 2, 28, 2011); validateDate("current month", 2, 28, 2011); validateDate("current year", 2, 28, 2011); + validateDate("first monday in 1 month", 3, 7, 2011); + validateDate("first monday of month in 1 month", 3, 7, 2011); + validateDate("first monday of 1 month", 3, 7, 2011); + validateDate("first monday in 2 months", 4, 4, 2011); + validateDate("first monday of 2 months", 4, 4, 2011); + validateDate("first monday of month 2 months", 4, 4, 2011); + validateDate("first monday of month in 2 months", 4, 4, 2011); + validateDate("first monday in 3 months", 5, 2, 2011); + validateDate("first monday of 3 months", 5, 2, 2011); + validateDate("first monday of month in 3 months", 5, 2, 2011); } @Test @@ -346,7 +356,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Monday before May 25"; + String value = "first monday of 2 months"; Parser parser = new Parser(); List groups = parser.parse(value); From 66e32b75a153ac0f91a4c43265c313b5c63ebccd Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Wed, 11 Feb 2015 22:48:16 -0500 Subject: [PATCH 115/134] If time information is found in a later date capture, ensure the information is inherited by previously captured dates with no time information. fixes #86 --- .../joestelmach/natty/generated/TreeRewrite.g | 1 - .../com/joestelmach/natty/WalkerState.java | 82 ++++++++++++++++--- .../java/com/joestelmach/natty/DateTest.java | 2 +- .../com/joestelmach/natty/DateTimeTest.java | 6 +- .../java/com/joestelmach/natty/TimeTest.java | 27 +++++- 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index 21a724c4..c164c5a3 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -36,7 +36,6 @@ topdown -> ^(SEEK DIRECTION SEEK_BY $amt $span) // ensure year seek happens before day of week seek - | ^(RELATIVE_DATE ^(SEEK dir=DIRECTION seekby=SEEK_BY day=INT ^(MONTH_OF_YEAR month=INT)) ^(EXPLICIT_SEEK amount=INT ^(DAY_OF_WEEK dow=INT)) ^(EXPLICIT_SEEK ^(YEAR_OF year=INT)) diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index 2e5841a3..c0aa1a4a 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -1,7 +1,6 @@ package com.joestelmach.natty; import java.util.*; -import java.util.logging.Logger; /** * @author Joe Stelmach @@ -26,12 +25,9 @@ public class WalkerState { private static final String PLUS = "+"; private static final String MINUS = "-"; private static final String GMT = "GMT"; - private static final String VEVENT = "VEVENT"; - private static final String SUMMARY = "SUMMARY"; private static final String HOLIDAY_ICS_FILE = "/holidays.ics"; private static final String SEASON_ICS_FILE = "/seasons.ics"; - private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); - + private GregorianCalendar _calendar; private TimeZone _defaultTimeZone; private int _currentYear; @@ -40,7 +36,9 @@ public class WalkerState { private boolean _dateGivenInGroup = false; private boolean _updatePreviousDates = false; private DateGroup _dateGroup; - + private List _amPmGivenPerCapture = new ArrayList(); + private List _timeGivenPerCapture = new ArrayList(); + /** * Creates a new WalkerState representing the start of * the next hour from the current time @@ -209,7 +207,13 @@ public void seekBySpan(String direction, String seekAmount, String span) { boolean isDateSeek = span.equals(DAY) || span.equals(WEEK) || span.equals(MONTH) || span.equals(YEAR); - if(isDateSeek) markDateInvocation(); else markTimeInvocation(); + + if(isDateSeek) { + markDateInvocation(); + } + else { + markTimeInvocation(null); + } int sign = direction.equals(DIR_RIGHT) ? 1 : -1; int field = @@ -219,7 +223,7 @@ public void seekBySpan(String direction, String seekAmount, String span) { span.equals(YEAR) ? Calendar.YEAR : span.equals(HOUR) ? Calendar.HOUR: span.equals(MINUTE) ? Calendar.MINUTE: - span.equals(SECOND) ? Calendar.SECOND: + span.equals(SECOND) ? Calendar.SECOND: null; if(field > 0) _calendar.add(field, seekAmountInt * sign); } @@ -327,7 +331,7 @@ public void setExplicitTime(String hours, String minutes, String seconds, String assert(hoursInt >= 0); assert(minutesInt >= 0 && minutesInt < 60); - markTimeInvocation(); + markTimeInvocation(amPm); // reset milliseconds to 0 _calendar.set(Calendar.MILLISECOND, 0); @@ -433,8 +437,20 @@ public void setRecurring() { * */ public void captureDateTime() { + + // The list of times found per capture should always be 1 larger than the number of captures. + // If not, that mean no time was given for this capture + if(_timeGivenPerCapture.size() < _dateGroup.getDates().size() + 1) { + _timeGivenPerCapture.add(false); + } + + // ditto for meridien indicators found per capture + if(_amPmGivenPerCapture.size() < _dateGroup.getDates().size() + 1) { + _amPmGivenPerCapture.add(null); + } + // if other dates have already been added to the date group, we'll - // update their date portion to match this one + // update them to match this one if(_updatePreviousDates) { List dates = _dateGroup.getDates(); if (!dates.isEmpty()) { @@ -450,6 +466,46 @@ public void captureDateTime() { _updatePreviousDates = false; } + // if a time was given in this capture, we'll want to use the same time for any previous + // captures that lacked time information + boolean thisTimeGiven = _timeGivenPerCapture.get(_timeGivenPerCapture.size() - 1); + if(thisTimeGiven) { + for (int i = 0; i < _timeGivenPerCapture.size() - 1; i++) { + boolean timeGiven = _timeGivenPerCapture.get(i); + if (!timeGiven) { + Date date = _dateGroup.getDates().get(i); + Calendar calendar = getCalendar(); + calendar.setTime(date); + for (int field : new int[] { Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND}) { + calendar.set(field, _calendar.get(field)); + } + date.setTime(calendar.getTimeInMillis()); + } + } + } + + // similarly, if a meridian indicator was given in this capture, we'll want to use the same + // indicator for any previous times found without an explicit indicator + String thisAmPm = _amPmGivenPerCapture.get(_amPmGivenPerCapture.size() - 1); + if (thisAmPm != null) { + for (int i = 0; i < _amPmGivenPerCapture.size() - 1; i++) { + String amPm = _amPmGivenPerCapture.get(i); + if (amPm == null && _timeGivenPerCapture.get(i)) { + Date date = _dateGroup.getDates().get(i); + Calendar calendar = getCalendar(); + calendar.setTime(date); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + if(thisAmPm.equals("am") && hour > 11) { + calendar.set(Calendar.HOUR_OF_DAY, hour - 12); + } + if(thisAmPm.equals("pm") && calendar.get(Calendar.HOUR_OF_DAY) < 12) { + calendar.set(Calendar.HOUR_OF_DAY, hour + 12); + } + date.setTime(calendar.getTimeInMillis()); + } + } + } + Date date = _calendar.getTime(); if(_dateGroup.isRecurring()) { _dateGroup.setRecurringUntil(date); @@ -526,7 +582,7 @@ private void seekToIcsEventYear(String icsFileName, String yearString, String ev int year = getFullYear(yearInt); Map dates = getDatesFromIcs(icsFileName, eventSummary, year, year); Date date = dates.get(year - (eventSummary.equals(Holiday.NEW_YEARS_EVE.getSummary()) ? 1 : 0)); - + if(date != null) { Calendar cal = getCalendar(); cal.setTimeZone(_calendar.getTimeZone()); @@ -568,9 +624,11 @@ private void markDateInvocation() { /** * */ - private void markTimeInvocation() { + private void markTimeInvocation(String amPm) { _timeGivenInGroup = true; _dateGroup.setIsTimeInferred(false); + _amPmGivenPerCapture.add(amPm); + _timeGivenPerCapture.add(true); } private Map getDatesFromIcs(String icsFileName, diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index ab4ebf19..fe5740a0 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -356,7 +356,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "first monday of 2 months"; + String value = "June 25th and July 2nd and August 16th at 10am"; Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 6bdf4b11..1e9016f9 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -147,14 +147,14 @@ public void testList() throws Exception { dates = parseCollection("June 25th and July 2nd at 10am and August 16th"); Assert.assertEquals(3, dates.size()); - validateDateTime(dates.get(0), 6, 25, 2012, 0, 0, 0); + validateDateTime(dates.get(0), 6, 25, 2012, 10, 0, 0); validateDateTime(dates.get(1), 7, 2, 2012, 10, 0, 0); validateDateTime(dates.get(2), 8, 16, 2012, 10, 0, 0); dates = parseCollection("June 25th and July 2nd and August 16th at 10am"); Assert.assertEquals(3, dates.size()); - validateDateTime(dates.get(0), 6, 25, 2012, 0, 0, 0); - validateDateTime(dates.get(1), 7, 2, 2012, 0, 0, 0); + validateDateTime(dates.get(0), 6, 25, 2012, 10, 0, 0); + validateDateTime(dates.get(1), 7, 2, 2012, 10, 0, 0); validateDateTime(dates.get(2), 8, 16, 2012, 10, 0, 0); dates = parseCollection("slept from 3:30 a.m. To 9:41 a.m. On April 10th"); diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index 99f3df35..e664ac10 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -88,7 +88,32 @@ public void testRelative() throws Exception { validateTime("this minute", 12, 0, 0); validateTime("this hour", 12, 0, 0); } - + + @Test + public void testAlternatives() throws Exception { + CalendarSource.setBaseDate(DateFormat.getTimeInstance(DateFormat.SHORT).parse("12:00 pm")); + + List dates = parseCollection("12 or 12:30"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 12, 30, 0); + + dates = parseCollection("12pm or 12:30"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 12, 30, 0); + + dates = parseCollection("noon or 12:30"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 12, 0, 0); + validateTime(dates.get(1), 12, 30, 0); + + dates = parseCollection("12 or 12:30am"); + Assert.assertEquals(2, dates.size()); + validateTime(dates.get(0), 0, 0, 0); + validateTime(dates.get(1), 0, 30, 0); + } + @Test public void testRange() throws Exception { CalendarSource.setBaseDate(DateFormat.getTimeInstance(DateFormat.SHORT).parse("12:00 pm")); From b01cff3f15ae4023a36e1a12aef4674d90c232fb Mon Sep 17 00:00:00 2001 From: Viktoras Makauskas Date: Thu, 12 Feb 2015 22:24:43 +0200 Subject: [PATCH 116/134] fixing 'days ago' for numbers with one or two trailing zeroes --- .../com/joestelmach/natty/generated/imports/NumericRules.g | 3 ++- .../java/com/joestelmach/natty/grammar/DateGrammarTest.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g index 7b631565..b23d78c7 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/imports/NumericRules.g @@ -65,7 +65,8 @@ spelled_or_int_01_to_31_optional_prefix spelled_or_int_optional_prefix : spelled_one_to_thirty_one // TODO expand this spelled range to at least ninety-nine | ((int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99) - (int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? + ( INT_0 | INT_00 | int_01_to_31_optional_prefix | int_32_to_59 | int_60_to_99)? + -> INT[$spelled_or_int_optional_prefix.text]) ; diff --git a/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java index 83ca241e..392061cc 100644 --- a/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java +++ b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java @@ -221,6 +221,8 @@ public void relative_date() throws Exception { assertAST("seven years ago", "(RELATIVE_DATE (SEEK < by_day 7 year))"); assertAST("60 years ago", "(RELATIVE_DATE (SEEK < by_day 60 year))"); assertAST("32 days ago", "(RELATIVE_DATE (SEEK < by_day 32 day))"); + assertAST("150 days ago", "(RELATIVE_DATE (SEEK < by_day 150 day))"); + assertAST("1500 days ago", "(RELATIVE_DATE (SEEK < by_day 1500 day))"); assertAST("next monday", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2)))"); assertAST("next mon", "(RELATIVE_DATE (SEEK > by_week 1 (DAY_OF_WEEK 2)))"); assertAST("4 mondays from now", "(RELATIVE_DATE (SEEK > by_day 4 (DAY_OF_WEEK 2)))"); From f5814e83c4af4e698cc5dde16cde89a9b11302a7 Mon Sep 17 00:00:00 2001 From: Viktoras Makauskas Date: Thu, 12 Feb 2015 22:35:57 +0200 Subject: [PATCH 117/134] additional tests for #99 fix --- src/test/java/com/joestelmach/natty/DateTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index fe5740a0..2be98dce 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -85,6 +85,8 @@ public void testRelative() throws Exception { validateDate("seven years ago", 2, 28, 2004); validateDate("60 years ago", 2, 28, 1951); validateDate("32 days ago", 1, 27, 2011); + validateDate("320 days ago", 4, 14, 2010); + validateDate("1200 days ago", 11, 16, 2007); validateDate("next monday", 3, 7, 2011); validateDate("next mon", 3, 7, 2011); validateDate("4 mondays from now", 3, 28, 2011); From 8efbc33ec200d0103bf5d7c2743ce8ce40ccd190 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 12 Feb 2015 23:21:24 -0500 Subject: [PATCH 118/134] future date tests for #99 --- src/test/java/com/joestelmach/natty/DateTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 2be98dce..af9d6c47 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -87,6 +87,9 @@ public void testRelative() throws Exception { validateDate("32 days ago", 1, 27, 2011); validateDate("320 days ago", 4, 14, 2010); validateDate("1200 days ago", 11, 16, 2007); + validateDate("365 days from now", 2, 28, 2012); + validateDate("100 months now", 6, 28, 2019); + validateDate("100 years from now", 2, 28, 2111); validateDate("next monday", 3, 7, 2011); validateDate("next mon", 3, 7, 2011); validateDate("4 mondays from now", 3, 28, 2011); @@ -358,7 +361,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "June 25th and July 2nd and August 16th at 10am"; + String value = "96 months from now"; Parser parser = new Parser(); List groups = parser.parse(value); From 66d944959e4bba408f5effcf148b34311ff1bdfd Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 12 Feb 2015 23:34:58 -0500 Subject: [PATCH 119/134] dot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 008e7576..618d4f0c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.11-SNAPSHOT + 0.10.1-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From b6b6d44ecf4ce088f5e849435800bee0de0feed2 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 12 Feb 2015 23:37:05 -0500 Subject: [PATCH 120/134] [maven-release-plugin] prepare release natty-0.10.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 618d4f0c..5c71340e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.10.1-SNAPSHOT + 0.10.1 Natty Date Parser natural language date parser http://natty.joestelmach.com From e01e7b612b41bd2991e9170a0edabbc0edd54a51 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 12 Feb 2015 23:37:06 -0500 Subject: [PATCH 121/134] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c71340e..008e7576 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.10.1 + 0.11-SNAPSHOT Natty Date Parser natural language date parser http://natty.joestelmach.com From bd7c3fbf83ed94cd9da7219c8e5ea623ee589a42 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 12 Feb 2015 23:53:52 -0500 Subject: [PATCH 122/134] adding 'tmr' as an alternative to 'tomorrow'. Fixes #94 --- src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g | 2 +- src/test/java/com/joestelmach/natty/DateTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 4fc84e47..a3ce3f8d 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -43,7 +43,7 @@ MONTH : 'month' | 'months'; YEAR : 'year' | 'year' SINGLE_QUOTE? 's' | 'yrs' DOT?; TODAY : 'today'; -TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow'; +TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow' | 'tmr'; TONIGHT : 'tonight'; YESTERDAY : 'yesterday'; diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index af9d6c47..1282b2c6 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -75,6 +75,7 @@ public void testRelative() throws Exception { validateDate("yesterday", 2, 27, 2011); validateDate("tomorrow", 3, 1, 2011); + validateDate("tmr", 3, 1, 2011); validateDate("in 3 days", 3, 3, 2011); validateDate("3 days ago", 2, 25, 2011); validateDate("in 3 weeks", 3, 21, 2011); @@ -361,7 +362,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "96 months from now"; + String value = "a month ago"; Parser parser = new Parser(); List groups = parser.parse(value); From be835fe50289fddb4768037d2e97362429a568b8 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Fri, 13 Feb 2015 23:46:04 -0500 Subject: [PATCH 123/134] test for alternative with 'ago' suffix. closes #65 --- src/test/java/com/joestelmach/natty/DateTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 1282b2c6..b89aa11d 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -302,6 +302,11 @@ public void testRange() throws Exception { validateDate(dates.get(0), 11, 2, 2010); validateDate(dates.get(1), 9, 2, 2010); + dates = parseCollection("2 or 3 days ago"); + Assert.assertEquals(2, dates.size()); + validateDate(dates.get(0), 12, 31, 2010); + validateDate(dates.get(1), 12, 30, 2010); + dates = parseCollection("1 to 2 days"); Assert.assertEquals(2, dates.size()); validateDate(dates.get(0), 1, 3, 2011); @@ -362,7 +367,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "a month ago"; + String value = "Two or three days ago"; Parser parser = new Parser(); List groups = parser.parse(value); From 0911062ac35edcbec88507fac163240b4486e62e Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 16 Mar 2015 19:42:19 -0400 Subject: [PATCH 124/134] Ensure no dates found in strings like 'fried' or 'wedding' Fixes #46 --- src/test/java/com/joestelmach/natty/AbstractTest.java | 10 ++++++---- src/test/java/com/joestelmach/natty/DateTest.java | 10 ++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/joestelmach/natty/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java index f2f499cd..1cc397ec 100644 --- a/src/test/java/com/joestelmach/natty/AbstractTest.java +++ b/src/test/java/com/joestelmach/natty/AbstractTest.java @@ -1,12 +1,13 @@ package com.joestelmach.natty; +import org.junit.Assert; +import org.junit.Before; + +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; -import org.junit.Assert; -import org.junit.Before; - /** * * @author Joe Stelmach @@ -36,7 +37,8 @@ public void before() { * @return */ protected List parseCollection(String value) { - return _parser.parse(value).get(0).getDates(); + List dateGroup = _parser.parse(value); + return dateGroup.isEmpty() ? new ArrayList() : dateGroup.get(0).getDates(); } /** diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index b89aa11d..edadaa50 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -359,7 +359,13 @@ public void testRelativeDateDifferentTimezone() { TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); } - + + @Test + public void testNoDates() { + List dates = parseCollection("Fried Chicken, Wedding Dinner"); + Assert.assertEquals(0, dates.size()); + } + public static void main(String[] args) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.ALL); @@ -367,7 +373,7 @@ public static void main(String[] args) { logger.setLevel(Level.FINEST); logger.addHandler(handler); - String value = "Two or three days ago"; + String value = "Fried Chicken, Wedding Dinner"; Parser parser = new Parser(); List groups = parser.parse(value); From aa1426e16041c0fe616b5dbd0fa5c387fd20a84a Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 16 Mar 2015 22:10:43 -0400 Subject: [PATCH 125/134] Ensure relative minutes can exceed 99. Closes #49 --- src/test/java/com/joestelmach/natty/DateTimeTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index 1e9016f9..f7c4c139 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -84,6 +84,8 @@ public void testRelative() throws Exception { validateDateTime("this evening at 2", 2, 25, 2011, 2, 0, 0); validateDateTime("tomorrow evening at 5", 2, 25, 2011, 17, 0, 0); validateDateTime("wed evening at 8:30", 3, 2, 2011, 20, 30, 0); + validateDateTime("750 minutes from now", 2, 24, 2011, 12, 30, 0); + validateDateTime("1500 minutes from now", 2, 25, 2011, 1, 0, 0); } @Test From 0d168c6cdaeac14851bd283e43efa7262bb69352 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 16 Mar 2015 23:04:05 -0400 Subject: [PATCH 126/134] Ignore known tokens directly following unknown tokens. Fixes #73 --- .../java/com/joestelmach/natty/Parser.java | 46 +++++++++++-------- .../com/joestelmach/natty/SearchTest.java | 19 ++++---- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 76c0ff81..1f23a701 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -1,16 +1,9 @@ package com.joestelmach.natty; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.TimeZone; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; - +import com.joestelmach.natty.generated.DateLexer; +import com.joestelmach.natty.generated.DateParser; +import com.joestelmach.natty.generated.DateWalker; +import com.joestelmach.natty.generated.TreeRewrite; import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.Token; @@ -19,10 +12,11 @@ import org.antlr.runtime.tree.CommonTreeNodeStream; import org.antlr.runtime.tree.Tree; -import com.joestelmach.natty.generated.DateLexer; -import com.joestelmach.natty.generated.DateParser; -import com.joestelmach.natty.generated.DateWalker; -import com.joestelmach.natty.generated.TreeRewrite; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -202,6 +196,7 @@ private List collectTokenStreams(TokenStream stream) { List currentGroup = null; List groups = new ArrayList(); Token currentToken; + Token previousToken = null; StringBuilder tokenString = new StringBuilder(); while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { if(_logger.getLevel() != null && _logger.getLevel().intValue() <= Level.FINE.intValue()) { @@ -211,21 +206,28 @@ private List collectTokenStreams(TokenStream stream) { // we're currently NOT collecting for a possible date group if(currentGroup == null) { - // ignore white space in-between possible rules - if(currentToken.getType() != DateLexer.WHITE_SPACE) { + // skip over white space, unknowns, and tokens directly prefixed by unknown. (For example: + // 'know' will lex to UNKNOWN NOW, but NOW can only be valid if not part of a larger word. + if(currentToken.getType() != DateLexer.WHITE_SPACE && + (previousToken == null || previousToken.getType() != DateLexer.UNKNOWN)) { + // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType()) || currentToken.getType() == DateLexer.UNKNOWN) { + if(DateParser.FOLLOW_empty_in_parse186.member( + currentToken.getType()) || currentToken.getType() == DateLexer.UNKNOWN) { + currentGroup = new ArrayList(); currentGroup.add(currentToken); } } } + // we're currently collecting else { // preserve white space if(currentToken.getType() == DateLexer.WHITE_SPACE) { currentGroup.add(currentToken); } + else { // if this is an unknown token, we need to end the current group if(currentToken.getType() == DateLexer.UNKNOWN) { @@ -243,7 +245,10 @@ else if(currentToken.getType() != DateLexer.DOT) { } } } + + previousToken = currentToken; } + if(currentGroup != null) { cleanupGroup(currentGroup); groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); @@ -260,7 +265,7 @@ else if(currentToken.getType() != DateLexer.DOT) { */ private void cleanupGroup(List group) { - // remove contiguous white space + // remove contiguous white space Iterator iter = group.iterator(); Token previousToken = null; while(iter.hasNext()) { @@ -272,7 +277,7 @@ private void cleanupGroup(List group) { } previousToken = token; } - + // remove leading white space if(group.size() > 0) { boolean skip = false; @@ -312,5 +317,6 @@ else if(skip) { else break; } } + } } diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index f5787d89..71fb2b5a 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -1,20 +1,15 @@ package com.joestelmach.natty; +import junit.framework.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.TimeZone; -import junit.framework.Assert; - -import org.junit.Test; -import org.junit.BeforeClass; - -import com.joestelmach.natty.CalendarSource; -import com.joestelmach.natty.DateGroup; -import com.joestelmach.natty.Parser; - /** * * @author Joe Stelmach @@ -283,5 +278,11 @@ public void test() throws Exception { groups = parser.parse("dinner bmong friends"); Assert.assertEquals(0, groups.size()); + + groups = parser.parse("I know we should meet tomorrow"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 2, 21, 2011); } } From 2a55319f409e1903ac019e15362ab61fd1773411 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 19 Mar 2015 20:04:09 -0400 Subject: [PATCH 127/134] log token groups --- .../java/com/joestelmach/natty/Parser.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 1f23a701..b6ace9d8 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -194,7 +194,7 @@ private List collectTokenStreams(TokenStream stream) { // walk through the token stream and build a collection // of sub token streams that represent possible date locations List currentGroup = null; - List groups = new ArrayList(); + List> groups = new ArrayList>(); Token currentToken; Token previousToken = null; StringBuilder tokenString = new StringBuilder(); @@ -233,8 +233,7 @@ private List collectTokenStreams(TokenStream stream) { if(currentToken.getType() == DateLexer.UNKNOWN) { if(currentGroup.size() > 0) { currentGroup.add(currentToken); - cleanupGroup(currentGroup); - groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + groups.add(currentGroup); } currentGroup = null; } @@ -250,13 +249,25 @@ else if(currentToken.getType() != DateLexer.DOT) { } if(currentGroup != null) { - cleanupGroup(currentGroup); - groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup))); + groups.add(currentGroup); } _logger.fine("global token stream: " + tokenString.toString()); - - return groups; + List streams = new ArrayList(); + for(List group:groups) { + cleanupGroup(group); + if(!group.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (Token token : group) { + builder.append(DateParser.tokenNames[token.getType()]).append(" "); + } + _logger.fine(builder.toString()); + + streams.add(new CommonTokenStream(new NattyTokenSource(group))); + } + } + + return streams; } /** From 742d64674af9eb2342e3b2177f9f1f34a4de2996 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Thu, 19 Mar 2015 22:49:58 -0400 Subject: [PATCH 128/134] improve logging, move to slf4j + log4j --- natty.iml | 3 ++ pom.xml | 12 +++++ .../joestelmach/natty/generated/DateLexer.g | 10 ++-- .../joestelmach/natty/generated/DateParser.g | 10 ++-- .../joestelmach/natty/generated/DateWalker.g | 28 ++++++---- .../joestelmach/natty/generated/TreeRewrite.g | 22 +++++--- .../java/com/joestelmach/natty/Parser.java | 51 +++++++++---------- src/main/resources/log4j.properties | 5 ++ .../java/com/joestelmach/natty/DateTest.java | 12 ++--- src/test/resources/log4j.properties | 5 ++ 10 files changed, 97 insertions(+), 61 deletions(-) create mode 100644 src/main/resources/log4j.properties create mode 100644 src/test/resources/log4j.properties diff --git a/natty.iml b/natty.iml index b97fc75c..a07e200b 100644 --- a/natty.iml +++ b/natty.iml @@ -28,6 +28,9 @@ + + + diff --git a/pom.xml b/pom.xml index 008e7576..dd63e1a5 100644 --- a/pom.xml +++ b/pom.xml @@ -236,6 +236,18 @@ ical4j 1.0.2 + + + org.slf4j + slf4j-api + 1.7.10 + + + + org.slf4j + slf4j-log4j12 + 1.7.10 + junit diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index a3ce3f8d..5ad24ad1 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -3,12 +3,14 @@ lexer grammar DateLexer; @header { package com.joestelmach.natty.generated; } @members { - private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - + private org.slf4j.Logger _logger = + org.slf4j.LoggerFactory.getLogger(com.joestelmach.natty.generated.DateLexer.class); + + @Override public void displayRecognitionError(String[] tokenNames, RecognitionException re) { String message = getErrorHeader(re); try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.fine(message); + _logger.warn(message); } } @@ -296,7 +298,7 @@ INAUGURATION : 'inauguration' | 'inaugaration'; INDEPENDENCE : 'independence' | 'independance'; KWANZAA : ('kwanza' 'a'?) 's'?; LABOR : 'labor'; -MLK : 'mlk' | 'martin' WHITE_SPACE 'luther' WHITE_SPACE 'king' SINGLE_QUOTE? 's'? ('jr' DOT? SINGLE_QUOTE? 's'?)?; +MLK : 'mlk' | 'martin' WHITE_SPACE 'luther' WHITE_SPACE 'king' (COMMA SPACE 'jr' DOT)?; MEMORIAL : 'memorial'; MOTHER : 'mother' SINGLE_QUOTE? 's'?; NEW : 'new'; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 15a0c048..111930b5 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -41,12 +41,14 @@ tokens { } @members { - private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - + private org.slf4j.Logger _logger = + org.slf4j.LoggerFactory.getLogger(com.joestelmach.natty.generated.DateParser.class); + + @Override public void displayRecognitionError(String[] tokenNames, RecognitionException re) { String message = getErrorHeader(re); try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.fine(message); + _logger.warn(message); } } @@ -724,7 +726,7 @@ holiday_name | LABOR WHITE_SPACE DAY -> HOLIDAY["LABOR_DAY"] - | MLK WHITE_SPACE DAY + | MLK (WHITE_SPACE DAY)? -> HOLIDAY["MLK_DAY"] | MEMORIAL WHITE_SPACE DAY diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g index 3c6009bb..223384bd 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g @@ -9,24 +9,30 @@ options { @members { private com.joestelmach.natty.WalkerState _walkerState = new com.joestelmach.natty.WalkerState(); - private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - - public void displayRecognitionError(String[] tokenNames, RecognitionException re) { - String message = getErrorHeader(re); - try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.fine(message); + + @Override + protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) + throws RecognitionException { + throw new MismatchedTokenException(ttype, input); } - - public void recover(IntStream input, RecognitionException re) { - reportError(re); - _walkerState.clearDateGroup(); + + @Override + public Object recoverFromMismatchedSet(IntStream Input, RecognitionException e, BitSet follow) + throws RecognitionException { + throw e; } - + public com.joestelmach.natty.WalkerState getState() { return _walkerState; } } +@rulecatch { + catch(RecognitionException e) { + throw e; + } +} + parse : date_time_alternative recurrence? ; diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g index c164c5a3..4ca30869 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g @@ -10,12 +10,22 @@ options { @header { package com.joestelmach.natty.generated; } @members { - private java.util.logging.Logger _logger = java.util.logging.Logger.getLogger("com.joestelmach.natty"); - - public void displayRecognitionError(String[] tokenNames, RecognitionException re) { - String message = getErrorHeader(re); - try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.fine(message); + @Override + protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) + throws RecognitionException { + throw new MismatchedTokenException(ttype, input); + } + + @Override + public Object recoverFromMismatchedSet(IntStream Input, RecognitionException e, BitSet follow) + throws RecognitionException { + throw e; + } +} + +@rulecatch { + catch(RecognitionException e) { + throw e; } } diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index b6ace9d8..fa6c41f5 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -4,19 +4,19 @@ import com.joestelmach.natty.generated.DateParser; import com.joestelmach.natty.generated.DateWalker; import com.joestelmach.natty.generated.TreeRewrite; -import org.antlr.runtime.ANTLRInputStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.Token; -import org.antlr.runtime.TokenStream; +import org.antlr.runtime.*; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.CommonTreeNodeStream; import org.antlr.runtime.tree.Tree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; /** * @@ -25,8 +25,8 @@ public class Parser { private TimeZone _defaultTimeZone; - private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); - + private static final Logger _logger = LoggerFactory.getLogger(Parser.class); + /** * Creates a new parser using the given time zone as the default * @param defaultTimeZone @@ -57,7 +57,7 @@ public List parse(String value) { input = new ANTLRNoCaseInputStream(new ByteArrayInputStream(value.trim().getBytes())); } catch (IOException e) { - _logger.log(Level.SEVERE, "could not lex input", e); + _logger.error("could not lex input", e); } DateLexer lexer = new DateLexer(input); @@ -138,8 +138,7 @@ private DateGroup singleParse(TokenStream stream) { tokenString.append(DateParser.tokenNames[token.getType()]); tokenString.append(" "); } - _logger.fine("sub-token stream: " + tokenString.toString()); - + try { // parse ParseListener listener = new ParseListener(); @@ -147,23 +146,24 @@ private DateGroup singleParse(TokenStream stream) { DateParser.parse_return parseReturn = parser.parse(); Tree tree = (Tree) parseReturn.getTree(); - _logger.fine("AST: " + tree.toStringTree()); - + // we only continue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { - + _logger.info("sub-token stream: " + tokenString.toString()); + // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); TreeRewrite s = new TreeRewrite(nodes); tree = (CommonTree)s.downup(tree); - + // and walk it nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(stream); DateWalker walker = new DateWalker(nodes); walker.getState().setDefaultTimeZone(_defaultTimeZone); walker.parse(); - + _logger.info("AST: " + tree.toStringTree()); + // run through the results and append the parse information group = walker.getState().getDateGroup(); ParseLocation location = listener.getDateGroupLocation(); @@ -173,9 +173,9 @@ private DateGroup singleParse(TokenStream stream) { group.setSyntaxTree(tree); group.setParseLocations(listener.getLocations()); } - - } catch(Exception e) { - _logger.log(Level.SEVERE, "Could not parse input", e); + + } catch(RecognitionException e) { + _logger.debug("Could not parse input", e); } return group; @@ -199,11 +199,8 @@ private List collectTokenStreams(TokenStream stream) { Token previousToken = null; StringBuilder tokenString = new StringBuilder(); while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { - if(_logger.getLevel() != null && _logger.getLevel().intValue() <= Level.FINE.intValue()) { - tokenString.append(DateParser.tokenNames[currentToken.getType()]); - tokenString.append(" "); - } - + tokenString.append(DateParser.tokenNames[currentToken.getType()]).append(" "); + // we're currently NOT collecting for a possible date group if(currentGroup == null) { // skip over white space, unknowns, and tokens directly prefixed by unknown. (For example: @@ -252,7 +249,7 @@ else if(currentToken.getType() != DateLexer.DOT) { groups.add(currentGroup); } - _logger.fine("global token stream: " + tokenString.toString()); + _logger.info("global token stream: " + tokenString.toString()); List streams = new ArrayList(); for(List group:groups) { cleanupGroup(group); @@ -261,7 +258,7 @@ else if(currentToken.getType() != DateLexer.DOT) { for (Token token : group) { builder.append(DateParser.tokenNames[token.getType()]).append(" "); } - _logger.fine(builder.toString()); + _logger.info(builder.toString()); streams.add(new CommonTokenStream(new NattyTokenSource(group))); } diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 00000000..b9960fec --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=info, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.err +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss} %c{1}:%L - %m %n \ No newline at end of file diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index edadaa50..bd09e130 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -1,15 +1,13 @@ package com.joestelmach.natty; import junit.framework.Assert; +import org.apache.log4j.Level; import org.junit.BeforeClass; import org.junit.Test; import java.text.DateFormat; import java.util.*; import java.util.Map.Entry; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Runs the parser through the various date formats @@ -367,13 +365,9 @@ public void testNoDates() { } public static void main(String[] args) { - ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(Level.ALL); - Logger logger = Logger.getLogger("com.joestelmach.natty"); - logger.setLevel(Level.FINEST); - logger.addHandler(handler); - String value = "Fried Chicken, Wedding Dinner"; + String value = "martin luther king, jr. day"; + org.apache.log4j.Logger.getRootLogger().setLevel(Level.INFO); Parser parser = new Parser(); List groups = parser.parse(value); diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 00000000..0506e387 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=error, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss} %c{1}:%L - %m %n \ No newline at end of file From ffc7926236d62c25b16c999c5e24d8481a547a57 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 22 Mar 2015 10:31:53 -0400 Subject: [PATCH 129/134] Search improvements * Add prefix / suffix retrieval and isDateInferred to DateGroup * Correct off by one getPostion() values in DateGroup, and expose a new getAbsolutePosition() * Simplify and improve searching algorithm (fixes #111, fixes #90) --- .../joestelmach/natty/generated/DateLexer.g | 2 +- .../joestelmach/natty/generated/DateParser.g | 4 +- .../java/com/joestelmach/natty/DateGroup.java | 164 ++++++++++--- .../com/joestelmach/natty/IcsSearcher.java | 26 +-- .../com/joestelmach/natty/ParseListener.java | 2 +- .../java/com/joestelmach/natty/Parser.java | 215 ++++++++---------- .../com/joestelmach/natty/WalkerState.java | 1 + .../java/com/joestelmach/natty/DateTest.java | 9 +- .../com/joestelmach/natty/SearchTest.java | 152 ++++++++++--- 9 files changed, 372 insertions(+), 203 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g index 5ad24ad1..943678a2 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g @@ -10,7 +10,7 @@ lexer grammar DateLexer; public void displayRecognitionError(String[] tokenNames, RecognitionException re) { String message = getErrorHeader(re); try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.warn(message); + _logger.debug(message); } } diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index 111930b5..c19348f4 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -48,7 +48,7 @@ tokens { public void displayRecognitionError(String[] tokenNames, RecognitionException re) { String message = getErrorHeader(re); try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {} - _logger.warn(message); + _logger.debug(message); } } @@ -807,7 +807,7 @@ relative_time // a time with an hour, optional minutes, and optional meridian indicator explicit_time - : explicit_time_hours_minutes (WHITE_SPACE? time_zone)? + : (AT WHITE_SPACE)? explicit_time_hours_minutes (WHITE_SPACE? time_zone)? -> ^(EXPLICIT_TIME explicit_time_hours_minutes time_zone?) | named_time (WHITE_SPACE time_zone)? diff --git a/src/main/java/com/joestelmach/natty/DateGroup.java b/src/main/java/com/joestelmach/natty/DateGroup.java index 2bddef30..5adb99eb 100644 --- a/src/main/java/com/joestelmach/natty/DateGroup.java +++ b/src/main/java/com/joestelmach/natty/DateGroup.java @@ -8,15 +8,16 @@ import java.util.Map; /** - * * @author Joe Stelmach */ public class DateGroup { private List _dates; private String _text; + private String _fullText; private int _line; private int _position; private boolean _isRecurring; + private boolean _isDateInferred; private boolean _isTimeInferred; private Date _recurringUntil; private Map> _parseLocations; @@ -24,74 +25,179 @@ public class DateGroup { public DateGroup() { _dates = new ArrayList(); + _isDateInferred = true; _isTimeInferred = true; } - public List getDates() { - return _dates; - } + /** + * Adds a date to this group + * @param date + */ public void addDate(Date date) { _dates.add(date); } - - public String getText() { - return _text; + + /** + * @return The absolute position of this date group within fullText + */ + public int getAbsolutePosition() { + int lineCount = 1; + int columnCount = 1; + for(int i=0; i<_fullText.length(); i++) { + + // return current position if we've arrived at the requested character + if(lineCount == _line && columnCount == _position) { + return i; + } + + if(_fullText.charAt(i) == '\n') { + lineCount++; + columnCount = 1; + } + else { + columnCount++; + } + } + return -1; } - public void setText(String text) { - _text = text; + + /** + * @return The list of dates in this group + */ + public List getDates() { + return _dates; } + /** + * @return The fullText this date group was found within + */ + public String getFullText() { + return _fullText; + } + + /** + * The line within the fullText this date group is found on + * @return + */ public int getLine() { return _line; } - public void setLine(int line) { - _line = line; + + /** + * @return A map of all matching parse rules to their locations for this group + */ + public Map> getParseLocations() { + return _parseLocations; } + /** + * @return The character position of the matching text within the line of fullText + */ public int getPosition() { return _position; } - public void setPosition(int position) { - _position = position; + + /** + * @param length The maximum number of characters to include + * @return The immediate prefix within fullText to this group's text within the fullText + */ + public String getPrefix(int length) { + return _fullText.substring(Math.max(0, getAbsolutePosition() - length), getAbsolutePosition()); } - + + /** + * @return the end date of any recurrence found + */ + public Date getRecursUntil() { + return _recurringUntil; + } + + /** + * @param length The maximum number of characters to include + * @return The immediate suffix within fullText to this group's text within the fullText + */ + public String getSuffix(int length) { + int endPosition = getAbsolutePosition() + _text.length() + 1; + return _fullText.substring( + Math.min(endPosition - 1, _fullText.length()), + Math.min(endPosition + length - 1, _fullText.length())); + } + + /** + * @return The abstract syntax tree built for this group + */ + public Tree getSyntaxTree() { + return _syntaxTree; + } + + /** + * @return The text within fullText this group represents + */ + public String getText() { + return _text; + } + + /** + * @return true if the date information in this group has been inferred, + * as opposed to being explicitly defined in the input + */ + public boolean isDateInferred() { + return _isDateInferred; + } + + /** + * @return true if some recurrence information was found + */ public boolean isRecurring() { return _isRecurring; } - public void setRecurring(boolean isRecurring) { - _isRecurring = isRecurring; - } /** - * @return true if the time information in this date group has been inferred - * as opposed to being explicity defined in the input. + * @return true if the time information in this group has been inferred, + * as opposed to being explicitly defined in the input */ public boolean isTimeInferred() { return _isTimeInferred; } - public void setIsTimeInferred(boolean isTimeInferred) { - this._isTimeInferred = isTimeInferred; + + public void setDateInferred(boolean isDateInferred) { + this._isDateInferred = isDateInferred; } - public Date getRecursUntil() { - return _recurringUntil; + public void setFullText(String fullText) { + this._fullText = fullText; } - public void setRecurringUntil(Date recurringUntil) { - _recurringUntil = recurringUntil; + + public void setIsTimeInferred(boolean isTimeInferred) { + this._isTimeInferred = isTimeInferred; } - public Map> getParseLocations() { - return _parseLocations; + public void setLine(int line) { + _line = line; } + public void setParseLocations(Map> parseLocations) { _parseLocations = parseLocations; } - public Tree getSyntaxTree() { - return _syntaxTree; + public void setPosition(int position) { + _position = position; + } + + public void setRecurring(boolean isRecurring) { + _isRecurring = isRecurring; + } + + public void setRecurringUntil(Date recurringUntil) { + _recurringUntil = recurringUntil; } public void setSyntaxTree(Tree syntaxTree) { _syntaxTree = syntaxTree; } + + public void setText(String text) { + _text = text; + } + } diff --git a/src/main/java/com/joestelmach/natty/IcsSearcher.java b/src/main/java/com/joestelmach/natty/IcsSearcher.java index b74d9805..a422cfe4 100644 --- a/src/main/java/com/joestelmach/natty/IcsSearcher.java +++ b/src/main/java/com/joestelmach/natty/IcsSearcher.java @@ -1,28 +1,24 @@ package com.joestelmach.natty; -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; -import java.util.logging.Level; -import java.util.logging.Logger; - import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Period; import net.fortuna.ical4j.model.PeriodList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.*; public class IcsSearcher { private static final String GMT = "GMT"; private static final String VEVENT = "VEVENT"; private static final String SUMMARY = "SUMMARY"; - private static final Logger _logger = Logger.getLogger("com.joestelmach.natty"); + private static final Logger _logger = LoggerFactory.getLogger(IcsSearcher.class); private net.fortuna.ical4j.model.Calendar _holidayCalendar; private String _calendarFileName; private TimeZone _timeZone; @@ -41,11 +37,11 @@ public Map findDates(int startYear, int endYear, String eventSumm _holidayCalendar = new CalendarBuilder().build(fin); } catch (IOException e) { - _logger.severe("Couln't open " + _calendarFileName); + _logger.error("Couln't open " + _calendarFileName); return holidays; } catch (ParserException e) { - _logger.severe("Couln't parse " + _calendarFileName); + _logger.error("Couln't parse " + _calendarFileName); return holidays; } } @@ -57,7 +53,7 @@ public Map findDates(int startYear, int endYear, String eventSumm period = new Period(from, to); } catch (ParseException e) { - _logger.log(Level.SEVERE, "Invalid start or end year: " + startYear + ", " + endYear, e); + _logger.error("Invalid start or end year: " + startYear + ", " + endYear, e); return holidays; } diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java index 88bcadca..6784eb35 100644 --- a/src/main/java/com/joestelmach/natty/ParseListener.java +++ b/src/main/java/com/joestelmach/natty/ParseListener.java @@ -65,7 +65,7 @@ public void exitRule(String filename, String ruleName) { } String text = builder.toString(); int line = tokenList.get(0).getLine(); - int start = tokenList.get(0).getCharPositionInLine(); + int start = tokenList.get(0).getCharPositionInLine() + 1; int end = start + text.length(); ParseLocation location = new ParseLocation(); diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index fa6c41f5..fb36bc2d 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -50,11 +50,11 @@ public Parser() { * @return */ public List parse(String value) { - + // lex the input value to obtain our global token stream ANTLRInputStream input = null; try { - input = new ANTLRNoCaseInputStream(new ByteArrayInputStream(value.trim().getBytes())); + input = new ANTLRNoCaseInputStream(new ByteArrayInputStream(value.getBytes())); } catch (IOException e) { _logger.error("could not lex input", e); @@ -66,59 +66,87 @@ public List parse(String value) { // and parse each of them List groups = new ArrayList(); + TokenStream lastStream = null; for(TokenStream stream:streams) { + lastStream = stream; List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); - DateGroup group = singleParse(stream); + DateGroup group = singleParse(stream, value); while((group == null || group.getDates().size() == 0) && tokens.size() > 0) { if(group == null || group.getDates().size() == 0) { - // if we're down to only two tokens in our token stream, we can't continue - if(tokens.size() <= 2) { - tokens.clear(); + // we have two options: + // 1. Continuously remove tokens from the end of the stream and re-parse. This will + // recover from the case of an extraneous token at the end of the token stream. + // For example: 'june 20th on' + List endRemovedTokens = new ArrayList(tokens); + while((group == null || group.getDates().isEmpty()) && !endRemovedTokens.isEmpty()) { + endRemovedTokens = endRemovedTokens.subList(0, endRemovedTokens.size() - 1); + TokenStream newStream = new CommonTokenStream(new NattyTokenSource(endRemovedTokens)); + group = singleParse(newStream, value); + lastStream = newStream; } - - // otherwise, we have two options: - else { - - // 1. Continuously remove tokens from the end of the stream and re-parse. This will - // recover from the case of an extaneous token at the end of the token stream. - // For example: 'june 20th on' - List endRemovedTokens = new ArrayList(tokens); - while((group == null || group.getDates().isEmpty()) && endRemovedTokens.size() > 2) { - endRemovedTokens = endRemovedTokens.subList(0, endRemovedTokens.size() - 1); - cleanupGroup(endRemovedTokens); - TokenStream newStream = new CommonTokenStream(new NattyTokenSource(endRemovedTokens)); - group = singleParse(newStream); - } - - // 2. Continuously look for another possible starting point in the token - // stream and re-parse. - if(group == null || group.getDates().isEmpty()) { - tokens = tokens.subList(1, tokens.size()); - Iterator iter = tokens.iterator(); - while(iter.hasNext()) { - Token token = iter.next(); - if(!DateParser.FOLLOW_empty_in_parse186.member(token.getType())) { - iter.remove(); - } - else { - break; - } + + // 2. Continuously look for another possible starting point in the token + // stream and re-parse. + while((group == null || group.getDates().isEmpty()) && tokens.size() >= 1) { + tokens = tokens.subList(1, tokens.size()); + Iterator iter = tokens.iterator(); + while(iter.hasNext()) { + Token token = iter.next(); + if(!DateParser.FOLLOW_empty_in_parse186.member(token.getType())) { + iter.remove(); + } + else { + break; } - cleanupGroup(tokens); - TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); - group = singleParse(newStream); } + TokenStream newStream = new CommonTokenStream(new NattyTokenSource(tokens)); + group = singleParse(newStream, value); + lastStream = newStream; } } } - // if a group with some date(s) was found, we add it - if(group != null && group.getDates().size() > 0) { - groups.add(group); + + // If a group with at least one date was found, we'll most likely want to add it to our list, + // but not if multiple streams were found and the group contains only numeric time information. + // For example: A full text string of '1' should parse to 1 o'clock, but 'I need 1 hard drive' + // should result in no groups found. + if(group != null && !group.getDates().isEmpty() && + (streams.size() == 1 || !group.isDateInferred() || !isAllNumeric(lastStream))) { + + // Additionally, we'll only accept this group if the associated text does not have an + // alphabetic character to the immediate left or right, which would indicate a portion + // of a word was tokenized. For example, 'nightingale' will result in a 'NIGHT' token, + // but there's clearly no datetime information there. + group.setFullText(value); + String prefix = group.getPrefix(1); + String suffix = group.getSuffix(1); + if((prefix.isEmpty() || !Character.isLetter(prefix.charAt(0))) && + (suffix.isEmpty() || !Character.isLetter(suffix.charAt(0)))) { + + groups.add(group); + } } } return groups; } + + /** + * Determines if a token stream contains only numeric tokens + * @param stream + * @return true if all tokens in the given stream can be parsed as an integer + */ + private boolean isAllNumeric(TokenStream stream) { + List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); + for(Token token:tokens) { + try { + Integer.parseInt(token.getText()); + } catch(NumberFormatException e) { + return false; + } + } + return true; + } /** * Parses the token stream for a SINGLE date time alternative. This @@ -128,7 +156,7 @@ public List parse(String value) { * @param stream * @return */ - private DateGroup singleParse(TokenStream stream) { + private DateGroup singleParse(TokenStream stream, String fullText) { DateGroup group = null; List tokens = ((NattyTokenSource) stream.getTokenSource()).getTokens(); if(tokens.isEmpty()) return group; @@ -149,7 +177,7 @@ private DateGroup singleParse(TokenStream stream) { // we only continue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { - _logger.info("sub-token stream: " + tokenString.toString()); + _logger.info("PARSE: " + tokenString.toString()); // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); @@ -172,12 +200,24 @@ private DateGroup singleParse(TokenStream stream) { group.setPosition(location.getStart()); group.setSyntaxTree(tree); group.setParseLocations(listener.getLocations()); + group.setFullText(fullText); + + // if the group's matching text has an immediate alphabetic prefix or suffix, + // we ignore this result + String prefix = group.getPrefix(1); + String suffix = group.getSuffix(1); + if((!prefix.isEmpty() && Character.isLetter(prefix.charAt(0))) || + (!suffix.isEmpty() && Character.isLetter(suffix.charAt(0)))) { + + group = null; + } + } } catch(RecognitionException e) { _logger.debug("Could not parse input", e); } - + return group; } @@ -196,25 +236,19 @@ private List collectTokenStreams(TokenStream stream) { List currentGroup = null; List> groups = new ArrayList>(); Token currentToken; - Token previousToken = null; StringBuilder tokenString = new StringBuilder(); while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { tokenString.append(DateParser.tokenNames[currentToken.getType()]).append(" "); // we're currently NOT collecting for a possible date group if(currentGroup == null) { - // skip over white space, unknowns, and tokens directly prefixed by unknown. (For example: - // 'know' will lex to UNKNOWN NOW, but NOW can only be valid if not part of a larger word. + // skip over white space and tokens not immediately followed by white space or + // known tokens that cannot be the start of a date if(currentToken.getType() != DateLexer.WHITE_SPACE && - (previousToken == null || previousToken.getType() != DateLexer.UNKNOWN)) { - - // if the token is a possible date start token, we start a new collection - if(DateParser.FOLLOW_empty_in_parse186.member( - currentToken.getType()) || currentToken.getType() == DateLexer.UNKNOWN) { + DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) { - currentGroup = new ArrayList(); - currentGroup.add(currentToken); - } + currentGroup = new ArrayList(); + currentGroup.add(currentToken); } } @@ -226,35 +260,32 @@ private List collectTokenStreams(TokenStream stream) { } else { - // if this is an unknown token, we need to end the current group + // if this is an unknown token, we'll close out the current group if(currentToken.getType() == DateLexer.UNKNOWN) { - if(currentGroup.size() > 0) { - currentGroup.add(currentToken); + if(!currentGroup.isEmpty()) { groups.add(currentGroup); } currentGroup = null; } // otherwise, the token is known and we're currently collecting for - // a group, we add it if it's not a dot + // a group, so we'll add it if it's not a dot else if(currentToken.getType() != DateLexer.DOT) { currentGroup.add(currentToken); } } } - - previousToken = currentToken; } if(currentGroup != null) { groups.add(currentGroup); } - _logger.info("global token stream: " + tokenString.toString()); + _logger.info("STREAM: " + tokenString.toString()); List streams = new ArrayList(); for(List group:groups) { - cleanupGroup(group); if(!group.isEmpty()) { StringBuilder builder = new StringBuilder(); + builder.append("GROUP: "); for (Token token : group) { builder.append(DateParser.tokenNames[token.getType()]).append(" "); } @@ -267,64 +298,4 @@ else if(currentToken.getType() != DateLexer.DOT) { return streams; } - /** - * Removes unwanted tokens from the given token group - * @param group - */ - private void cleanupGroup(List group) { - - // remove contiguous white space - Iterator iter = group.iterator(); - Token previousToken = null; - while(iter.hasNext()) { - Token token = iter.next(); - if(previousToken != null && previousToken.getType() == DateParser.WHITE_SPACE) { - if(token.getType() == DateParser.WHITE_SPACE) { - iter.remove(); - } - } - previousToken = token; - } - - // remove leading white space - if(group.size() > 0) { - boolean skip = false; - Iterator it1 = group.iterator(); - while(it1.hasNext()) { - Token tk = it1.next(); - if(tk.getType() == DateParser.WHITE_SPACE) { - it1.remove(); - skip = false; - } else if(tk.getType() == DateParser.UNKNOWN) { - it1.remove(); - skip = true; - } else if(skip) { - it1.remove(); - } else if(!DateParser.FOLLOW_empty_in_parse186.member(tk.getType())) { - it1.remove(); - } else break; - } - } - - // and trailing white space - if(group.size() > 0) { - boolean skip = false; - while(group.size() > 0) { - Token lastToken = group.get(group.size() - 1); - if(lastToken.getType() == DateParser.WHITE_SPACE) { - group.remove(lastToken); - skip = false; - } - else if(lastToken.getType() == DateParser.UNKNOWN) { - group.remove(lastToken); - skip = true; - } - else if(skip) { - group.remove(lastToken); - } - else break; - } - } - - } } diff --git a/src/main/java/com/joestelmach/natty/WalkerState.java b/src/main/java/com/joestelmach/natty/WalkerState.java index c0aa1a4a..fe35927d 100644 --- a/src/main/java/com/joestelmach/natty/WalkerState.java +++ b/src/main/java/com/joestelmach/natty/WalkerState.java @@ -601,6 +601,7 @@ private void markDateInvocation() { _updatePreviousDates = !_dateGivenInGroup; _dateGivenInGroup = true; + _dateGroup.setDateInferred(false); if(_firstDateInvocationInGroup) { // if a time has been given within the current date group, diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index bd09e130..78d44f3a 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -358,15 +358,10 @@ public void testRelativeDateDifferentTimezone() { TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern")); } - @Test - public void testNoDates() { - List dates = parseCollection("Fried Chicken, Wedding Dinner"); - Assert.assertEquals(0, dates.size()); - } - public static void main(String[] args) { - String value = "martin luther king, jr. day"; + String value="this friday."; + org.apache.log4j.Logger.getRootLogger().setLevel(Level.INFO); Parser parser = new Parser(); diff --git a/src/test/java/com/joestelmach/natty/SearchTest.java b/src/test/java/com/joestelmach/natty/SearchTest.java index 71fb2b5a..e3addef4 100644 --- a/src/test/java/com/joestelmach/natty/SearchTest.java +++ b/src/test/java/com/joestelmach/natty/SearchTest.java @@ -32,7 +32,7 @@ public void test() throws Exception { Assert.assertEquals(1, groups.size()); DateGroup group = groups.get(0); Assert.assertEquals(1, group.getLine()); - Assert.assertEquals(5, group.getPosition()); + Assert.assertEquals(6, group.getPosition()); Assert.assertEquals(1, group.getDates().size()); validateDate(group.getDates().get(0), 2, 21, 2011); validateTime(group.getDates().get(0), 9, 0, 0); @@ -41,7 +41,7 @@ public void test() throws Exception { Assert.assertEquals(1, groups.size()); group = groups.get(0); Assert.assertEquals(1, group.getLine()); - Assert.assertEquals(18, group.getPosition()); + Assert.assertEquals(19, group.getPosition()); Assert.assertEquals(1, group.getDates().size()); validateDate(group.getDates().get(0), 2, 21, 2011); validateTime(group.getDates().get(0), 10, 0, 0); @@ -161,21 +161,15 @@ public void test() throws Exception { validateDate(dates.get(0), 2, 27, 2011); groups = parser.parse("I want to be able to jog 3 miles non-stop by September."); - Assert.assertEquals(2, groups.size()); + Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDateTime(dates.get(0), 2, 20, 2011, 3, 0, 0); - dates = groups.get(1).getDates(); - Assert.assertEquals(1, dates.size()); - validateDate(dates.get(0), 9, 1, 2011); + validateDate(dates.get(0), 9, 1, 2011); groups = parser.parse("I want to lose 10 lbs in 10 days"); - Assert.assertEquals(2, groups.size()); + Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDateTime(dates.get(0), 2, 20, 2011, 10, 0, 0); - dates = groups.get(1).getDates(); - Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 3, 2, 2011); groups = parser.parse("I want to visit my grandfathers grave on December 30 2011"); @@ -185,27 +179,21 @@ public void test() throws Exception { validateDate(dates.get(0), 12, 30, 2011); groups = parser.parse("i want to have 1 kid this year"); - Assert.assertEquals(2, groups.size()); + Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDateTime(dates.get(0), 2, 20, 2011, 1, 0, 0); - dates = groups.get(1).getDates(); - Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 2, 20, 2011); - + groups = parser.parse("save $1000 by September"); Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDate(dates.get(0), 9, 1, 2011); - + validateDate(dates.get(0), 9, 1, 2011); + groups = parser.parse("have my son play at muse music in provo UT at the 3 band cause they always have fog on the third band at 7:30"); - Assert.assertEquals(2, groups.size()); + Assert.assertEquals(1, groups.size()); dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); - validateDateTime(dates.get(0), 2, 20, 2011, 3, 0, 0); - dates = groups.get(1).getDates(); - Assert.assertEquals(1, dates.size()); validateDateTime(dates.get(0), 2, 20, 2011, 7, 30, 0); groups = parser.parse("i want to eat chinese tonight"); @@ -237,7 +225,7 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 13, 2013); - Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals(17, groups.get(0).getPosition()); Assert.assertEquals("sep 13, 2013", groups.get(0).getText()); groups = parser.parse("hillary clinton 9/13/2013"); @@ -245,7 +233,7 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 13, 2013); - Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals(17, groups.get(0).getPosition()); Assert.assertEquals("9/13/2013", groups.get(0).getText()); groups = parser.parse("hillary clintoo sep 13, 2013"); @@ -253,7 +241,7 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 13, 2013); - Assert.assertEquals(16, groups.get(0).getPosition()); + Assert.assertEquals(17, groups.get(0).getPosition()); Assert.assertEquals("sep 13, 2013", groups.get(0).getText()); groups = parser.parse("clinton sep 13 2013"); @@ -261,7 +249,7 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 9, 13, 2013); - Assert.assertEquals(8, groups.get(0).getPosition()); + Assert.assertEquals(9, groups.get(0).getPosition()); Assert.assertEquals("sep 13 2013", groups.get(0).getText()); groups = parser.parse("wedding dinner with Pam"); @@ -284,5 +272,117 @@ public void test() throws Exception { dates = groups.get(0).getDates(); Assert.assertEquals(1, dates.size()); validateDate(dates.get(0), 2, 21, 2011); + + groups = parser.parse("**SHOT 01/31/15**"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 1, 31, 2015); + + groups = parser.parse("KOSTROMA REGION, RUSSIA. SEPTEMBER 24, 2014. A woman cleaning up fallen leaves on the grounds of the Shchelykovo museum reserve of Russian playwright Alexander Ostrovsky."); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 9, 24, 2014); + + groups = parser.parse("21 November 2014-NYC-USA **** STRICTLY NOT AVAILABLE FOR USA ***"); + Assert.assertEquals(1, groups.size()); + dates = groups.get(0).getDates(); + Assert.assertEquals(1, dates.size()); + validateDate(dates.get(0), 11, 21, 2014); + } + + @Test + public void testLocations() throws Exception { + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("2/20/2011"); + CalendarSource.setBaseDate(reference); + + Parser parser = new Parser(); + List groups = parser.parse("I want to go to the movies on september 1st. Or maybe we should go on October 3rd."); + Assert.assertEquals(2, groups.size()); + DateGroup group = groups.get(0); + Assert.assertEquals(1, group.getLine()); + Assert.assertEquals(31, group.getPosition()); + group = groups.get(1); + Assert.assertEquals(1, group.getLine()); + Assert.assertEquals(72, group.getPosition()); + + groups = parser.parse("I want to go to \nthe movies on september 1st to see The Alan Turing Movie."); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + Assert.assertEquals(2, group.getLine()); + Assert.assertEquals(15, group.getPosition()); + } + + @Test + public void testPrefixSuffix() throws Exception { + + Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("2/20/2011"); + CalendarSource.setBaseDate(reference); + + // no prefix or suffix + Parser parser = new Parser(); + List groups = parser.parse("Sept. 1st"); + Assert.assertEquals(1, groups.size()); + DateGroup group = groups.get(0); + Assert.assertEquals(0, group.getPrefix(3).length()); + Assert.assertEquals(0, group.getSuffix(3).length()); + + // no prefix + groups = parser.parse("Sept. 1st is the date"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + Assert.assertEquals(0, group.getPrefix(3).length()); + String suffix = group.getSuffix(3); + Assert.assertEquals(3, suffix.length()); + Assert.assertEquals(" is",suffix); + + // no suffix + groups = parser.parse("The date is Sept. 1st"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + String prefix = group.getPrefix(3); + Assert.assertEquals(3, prefix.length()); + Assert.assertEquals("is ", prefix); + Assert.assertEquals(0, group.getSuffix(3).length()); + + // ask for a larger prefix than available + groups = parser.parse("a Sept. 1st"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + prefix = group.getPrefix(5); + Assert.assertEquals(2, prefix.length()); + Assert.assertEquals("a ", prefix); + Assert.assertEquals(0, group.getSuffix(3).length()); + + // ask for a larger suffix than available + groups = parser.parse("Sept. 1st a"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + suffix = group.getSuffix(5); + Assert.assertEquals(2,suffix.length()); + Assert.assertEquals(" a", suffix); + Assert.assertEquals(0, group.getPrefix(3).length()); + + // ask for a larger prefix and suffix than available + groups = parser.parse("a Sept. 1st a"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + prefix = group.getPrefix(5); + suffix = group.getSuffix(5); + Assert.assertEquals(2,prefix.length()); + Assert.assertEquals("a ", prefix); + Assert.assertEquals(2,suffix.length()); + Assert.assertEquals(" a", suffix); } + + @Test + public void testNoDates() { + List dates = parseCollection("Fried Chicken, Wedding Dinner"); + Assert.assertEquals(0, dates.size()); + + parseCollection("Cleveland"); + Assert.assertEquals(0, dates.size()); + } + } From 27841adae55bdd320c9f3b5636ec98b8b16d5640 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 22 Mar 2015 19:53:51 -0400 Subject: [PATCH 130/134] Preserve dots in the middle of the token stream Fixes #116 --- .../joestelmach/natty/generated/DateParser.g | 2 +- .../java/com/joestelmach/natty/Parser.java | 69 ++++++++++++++----- .../java/com/joestelmach/natty/DateTest.java | 4 +- .../java/com/joestelmach/natty/TimeTest.java | 19 +++++ 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g index c19348f4..c195947a 100644 --- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g +++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g @@ -815,7 +815,7 @@ explicit_time ; explicit_time_hours_minutes returns [String hours, String minutes, String ampm] - : hours COLON? minutes (COLON? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? + : hours (COLON | DOT)? minutes ((COLON | DOT)? seconds)? (WHITE_SPACE? (meridian_indicator | (MILITARY_HOUR_SUFFIX | HOUR)))? {$hours=$hours.text; $minutes=$minutes.text; $ampm=$meridian_indicator.text;} -> hours minutes seconds? meridian_indicator? diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index fb36bc2d..84f55dc6 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -13,10 +13,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.TimeZone; +import java.util.*; /** * @@ -27,6 +24,23 @@ public class Parser { private static final Logger _logger = LoggerFactory.getLogger(Parser.class); + /** + * Tokens that should be removed from the end any list of tokens before parsing. These are + * valid tokens, but could never add any meaningful parsing information when located at the + * end of a token stream. + */ + private static final Set IGNORED_TRAILING_TOKENS = + new HashSet(Arrays.asList(new Integer[] { + DateLexer.DOT, + DateLexer.COLON, + DateLexer.COMMA, + DateLexer.DASH, + DateLexer.SLASH, + DateLexer.DOT, + DateLexer.PLUS, + DateLexer.SINGLE_QUOTE + })); + /** * Creates a new parser using the given time zone as the default * @param defaultTimeZone @@ -236,16 +250,17 @@ private List collectTokenStreams(TokenStream stream) { List currentGroup = null; List> groups = new ArrayList>(); Token currentToken; + int currentTokenType; StringBuilder tokenString = new StringBuilder(); while((currentToken = stream.getTokenSource().nextToken()).getType() != DateLexer.EOF) { - tokenString.append(DateParser.tokenNames[currentToken.getType()]).append(" "); + currentTokenType = currentToken.getType(); + tokenString.append(DateParser.tokenNames[currentTokenType]).append(" "); // we're currently NOT collecting for a possible date group if(currentGroup == null) { - // skip over white space and tokens not immediately followed by white space or - // known tokens that cannot be the start of a date - if(currentToken.getType() != DateLexer.WHITE_SPACE && - DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) { + // skip over white space and known tokens that cannot be the start of a date + if(currentTokenType != DateLexer.WHITE_SPACE && + DateParser.FOLLOW_empty_in_parse186.member(currentTokenType)) { currentGroup = new ArrayList(); currentGroup.add(currentToken); @@ -255,21 +270,19 @@ private List collectTokenStreams(TokenStream stream) { // we're currently collecting else { // preserve white space - if(currentToken.getType() == DateLexer.WHITE_SPACE) { + if(currentTokenType == DateLexer.WHITE_SPACE) { currentGroup.add(currentToken); } else { // if this is an unknown token, we'll close out the current group - if(currentToken.getType() == DateLexer.UNKNOWN) { - if(!currentGroup.isEmpty()) { - groups.add(currentGroup); - } + if(currentTokenType == DateLexer.UNKNOWN) { + addGroup(currentGroup, groups); currentGroup = null; } // otherwise, the token is known and we're currently collecting for - // a group, so we'll add it if it's not a dot - else if(currentToken.getType() != DateLexer.DOT) { + // a group, so we'll add it to the current group + else { currentGroup.add(currentToken); } } @@ -277,7 +290,7 @@ else if(currentToken.getType() != DateLexer.DOT) { } if(currentGroup != null) { - groups.add(currentGroup); + addGroup(currentGroup, groups); } _logger.info("STREAM: " + tokenString.toString()); @@ -297,5 +310,25 @@ else if(currentToken.getType() != DateLexer.DOT) { return streams; } - + + /** + * Cleans up the given group and adds it to the list of groups if still valid + * @param group + * @param groups + */ + private void addGroup(List group, List> groups) { + + if(group.isEmpty()) return; + + // remove trailing tokens that should be ignored + while(!group.isEmpty() && IGNORED_TRAILING_TOKENS.contains( + group.get(group.size() - 1).getType())) { + group.remove(group.size() - 1); + } + + // if the group still has some tokens left, we'll add it to our list of groups + if(!group.isEmpty()) { + groups.add(group); + } + } } diff --git a/src/test/java/com/joestelmach/natty/DateTest.java b/src/test/java/com/joestelmach/natty/DateTest.java index 78d44f3a..d46059dc 100644 --- a/src/test/java/com/joestelmach/natty/DateTest.java +++ b/src/test/java/com/joestelmach/natty/DateTest.java @@ -360,7 +360,9 @@ public void testRelativeDateDifferentTimezone() { public static void main(String[] args) { - String value="this friday."; + String value="5.30pm"; + value = "this friday."; + value = "I want to plan a get-together with my friends for this Friday."; org.apache.log4j.Logger.getRootLogger().setLevel(Level.INFO); diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java index e664ac10..ec7c21af 100644 --- a/src/test/java/com/joestelmach/natty/TimeTest.java +++ b/src/test/java/com/joestelmach/natty/TimeTest.java @@ -133,4 +133,23 @@ public void testRange() throws Exception { validateTime(dates.get(0), 12, 0, 0); validateTime(dates.get(1), 12, 0, 10); } + + @Test + public void testText() throws Exception { + CalendarSource.setBaseDate(DateFormat.getTimeInstance(DateFormat.SHORT).parse("12:00 pm")); + + List groups = _parser.parse("5.30pm"); + Assert.assertEquals(1, groups.size()); + DateGroup group = groups.get(0); + Assert.assertEquals(1, group.getDates().size()); + validateTime(group.getDates().get(0), 17, 30, 0); + Assert.assertEquals("5.30pm", group.getText()); + + groups = _parser.parse("5:30pm"); + Assert.assertEquals(1, groups.size()); + group = groups.get(0); + Assert.assertEquals(1, group.getDates().size()); + validateTime(group.getDates().get(0), 17, 30, 0); + Assert.assertEquals("5:30pm", group.getText()); + } } From 8be500a92443a846b89718bdae958fd96f327217 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Sun, 22 Mar 2015 20:09:31 -0400 Subject: [PATCH 131/134] closes #113 --- src/test/java/com/joestelmach/natty/DateTimeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java index f7c4c139..ac4152b6 100644 --- a/src/test/java/com/joestelmach/natty/DateTimeTest.java +++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java @@ -42,6 +42,7 @@ public void testSpecific() throws Exception { validateDateTime("April 20, 10am", 4, 20, 2012, 10, 0, 0); validateDateTime("April 20 10", 4, 20, 2012, 10, 0, 0); validateDateTime("April 20 at 10 am", 4, 20, 2012, 10, 0, 0); + validateDateTime("Mar 16, 2015 3:33:39 PM", 3, 16, 2015, 15, 33, 39); } @Test From 6f47895eeda4b0769dd80f426d62b329b7d0be81 Mon Sep 17 00:00:00 2001 From: Brian Kramer Date: Mon, 20 Apr 2015 08:15:47 -0500 Subject: [PATCH 132/134] Make log4j test only dependency. Allow any slf4j backend to be used instead of always bringing in log4j. --- pom.xml | 1 + src/main/resources/log4j.properties | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 src/main/resources/log4j.properties diff --git a/pom.xml b/pom.xml index dd63e1a5..d89433bf 100644 --- a/pom.xml +++ b/pom.xml @@ -247,6 +247,7 @@ org.slf4j slf4j-log4j12 1.7.10 + test diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties deleted file mode 100644 index b9960fec..00000000 --- a/src/main/resources/log4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -log4j.rootLogger=info, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.err -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss} %c{1}:%L - %m %n \ No newline at end of file From 07c9b4ae289468ea43b5ede2c9acb47e258b49b1 Mon Sep 17 00:00:00 2001 From: Joe Stelmach Date: Mon, 11 May 2015 23:28:04 -0400 Subject: [PATCH 133/134] [maven-release-plugin] prepare release natty-0.11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d89433bf..f1055a4b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.joestelmach natty jar - 0.11-SNAPSHOT + 0.11 Natty Date Parser natural language date parser http://natty.joestelmach.com From d6aad83ba73ef48f0fd736c4247347b2dd803276 Mon Sep 17 00:00:00 2001 From: olegkuznetsov Date: Sat, 10 Mar 2018 15:57:33 +0300 Subject: [PATCH 134/134] Set log level to ERROR --- src/main/java/com/joestelmach/natty/Parser.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/joestelmach/natty/Parser.java b/src/main/java/com/joestelmach/natty/Parser.java index 84f55dc6..d43120df 100644 --- a/src/main/java/com/joestelmach/natty/Parser.java +++ b/src/main/java/com/joestelmach/natty/Parser.java @@ -191,7 +191,7 @@ private DateGroup singleParse(TokenStream stream, String fullText) { // we only continue if a meaningful syntax tree has been built if(tree.getChildCount() > 0) { - _logger.info("PARSE: " + tokenString.toString()); + _logger.error("PARSE: " + tokenString.toString()); // rewrite the tree (temporary fix for http://www.antlr.org/jira/browse/ANTLR-427) CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); @@ -204,7 +204,7 @@ private DateGroup singleParse(TokenStream stream, String fullText) { DateWalker walker = new DateWalker(nodes); walker.getState().setDefaultTimeZone(_defaultTimeZone); walker.parse(); - _logger.info("AST: " + tree.toStringTree()); + _logger.error("AST: " + tree.toStringTree()); // run through the results and append the parse information group = walker.getState().getDateGroup(); @@ -293,7 +293,7 @@ private List collectTokenStreams(TokenStream stream) { addGroup(currentGroup, groups); } - _logger.info("STREAM: " + tokenString.toString()); + _logger.error("STREAM: " + tokenString.toString()); List streams = new ArrayList(); for(List group:groups) { if(!group.isEmpty()) { @@ -302,7 +302,7 @@ private List collectTokenStreams(TokenStream stream) { for (Token token : group) { builder.append(DateParser.tokenNames[token.getType()]).append(" "); } - _logger.info(builder.toString()); + _logger.error(builder.toString()); streams.add(new CommonTokenStream(new NattyTokenSource(group))); }