Skip to content

Commit

Permalink
adding basic season support. closes #22
Browse files Browse the repository at this point in the history
  • Loading branch information
joestelmach committed Oct 28, 2012
1 parent 76215d1 commit 7073eac
Show file tree
Hide file tree
Showing 10 changed files with 899 additions and 116 deletions.
8 changes: 8 additions & 0 deletions src/main/antlr3/com/joestelmach/natty/generated/DateLexer.g
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions src/main/antlr3/com/joestelmach/natty/generated/DateParser.g
Expand Up @@ -33,6 +33,7 @@ tokens {
ZONE_OFFSET;
RECURRENCE;
HOLIDAY;
SEASON;
}

@header {
Expand Down Expand Up @@ -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 **********
Expand Down Expand Up @@ -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 **********

Expand Down
6 changes: 6 additions & 0 deletions src/main/antlr3/com/joestelmach/natty/generated/DateWalker.g
Expand Up @@ -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
Expand All @@ -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);}

Expand Down
92 changes: 92 additions & 0 deletions 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;
}

}
4 changes: 2 additions & 2 deletions src/main/java/com/joestelmach/natty/Parser.java
Expand Up @@ -102,7 +102,7 @@ public List<DateGroup> parse(String value) {
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 {
Expand Down Expand Up @@ -209,7 +209,7 @@ private List<TokenStream> collectTokenStreams(TokenStream stream) {
// ignore white space in-between possible rules
if(currentToken.getType() != DateLexer.WHITE_SPACE) {
// if the token is a possible date start token, we start a new collection
if(DateParser.FOLLOW_empty_in_parse181.member(currentToken.getType())) {
if(DateParser.FOLLOW_empty_in_parse186.member(currentToken.getType())) {
currentGroup = new ArrayList<Token>();
currentGroup.add(currentToken);
}
Expand Down
33 changes: 33 additions & 0 deletions 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);
}
}

0 comments on commit 7073eac

Please sign in to comment.