an iCal processing library

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