an iCal processing library
Diff: iCal.cpp
- Revision:
- 0:49245357cd1b
- Child:
- 1:db274b9e40cc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iCal.cpp Sun Apr 20 13:25:50 2014 +0000 @@ -0,0 +1,404 @@ + + +#include "iCal.h" +#include <algorithm> + +//#define DEBUG "iCal" +#include <cstdio> +#if (defined(DEBUG) && !defined(TARGET_LPC11U24)) +#define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#else +#define DBG(x, ...) +#define WARN(x, ...) +#define ERR(x, ...) +#define INFO(x, ...) +#endif + +Event_T EventList[EVENT_COUNT]; +int EventCount = 0; + + +const char * RPT_DAYS[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA", "" }; + +int GetNumEvents(void) +{ + return EventCount; +} + +const char * RepeatDayAbbrev(int i) +{ + if (i < 7) + return RPT_DAYS[i]; + else + return RPT_DAYS[7]; +} + +void SortEvents() +{ + bool swapped; + int e; + Event_T Event; + + do { + swapped = false; + for (e=0; e<EventCount-1; e++) { + if (EventList[e].Start > EventList[e+1].Start) { + Event = EventList[e]; + EventList[e] = EventList[e+1]; + EventList[e+1] = Event; + swapped = true; + } + } + } while (swapped); +} + +uint16_t AtoIxN(char * p, int n) +{ + uint16_t res = 0; + + while (n--) { + res = (res * 10) + (*p - '0'); + p++; + } + return res; +} + +time_t ParseDateStamp(char * string, NTPClient & ntp) +{ + time_t tStamp; + struct tm t; + //INFO("ParseDateStamp(..., %s)\r\n", string); + t.tm_year = AtoIxN(string, 4) - 1900; + t.tm_mon = AtoIxN(string+4, 2) - 1; + t.tm_mday = AtoIxN(string+6, 2); + t.tm_hour = AtoIxN(string+9, 2); + t.tm_min = AtoIxN(string+11, 2); + t.tm_sec = AtoIxN(string+13, 2); + tStamp = mktime(&t); + if (string[strlen(string)-1] == 'Z') + tStamp = tStamp + ntp.getTZO(); + return tStamp; + //int tm_sec //seconds after the minute – [0, 60][@1] (public member object) + //int tm_min //minutes after the hour – [0, 59] (public member object) + //int tm_hour //hours since midnight – [0, 23] (public member object) + //int tm_mday //day of the month – [1, 31] (public member object) + //int tm_mon //months since January – [0, 11] (public member object) + //int tm_year //years since 1900 (public member object) + //int tm_wday //days since Sunday – [0, 6] (public member object) + //int tm_yday //days since January 1 – [0, 365] + //int tm_isdst +} + +char * FormatCTime(time_t t) +{ + static char temp[4][80]; + static int i = 0; + + i &= 3; + strcpy(temp[i], ctime(&t)); + temp[i][strlen(temp[i])-1] = '\0'; + return temp[i++]; +} + + +void ShowEventInfo(Event_T & Event) +{ + char temp[80]; + + INFO("*** Summary: %s", Event.Summary); + strcpy(temp, ctime(&Event.Start)); + temp[strlen(temp)-1] = '\0'; + INFO(" Start: %d %s", Event.Start, temp); + strcpy(temp, ctime(&Event.End)); + temp[strlen(temp)-1] = '\0'; + INFO(" End: %d %s", Event.End, temp ); + INFO(" Count: %d", Event.Count); + INFO(" RepeatFrq: %d", Event.RepeatFreq); + INFO(" RepeatDay: %02X", Event.RepeatDays); + strcpy(temp, ctime(&Event.Until)); + temp[strlen(temp)-1] = '\0'; + INFO(" Until: %d %s", Event.Until, temp); + INFO(" Location: %s", Event.Location); + INFO(" Category: %s", Event.Category); + INFO(" Priority: %d", Event.Priority); +} + + +/// Computes the intersection of time1 and time2 ranges, and modifies time1 +/// range to represent the intersection. +/// +/// @param start1 is input as the start of the time1 range, and is written +/// to represent the intersection of the two ranges. +/// @param end1 is input as the end of the time1 range and is written to +/// represent the intersection of the two ranges. +/// @param start2 is the start of the time2 range. +/// @param end2 is the end of the time2 range. +/// @returns true if the ranges have an intersection, and the time1 range +/// values have been modified. +/// +bool TimeIntersects(time_t * start1, time_t * end1, time_t * start2, time_t * end2) +{ + // |----Time1----| + // |--Time2--| false + // + // |----Time1----| + // |--Time2--| false + // + // |----Time1----| + // |----Time2----| + // |-Time1-| true + // + // |----Time1----| + // |----Time2----| + // |-Time1-| true + // + // |----Time1-------| + // |-Time2-| + // |-Time1-| true + // + if (*end1 < *start2 || *end2 < *start1) + return false; + if (max(*start1,*start2) < min(*end1,*end2)) { + *start1 = max(*start1,*start2); + *end1 = min(*end1,*end2); + return true; + } else { + return false; + } +} + +time_t NextInterval(int repeatFreq, int interval) +{ + time_t secperday = 60*60*24; + const int repeatFactor[] = {1, 7, 30, 365}; + INFO("freq %d, interval %d", repeatFreq, interval); + return repeatFactor[repeatFreq-1] * interval * secperday; +} + +bool RepeatIntersects(time_t * start1, time_t * end1, time_t * start2, time_t * end2, Event_T & Event) +{ + if (Event.RepeatFreq) { + INFO("%s", Event.Summary); + INFO("** 1: (%s, %s)", FormatCTime(*start1), FormatCTime(*end1)); + INFO(" 2: (%s, %s)", FormatCTime(*start2), FormatCTime(*end2)); + if (TimeIntersects(start1, end1, start2, end2)) { + return true; + } else if (Event.Start < *start2 && Event.Until > *start2 ) { // Until=.... + do { + time_t interval = NextInterval(Event.RepeatFreq, (Event.Interval == 0) ? 1 : Event.Interval); + *start1 = *start1 + interval; + *end1 = *end1 + interval; + INFO("** 1: (%s, %s)", FormatCTime(*start1), FormatCTime(*end1)); + INFO("until (%24s, %s)", " ", FormatCTime(Event.Until)); + INFO(" 2: (%s, %s)", FormatCTime(*start2), FormatCTime(*end2)); + if (TimeIntersects(start1, end1, start2, end2)) { + return true; + } + } while (*start1 < *end2 && *start1 < Event.Until); + } else if (Event.Start < *start2 && Event.Count) { // Count= + int count = Event.Count - 1; + do { + time_t interval = NextInterval(Event.RepeatFreq, (Event.Interval == 0) ? 1 : Event.Interval); + *start1 = *start1 + interval; + *end1 = *end1 + interval; + INFO("** 1: (%s, %s) - %d", FormatCTime(*start1), FormatCTime(*end1), count); + INFO(" 2: (%s, %s)", FormatCTime(*start2), FormatCTime(*end2)); + if (TimeIntersects(start1, end1, start2, end2)) { + return true; + } + } while (--count); + } else if (Event.Start < *start2) { // no Count= and no Until= + do { + time_t interval = NextInterval(Event.RepeatFreq, (Event.Interval == 0) ? 1 : Event.Interval); + *start1 = *start1 + interval; + *end1 = *end1 + interval; + INFO("== 1: (%s, %s)", FormatCTime(*start1), FormatCTime(*end1)); + INFO(" 2: (%s, %s)", FormatCTime(*start2), FormatCTime(*end2)); + if (TimeIntersects(start1, end1, start2, end2)) { + return true; + } + } while (*start1 < *end2); + } + } + return false; +} + + + +void ParseICalStream(char * pStart, time_t gridStartTime, time_t gridEndTime, NTPClient & ntp) +{ + Event_T Event; + bool tzAdjusted = false; + char * pEnd; + typedef enum { idle, inTimeZone, inEvent } seekstate_t; + seekstate_t seeking = idle; + EventCount = 0; + + while (pStart && EventCount < EVENT_COUNT) { + pEnd = strchr(pStart, '\n'); + if (pEnd) { + if (*(pEnd-1) == '\r') + pEnd--; + *pEnd++ = '\0'; + while (*pEnd && *pEnd < ' ') { + pEnd++; + } + // pStart now has a single null terminated line of text. + //INFO("*** %s", pStart); + switch (seeking) { + case idle: + if (strcmp(pStart, "BEGIN:VTIMEZONE") == 0) + seeking = inTimeZone; + else if (strcmp(pStart, "BEGIN:VEVENT") == 0) { + seeking = inEvent; + Event.Start = 0; + Event.End = 0; + Event.Until = 0; + Event.Summary[0] = '\0'; + Event.Location[0] = '\0'; + Event.Category[0] = '\0'; + Event.Count = 0; + Event.Interval = 0; + Event.RepeatFreq = rptfNone; + Event.RepeatDays = 0; + Event.Priority = 5; // 5 is Normal + //ShowEventInfo(Event, pc); + } + break; + case inTimeZone: + // Can also pick up daylight savings time + if (strcmp(pStart, "END:VTIMEZONE") == 0) + seeking = idle; + else if (strncmp(pStart, "TZID", 4) == 0) + tzAdjusted = true; + break; + case inEvent: + // inEvent + if (strcmp(pStart, "END:VEVENT") == 0) { + // Timezone offset + if (!tzAdjusted) { + Event.Start += ntp.getTZO(); + Event.End += ntp.getTZO(); + } + // Process it + ShowEventInfo(Event); + if (1 || Event.Start >= gridStartTime && Event.Start < gridEndTime + || Event.End >= gridStartTime && Event.End < gridEndTime) { + EventList[EventCount++] = Event; + INFO(" ++++++++++++ Added Event %d", EventCount); + } + seeking = idle; + } else if (strncmp(pStart, "DTSTART:", 8) == 0) { + Event.Start = ParseDateStamp(pStart+8, ntp); + //INFO(" Start: %d\r\n", mktime(&Event.eventStart)); + } else if (strncmp(pStart, "DTSTART;", 8) == 0) { + char * p = strrchr(pStart, ':'); + if (p) { + Event.Start = ParseDateStamp(p+1, ntp); + //INFO(" Start: %d\r\n", mktime(&Event.eventStart)); + } + } else if (strncmp(pStart, "DTEND:", 6) == 0) { + Event.End = ParseDateStamp(pStart+6, ntp); + //INFO(" End: %d\r\n", mktime(&Event.eventEnd)); + } else if (strncmp(pStart, "DTEND;", 6) == 0) { + char * p = strrchr(pStart, ':'); + if (p) { + Event.End = ParseDateStamp(p+1, ntp); + //INFO(" End: %d\r\n", mktime(&Event.eventEnd)); + } + } else if (strncmp(pStart, "SUMMARY:", 8) == 0) { + strncpy(Event.Summary, pStart+8, SUMMARY_CHARS-1); + Event.Summary[SUMMARY_CHARS-1] = '\0'; + //INFO(" Summary: %s\r\n", Event.Summary); + } else if (strncmp(pStart, "LOCATION:", 9) == 0) { + strncpy(Event.Location, pStart+9, LOCATION_CHARS-1); + Event.Location[LOCATION_CHARS-1] = '\0'; + //INFO(" Location: %s\r\n", Event.Location); + } else if (strncmp(pStart, "PRIORITY:", 9) == 0) { + Event.Priority = *(pStart+9) - '0'; + //INFO(" Priority: %d\r\n", Event.Priority); + } else if (strncmp(pStart, "CATEGORIES:", 11) == 0) { + strncpy(Event.Category, pStart+11, CATEGORY_CHARS-1); + Event.Category[CATEGORY_CHARS-1] = '\0'; + //INFO(" Category: %s\r\n", Event.Category); + } else if (strncmp(pStart, "RRULE:", 6) == 0) { + //RRULE:FREQ=WEEKLY;UNTIL=20140502T180000;BYDAY=MO,TU,WE,TH,FR + char * p1, *p2; + INFO("%s", pStart); + p1 = pStart + 6; // p1 = FREQ=WEEKLY;UNTIL=20140502T180000;BYDAY=MO,TU,WE,TH,FR + p2 = strchr(p1, ';'); + if (p2) + *p2++ = '\0'; + while (*p1) { + INFO("%s", p1); + if (strncmp(p1, "FREQ=", 5) == 0) { + INFO("%s", p1); + p1 += 5; // p1 = WEEKLY;UNTIL=20140502T180000;BYDAY=MO,TU,WE,TH,FR + if (strncmp(p1, "WEEKLY", 6) == 0) { + INFO(" %s", p1); + Event.RepeatFreq = rptfWeekly; + p1 += 6; + } else if (strncmp(p1, "DAILY", 5) == 0) { + INFO(" %s", p1); + Event.RepeatFreq = rptfDaily; + p1 += 5; + } else if (strncmp(p1, "MONTHLY", 7) == 0) { + INFO(" %s", p1); + Event.RepeatFreq = rptfMonthly; + p1 += 7; + } else if (strncmp(p1, "YEARLY", 6) == 0) { + INFO(" %s", p1); + Event.RepeatFreq = rptfYearly; + p1 += 7; + } + } else if (strncmp(p1, "INTERVAL=", 9) == 0) { // INTERVAL=2 + INFO("%s", p1); + p1 += 9; + Event.Interval = atoi(p1); + } else if (strncmp(p1, "COUNT=", 6) == 0) { // COUNT=12; + INFO("%s", p1); + p1 += 6; // p1 = + Event.Count = atoi(p1); + } else if (strncmp(p1, "UNTIL=", 6) == 0) { + INFO("%s", p1); + p1 += 6; // p1 = 20140502T180000;BYDAY=MO,TU,WE,TH,FR + Event.Until = ParseDateStamp(p1, ntp); + } else if (strncmp(p1, "BYDAY=", 6) == 0) { + INFO("%s", p1); + p1 += 6; // p1 = MO,TU,WE,TH,FR + while (*p1 >= ' ') { + INFO(" %s", p1); + for (int d=0; d<7; d++) { + if (strncmp(p1,RepeatDayAbbrev(d),2) == 0) { + Event.RepeatDays |= (1 << d); + INFO(" %s %02X", RepeatDayAbbrev(d), Event.RepeatDays); + break; + } + } + p1 += 3; + } + INFO(" RepeatDay: %02X", Event.RepeatDays); + } + if (!p2) + break; + p1 = p2; + p2 = strchr(p1, ';'); + if (p2) + *p2++ = '\0'; + } + } + // End of inEvent + break; + default: + seeking = idle; + break; + } + pStart = pEnd; + } else { + pStart = NULL; + } + } // while +} \ No newline at end of file