diff --git a/.gitignore b/.gitignore
index eba5e86e..3e7a90fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
.classpath
.settings
.project
+.idea
+.idea/misc.xml
+workspace.xml
bin
target
src/main/java/com/joestelmach/natty/generated/*
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..c857101c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+
+
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/README b/README
index 570e8f73..69d4bdd0 100644
--- a/README
+++ b/README
@@ -1,35 +1,7 @@
-Natty is a natural language date parser written in Java. Given a date
-expression, natty will apply standard language recognition and translation
+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 ==========
-
-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
+Complete documentation can be found here:
+http://natty.joestelmach.com
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 b995ea01..1d1fbd30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
com.joestelmach
natty
jar
- 0.2.2-SNAPSHOT
+ 0.9-SNAPSHOT
Natty Date Parser
natural language date parser
http://natty.joestelmach.com
@@ -70,9 +70,10 @@
maven-compiler-plugin
+ 2.3.2
- 1.5
- 1.5
+ 1.6
+ 1.6
@@ -93,46 +94,6 @@
-
-
- maven-antrun-plugin
-
-
- createDebugGrammars
- validate
-
- run
-
-
-
-
-
-
-
-
-
-
-
-
-
- deleteDebugGrammars
- test
-
- run
-
-
-
-
-
-
-
-
-
-
-
-
-
-
org.antlr
@@ -140,37 +101,44 @@
3.2
-
- non-debug
+ debug
antlr
- false
+ 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
+
- com/joestelmach/natty/generated/DebugDateParser.g
- com/joestelmach/natty/generated/DebugDateWalker.g
+ com/joestelmach/natty/generated/imports/NumericRules.g
-
-
+
- debug
+ non-debug
antlr
- true
+ false
src/main/java
-
- com/joestelmach/natty/generated/DebugDateParser.g
- com/joestelmach/natty/generated/DebugDateWalker.g
-
+ src/main/antlr3/com/joestelmach/natty/generated/imports
+ 10000
+
+ com/joestelmach/natty/generated/DateLexer.g
+ com/joestelmach/natty/generated/DateParser.g
+ com/joestelmach/natty/generated/imports/NumericRules.g
+
+
@@ -209,6 +177,7 @@
org.apache.maven.plugins
maven-gpg-plugin
+ 1.4
sign-artifacts
@@ -224,6 +193,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
+ 2.9.1
attach-javadocs
@@ -240,6 +210,7 @@
org.apache.maven.plugins
maven-source-plugin
+ 2.2.1
attach-sources
@@ -262,11 +233,11 @@
3.2
-
- antlr
- antlr
- 2.7.7
-
+
+ org.mnode.ical4j
+ ical4j
+ 1.0.2
+
junit
diff --git a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g
index d892d9dc..a8e87bbc 100644
--- a/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g
+++ b/src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g
@@ -2,51 +2,72 @@ 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 re) {
+ String message = getErrorHeader(re);
+ try { message += getErrorMessage(re, tokenNames); } catch(Exception e) {}
+ _logger.fine(message);
+ }
+}
+
// ********** 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' ;
-DAY : 'day' | 'days' ;
-WEEK : 'week' | 'weeks' ;
-MONTH : 'month' | 'months' ;
-YEAR : 'year' | 'years' ;
+HOUR : 'hour' | 'hours' | 'hr' DOT? | 'hrs' DOT?;
+MINUTE : 'minute' | 'minutes' | 'min' DOT? | 'mins' DOT?;
+DAY : 'day' | 'days' ;
+WEEK : 'week' | 'weeks' | 'wks' DOT?;
+MONTH : 'month' | 'months';
+YEAR : 'year' | 'year' SINGLE_QUOTE? 's' | 'yrs' DOT?;
TODAY : 'today';
TOMORROW : 'tomorow' | 'tomorrow' | 'tommorow' | 'tommorrow';
+TONIGHT : 'tonight';
YESTERDAY : 'yesterday';
+// ********** recurrence rules **********
+
+EVERY : 'every';
+UNTIL : 'until';
+
// ********** time rules **********
-
-AM : 'am' | 'a.m.' | 'a';
-PM : 'pm' | 'p.m.' | 'p';
+
+AT : 'at' | '@';
+AFTER : 'after';
+PAST : 'past';
+AM : 'am' | 'a.m' DOT? | 'a';
+PM : 'pm' | 'p.m' DOT? | 'p';
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' | 'eve';
+NIGHT : 'night';
UTC : 'utc' | 'gmt' | 'z';
EST : 'est' | 'edt' | 'et';
@@ -192,7 +213,7 @@ TWENTY : 'twenty';
THIRTY : 'thirty';
FIRST : 'first';
-SECOND : 'second';
+SECOND : 'second' | 'seconds' | 'sec' | 'secs';
THIRD : 'third';
FOURTH : 'fourth';
FIFTH : 'fifth';
@@ -229,24 +250,26 @@ DOT : '.';
PLUS : '+';
SINGLE_QUOTE : '\'';
+FOR : 'for';
IN : 'in';
+AN : 'an';
THE : 'the';
OR : 'or';
-AT : 'at';
+AND : 'and';
+TO : 'to';
+THROUGH : 'through';
ON : 'on';
OF : 'of';
THIS : 'this';
THAT : 'that';
-LAST : 'last';
+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';
@@ -255,6 +278,56 @@ 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' | 'patty' | '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';
+
+// ********** season specific **********
+
+WINTER : 'winter' 's'?;
+FALL : 'fall' 's'?;
+AUTUMN : 'autumn' 's'?;
+SPRING : 'spring' 's'?;
+SUMMER : 'summer' 's'?;
+
+UNKNOWN
+ : UNKNOWN_CHAR
+ ;
+
+fragment UNKNOWN_CHAR
+ : ~(SPACE | DOT)
+ ;
+
fragment DIGIT : '0'..'9';
-fragment SPACE : ' ' | '\t' | '\n' | '\r' ;
\ No newline at end of file
+fragment SPACE : ' ' | '\t' | '\n' | '\r' | '\u00A0';
\ 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 c17066ad..83b38eaf 100644
--- a/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g
+++ b/src/main/antlr3/com/joestelmach/natty/generated/DateParser.g
@@ -1,15 +1,18 @@
parser grammar DateParser;
options {
- output=AST;
tokenVocab=DateLexer;
+ output=AST;
}
+import NumericRules;
+
tokens {
INT;
MONTH_OF_YEAR;
DAY_OF_MONTH;
DAY_OF_WEEK;
+ DAY_OF_YEAR;
YEAR_OF;
DATE_TIME;
DATE_TIME_ALTERNATIVE;
@@ -21,32 +24,51 @@ tokens {
EXPLICIT_SEEK;
SPAN;
EXPLICIT_TIME;
+ RELATIVE_TIME;
HOURS_OF_DAY;
MINUTES_OF_HOUR;
SECONDS_OF_MINUTE;
AM_PM;
ZONE;
ZONE_OFFSET;
+ RECURRENCE;
+ HOLIDAY;
+ SEASON;
}
@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);
+ }
+}
+
parse
- : (date_time_alternative)=> date_time_alternative
- | date_time -> ^(DATE_TIME_ALTERNATIVE date_time)
+ : 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
+ :
+ ;
+
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?)
+ | relative_time -> ^(DATE_TIME relative_time?)
;
date_time_separator
@@ -61,63 +83,123 @@ time_date_separator
;
date
- : (formal_date)=> formal_date
- | (relaxed_date)=> relaxed_date
- | explicit_relative_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
- // "next wed or thurs" , "next wed, thurs, or fri"
- : (alternative_day_of_week_list)=> alternative_day_of_week_list
+
+ // 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
-> ^(DATE_TIME_ALTERNATIVE alternative_day_of_week_list)
- // month day or day time
+ // feb 16, 17, or 18
| (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 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?)+)
-
+
// 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 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)) 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?)))
+
+ // 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)+
+ -> ^(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)
+ ;
+
+conjunction
+ : COMMA? WHITE_SPACE (AND | OR | TO | THROUGH | DASH) WHITE_SPACE
;
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?)+
+ // 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?)
;
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
- : COMMA (WHITE_SPACE | WHITE_SPACE OR WHITE_SPACE)
- | WHITE_SPACE OR WHITE_SPACE
- ;
-
-date_list_separator
- : (COMMA WHITE_SPACE?) | (WHITE_SPACE OR WHITE_SPACE)
+ : COMMA (WHITE_SPACE | conjunction) | conjunction
;
alternative_direction
@@ -139,7 +221,10 @@ global_date_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"]
+
// 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
@@ -161,7 +246,7 @@ global_date_prefix
;
prefix_direction
- : AFTER -> DIRECTION[">"]
+ : (AFTER | FROM | ON) -> DIRECTION[">"]
| BEFORE -> DIRECTION["<"]
;
@@ -170,32 +255,18 @@ 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
- WHITE_SPACE (OF WHITE_SPACE)? relaxed_month relaxed_year_prefix relaxed_year)=>
- 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
-
- // 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
-
- // 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
+ 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)?
+
+ // 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_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
@@ -225,8 +296,17 @@ 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
+ : SINGLE_QUOTE int_00_to_99_mandatory_prefix
-> ^(YEAR_OF int_00_to_99_mandatory_prefix)
| int_four_digits
@@ -241,12 +321,21 @@ relaxed_year_prefix
formal_date
// 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 formal_date_separator formal_day_of_month
- -> ^(EXPLICIT_DATE formal_month_of_year formal_day_of_month relaxed_day_of_week? formal_year_four_digits)
+ : 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
| 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?)
+
+ // 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_year_four_digits
+ -> ^(EXPLICIT_DATE formal_year_four_digits)
;
formal_month_of_year
@@ -275,8 +364,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,16 +376,32 @@ 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_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))
+
+ // a month from now
+ | relative_target WHITE_SPACE relative_date_suffix
+ -> ^(RELATIVE_DATE ^(SEEK relative_date_suffix INT["1"] relative_target))
+
// the week after next
| (THE WHITE_SPACE)? relative_date_span WHITE_SPACE AFTER WHITE_SPACE NEXT
-> ^(RELATIVE_DATE ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["2"] relative_date_span))
// today, tomorrow
| named_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 **********
@@ -304,14 +409,14 @@ 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
- -> ^(RELATIVE_DATE
- ^(SEEK relative_suffix spelled_or_int_optional_prefix explicit_relative_month)
- explicit_day_of_month_part)
+ : (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)
@@ -322,18 +427,32 @@ explicit_relative_date
-> ^(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)=>
+ 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
- WHITE_SPACE WEEK WHITE_SPACE relative_suffix
+ | (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_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
@@ -355,25 +474,29 @@ 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
;
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
@@ -381,6 +504,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
@@ -422,35 +570,66 @@ relative_occurrence_index
;
relative_target
- : day_of_week
+ : day_of_week
| relaxed_month
| relative_date_span
;
+relative_time_target
+ : 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"]
| (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
;
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"]
@@ -474,17 +653,165 @@ 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"]))
+ ;
+
+// ********** holidays **********
+
+holiday
+ : 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)
+
+ | 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)
+ ;
+
+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"]
+ ;
+
+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 **********
+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))
+ ;
+
// 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?)
| 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,23 +834,34 @@ 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
- : (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"]
+ | 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"]
;
time_zone
- : time_zone_abbreviation
- | time_zone_offset
+ : time_zone_plus_offset
+ | time_zone_abbreviation
;
+time_zone_plus_offset
+ : UTC? time_zone_offset -> ZONE_OFFSET[$time_zone_offset.text]
+ ;
+
+
time_zone_offset
: (PLUS | DASH) hours (COLON? minutes)?
- -> ZONE_OFFSET[$time_zone_offset.text]
;
time_zone_abbreviation
@@ -535,172 +873,3 @@ time_zone_abbreviation
| 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 -> 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"]
- ;
-
-// 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/DateWalker.g b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g
index 822f917b..847851d3 100644
--- a/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g
+++ b/src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g
@@ -9,12 +9,35 @@ 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);
+ }
+
+ public void recover(IntStream input, RecognitionException re) {
+ reportError(re);
+ _walkerState.clearDateGroup();
+ }
public com.joestelmach.natty.WalkerState getState() {
return _walkerState;
}
}
+parse
+ : date_time_alternative recurrence?
+ ;
+
+recurrence
+ @init {
+ _walkerState.setRecurring();
+ }
+ : ^(RECURRENCE date_time?{ _walkerState.captureDateTime(); })
+ ;
+
date_time_alternative
: ^(DATE_TIME_ALTERNATIVE date_time+)
;
@@ -23,16 +46,17 @@ date_time
@after {
_walkerState.captureDateTime();
}
- : ^(DATE_TIME date time?)
+ : ^(DATE_TIME date? time?)
;
date
: relative_date
| explicit_date
+ | explicit_year_only_date
;
relative_date
- : ^(RELATIVE_DATE seek explicit_seek*)
+ : ^(RELATIVE_DATE seek? explicit_seek*)
;
week_index
@@ -45,37 +69,71 @@ explicit_date
(^(DAY_OF_WEEK dow=INT))? (^(YEAR_OF year=INT))?)
{_walkerState.setExplicitDate($month.text, $dom.text, $dow.text, $year.text);}
;
-
+
+explicit_year_only_date
+ : ^(EXPLICIT_DATE ^(YEAR_OF year=INT))
+ {_walkerState.setExplicitYearOnlyDate($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) date?)
{_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)
{_walkerState.seekBySpan($DIRECTION.text, $INT.text, $SEEK_BY.text);}
+
+ | ^(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
- : ^(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);}
+ | ^(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);}
- ;
+
+ | ^(EXPLICIT_SEEK explicit_time)
+ ;
\ No newline at end of file
diff --git a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g
index bad47b16..1e460e03 100644
--- a/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g
+++ b/src/main/antlr3/com/joestelmach/natty/generated/TreeRewrite.g
@@ -9,7 +9,38 @@ 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))
+
+ | ^(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))
+
+ // 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))
+ )
+
+ -> ^(RELATIVE_DATE ^(SEEK $dir $seekby $day ^(MONTH_OF_YEAR $month))
+ ^(EXPLICIT_SEEK ^(YEAR_OF $year))
+ ^(EXPLICIT_SEEK $amount ^(DAY_OF_WEEK $dow))
+ )
;
\ 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/CalendarSource.java b/src/main/java/com/joestelmach/natty/CalendarSource.java
new file mode 100644
index 00000000..1ffd1a9a
--- /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 ThreadLocal _baseDate = new ThreadLocal();
+
+ public static void setBaseDate(Date baseDate) {
+ _baseDate.set(baseDate);
+ }
+
+ public static GregorianCalendar getCurrentCalendar() {
+ GregorianCalendar calendar = new GregorianCalendar();
+ if(_baseDate.get() != null) {
+ calendar.setTime(_baseDate.get());
+ }
+ return calendar;
+ }
+}
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..9ec6c4f3
--- /dev/null
+++ b/src/main/java/com/joestelmach/natty/DateGroup.java
@@ -0,0 +1,98 @@
+package com.joestelmach.natty;
+
+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
+ */
+public class DateGroup {
+ private List _dates;
+ private String _text;
+ 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;
+ }
+ 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;
+ }
+
+ 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 _text input.
+ */
+ public boolean isTimeInferred() {
+ return _isTimeInferred;
+ }
+ public void setIsTimeInferred(boolean isTimeInferred) {
+ this._isTimeInferred = isTimeInferred;
+ }
+
+ public Date getRecursUntil() {
+ return _recurringUntil;
+ }
+ public void setRecurringUntil(Date recurringUntil) {
+ _recurringUntil = recurringUntil;
+ }
+
+ public Map> getParseLocations() {
+ return _parseLocations;
+ }
+ public void setParseLocations(Map> parseLocations) {
+ _parseLocations = parseLocations;
+ }
+
+ public Tree getSyntaxTree() {
+ return _syntaxTree;
+ }
+
+ public void setSyntaxTree(Tree syntaxTree) {
+ _syntaxTree = syntaxTree;
+ }
+
+}
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..fdb5414f
--- /dev/null
+++ b/src/main/java/com/joestelmach/natty/Holiday.java
@@ -0,0 +1,57 @@
+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"),
+ 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/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/NattyTokenSource.java b/src/main/java/com/joestelmach/natty/NattyTokenSource.java
new file mode 100644
index 00000000..df4095a6
--- /dev/null
+++ b/src/main/java/com/joestelmach/natty/NattyTokenSource.java
@@ -0,0 +1,27 @@
+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";
+ }
+
+ public List getTokens() {
+ return _tokens;
+ }
+}
diff --git a/src/main/java/com/joestelmach/natty/ParseListener.java b/src/main/java/com/joestelmach/natty/ParseListener.java
index ba17bff8..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,52 +17,24 @@
*/
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("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;
}
+ public ParseLocation getDateGroupLocation() {
+ return _dateGroupLocation;
+ }
+
// don't add backtracking or cyclic DFA nodes
public void enterDecision(int d) {
backtracking++;
@@ -89,22 +60,36 @@ 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");
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(INTERESTING_RULES.get(ruleName));
+ location.setRuleName(ruleName);
location.setText(text);
+ location.setLine(line);
location.setStart(start);
location.setEnd(end);
- _locations.add(location);
+
+ if(isAlternative) {
+ _dateGroupLocation = 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 6eed1101..3636d024 100644
--- a/src/main/java/com/joestelmach/natty/ParseLocation.java
+++ b/src/main/java/com/joestelmach/natty/ParseLocation.java
@@ -8,13 +8,13 @@
public class ParseLocation {
private String _ruleName;
private String _text;
+ private int _line;
private int _start;
private int _end;
public String getRuleName() {
return _ruleName;
}
-
public void setRuleName(String ruleName) {
_ruleName = ruleName;
}
@@ -22,15 +22,20 @@ public void setRuleName(String ruleName) {
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 getStart() {
return _start;
}
-
public void setStart(int start) {
_start = start;
}
@@ -38,8 +43,11 @@ public void setStart(int start) {
public int getEnd() {
return _end;
}
-
public void setEnd(int end) {
_end = end;
}
+
+ public String toString() {
+ return _text;
+ }
}
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 bca598b4..00000000
--- a/src/main/java/com/joestelmach/natty/ParseResult.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.joestelmach.natty;
-
-import java.util.Date;
-import java.util.List;
-
-/**
- * Represents the result of a parse
- *
- * @author Joe Stelmach
- */
-public class ParseResult {
-
- private List _dateTimes;
- private List _parseLocations;
- private String _syntaxTree;
-
- public List getDates() {
- return _dateTimes;
- }
-
- public void setDateTimes(List dateTimes) {
- _dateTimes = dateTimes;
- }
-
- 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 0456ab19..76c0ff81 100644
--- a/src/main/java/com/joestelmach/natty/Parser.java
+++ b/src/main/java/com/joestelmach/natty/Parser.java
@@ -2,13 +2,19 @@
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 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;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.antlr.runtime.tree.Tree;
@@ -16,7 +22,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 +31,7 @@
public class Parser {
private TimeZone _defaultTimeZone;
- private boolean _debug;
- private ParseListener _debugListener;
- 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
@@ -42,70 +45,272 @@ 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();
}
/**
- * Enables the collection of parse information
- * @param debug
+ * Parses the given input value for one or more groups of
+ * date alternatives
+ *
+ * @param value
+ * @return
*/
- public void setDebug(final boolean debug) {
- _debug = debug;
+ 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()));
+
+ } 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) {
+ 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 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;
+ }
+ }
+ cleanupGroup(tokens);
+ 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);
+ }
+ }
+ return groups;
}
/**
- * Parses the input string for a list of date times, assuming no
- * extraneous text is present.
- * @param inputString
+ * 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
*/
- public ParseResult parse(final String inputString) {
- ParseResult result = new ParseResult();
+ 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:tokens) {
+ tokenString.append(DateParser.tokenNames[token.getType()]);
+ tokenString.append(" ");
+ }
+ _logger.fine("sub-token stream: " + tokenString.toString());
+
try {
- // lex
- ANTLRInputStream input = new ANTLRNoCaseInputStream(
- new ByteArrayInputStream(inputString.trim().getBytes()));
- DateLexer lexer = new DateLexer(input);
- CommonTokenStream tokens = new CommonTokenStream(lexer);
+ // parse
+ ParseListener listener = new ParseListener();
+ DateParser parser = new DateParser(stream, listener);
+ DateParser.parse_return parseReturn = parser.parse();
- // 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();
- }
-
- // 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());
+ Tree tree = (Tree) parseReturn.getTree();
+ _logger.fine("AST: " + 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());
+ // we only continue if a meaningful syntax tree has been built
+ if(tree.getChildCount() > 0) {
- } catch(IOException e) {
- _logger.log(Level.SEVERE, "Could not read from input stream", e);
+ // 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();
+
+ // 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) {
+ } catch(Exception e) {
_logger.log(Level.SEVERE, "Could not parse input", e);
}
- return result;
+ 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) {
+
+ // 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
+ 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) {
+ if(currentGroup.size() > 0) {
+ currentGroup.add(currentToken);
+ cleanupGroup(currentGroup);
+ groups.add(new CommonTokenStream(new NattyTokenSource(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
+ else if(currentToken.getType() != DateLexer.DOT) {
+ currentGroup.add(currentToken);
+ }
+ }
+ }
+ }
+ if(currentGroup != null) {
+ cleanupGroup(currentGroup);
+ groups.add(new CommonTokenStream(new NattyTokenSource(currentGroup)));
+ }
+
+ _logger.fine("global token stream: " + tokenString.toString());
+
+ return groups;
+ }
+
+ /**
+ * 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/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 a4d38b4b..7a945487 100644
--- a/src/main/java/com/joestelmach/natty/WalkerState.java
+++ b/src/main/java/com/joestelmach/natty/WalkerState.java
@@ -1,33 +1,62 @@
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;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* @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 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 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;
- private static final int TWO_DIGIT_YEAR_CENTURY_THRESHOLD = 20;
- private List _currentDateTimes;
-
+ private boolean _firstDateInvocationInGroup = true;
+ private boolean _timeGivenInGroup = false;
+ private boolean _dateGivenInGroup = false;
+ private boolean _updatePreviousDates = false;
+ private DateGroup _dateGroup;
+
+
+ private static final Logger log = Logger.getLogger(WalkerState.class.getName());
+
/**
* Creates a new WalkerState representing the start of
* the next hour from the current time
*/
public WalkerState() {
- resetCalender();
- _currentDateTimes = new ArrayList();
+ resetCalendar();
+ _dateGroup = new DateGroup();
}
public void setDefaultTimeZone(final TimeZone zone) {
_defaultTimeZone = zone;
+ resetCalendar();
}
/**
@@ -43,7 +72,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
@@ -51,19 +80,22 @@ 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);
- int sign = direction.equals(">") ? 1 : -1;
- if(seekType.equals("by_week")) {
+ markDateInvocation();
+
+ 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);
@@ -86,25 +118,41 @@ 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);
}
+ /**
+ * 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
*/
public void seekToYear(String year) {
int yearInt = Integer.parseInt(year);
- assert(yearInt > 0 && yearInt < 9999);
-
- // 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;
- }
+ assert(yearInt >= 0 && yearInt < 9999);
- _calendar.set(Calendar.YEAR, yearInt);
+ markDateInvocation();
+
+ _calendar.set(Calendar.YEAR, getFullYear(yearInt));
}
/**
@@ -114,7 +162,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
@@ -122,20 +170,24 @@ 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();
+
// 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.
_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(DIR_RIGHT) ? 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);
@@ -148,22 +200,32 @@ 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
*/
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(span.equals("day") || span.equals("week") || span.equals("month") || span.equals("year"));
+ 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));
- int sign = direction.equals(">") ? 1 : -1;
+ boolean isDateSeek = span.equals(DAY) || span.equals(WEEK) ||
+ span.equals(MONTH) || span.equals(YEAR);
+ if(isDateSeek) markDateInvocation(); else markTimeInvocation();
+
+
+ int sign = direction.equals(DIR_RIGHT) ? 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(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);
}
@@ -172,7 +234,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);
@@ -181,6 +242,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);
@@ -225,13 +288,17 @@ 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);
if(year != null) {
seekToYear(year);
}
-
+
+
// if no year is given, but a day of week is, we ensure that the resulting
// date falls on the given day of week.
else if(dayOfWeek != null) {
@@ -242,7 +309,21 @@ else if(dayOfWeek != null) {
}
}
}
-
+
+ /**
+ *
+ * @param year the year to set. If present, must be guaranteed to
+ * parse as an integer with 4 digits (xxxx) between 0 and 9999
+ */
+ public void setExplicitYearOnlyDate(String year) {
+// debug("BEFORE year: %s, month: %s, day: %s\n", _calendar.get(Calendar.YEAR), _calendar.get(Calendar.MONTH), _calendar.get(Calendar.DAY_OF_MONTH));
+ seekToYear(year); //set the year
+ //WARNING: Months needs to be set AFTER the YEAR is set - RB
+ _calendar.set(Calendar.MONTH, Calendar.JANUARY);
+ _calendar.set(Calendar.DAY_OF_MONTH, 1);
+ debug("AFTER year: %s, month: %s, day: %s\n", _calendar.get(Calendar.YEAR), _calendar.get(Calendar.MONTH), _calendar.get(Calendar.DAY_OF_MONTH));
+ }
+
/**
* Sets the the time of day
*
@@ -257,16 +338,20 @@ 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)
*/
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);
+
+ debug("setExplicitTime: %s, %s %s, %s, %s", hours, minutes, seconds, amPm, zoneString);
+
+ markTimeInvocation();
// reset milliseconds to 0
_calendar.set(Calendar.MILLISECOND, 0);
@@ -274,8 +359,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);
}
@@ -286,8 +371,8 @@ 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) :
- amPm.equals("pm") ? Calendar.PM : Calendar.AM;
+ (hoursInt >= 12 ? 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)
@@ -300,31 +385,243 @@ 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);
}
+ /**
+ * 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);
+ assert(holiday != null);
+
+ seekToIcsEvent(HOLIDAY_ICS_FILE, holiday.getSummary(), direction, seekAmount);
+ }
+
+ /**
+ * 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);
+ assert(holiday != null);
+
+ 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);
+
+ 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);
+
+ seekToIcsEventYear(SEASON_ICS_FILE, yearString, season.getSummary());
+ }
+
+ /**
+ *
+ */
+ public void setRecurring() {
+ _dateGroup.setRecurring(true);
+ }
+
/**
*
*/
public void captureDateTime() {
+ // if other dates have already been added to the date group, we'll
+ // update their date portion to match this one
+ if(_updatePreviousDates) {
+ List dates = _dateGroup.getDates();
+ if (!dates.isEmpty()) {
+ for (Date date : dates) {
+ Calendar calendar = getCalendar();
+ calendar.setTime(date);
+ for (int field : new int[] { Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR }) {
+ calendar.set(field, _calendar.get(field));
+ }
+ date.setTime(calendar.getTimeInMillis());
+ }
+ }
+ _updatePreviousDates = false;
+ }
+
Date date = _calendar.getTime();
- _currentDateTimes.add(date);
- resetCalender();
+ if(_dateGroup.isRecurring()) {
+ _dateGroup.setRecurringUntil(date);
+ }
+ else {
+ _dateGroup.addDate(date);
+ }
+ _firstDateInvocationInGroup = true;
}
/**
* @return the list of date times found
*/
- public List getDateTimes() {
- return _currentDateTimes;
+ public DateGroup getDateGroup() {
+ return _dateGroup;
+ }
+
+ /**
+ * Clears any date/times that have been captured
+ */
+ public void clearDateGroup() {
+ _dateGroup = new DateGroup();
}
/**
* Resets the calendar
*/
- private void resetCalender() {
- _calendar = new GregorianCalendar();
+ private void resetCalendar() {
+ _calendar = getCalendar();
+ if (_defaultTimeZone != null) {
+ _calendar.setTimeZone(_defaultTimeZone);
+ }
_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
+ */
+ private void markDateInvocation() {
+
+ _updatePreviousDates = !_dateGivenInGroup;
+ _dateGivenInGroup = true;
+
+ 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);
+ resetCalendar();
+ _calendar.set(Calendar.HOUR_OF_DAY, hours);
+ _calendar.set(Calendar.MINUTE, minutes);
+ _calendar.set(Calendar.SECOND, seconds);
+ }
+ else {
+ resetCalendar();
+ }
+ _firstDateInvocationInGroup = false;
+ }
+ }
+
+ /**
+ *
+ */
+ private void markTimeInvocation() {
+ _timeGivenInGroup = true;
+ _dateGroup.setIsTimeInferred(false);
+ }
+
+ 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) {
+ 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
+ */
+ protected GregorianCalendar getCalendar() {
+ return CalendarSource.getCurrentCalendar();
+ }
+
+ private void debug(String msg, Object... params) {
+ _logger.finest(String.format(msg, params));
+ }
}
\ No newline at end of file
diff --git a/src/main/resources/holidays.ics b/src/main/resources/holidays.ics
new file mode 100644
index 00000000..0e50d77f
--- /dev/null
+++ b/src/main/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 (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/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/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/gunit/DateParser.testsuite b/src/test/gunit/DateParser.testsuite
deleted file mode 100644
index e6d1beff..00000000
--- a/src/test/gunit/DateParser.testsuite
+++ /dev/null
@@ -1,1757 +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))))
-
-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)))
-
-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_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_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 **********
-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) 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)
-
-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/java/com/joestelmach/natty/AbstractTest.java b/src/test/java/com/joestelmach/natty/AbstractTest.java
new file mode 100644
index 00000000..b5662939
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/AbstractTest.java
@@ -0,0 +1,156 @@
+package com.joestelmach.natty;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+
+/**
+ *
+ * @author Joe Stelmach
+ */
+public abstract class AbstractTest {
+ 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
+ */
+ @Before
+ public void before() {
+ CalendarSource.setBaseDate(null);
+ }
+
+ /**
+ * Parses the given value into a collection of dates
+ *
+ * @param value
+ * @return
+ */
+ protected List parseCollection(String value) {
+ return _parser.parse(value).get(0).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", month -1, _calendar.get(Calendar.MONTH));
+ Assert.assertEquals("day of the month", day, _calendar.get(Calendar.DAY_OF_MONTH));
+ Assert.assertEquals("year", year, _calendar.get(Calendar.YEAR));
+ Assert.assertEquals("hour", hours, _calendar.get(Calendar.HOUR_OF_DAY));
+ Assert.assertEquals("minutes", minutes, _calendar.get(Calendar.MINUTE));
+ Assert.assertEquals("seconds", seconds, _calendar.get(Calendar.SECOND));
+ }
+}
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..490f3b60
--- /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
new file mode 100644
index 00000000..5c2c91c6
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/DateTest.java
@@ -0,0 +1,322 @@
+package com.joestelmach.natty;
+
+import junit.framework.Assert;
+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
+ *
+ * @author Joe Stelmach
+ */
+public class DateTest extends AbstractTest {
+ @BeforeClass
+ public static void oneTime() {
+ Locale.setDefault(Locale.US);
+ TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern"));
+ initCalendarAndParser();
+ }
+
+ @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);
+ validateDate("in october 2006", 10, 1, 2006);
+ validateDate("feb 1979", 2, 1, 1979);
+ validateDate("jan '80", 1, 1, 1980);
+ validateDate("2006-Jun-16", 6, 16, 2006);
+ validateDate("28-Feb-2010", 2, 28, 2010);
+ validateDate("9-Apr", 4, 9, Calendar.getInstance().get(Calendar.YEAR));
+ validateDate("jan 10, '00", 1, 10, 2000);
+ }
+
+ @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);
+ validateDate("Second Monday in October 2017", 10, 9, 2017);
+ validateDate("2nd thursday in sept. '02", 9, 12, 2002);
+ }
+
+ @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);
+ 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);
+ 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("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("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);
+ validateDate("A week ago", 2, 21, 2011);
+ validateDate("A year ago", 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+ }
+
+ // 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/Pacific 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();
+ handler.setLevel(Level.ALL);
+ Logger logger = Logger.getLogger("com.joestelmach.natty");
+ logger.setLevel(Level.FINEST);
+ logger.addHandler(handler);
+
+ String value = "Monday after next";
+
+ Parser parser = new Parser();
+ List groups = parser.parse(value);
+ for(DateGroup group:groups) {
+ System.out.println(value);
+ 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());
+ System.out.println("is time inferred: " + group.isTimeInferred());
+ System.out.println("is recurring: " + group.isRecurring());
+ System.out.println("recurs until: " + group.getRecursUntil());
+
+ 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() + " ");
+ }
+ }
+ System.out.print("\n\n");
+ }
+ }
+}
diff --git a/src/test/java/com/joestelmach/natty/DateTimeTest.java b/src/test/java/com/joestelmach/natty/DateTimeTest.java
new file mode 100644
index 00000000..b5a4d5b2
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/DateTimeTest.java
@@ -0,0 +1,180 @@
+package com.joestelmach.natty;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Runs the parser through the various datetime formats
+ *
+ * @author Joe Stelmach
+ */
+public class DateTimeTest extends AbstractTest {
+ @BeforeClass
+ public static void oneTime() {
+ Locale.setDefault(Locale.US);
+ TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern"));
+ initCalendarAndParser();
+ }
+
+ @Test
+ 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);
+ 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);
+ 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);
+ validateDateTime("05-Aug-2013 14:10:56 UTC", 8, 5, 2013, 10, 10, 56);
+ validateDateTime("5/1/13 01:00:00-8", 5, 1, 2013, 5, 0, 0);
+ validateDateTime("5/1/13 01:00:00 UTC", 4, 30, 2013, 21, 0, 0);
+ validateDateTime("5/1/13 01:00:00 UTC+8", 4, 30, 2013, 13, 0, 0);
+ validateDateTime("5/1/13 01:00:00 GMT-1", 4, 30, 2013, 22, 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);
+ validateDateTime("Acknowledged. Let's meet at 9pm.", 2, 24, 2011, 21, 0, 0);
+
+ validateDateTime("tuesday, 12:50 PM", 3, 1, 2011, 12, 50, 0);
+ }
+
+ @Test
+ @Ignore("breaks with non-breaking space utf-8")
+ public void testRelativeWithNonBreakingSpaceUTF8() {
+ validateDateTime("tuesday,\u00A012:50 PM", 2, 24, 2011, 12, 50, 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);
+
+ 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);
+ }
+
+ @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);
+
+ dates = parseCollection("slept from 3:30 a.m. To 9:41 a.m. On April 10th");
+ Assert.assertEquals(2, dates.size());
+ validateDateTime(dates.get(0), 4, 10, 2012, 3, 30, 0);
+ validateDateTime(dates.get(1), 4, 10, 2012, 9, 41, 0);
+ }
+
+ @Test
+ public void shouldParseYYYYasDateNotTime() throws Exception {
+ Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("06/30/2014");
+ CalendarSource.setBaseDate(reference);
+
+ //Assert.assertEquals("Thu Jan 01 00:00:00 EDT 2009", parseSingleDate("2009").toString());
+ validateDateTime("2009", 1, 1, 2009, 0, 0, 0);
+ }
+
+}
diff --git a/src/test/java/com/joestelmach/natty/IcsTest.java b/src/test/java/com/joestelmach/natty/IcsTest.java
new file mode 100644
index 00000000..f344a8e0
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/IcsTest.java
@@ -0,0 +1,149 @@
+package com.joestelmach.natty;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+public class IcsTest extends AbstractTest {
+
+ @BeforeClass
+ public static void oneTime() {
+ Locale.setDefault(Locale.US);
+ TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern"));
+ 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");
+ CalendarSource.setBaseDate(reference);
+
+ 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 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");
+ 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);
+
+ 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);
+ }
+}
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..187ac292
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/RecurrenceTest.java
@@ -0,0 +1,44 @@
+package com.joestelmach.natty;
+
+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;
+
+/**
+ *
+ * @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 {
+ 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
new file mode 100644
index 00000000..f5787d89
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/SearchTest.java
@@ -0,0 +1,287 @@
+package com.joestelmach.natty;
+
+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
+ */
+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 {
+ 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);
+
+ 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);
+
+ 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);
+
+ 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(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());
+ 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(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());
+ 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);
+
+ 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);
+
+ 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(1, groups.size());
+ dates = groups.get(0).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());
+ 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);
+
+ 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());
+
+ 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());
+ }
+}
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);
+ }
+}
diff --git a/src/test/java/com/joestelmach/natty/TimeTest.java b/src/test/java/com/joestelmach/natty/TimeTest.java
new file mode 100644
index 00000000..1752b93d
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/TimeTest.java
@@ -0,0 +1,107 @@
+package com.joestelmach.natty;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Runs the parser through the various time formats
+ *
+ * @author Joe Stelmach
+ */
+public class TimeTest extends AbstractTest {
+ @BeforeClass
+ public static void oneTime() {
+ Locale.setDefault(Locale.US);
+ TimeZone.setDefault(TimeZone.getTimeZone("US/Eastern"));
+ initCalendarAndParser();
+ }
+
+ /**
+ * Runs the parser through the various time formats
+ * @throws Exception
+ */
+ @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);
+ 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 {
+ 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);
+ 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);
+ 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);
+ }
+}
diff --git a/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java
new file mode 100644
index 00000000..1e3a69fc
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/AbstractGrammarTest.java
@@ -0,0 +1,70 @@
+package com.joestelmach.natty.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/joestelmach/natty/grammar/DateGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java
new file mode 100644
index 00000000..4f714188
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/DateGrammarTest.java
@@ -0,0 +1,647 @@
+package com.joestelmach.natty.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 (YEAR_OF 1999)) (EXPLICIT_SEEK 5 (DAY_OF_WEEK 5)))");
+
+ 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 (YEAR_OF 2004)) (EXPLICIT_SEEK 1 (DAY_OF_WEEK 4)))");
+
+ 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))");
+ }
+
+ @Test
+ public void date_time_should_parse_YYYY_as_explicit_date() throws Exception {
+ _ruleName = "date_time";
+
+ assertAST("2014", "(DATE_TIME (EXPLICIT_DATE (YEAR_OF 2014)))");
+ }
+
+ @Test
+ public void date_should_parse_YYYY_as_explicit_date() throws Exception {
+ _ruleName = "date";
+
+ assertAST("2014", "(EXPLICIT_DATE (YEAR_OF 2014))");
+ }
+
+}
diff --git a/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java
new file mode 100644
index 00000000..49d09f97
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/DateTimeGrammarTest.java
@@ -0,0 +1,86 @@
+package com.joestelmach.natty.grammar;
+
+import org.junit.Test;
+
+public class DateTimeGrammarTest 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))) (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))))");
+ 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)))");
+ }
+}
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..fce221b4
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/HolidayGrammarTest.java
@@ -0,0 +1,132 @@
+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("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("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/java/com/joestelmach/natty/grammar/NumericGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java
new file mode 100644
index 00000000..606dd15b
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/NumericGrammarTest.java
@@ -0,0 +1,725 @@
+package com.joestelmach.natty.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/joestelmach/natty/grammar/TimeGrammarTest.java b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java
new file mode 100644
index 00000000..b0eaba2f
--- /dev/null
+++ b/src/test/java/com/joestelmach/natty/grammar/TimeGrammarTest.java
@@ -0,0 +1,189 @@
+package com.joestelmach.natty.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)");
+ }
+}
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 b3e570d3..00000000
--- a/src/test/java/com/natty/parse/ParserTest.java
+++ /dev/null
@@ -1,40 +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.Test;
-
-import com.joestelmach.natty.ParseResult;
-import com.joestelmach.natty.Parser;
-import com.joestelmach.natty.WalkerState;
-
-/**
- * Runs the DateParser through it's paces
- *
- * @author Joe Stelmach
- */
-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";
- 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()));
- }
-}
diff --git a/src/test/resources/cpan.txt b/src/test/resources/cpan.txt
new file mode 100644
index 00000000..2f46f92f
--- /dev/null
+++ b/src/test/resources/cpan.txt
@@ -0,0 +1,320 @@
+ 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