an iCal processing library
iCal.cpp
- Committer:
- WiredHome
- Date:
- 2017-10-01
- Revision:
- 12:3dc52826343f
- Parent:
- 10:deeaec151283
File content as of revision 12:3dc52826343f:
// // TODO // Repeat parsing is quite weak right now. It handles repeating days w/in a week // (e.g. repeat weekly, on Mon, Tue, Thu) // //#include "stdafx.h" //#pragma warning (disable: 4996) #include "iCal.h" #include <algorithm> //#ifdef _DEBUG //#define DEBUG "iCal" //#endif #ifdef WIN32 #define LF "\n" #else // mbed #define LF "\r\n" #endif // WIN32 #include <cstdio> #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) #define DBG(x, ...) std::printf("[DBG %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__) #define WARN(x, ...) std::printf("[WRN %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__) #define ERR(x, ...) std::printf("[ERR %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__) #define INFO(x, ...) std::printf("[INF %s %3d] " x LF, 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; int32_t tzoTZIDSec = 0; static 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; } // YYYYMMDD[THHMMSS[Z]] // VALUE=DATE:YYYYMMDD time_t ParseDateStamp(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 * 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 scratch[80]; 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 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 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 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 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 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) : ""); if (TimeIntersects(start1, end1, start2, end2)) return true; if (Event && Event->RepeatFreq) { //INFO("Summary: %s", Event->Summary); if (Event->Start < *start2 && Event->Until > *start2 ) { // 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= 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); } else if (Event->Start < *start2) { // no Count= and no Until= 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)); } } //INFO(" no intersection"); return false; } // All the stuff between // BEGIN:VEVENT // ... // END:VEVENT // void ParseEvent(Event_T * Event, 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) { char * p = pStart + 8; tzoSec = ParseTZID(p); p = strrchr(pStart, ':'); if (p) { Event->Start = ParseDateStamp(p+1, tzoSec); INFO(" Start: %s\n", 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) { char * p = pStart + 8; tzoSec = ParseTZID(p); p = strrchr(pStart, ':'); if (p) { Event->End = ParseDateStamp(p+1, tzoSec); INFO(" End: %s\n", 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 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 >= ' ') { 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 >= ' ') { 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; p2 = strchr(p1, ';'); if (p2) *p2++ = '\0'; } } } // TZID="(GMT -06:00)":20140519T063000 // TZID:(UTC-06:00) Central Time (US & Canada) // TZID:(GMT -06:00) tz_sec_t ParseTZID(char * string) { tz_sec_t tzo = 0; bool sign = false; INFO("ParseTZID(%s)", string); if (*string == '"') // TZID="(GMT -06:00)":20140519T063000 string++; 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; } int ParseICalStream(char * pStart, time_t gridStartTime, time_t gridEndTime, tz_min_t tzoMin, bool showEvents) { Event_T Event; bool tzAdjusted = false; char * pEnd; typedef enum { idle, inTimeZone, inEvent } seekstate_t; seekstate_t seeking = idle; EventCount = 0; if (gridEndTime == gridStartTime) gridEndTime++; // advance so they are not equal 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. if (showEvents) 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.RepeatMonthDay = 0; Event.RepeatMonthDayRev = 0; Event.RepeatMonths = 0; Event.Priority = 5; // 5 is Normal } break; case inTimeZone: // Can also pick up daylight savings time if (strcmp(pStart, "END:VTIMEZONE") == 0) { seeking = idle; } else if ((strncmp(pStart, "TZID:", 5) == 0) || (strncmp(pStart, "TZID=", 5) == 0) ) { tzoTZIDSec = ParseTZID(pStart + 5); tzAdjusted = true; } else if (strncmp(pStart, "BEGIN:STANDARD", 14) == 0) { } else if (strncmp(pStart, "BEGIN:DAYLIGHT", 14) == 0) { } break; case inEvent: // inEvent if (strcmp(pStart, "END:VEVENT") == 0) { // Timezone offset if (!tzAdjusted) { Event.Start += (60 * tzoMin); if (Event.End) Event.End += (60 * tzoMin); } // Process it if (showEvents) ShowEventInfo(Event); // Force to ALWAYS if (1 || (Event.Start >= gridStartTime && Event.Start < gridEndTime) || (Event.End >= gridStartTime && Event.End < gridEndTime)) { EventList[EventCount++] = Event; if (showEvents) { INFO(" +++++ Added Event %d: %s", EventCount, Event.Summary); } } seeking = idle; } else { ParseEvent(&Event, pStart, 60 * tzoMin); } // End of inEvent break; default: seeking = idle; break; } pStart = pEnd; } else { pStart = NULL; } } // while if (EventCount > 0) SortEvents(); return GetNumEvents(); }