Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

adding basic season support. closes #22

  • Loading branch information...
commit 7073eac53a24032dfdf7fe92506111f9b4766499 1 parent 76215d1
@joestelmach authored
View
8 src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g
@@ -311,6 +311,14 @@ VALENTINE : 'valentine' SINGLE_QUOTE? 's'?;
VETERAN : 'veteran' SINGLE_QUOTE? 's'?;
fragment GROUND : 'ground';
fragment HOG : 'hog';
+
+// ********** season specific **********
+
+WINTER : 'winter' 's'?;
+FALL : 'fall' 's'?;
+AUTUMN : 'autumn' 's'?;
+SPRING : 'spring' 's'?;
+SUMMER : 'summer' 's'?;
UNKNOWN
: UNKNOWN_CHAR
View
29 src/main/antlr3/com/joestelmach/natty/generated/DateParser.g
@@ -33,6 +33,7 @@ tokens {
ZONE_OFFSET;
RECURRENCE;
HOLIDAY;
+ SEASON;
}
@header {
@@ -390,6 +391,10 @@ relative_date
// next christmas, 2 thanksgivings ago
| holiday
-> ^(RELATIVE_DATE holiday)
+
+ // next fall, 2 summers from now
+ | season
+ -> ^(RELATIVE_DATE season)
;
// ********** explicit relative date rules **********
@@ -738,6 +743,30 @@ holiday_name
-> HOLIDAY["VETERANS_DAY"]
;
+season
+ : spelled_or_int_optional_prefix WHITE_SPACE season_name WHITE_SPACE relative_date_suffix
+ -> ^(SEEK relative_date_suffix spelled_or_int_optional_prefix season_name)
+
+ | relative_date_prefix WHITE_SPACE season_name
+ -> ^(SEEK relative_date_prefix season_name)
+
+ | season_name relaxed_year_prefix relaxed_year
+ -> ^(EXPLICIT_SEEK season_name relaxed_year)
+
+ | season_name
+ -> ^(SEEK DIRECTION[">"] SEEK_BY["by_day"] INT["1"] season_name)
+ ;
+
+season_name
+ :WINTER
+ -> SEASON["WINTER"]
+ | SPRING
+ -> SEASON["SPRING"]
+ | SUMMER
+ -> SEASON["SUMMER"]
+ | (FALL | AUTUMN)
+ -> SEASON["FALL"]
+ ;
// ********** time rules **********
View
6 src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g
@@ -99,6 +99,9 @@ seek
| ^(SEEK DIRECTION SEEK_BY INT HOLIDAY)
{_walkerState.seekToHoliday($HOLIDAY.text, $DIRECTION.text, $INT.text);}
+
+ | ^(SEEK DIRECTION SEEK_BY INT SEASON)
+ {_walkerState.seekToSeason($SEASON.text, $DIRECTION.text, $INT.text);}
;
explicit_seek
@@ -120,6 +123,9 @@ explicit_seek
| ^(EXPLICIT_SEEK HOLIDAY ^(YEAR_OF year=INT))
{_walkerState.seekToHolidayYear($HOLIDAY.text, $year.text);}
+ | ^(EXPLICIT_SEEK SEASON ^(YEAR_OF year=INT))
+ {_walkerState.seekToSeasonYear($SEASON.text, $year.text);}
+
| ^(EXPLICIT_SEEK index=INT ^(DAY_OF_WEEK day=INT))
{_walkerState.setDayOfWeekIndex($index.text, $day.text);}
View
92 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<Integer, Date> findDates(int startYear, int endYear, String eventSummary) {
+ Map<Integer, Date> holidays = new HashMap<Integer, Date>();
+
+ 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;
+ }
+
+}
View
4 src/main/java/com/joestelmach/natty/Parser.java
@@ -102,7 +102,7 @@ public Parser() {
Iterator<Token> iter = tokens.iterator();
while(iter.hasNext()) {
Token token = iter.next();
- if(!DateParser.FOLLOW_empty_in_parse181.member(token.getType())) {
+ if(!DateParser.FOLLOW_empty_in_parse186.member(token.getType())) {
iter.remove();
}
else {
@@ -209,7 +209,7 @@ private DateGroup singleParse(TokenStream stream) {
// ignore white space in-between possible rules
if(currentToken.getType() != DateLexer.WHITE_SPACE) {
// if the token is a possible date start token, we start a new collection
- if(DateParser.FOLLOW_empty_in_parse181.member(currentToken.getType())) {
+ if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) {
currentGroup = new ArrayList<Token>();
currentGroup.add(currentToken);
}
View
33 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<String, Season> lookup;
+ static {
+ lookup = new HashMap<String, Season>();
+ 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);
+ }
+}
View
198 src/main/java/com/joestelmach/natty/WalkerState.java
@@ -1,24 +1,12 @@
package com.joestelmach.natty;
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
-import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
-import java.util.logging.Level;
import java.util.logging.Logger;
-import net.fortuna.ical4j.data.CalendarBuilder;
-import net.fortuna.ical4j.data.ParserException;
-import net.fortuna.ical4j.model.Component;
-import net.fortuna.ical4j.model.DateTime;
-import net.fortuna.ical4j.model.Period;
-import net.fortuna.ical4j.model.PeriodList;
-
/**
* @author Joe Stelmach
*/
@@ -45,6 +33,7 @@
private static final String VEVENT = "VEVENT";
private static final String SUMMARY = "SUMMARY";
private static final String HOLIDAY_ICS_FILE = "/holidays.ics";
+ private static final String SEASON_ICS_FILE = "/seasons.ics";
private static final Logger _logger = Logger.getLogger("com.joestelmach.natty");
private GregorianCalendar _calendar;
@@ -52,7 +41,6 @@
private int _currentYear;
private boolean _firstDateInvocationInGroup = true;
private boolean _timeGivenInGroup = false;
- private net.fortuna.ical4j.model.Calendar _holidayCalendar;
private DateGroup _dateGroup;
@@ -391,39 +379,12 @@ public void setExplicitTime(String hours, String minutes, String seconds, String
* @param seekAmount The number of years to seek
*/
public void seekToHoliday(String holidayString, String direction, String seekAmount) {
-
Holiday holiday = Holiday.valueOf(holidayString);
- int seekAmountInt = Integer.parseInt(seekAmount);
- assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT));
- assert(seekAmountInt >= 0);
assert(holiday != null);
- markDateInvocation();
-
- // get the current year
- Calendar cal = getCalendar();
- cal.setTimeZone(_defaultTimeZone);
- int currentYear = cal.get(Calendar.YEAR);
-
- // look up a suitable period of holiday occurrences
- boolean forwards = direction.equals(DIR_RIGHT);
- int startYear = forwards ? currentYear : currentYear - seekAmountInt - 1;
- int endYear = forwards ? currentYear + seekAmountInt + 1 : currentYear;
- Map<Integer, Date> dates = getDatesForHoliday(startYear, endYear, holiday);
-
- // grab the right one
- boolean hasPassed = cal.getTime().after(dates.get(currentYear));
- int targetYear = currentYear +
- (forwards ? seekAmountInt + (hasPassed ? 0 : -1) :
- (seekAmountInt - (hasPassed ? 1 : 0)) * -1);
-
- cal.setTimeZone(_calendar.getTimeZone());
- cal.setTime(dates.get(targetYear));
- _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR));
- _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH));
- _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH));
+ seekToIcsEvent(HOLIDAY_ICS_FILE, holiday.getSummary(), direction, seekAmount);
}
-
+
/**
* Seeks to the given holiday within the given year
*
@@ -432,26 +393,38 @@ public void seekToHoliday(String holidayString, String direction, String seekAmo
*/
public void seekToHolidayYear(String holidayString, String yearString) {
Holiday holiday = Holiday.valueOf(holidayString);
- int yearInt = Integer.parseInt(yearString);
assert(holiday != null);
- assert(yearInt >= 0);
- markDateInvocation();
+ seekToIcsEventYear(HOLIDAY_ICS_FILE, yearString, holiday.getSummary());
+ }
+
+ /**
+ * Seeks forward or backwards to a particular season based on the current date
+ *
+ * @param seasonString The season to seek to
+ * @param direction The direction to seek
+ * @param seekAmount The number of years to seek
+ */
+ public void seekToSeason(String seasonString, String direction, String seekAmount) {
+ Season season = Season.valueOf(seasonString);
+ assert(season!= null);
- int year = getFullYear(yearInt);
- Map<Integer, Date> dates = getDatesForHoliday(year, year, holiday);
- Date date = dates.get(year - (holiday.equals(Holiday.NEW_YEARS_EVE) ? 1 : 0));
+ seekToIcsEvent(SEASON_ICS_FILE, season.getSummary(), direction, seekAmount);
+ }
+
+ /**
+ * Seeks to the given season within the given year
+ *
+ * @param seasonString
+ * @param yearString
+ */
+ public void seekToSeasonYear(String seasonString, String yearString) {
+ Season season = Season.valueOf(seasonString);
+ assert(season != null);
- if(date != null) {
- Calendar cal = getCalendar();
- cal.setTimeZone(_calendar.getTimeZone());
- cal.setTime(date);
- _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR));
- _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH));
- _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH));
- }
+ seekToIcsEventYear(SEASON_ICS_FILE, yearString, season.getSummary());
}
-
+
/**
*
*/
@@ -498,6 +471,58 @@ private void resetCalendar() {
_currentYear = _calendar.get(Calendar.YEAR);
}
+ private void seekToIcsEvent(String icsFileName, String eventSummary, String direction, String seekAmount) {
+ int seekAmountInt = Integer.parseInt(seekAmount);
+ assert(direction.equals(DIR_LEFT) || direction.equals(DIR_RIGHT));
+ assert(seekAmountInt >= 0);
+
+ markDateInvocation();
+
+ // get the current year
+ Calendar cal = getCalendar();
+ cal.setTimeZone(_defaultTimeZone);
+ int currentYear = cal.get(Calendar.YEAR);
+
+ // look up a suitable period of occurrences
+ boolean forwards = direction.equals(DIR_RIGHT);
+ int startYear = forwards ? currentYear : currentYear - seekAmountInt - 1;
+ int endYear = forwards ? currentYear + seekAmountInt + 1 : currentYear;
+ Map<Integer, Date> 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<Integer, Date> dates = getDatesFromIcs(icsFileName, eventSummary, year, year);
+ Date date = dates.get(year - (eventSummary.equals(Holiday.NEW_YEARS_EVE.getSummary()) ? 1 : 0));
+
+ if(date != null) {
+ Calendar cal = getCalendar();
+ cal.setTimeZone(_calendar.getTimeZone());
+ cal.setTime(date);
+ _calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR));
+ _calendar.set(Calendar.MONTH, cal.get(Calendar.MONTH));
+ _calendar.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+
/**
* ensures that the first invocation of a date seeking
* rule is captured
@@ -530,61 +555,10 @@ private void markTimeInvocation() {
_dateGroup.setIsTimeInferred(false);
}
- private Map<Integer, Date> getDatesForHoliday(int startYear, int endYear, Holiday holiday) {
- Map<Integer, Date> holidays = new HashMap<Integer, Date>();
-
- if(_holidayCalendar == null) {
- InputStream fin = WalkerState.class.getResourceAsStream(HOLIDAY_ICS_FILE);
- try {
- _holidayCalendar = new CalendarBuilder().build(fin);
-
- } catch (IOException e) {
- _logger.severe("Couln't open " + HOLIDAY_ICS_FILE);
- return holidays;
-
- } catch (ParserException e) {
- _logger.severe("Couln't parse " + HOLIDAY_ICS_FILE);
- return holidays;
- }
- }
-
- Period period = null;
- try {
- DateTime from = new DateTime(startYear + "0101T000000Z");
- DateTime to = new DateTime(endYear + "1231T000000Z");;
- period = new Period(from, to);
-
- } catch (ParseException e) {
- _logger.log(Level.SEVERE, "Invalid start or end year: " + startYear + ", " + endYear, e);
- return holidays;
- }
-
- for (Object component : _holidayCalendar.getComponents(VEVENT)) {
- Component vevent = (Component) component;
- String summary = vevent.getProperty(SUMMARY).getValue();
- if(summary.equals(holiday.getSummary())) {
- PeriodList list = vevent.calculateRecurrenceSet(period);
- for(Object p : list) {
- DateTime date = ((Period) p).getStart();
-
- // this date is at the date of the holiday at 12 AM UTC
- Calendar utcCal = getCalendar();
- utcCal.setTimeZone(TimeZone.getTimeZone(GMT));
- utcCal.setTime(date);
-
- // use the year, month and day components of our UTC date to form a new local date
- Calendar localCal = getCalendar();
- localCal.setTimeZone(_defaultTimeZone);
- localCal.set(Calendar.YEAR, utcCal.get(Calendar.YEAR));
- localCal.set(Calendar.MONTH, utcCal.get(Calendar.MONTH));
- localCal.set(Calendar.DAY_OF_MONTH, utcCal.get(Calendar.DAY_OF_MONTH));
-
- holidays.put(localCal.get(Calendar.YEAR), localCal.getTime());
- }
- }
- }
-
- return holidays;
+ private Map<Integer, Date> 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) {
View
600 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
View
2  src/test/java/com/joestelmach/natty/DateTest.java
@@ -274,7 +274,7 @@ public static void main(String[] args) {
logger.setLevel(Level.FINEST);
logger.addHandler(handler);
- String value = "Watch School Spirits on June 20 on syfy channel";
+ String value = "next fall";
Parser parser = new Parser();
List<DateGroup> groups = parser.parse(value);
View
43 src/test/java/com/joestelmach/natty/HolidayTest.java → src/test/java/com/joestelmach/natty/IcsTest.java
@@ -9,7 +9,7 @@
import org.junit.Test;
-public class HolidayTest extends AbstractTest {
+public class IcsTest extends AbstractTest {
@BeforeClass
public static void oneTime() {
@@ -19,6 +19,18 @@ public static void oneTime() {
}
@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);
@@ -63,6 +75,25 @@ public void testRelativeHolidays() throws Exception {
}
@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);
@@ -105,4 +136,14 @@ public void testHolidaysWithModifiers() throws Exception {
validateDate("four days before veterans day 2013", 11, 7, 2013);
validateDate("two days after two thanksgivings from now", 11, 24, 2012);
}
+
+ @Test
+ public void testSeasonsWithModifiers() throws Exception {
+ Date reference = DateFormat.getDateInstance(DateFormat.SHORT).parse("11/05/2011");
+ CalendarSource.setBaseDate(reference);
+
+ validateDate("four days before fall 2013", 9, 18, 2013);
+ validateDate("two days after two summers from now", 6, 23, 2013);
+ validateDate("three summers ago", 6, 21, 2009);
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.