forked from weirdhome

Fork of HTTPClient by David Smart

Revision:
39:21fc7a4b6927
Child:
40:bcb19f8dbba3
diff -r 2ef07232f65c -r 21fc7a4b6927 data/HTTPiCal.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/HTTPiCal.cpp	Fri Mar 10 02:53:09 2017 +0000
@@ -0,0 +1,696 @@
+#include "HTTPiCal.h"
+
+#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
+
+HTTPiCal::HTTPiCal(int count) {
+    EventList = (Event_T *)malloc(count * sizeof(Event_T));
+    if (EventList) {
+        EventSpaceCount = count;
+        EventCount = 0;
+        seeking = idle;
+    } else {
+        error("no space for event list");
+    }
+}
+
+HTTPiCal::~HTTPiCal() {
+    if (EventList)
+        free(EventList);
+}
+
+void HTTPiCal::SetTimeWindow(time_t StartTime, time_t EndTime) {
+    gridStartTime = StartTime;
+    gridEndTime = EndTime;
+}
+
+bool HTTPiCal::GetEvent(unsigned int i, Event_T * event) {
+    if (i < EventCount) {
+        *event = EventList[i];
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void HTTPiCal::writeReset() {
+    //INFO("writeReset()");
+    EventCount = 0;
+    lineBuf[0] = '\0';
+}
+
+int HTTPiCal::write(const char * buf, size_t len) {
+    const char * pStart = buf;
+    const char * pEOL;
+    size_t origLen = len;
+        
+    pEOL = strchr(pStart, '\n');
+    //INFO("\r\n\r\nwrite[%d:%d] = \r\n%s\r\nend-write\r\n", len, pEOL - pStart, buf);
+    while (pEOL && (pEOL - pStart) < len) {
+        int lbLen = strlen(lineBuf);
+        strncpy(lineBuf + lbLen, pStart, (pEOL - pStart));
+        lineBuf[lbLen + (pEOL - pStart) + 1] = '\0';
+        if (lineBuf[pEOL - pStart + lbLen - 1] == '\r')
+            lineBuf[pEOL - pStart + lbLen - 1] = '\0';
+        //INFO("lineBuf:[%s]", lineBuf);
+        ParseICalStream(lineBuf, gridStartTime, gridEndTime, -300);
+        //INFO("");
+        lineBuf[0] = '\0';
+        len -= (pEOL - pStart);
+        pStart = pEOL + 1;
+        while (pStart && *pStart && *pStart <= ' ') {
+            pStart++;
+            len--;
+        }
+        if (*pStart)
+            pEOL = strchr(pStart, '\n');
+        else
+            pEOL = NULL;
+    }
+    if (len) {
+        strncpy(lineBuf, pStart, len);
+        lineBuf[len] = '\0';
+        //INFO("fragment:[%s]", lineBuf);
+    }
+    //INFO("write returns %d", origLen);
+    return origLen;
+}
+
+void HTTPiCal::setDataType(const char* type) {
+    //INFO("setDataType(%s)", type);
+}
+
+//void HTTPiCal::setLocation(const char * location) {
+//
+//}
+
+void HTTPiCal::setIsChunked(bool chunked) {
+    INFO("setIsChunked(%d)", chunked);
+    m_chunked = chunked;
+}
+
+void HTTPiCal::setDataLen(size_t len) {
+    //INFO("setDataLen(%d)", len);
+}
+
+
+const char * RPT_DAYS[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA", "" };
+
+int HTTPiCal::GetNumEvents(void)
+{
+    return EventCount;
+}
+
+const char * HTTPiCal::RepeatDayAbbrev(int i)
+{
+    if (i < 7)
+        return RPT_DAYS[i];
+    else
+        return RPT_DAYS[7];
+}
+
+void HTTPiCal::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 HTTPiCal::AtoIxN(const char * p, int n)
+{
+    uint16_t res = 0;
+
+    while (n--) {
+        res = (res * 10) + (*p - '0');
+        p++;
+    }
+    return res;
+}
+
+// YYYYMMDD[THHMMSS[Z]]
+// VALUE=DATE:YYYYMMDD
+time_t HTTPiCal::ParseDateStamp(const char * string, tz_sec_t tzoSec)
+{
+    time_t tStamp;
+    struct tm t;
+    struct tm * tnow;
+
+    time(&tStamp);
+    tnow = localtime(&tStamp);
+    if (strncmp(string, "VALUE=DATE:", 11) == 0) {
+        string += 11;
+    }
+    //INFO("ParseDateStamp(%s,%d)\n", string, tzoSec);
+    t.tm_year = AtoIxN(string, 4) - 1900;
+    t.tm_mon  = AtoIxN(string+4, 2) - 1;
+    t.tm_mday = AtoIxN(string+6, 2);
+    if (strlen(string) > 8) {
+        t.tm_hour = AtoIxN(string+9, 2);
+        t.tm_min  = AtoIxN(string+11, 2);
+        t.tm_sec  = AtoIxN(string+13, 2);
+        t.tm_isdst = tnow->tm_isdst;
+    } else {
+        t.tm_hour = 0;
+        t.tm_min = 0;
+        t.tm_sec = 0;
+        t.tm_isdst = tnow->tm_isdst;
+    }
+    tStamp = mktime(&t);
+    if (string[strlen(string)-1] == 'Z') {
+        //INFO("Applying tzoSec %d", tzoSec);
+        tStamp = tStamp + tzoTZIDSec;
+    } else {
+        tStamp = tStamp + tzoSec;
+    }
+    return tStamp;
+}
+
+char * HTTPiCal::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 HTTPiCal::ShowEventInfo(Event_T & Event)
+{
+    char scratch[80];
+    #define LF "\r\n"
+
+    printf("*******    Summary: %s" LF, Event.Summary);
+    printf("          Location: %s" LF, Event.Location);
+    printf("          Category: %s" LF, Event.Category);
+    printf("          Priority: %d" LF, Event.Priority);
+    sprintf(scratch, "%lu ", Event.Start);
+    sprintf(scratch + strlen(scratch), "%s", (Event.Start == 0) ? "" : ctime(&Event.Start));
+    scratch[strlen(scratch)-1] = '\0';
+    printf("             Start: %s" LF, scratch);
+    sprintf(scratch, "%lu ", Event.End);
+    sprintf(scratch + strlen(scratch), "%s", (Event.End == 0) ? "" : ctime(&Event.End));
+    scratch[strlen(scratch)-1] = '\0';
+    printf("               End: %s" LF, scratch);
+    printf("             Count: %d" LF,  Event.Count);
+    printf("          Interval: %d" LF, Event.Interval);
+    printf("         RepeatFrq: %d" LF,  Event.RepeatFreq);
+    printf("         RepeatDay: %02X" LF,  Event.RepeatDays);
+    printf("    RepeatMonthDay: %08X" LF,  Event.RepeatMonthDay);
+    printf(" RepeatMonthDayRev: %08X" LF, Event.RepeatMonthDayRev);
+    printf("       RepeatMonth: %04X" LF,  Event.RepeatMonths);
+    sprintf(scratch, "%lu ", Event.Until);
+    sprintf(scratch + strlen(scratch), "%s", (Event.Until == 0) ? "" : ctime(&Event.Until));
+    scratch[strlen(scratch)-1] = '\0';
+    printf("             Until: %s" LF, scratch);
+    printf("" LF);
+}
+
+
+/// Computes the intersection of time1 and time2 ranges, and modifies time1
+/// range to represent the intersection.
+///
+/// start1 is input as the start of the time1 range, and is written
+///     to represent the intersection of the two ranges.
+/// end1 is input as the end of the time1 range and is written to
+///     represent the intersection of the two ranges.
+/// start2 is the start of the time2 range.
+/// 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 HTTPiCal::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
+    //
+    // | Time1 (end1 == 0)
+    // | Time2 (end2 == 0)                     true
+    //
+    if (*start1 == *start2 && *end1 == 0 && *end2 == 0)
+        return 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;
+    }
+}
+
+bool HTTPiCal::isLeapYear(time_t t)
+{
+    int year;
+    struct tm * ts;
+    ts = localtime(&t);
+
+    year = 1900 + ts->tm_year + 1;
+    if ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0))
+        return true;
+    else
+        return false;
+}
+
+time_t HTTPiCal::NextInterval(time_t curTime, int repeatFreq, int interval)
+{
+    const time_t secperday = 60*60*24;
+    const int repeatFactor[] = {0, 1, 7, 30, 365};
+    int delta = repeatFactor[repeatFreq];
+    if (repeatFreq == 4 && isLeapYear(curTime))
+        delta += 1;
+    //INFO("freq %d, interval %d, delta %d", repeatFreq, interval, delta);
+    return delta * interval * secperday;
+}
+
+
+// start1,end1 is the time range representing the visible grid
+// start2,end2 is the time range of the event being tested
+// Event is also the event being tested and permits testing the repeat information.
+//
+// If the event repeat pattern intersects with the display pattern, indicate this as "true"
+//
+bool HTTPiCal::RepeatMaskIntersects(time_t * start1, time_t * end1, time_t * start2, time_t * end2, Event_T * Event)
+{
+    bool intersects = false;
+    
+    //INFO("RepeatFreq: %d", Event->RepeatFreq);
+    if (Event->RepeatFreq == rptfDaily) {
+        //INFO("rptfDaily is not handled");
+    } else if (Event->RepeatFreq == rptfWeekly) {
+        struct tm * timeinfo;
+        timeinfo = localtime(start1);
+        uint8_t daymask = Event->RepeatDays;
+        // now, check the tm_wday (0=Sunday, 1=Monday, ...) and see if we intersect with the event time
+        uint8_t testmask = 1 << timeinfo->tm_wday;
+        //INFO("Mask: Event mask: %02X, test mask: %02X", daymask, testmask);
+        if (daymask & testmask)
+            intersects = true;
+        else
+            intersects = false;
+        //INFO("  intersects: %02X", daymask & testmask);
+        return intersects;
+    } else if (Event->RepeatFreq == rptfYearly) {
+        //struct tm * timeinfo;
+        //timeinfo = localtime(start1);
+        //INFO("rptfYearly is not handled well yet");        
+    }
+    //INFO("Mask: no handler, returning true");
+    return true;
+}
+
+bool HTTPiCal::RepeatIntersects(time_t * start1, time_t * end1, time_t * start2, time_t * end2, Event_T * Event)
+{
+    INFO("** 1: (%s, %s)", FormatCTime(*start1), *end1 ? FormatCTime(*end1) : "");
+    INFO("   2: (%s, %s)", FormatCTime(*start2), *end2 ? FormatCTime(*end2) : "");
+    INFO("  ev: (%s, %s)", FormatCTime(Event->Start), Event->End ? FormatCTime(Event->End) : "");
+    if (TimeIntersects(start1, end1, start2, end2))
+        return true;
+    if (Event && Event->RepeatFreq) {
+        INFO("RepeatFreq: %d", Event->RepeatFreq);
+        if (Event->Start < *start2 && Event->Until > *start2 ) {           // Until=....
+            INFO("Repeat until: %d", Event->Until);
+            do {
+                time_t interval = NextInterval(*start1, Event->RepeatFreq, (Event->Interval == 0) ? 1 : Event->Interval);
+                *start1 = *start1 + interval;
+                if (*end1)
+                    *end1   = *end1   + interval;
+                INFO("** 1: (%s, %s)", FormatCTime(*start1), *end1 ? FormatCTime(*end1) : "");
+                INFO("until (%24s, %s)", " ", FormatCTime(Event->Until));
+                INFO("   2: (%s, %s)", FormatCTime(*start2), *end2 ? FormatCTime(*end2) : "");
+                if (!RepeatMaskIntersects(start1, end1, start2, end2, Event)) {
+                    continue;   // we're not on a repeat cycle (e.g. wrong day of the week)
+                }
+                if (TimeIntersects(start1, end1, start2, end2)) {
+                    return true;
+                }
+            } while ((*end2 == 0 || *start1 < *end2) && *start1 < Event->Until);
+        } else if (Event->Start < *start2 && Event->Count) {                      // Count=
+            INFO("Repeat count %d", Event->Count);
+            int count = Event->Count - 1;
+            do {
+                time_t interval = NextInterval(*start1, Event->RepeatFreq, (Event->Interval == 0) ? 1 : Event->Interval);
+                *start1 = *start1 + interval;
+                if (*end1)
+                    *end1 = *end1 + interval;
+                INFO("** 1: (%s, %s) - %d", FormatCTime(*start1), *end1 ? FormatCTime(*end1) : "", count);
+                INFO("   2: (%s, %s)", FormatCTime(*start2), *end2 ? FormatCTime(*end2) : "");
+                if (!RepeatMaskIntersects(start1, end1, start2, end2, Event)) {
+                    continue;   // we're not on a repeat cycle (e.g. wrong day of the week)
+                }
+                if (TimeIntersects(start1, end1, start2, end2)) {
+                    return true;
+                }
+            } while (--count && *end1 < *start2);
+        } else if (Event->Start < *start2) {                      // no Count= and no Until=
+            INFO(" no Repeat end");
+            do {
+                int rptFreq = Event->RepeatFreq;
+                if (Event->RepeatFreq == 2 && Event->RepeatDays != 0)
+                    rptFreq--;
+                time_t interval = NextInterval(*start1, rptFreq, (Event->Interval == 0) ? 1 : Event->Interval);
+                *start1 = *start1 + interval;
+                if (*end1)
+                    *end1   = *end1   + interval;
+                INFO("== 1: (%s, %s)", FormatCTime(*start1), *end1 ? FormatCTime(*end1) : "");
+                INFO("   2: (%s, %s)", FormatCTime(*start2), *end2 ? FormatCTime(*end2) : "");
+                if (!RepeatMaskIntersects(start1, end1, start2, end2, Event)) {
+                    continue;   // we're not on a repeat cycle (e.g. wrong day of the week)
+                }
+                if (TimeIntersects(start1, end1, start2, end2)) {
+                    return true;
+                }
+            } while (*start1 < *end2 || (*end2 == 0 && *start1 < *start2));
+        } else {
+            INFO("falling out");
+        }
+    }
+    INFO("  no intersection");
+    return false;
+}
+
+// All the stuff between
+// BEGIN:VEVENT
+// ...
+// END:VEVENT
+//
+void HTTPiCal::ParseEvent(Event_T * Event, const char * pStart, tz_sec_t tzoSec)
+{
+    INFO("ParseEvent(...,'%s',%d)", pStart, tzoSec);
+    if (strncmp(pStart, "DTSTART:", 8) == 0) {
+        Event->Start = ParseDateStamp(pStart+8, tzoSec);
+        INFO("  Start: %s\n", ctime(&Event->Start));
+    } else if (strncmp(pStart, "DTSTART;", 8) == 0) {
+        const char * p = pStart + 8;
+        tzoSec = ParseTZID(p);
+        p = strrchr(pStart, ':');
+        if (p) {
+            Event->Start = ParseDateStamp(p+1, 0);  // example was localtime with GMT -06:00 tzoSec);
+            INFO("  Start: %s", ctime(&Event->Start));
+        }
+    } else if (strncmp(pStart, "DTEND:", 6) == 0) {
+        Event->End = ParseDateStamp(pStart+6, tzoSec);
+        //INFO("    End: %d\n", mktime(&Event->eventEnd));
+    } else if (strncmp(pStart, "DTEND;", 6) == 0) {
+        const char * p = pStart + 6;
+        tzoSec = ParseTZID(p);
+        p = strrchr(pStart, ':');
+        if (p) {
+            Event->End = ParseDateStamp(p+1, 0);  // example was localtime with GMT -06:00 tzoSec);
+            INFO("    End: %s", ctime(&Event->End));
+        }
+    } else if (strncmp(pStart, "SUMMARY:", 8) == 0) {
+        strncpy(Event->Summary, pStart+8, SUMMARY_CHARS-1);
+        Event->Summary[SUMMARY_CHARS-1] = '\0';
+        //INFO(" Summary: %s\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\n", Event->Location);
+    } else if (strncmp(pStart, "PRIORITY:", 9) == 0) {
+        Event->Priority = *(pStart+9) - '0';
+        //INFO("  Priority: %d\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\n", Event->Category);
+    } else if (strncmp(pStart, "RRULE:", 6) == 0) {
+        //RRULE:FREQ=WEEKLY;UNTIL=20140502T180000;BYDAY=MO,TU,WE,TH,FR
+        const 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 += 6;
+                }
+            } 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
+                //printf("UNTIL= {%s}\n", p1);
+                Event->Until = ParseDateStamp(p1, tzoSec);
+                //printf("UNTIL:: %d: %d\n", Event->Until, tzoSec);
+            } 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);
+            } else if (strncmp(p1, "BYMONTHDAY=", 11) == 0) {
+                // RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15
+                p1 += 11;
+                while (*p1 >= ' ') {
+                    const char * px = p1;
+                    while (*px >= ' ' && *px != ',') {    // find , or ; or <nul>
+                        px++;
+                    }
+                    //if (*px)
+                    //    *px++ = '\0';
+                    int num = atoi(p1);
+                    if (num >= 0)
+                        Event->RepeatMonthDay |= (1 << num);
+                    else
+                        Event->RepeatMonthDayRev |= (1 << -num);
+                    p1 = px;
+                }
+                INFO("  RepeatMonthDay: %08X", Event->RepeatMonthDay);
+            } else if (strncmp(p1, "BYMONTH=", 8) == 0) {
+                // RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3
+                p1 += 8;
+                while (*p1 >= ' ') {
+                    const char * px = p1;
+                    while (*px >= ' ' && *px != ',') {    // find , or ; or <nul>
+                        px++;
+                    }
+                    //if (*px)
+                    //    *px++ = '\0';
+                    int num = atoi(p1);
+                    if (num >= 0)
+                        Event->RepeatMonths |= (1 << num);
+                    //else
+                    //    ; // Event->RepeatMonthsRev |= (1 << -num);
+                    p1 = px;
+                }
+                INFO("  RepeatMonths: %04X", Event->RepeatMonths);                
+            }
+            if (!p2)
+                break;
+            p1 = p2 + 1;
+            p2 = strchr(p1, ';');
+            //if (p2)
+            //    *p2++ = '\0';
+        }
+    }
+}
+
+
+// TZID="(GMT -06:00)":20140519T063000
+// TZID:(UTC-06:00) Central Time (US & Canada)
+// TZID:(GMT -06:00)
+HTTPiCal::tz_sec_t HTTPiCal::ParseTZID(const char * string)
+{
+    tz_sec_t tzo = 0;
+    bool sign = false;
+
+    INFO("ParseTZID(%s)", string);  // TZID="(GMT -06:00)":20140519T063000
+    string += 5;                    // "(GMT -06:00)":20140519T063000
+    if (*string == '"')
+        string++;                   // (GMT -06:00)":20140519T063000
+    if ((strncmp(string, "(UTC", 4) == 0) 
+    ||  (strncmp(string, "(GMT", 4) == 0) ){
+        string += 4;
+        if (*string == ' ')
+            string++;
+        if (*string == '-') {
+            sign = true;
+            string++;
+        }
+        tzo = atoi(string) * 3600;
+        string = strchr(string, ':');
+        if (string) {
+            string++;
+            tzo += atoi(string) * 60;
+        }
+        if (sign)
+            tzo = -tzo;
+        INFO("  tzo = %d", tzo);
+    } else {
+        ERR("Unhandled TZID(%s)", string);
+    }
+    return tzo;
+}
+
+void HTTPiCal::ParseICalStart(void) {
+    tzAdjusted = false;
+    seeking = idle;
+    EventCount = 0;
+
+}
+
+int HTTPiCal::ParseICalClose(void) {
+    if (EventCount > 0)
+        SortEvents();
+    return GetNumEvents();
+}
+
+int HTTPiCal::ParseICalStream(const char * pStart, time_t gridStartTime, time_t gridEndTime, tz_min_t tzoMin, bool showEvents)
+{
+    INFO("Parse(%s)", pStart);
+    //INFO("EventCount: %d, EventSpaceCount: %d, seeking: %d", EventCount, EventSpaceCount, seeking);
+    if (pStart && *pStart && EventCount < EventSpaceCount) {
+        switch (seeking) {
+            case idle:
+                if (strncmp(pStart, "BEGIN:VTIMEZONE", 15) == 0) {
+                    //INFO("begin:timezone");
+                    seeking = inTimeZone;
+                } else if (strncmp(pStart, "BEGIN:VEVENT", 12) == 0) {
+                    //INFO("begin:vevent");
+                    seeking = inEvent;
+                    EventList[EventCount].Start = 0;
+                    EventList[EventCount].End = 0;
+                    EventList[EventCount].Until = 0;
+                    EventList[EventCount].Summary[0] = '\0';
+                    EventList[EventCount].Location[0] = '\0';
+                    EventList[EventCount].Category[0] = '\0';
+                    EventList[EventCount].Count = 0;
+                    EventList[EventCount].Interval = 0;
+                    EventList[EventCount].RepeatFreq = rptfNone;
+                    EventList[EventCount].RepeatDays = 0;
+                    EventList[EventCount].RepeatMonthDay = 0;
+                    EventList[EventCount].RepeatMonthDayRev = 0;
+                    EventList[EventCount].RepeatMonths = 0;
+                    EventList[EventCount].Priority = 5;     // 5 is Normal
+                }
+                break;
+            case inTimeZone:
+                //INFO("inTimeZone:");
+                // Can also pick up daylight savings time
+                if (strncmp(pStart, "END:VTIMEZONE", 13) == 0) {
+                    seeking = idle;
+                } else if ((strncmp(pStart, "TZID:", 5) == 0) 
+                || (strncmp(pStart, "TZID=", 5) == 0) ) {
+                    tzoTZIDSec = ParseTZID(pStart);
+                    tzAdjusted = true;
+                    pStart += 5;
+                } else if (strncmp(pStart, "BEGIN:STANDARD", 14) == 0) {
+                    
+                } else if (strncmp(pStart, "BEGIN:DAYLIGHT", 14) == 0) {
+                    
+                }
+                break;
+            case inEvent:
+                //INFO("inEvent:");
+                // inEvent
+                if (strncmp(pStart, "END:VEVENT", 10) == 0) {
+                    // Timezone offset
+                    if (!tzAdjusted) {
+                        EventList[EventCount].Start += (60 * tzoMin);
+                        if (EventList[EventCount].End)
+                            EventList[EventCount].End += (60 * tzoMin);
+                    }
+                    // Process it
+                    if (showEvents)
+                        ShowEventInfo(EventList[EventCount]);
+                    // Force to ALWAYS
+                    time_t aStart = EventList[EventCount].Start;
+                    time_t aEnd   = EventList[EventCount].End;
+                    if (gridStartTime 
+                    && RepeatIntersects(&aStart, &aEnd, &gridStartTime, &gridEndTime, &EventList[EventCount])) {
+                        EventCount++;
+                        if (showEvents) {
+                            INFO(" +++++ Added Event %d", EventCount);//, EventList[EventCount].Summary);
+                        }
+                    }
+                    seeking = idle;
+                } else {
+                    //INFO("parse event data");
+                    ParseEvent(&EventList[EventCount], pStart, 60 * tzoMin);
+                }
+                // End of inEvent
+                break;
+            default:
+                INFO("default:");
+                seeking = idle;
+                break;
+        }
+    } // while
+    return EventCount;
+}