changeset 199:e3a46f66332b

more changes in recurrence decoding
author Carl Byington <carl@five-ten-sg.com>
date Wed, 13 May 2009 11:59:55 -0700
parents 7c60d6d1c681
children d360f96f71f6
files configure.in libpst.spec.in python/python-libpst.cpp regression/regression-tests.bash src/libpst.c src/libpst.h src/lspst.c src/readpst.c src/timeconv.c src/timeconv.h
diffstat 10 files changed, 155 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Tue May 12 19:34:49 2009 -0700
+++ b/configure.in	Wed May 13 11:59:55 2009 -0700
@@ -272,7 +272,7 @@
         esac
     ],
     [
-        enable_python=no
+        enable_python=yes
     ])
 AC_MSG_RESULT([$enable_python])
 AM_CONDITIONAL(PYTHON_INTERFACE, [test "$enable_python" = "yes"])
--- a/libpst.spec.in	Tue May 12 19:34:49 2009 -0700
+++ b/libpst.spec.in	Wed May 13 11:59:55 2009 -0700
@@ -83,7 +83,7 @@
 
 
 %build
-%configure --enable-libpst-shared --enable-python
+%configure --enable-libpst-shared
 make %{?_smp_mflags}
 
 
@@ -150,7 +150,8 @@
 %changelog
 * Mon Apr 20 2009 Carl Byington <carl@five-ten-sg.com> - 0.6.38-1
 - add python interface to the shared library.
-- bump soname to version 4.
+- bump soname to version 4 for many changes to the interface.
+- better decoding of recurrence data in appointments.
 
 * Fri Apr 17 2009 Carl Byington <carl@five-ten-sg.com> - 0.6.37-1
 - add pst_attach_to_mem() back into the shared library interface.
--- a/python/python-libpst.cpp	Tue May 12 19:34:49 2009 -0700
+++ b/python/python-libpst.cpp	Wed May 13 11:59:55 2009 -0700
@@ -134,11 +134,15 @@
 }
 
 string          pst::pst_rfc2425_datetime_format(const FILETIME *ft) {
-    return ::pst_rfc2425_datetime_format((FILETIME *)ft);   // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions
+    char buf[30];
+    ::pst_rfc2425_datetime_format(ft, sizeof(buf), buf);
+    return string(buf);
 }
 
 string          pst::pst_rfc2445_datetime_format(const FILETIME *ft) {
-    return ::pst_rfc2445_datetime_format((FILETIME *)ft);   // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions
+    char buf[30];
+    ::pst_rfc2445_datetime_format(ft, sizeof(buf), buf);
+    return string(buf);
 }
 
 string          pst::pst_default_charset(pst_item *item) {
@@ -203,13 +207,6 @@
     }
 };
 
-struct make_python_pst_recurrence {
-    static PyObject* convert(pst_recurrence* const &s) {
-        if (s) return to_python_indirect<pst_recurrence*, detail::make_reference_holder>()(s);
-        return NULL;
-    }
-};
-
 struct make_python_pst_item_email {
     static PyObject* convert(pst_item_email* const &s) {
         if (s) return to_python_indirect<pst_item_email*, detail::make_reference_holder>()(s);
@@ -252,7 +249,6 @@
     to_python_converter<pst_binary,       make_python_pst_binary>();
     to_python_converter<ppst_binary,      make_python_ppst_binary>();
     to_python_converter<char*,            make_python_string>();
-    to_python_converter<pst_recurrence*,  make_python_pst_recurrence>();
     to_python_converter<pst_item_email*,  make_python_pst_item_email>();
     to_python_converter<pst_item_attach*, make_python_pst_item_attach>();
     to_python_converter<pst_desc_tree*,   make_python_pst_desc_tree>();
@@ -528,6 +524,10 @@
         .def_readonly("parm5",                        &pst_recurrence::parm5)
         .def_readonly("termination",                  &pst_recurrence::termination)
         .def_readonly("interval",                     &pst_recurrence::interval)
+        .def_readonly("bydaymask",                    &pst_recurrence::bydaymask)
+        .def_readonly("dayofmonth",                   &pst_recurrence::dayofmonth)
+        .def_readonly("monthofyear",                  &pst_recurrence::monthofyear)
+        .def_readonly("position",                     &pst_recurrence::position)
         .def_readonly("count",                        &pst_recurrence::count)
         ;
 
--- a/regression/regression-tests.bash	Tue May 12 19:34:49 2009 -0700
+++ b/regression/regression-tests.bash	Wed May 13 11:59:55 2009 -0700
@@ -121,9 +121,10 @@
     #dopst  18 test-mac.pst
     ##dopst  19 harris.pst
     #dopst  20 spam.pst
-    dopst  21 rendgen.pst       # single email appointment
+    #dopst  21 rendgen.pst       # single email appointment
     dopst  22 rendgen2.pst      # email appointment with no termination date
-    dopst  23 rendgen3.pst      # mime signed email
+    #dopst  23 rendgen3.pst      # mime signed email
+    dopst  24 rendgen4.pst      # appointment test cases
 fi
 
 grep 'lost:' *err | grep -v 'lost: 0 '
--- a/src/libpst.c	Tue May 12 19:34:49 2009 -0700
+++ b/src/libpst.c	Wed May 13 11:59:55 2009 -0700
@@ -2014,14 +2014,14 @@
 // malloc space and copy the item filetime
 #define LIST_COPY_TIME(label, targ) {                                       \
     if (list->elements[x]->type != 0x40) {                                  \
-        DEBUG_WARN(("src not 0x40 for filetime dst\n"));                   \
+        DEBUG_WARN(("src not 0x40 for filetime dst\n"));                    \
         DEBUG_HEXDUMP(list->elements[x]->data, list->elements[x]->size);    \
     }                                                                       \
     targ = (FILETIME*) realloc(targ, sizeof(FILETIME));                     \
     memcpy(targ, list->elements[x]->data, list->elements[x]->size);         \
     LE32_CPU(targ->dwLowDateTime);                                          \
     LE32_CPU(targ->dwHighDateTime);                                         \
-    DEBUG_EMAIL((label" - %s", pst_fileTimeToAscii(targ)));                     \
+    DEBUG_EMAIL((label" - %s", pst_fileTimeToAscii(targ, time_buffer)));    \
 }
 
 #define LIST_COPY_EMAIL_TIME(label, targ) {                     \
@@ -2096,6 +2096,7 @@
 
     while (list) {
         int32_t x;
+        char time_buffer[30];
         for (x=0; x<list->count_elements; x++) {
             int32_t t;
             DEBUG_EMAIL(("#%d - mapi-id: %#x type: %#x length: %#x\n", x, list->elements[x]->mapi_id, list->elements[x]->type, list->elements[x]->size));
@@ -2961,10 +2962,10 @@
                     LIST_COPY_APPT_BOOL("Reminder alarm", item->appointment->alarm);
                     break;
                 case 0x8516: // Common start
-                    DEBUG_EMAIL(("Common Start Date - %s\n", pst_fileTimeToAscii((FILETIME*)list->elements[x]->data)));
+                    DEBUG_EMAIL(("Common Start Date - %s\n", pst_fileTimeToAscii((FILETIME*)list->elements[x]->data, time_buffer)));
                     break;
                 case 0x8517: // Common end
-                    DEBUG_EMAIL(("Common End Date - %s\n", pst_fileTimeToAscii((FILETIME*)list->elements[x]->data)));
+                    DEBUG_EMAIL(("Common End Date - %s\n", pst_fileTimeToAscii((FILETIME*)list->elements[x]->data, time_buffer)));
                     break;
                 case 0x851f: // Play reminder sound filename
                     LIST_COPY_APPT_STR("Appointment reminder sound filename", item->appointment->alarm_filename);
@@ -3055,7 +3056,7 @@
 
                     } else if (list->elements[x]->type == (uint32_t)0x0040) {
                         DEBUG_WARN(("Unknown type %#x Date = \"%s\"\n", list->elements[x]->mapi_id,
-                            pst_fileTimeToAscii((FILETIME*)list->elements[x]->data)));
+                            pst_fileTimeToAscii((FILETIME*)list->elements[x]->data, time_buffer)));
 
                     } else if (list->elements[x]->type == (uint32_t)0x0048) {
                         DEBUG_WARN(("Unknown type %#x OLE GUID [size = %#x]\n", list->elements[x]->mapi_id,
@@ -4107,7 +4108,7 @@
 }
 
 
-char *pst_rfc2426_escape(char *str) {
+char* pst_rfc2426_escape(char *str) {
     static char*  buf    = NULL;
     static size_t buflen = 0;
     char *ret, *a, *b;
@@ -4171,43 +4172,40 @@
 }
 
 
-char *pst_rfc2425_datetime_format(const FILETIME *ft) {
-    static char buffer[30];
-    struct tm* stm = NULL;
+char* pst_rfc2425_datetime_format(const FILETIME* ft, int buflen, char* result) {
+    struct tm stm;
     DEBUG_ENT("rfc2425_datetime_format");
-    stm = pst_fileTimeToStructTM(ft);
-    if (strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", stm)==0) {
+    pst_fileTimeToStructTM(ft, &stm);
+    if (strftime(result, buflen, "%Y-%m-%dT%H:%M:%SZ", &stm)==0) {
         DEBUG_INFO(("Problem occured formatting date\n"));
     }
     DEBUG_RET();
-    return buffer;
+    return result;
 }
 
 
-char *pst_rfc2445_datetime_format(const FILETIME *ft) {
-    static char buffer[30];
-    struct tm* stm = NULL;
+char* pst_rfc2445_datetime_format(const FILETIME* ft, int buflen, char* result) {
+    struct tm stm;
     DEBUG_ENT("rfc2445_datetime_format");
-    stm = pst_fileTimeToStructTM(ft);
-    if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", stm)==0) {
+    pst_fileTimeToStructTM(ft, &stm);
+    if (strftime(result, buflen, "%Y%m%dT%H%M%SZ", &stm)==0) {
         DEBUG_INFO(("Problem occured formatting date\n"));
     }
     DEBUG_RET();
-    return buffer;
+    return result;
 }
 
 
-char *pst_rfc2445_datetime_format_now() {
-    static char buffer[30];
+char* pst_rfc2445_datetime_format_now(int buflen, char* result) {
     struct tm stm;
     time_t t = time(NULL);
     DEBUG_ENT("rfc2445_datetime_format_now");
     gmtime_r(&t, &stm);
-    if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", &stm)==0) {
+    if (strftime(result, buflen, "%Y%m%dT%H%M%SZ", &stm)==0) {
         DEBUG_INFO(("Problem occured formatting date\n"));
     }
     DEBUG_RET();
-    return buffer;
+    return result;
 }
 
 
@@ -4317,6 +4315,7 @@
  */
 pst_recurrence* pst_convert_recurrence(pst_item_appointment* appt)
 {
+    const int bias = 30 * 24 * 60;  // minutes in 30 days
     int m[4] = {3,4,4,5};
     pst_recurrence *r = pst_malloc(sizeof(pst_recurrence));
     memset(r, 0, sizeof(pst_recurrence));
@@ -4328,7 +4327,7 @@
         if (i   <= s) { r->type             = PST_LE_GET_UINT8(p+i) - 0x0a;  i += 2; }
         if (i+4 <= s) { r->sub_type         = PST_LE_GET_UINT32(p+i);        i += 4; }
         if (r->sub_type <= 3) {
-            int n = m[r->sub_type];
+            int n = m[r->sub_type]; // number of parms for this sub_type
             int j = 0;
             for (j=0; j<n; j++) {
                 if (i+4 <= s) { *(&r->parm1 + j) = PST_LE_GET_UINT32(p+i);   i += 4; }
@@ -4338,21 +4337,44 @@
         if (i+4 <= s) { r->count            = PST_LE_GET_UINT32(p+i);        i += 4; }
         switch (r->type) {
             case 0: // daily
-                r->interval = r->parm2 / (24 * 60); // was minutes between recurrences
-                if (r->sub_type) r->interval = 0;   // !! don't handle sub-type 1 yet
+                if (r->sub_type == 0) {
+                    // simple daily
+                    r->interval = r->parm2 / (24 * 60); // was minutes between recurrences
+                }
+                else {
+                    // daily every weekday, subset of weekly
+                    r->interval  = 1;
+                    r->bydaymask = r->parm4;
+                }
                 break;
             case 1: // weekly
-                r->interval = r->parm2;
+                r->interval  = r->parm2;
+                r->bydaymask = r->parm4;
                 break;
             case 2: // monthly
                 r->interval = r->parm2;
-                // two flavors, every month on the Dth day, and every month on the Nth Tuesday
-                // those are not handled here.
+                if (r->sub_type == 2) {
+                    // monthly on day d
+                    r->dayofmonth = r->parm4;
+                }
+                else {
+                    // monthly on 2nd tuesday
+                    r->bydaymask = r->parm4;
+                    r->position  = r->parm5;
+                }
                 break;
             case 3: // yearly
-                r->interval = 0;
-                // two flavors, every year on the Dth day of the Mth month, and every year on the Nth Tuesday of the Mth month
-                // those are not handled here.
+                r->interval    = 1;
+                r->monthofyear = ((r->parm1 + bias/2) / bias) + 1;
+                if (r->sub_type == 2) {
+                    // yearly on day d of month m
+                    r->dayofmonth  = r->parm4;
+                }
+                else {
+                    // yearly on 2nd tuesday of month m
+                    r->bydaymask = r->parm4;
+                    r->position  = r->parm5;
+                }
                 break;
             default:
                 break;
--- a/src/libpst.h	Tue May 12 19:34:49 2009 -0700
+++ b/src/libpst.h	Wed May 13 11:59:55 2009 -0700
@@ -655,6 +655,14 @@
     uint32_t    termination;
     /** recurrence interval in terms of the recurrence type */
     uint32_t    interval;
+    /** bit mask of days of the week */
+    uint32_t    bydaymask;
+    /** day of month for monthly and yearly recurrences */
+    uint32_t    dayofmonth;
+    /** month of year for yearly recurrences */
+    uint32_t    monthofyear;
+    /** occurence of day for 2nd Tuesday of month, in which case position is 2 */
+    uint32_t    position;
     /** number of occurrences, even if recurrence terminates based on date */
     uint32_t    count;
     // there is more data, including the termination date,
@@ -1025,28 +1033,32 @@
  *            to a different buffer containing the escaped string. In
  *            either case, you don't need to free this returned pointer.
  */
-char *         pst_rfc2426_escape(char *str);
+char*          pst_rfc2426_escape(char *str);
 
 
 /** Convert a FILETIME into rfc2425 date/time format 1953-10-15T23:10:00Z
  *  which is the same as one of the forms in the ISO3601 standard
- * @param ft time to be converted
+ * @param[in]  ft      time to be converted
+ * @param[in]  buflen  length of the output buffer
+ * @param[out] result  pointer to output buffer, must be at least 30 bytes
  * @return   time in rfc2425 format
  */
-char *         pst_rfc2425_datetime_format(const FILETIME *ft);
+char*          pst_rfc2425_datetime_format(const FILETIME* ft, int buflen, char* result);
 
 
 /** Convert a FILETIME into rfc2445 date/time format 19531015T231000Z
- * @param ft time to be converted
+ * @param[in]  ft time to be converted
+ * @param[in]  buflen  length of the output buffer
+ * @param[out] result  pointer to output buffer, must be at least 30 bytes
  * @return   time in rfc2445 format
  */
-char *         pst_rfc2445_datetime_format(const FILETIME *ft);
+char*          pst_rfc2445_datetime_format(const FILETIME* ft, int buflen, char* result);
 
 
 /** Convert the current time rfc2445 date/time format 19531015T231000Z
  * @return   time in rfc2445 format
  */
-char *         pst_rfc2445_datetime_format_now();
+char*          pst_rfc2445_datetime_format_now(int buflen, char* result);
 
 
 /** Get the default character set for this item. This is used to find
--- a/src/lspst.c	Tue May 12 19:34:49 2009 -0700
+++ b/src/lspst.c	Wed May 13 11:59:55 2009 -0700
@@ -108,6 +108,7 @@
                         printf("Journal\t%s\n", pst_rfc2426_escape(item->subject.str));
 
                 } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) {
+                    char time_buffer[30];
                     if (!ff.type) ff.type = item->type;
                     // Process Calendar Appointment item
                     DEBUG_MAIN(("main: Processing Appointment Entry\n"));
@@ -118,9 +119,9 @@
                     if (item->subject.str)
                         printf("\tSUMMARY: %s", pst_rfc2426_escape(item->subject.str));
                     if (item->appointment->start)
-                        printf("\tSTART: %s", pst_rfc2445_datetime_format(item->appointment->start));
+                        printf("\tSTART: %s", pst_rfc2445_datetime_format(item->appointment->start, sizeof(time_buffer), time_buffer));
                     if (item->appointment->end)
-                        printf("\tEND: %s", pst_rfc2445_datetime_format(item->appointment->end));
+                        printf("\tEND: %s", pst_rfc2445_datetime_format(item->appointment->end, sizeof(time_buffer), time_buffer));
                     printf("\tALL DAY: %s", (item->appointment->all_day==1 ? "Yes" : "No"));
                     printf("\n");
 
--- a/src/readpst.c	Tue May 12 19:34:49 2009 -0700
+++ b/src/readpst.c	Wed May 13 11:59:55 2009 -0700
@@ -57,8 +57,7 @@
 void      write_normal_email(FILE* f_output, char f_name[], pst_item* item, int mode, int mode_MH, pst_file* pst, int save_rtf, char** extra_mime_headers);
 void      write_vcard(FILE* f_output, pst_item *item, pst_item_contact* contact, char comment[]);
 void      write_journal(FILE* f_output, pst_item* item);
-int       file_time_compare(FILETIME* left, FILETIME* right);
-void      write_appointment(FILE* f_output, pst_item *item);
+void      write_appointment(FILE* f_output, pst_item *item, int event_open);
 void      create_enter_dir(struct file_ll* f, pst_item *item);
 void      close_enter_dir(struct file_ll *f);
 
@@ -220,7 +219,7 @@
             else {
                 ff.item_count++;
                 if (mode == MODE_SEPARATE) mk_separate_file(&ff);
-                write_appointment(ff.output, item);
+                write_appointment(ff.output, item, 0);
                 fprintf(ff.output, "\n");
             }
 
@@ -1042,10 +1041,11 @@
 {
     fprintf(f_output, "BEGIN:VCALENDAR\n");
     fprintf(f_output, "VERSION:2.0\n");
-    fprintf(f_output, "PRODID:LibPST\n");
+    fprintf(f_output, "PRODID:LibPST v%s\n", VERSION);
     fprintf(f_output, "METHOD:%s\n", method);
+    fprintf(f_output, "BEGIN:VEVENT\n");
     fprintf(f_output, "ORGANIZER;CN=\"%s\":MAILTO:%s\n", item->email->outlook_sender_name.str, sender);
-    write_appointment(f_output, item);
+    write_appointment(f_output, item, 1);
     fprintf(f_output, "END:VCALENDAR\n");
 }
 
@@ -1054,7 +1054,7 @@
 {
     const char* method  = "REQUEST";
     const char* charset = "utf-8";
-    char fname[20];
+    char fname[30];
     if (!item->appointment) return;
 
     // inline appointment request
@@ -1359,6 +1359,7 @@
 
 void write_vcard(FILE* f_output, pst_item *item, pst_item_contact* contact, char comment[])
 {
+    char time_buffer[30];
     // We can only call rfc escape once per printf, since the second call
     // may free the buffer returned by the first call.
     // I had tried to place those into a single printf - Carl.
@@ -1438,7 +1439,7 @@
     if (contact->address3.str)
         fprintf(f_output, "EMAIL:%s\n", pst_rfc2426_escape(contact->address3.str));
     if (contact->birthday)
-        fprintf(f_output, "BDAY:%s\n", pst_rfc2425_datetime_format(contact->birthday));
+        fprintf(f_output, "BDAY:%s\n", pst_rfc2425_datetime_format(contact->birthday, sizeof(time_buffer), time_buffer));
 
     if (contact->home_address.str) {
         //fprintf(f_output, "ADR;TYPE=home:%s;%s;%s;%s;%s;%s;%s\n",
@@ -1509,6 +1510,7 @@
 
 void write_journal(FILE* f_output, pst_item* item)
 {
+    char time_buffer[30];
     pst_item_journal* journal = item->journal;
 
     // make everything utf8
@@ -1516,40 +1518,24 @@
     pst_convert_utf8_null(item, &item->body);
 
     fprintf(f_output, "BEGIN:VJOURNAL\n");
-    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now());
+    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now(sizeof(time_buffer), time_buffer));
     if (item->create_date)
-        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date));
+        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date, sizeof(time_buffer), time_buffer));
     if (item->modify_date)
-        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date));
+        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date, sizeof(time_buffer), time_buffer));
     if (item->subject.str)
         fprintf(f_output, "SUMMARY:%s\n",                 pst_rfc2426_escape(item->subject.str));
     if (item->body.str)
         fprintf(f_output, "DESCRIPTION:%s\n",             pst_rfc2426_escape(item->body.str));
     if (journal && journal->start)
-        fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(journal->start));
+        fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(journal->start, sizeof(time_buffer), time_buffer));
     fprintf(f_output, "END:VJOURNAL\n");
 }
 
 
-/**
- compare two FILETIME objects after converting to unix time_t. This
- allows FILETIMEs that are beyond the range of time_t representaion to
- be converted to 0, and therefore seem to be less than the right
- side. The actual recurrence end values seen in pst files are very
- large (outside the range of 32 bit time_t), but still finite. That is
- a strange way to represent an infinite recurrence.
- */
-int file_time_compare(FILETIME* left, FILETIME* right)
+void write_appointment(FILE* f_output, pst_item* item, int event_open)
 {
-    time_t delta = pst_fileTimeToUnixTime(left) - pst_fileTimeToUnixTime(right);
-    if (delta < 0) return -1;
-    if (delta > 0) return 1;
-    return 0;
-}
-
-
-void write_appointment(FILE* f_output, pst_item* item)
-{
+    char time_buffer[30];
     pst_item_appointment* appointment = item->appointment;
 
     // make everything utf8
@@ -1557,20 +1543,20 @@
     pst_convert_utf8_null(item, &item->body);
     pst_convert_utf8_null(item, &appointment->location);
 
-    fprintf(f_output, "BEGIN:VEVENT\n");
-    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now());
+    if (!event_open) fprintf(f_output, "BEGIN:VEVENT\n");
+    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now(sizeof(time_buffer), time_buffer));
     if (item->create_date)
-        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date));
+        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date, sizeof(time_buffer), time_buffer));
     if (item->modify_date)
-        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date));
+        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date, sizeof(time_buffer), time_buffer));
     if (item->subject.str)
         fprintf(f_output, "SUMMARY:%s\n",                 pst_rfc2426_escape(item->subject.str));
     if (item->body.str)
         fprintf(f_output, "DESCRIPTION:%s\n",             pst_rfc2426_escape(item->body.str));
     if (appointment && appointment->start)
-        fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(appointment->start));
+        fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(appointment->start, sizeof(time_buffer), time_buffer));
     if (appointment && appointment->end)
-        fprintf(f_output, "DTEND;VALUE=DATE-TIME:%s\n",   pst_rfc2445_datetime_format(appointment->end));
+        fprintf(f_output, "DTEND;VALUE=DATE-TIME:%s\n",   pst_rfc2445_datetime_format(appointment->end, sizeof(time_buffer), time_buffer));
     if (appointment && appointment->location.str)
         fprintf(f_output, "LOCATION:%s\n",                pst_rfc2426_escape(appointment->location.str));
     if (appointment) {
@@ -1588,10 +1574,29 @@
         }
         if (appointment->is_recurring) {
             const char* rules[] = {"DAILY", "WEEKLY", "MONTHLY", "YEARLY"};
+            const char* days[]  = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"};
             pst_recurrence *rdata = pst_convert_recurrence(appointment);
             fprintf(f_output, "RRULE:FREQ=%s", rules[rdata->type]);
-            if (rdata->count)    fprintf(f_output, ";COUNT=%u", rdata->count);
-            if (rdata->interval) fprintf(f_output, ";INTERVAL=%u", rdata->interval);
+            if (rdata->count)       fprintf(f_output, ";COUNT=%u",      rdata->count);
+            if (rdata->interval)    fprintf(f_output, ";INTERVAL=%u",   rdata->interval);
+            if (rdata->dayofmonth)  fprintf(f_output, ";BYMONTHDAY=%d", rdata->dayofmonth);
+            if (rdata->monthofyear) fprintf(f_output, ";BYMONTH=%d",    rdata->monthofyear);
+            if (rdata->position)    fprintf(f_output, ";BYSETPOS=%d",   rdata->position);
+            if (rdata->bydaymask) {
+                char byday[40];
+                int  empty = 1;
+                int i=0;
+                memset(byday, 0, sizeof(byday));
+                for (i=0; i<6; i++) {
+                    int bit = 1 << i;
+                    if (bit & rdata->bydaymask) {
+                        char temp[40];
+                        snprintf(temp, sizeof(temp), "%s%s%s", byday, (empty) ? "BYDAY=" : ";", days[i]);
+                        strcpy(byday, temp);
+                        empty = 0;
+                    }
+                }
+            }
             fprintf(f_output, "\n");
             pst_free_recurrence(rdata);
         }
--- a/src/timeconv.c	Tue May 12 19:34:49 2009 -0700
+++ b/src/timeconv.c	Wed May 13 11:59:55 2009 -0700
@@ -2,17 +2,17 @@
 
 
 
-char * pst_fileTimeToAscii(const FILETIME* filetime) {
+char* pst_fileTimeToAscii(const FILETIME* filetime, char* result) {
     time_t t;
     t = pst_fileTimeToUnixTime(filetime);
-    return ctime(&t);
+    return ctime_r(&t, result);
 }
 
 
-struct tm * pst_fileTimeToStructTM (const FILETIME *filetime) {
+void pst_fileTimeToStructTM (const FILETIME *filetime, struct tm *result) {
     time_t t1;
     t1 = pst_fileTimeToUnixTime(filetime);
-    return gmtime(&t1);
+    gmtime_r(&t1, result);
 }
 
 
--- a/src/timeconv.h	Tue May 12 19:34:49 2009 -0700
+++ b/src/timeconv.h	Wed May 13 11:59:55 2009 -0700
@@ -6,9 +6,24 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
-    char * pst_fileTimeToAscii (const FILETIME *filetime);
-    struct tm * pst_fileTimeToStructTM (const FILETIME *filetime);
-    time_t pst_fileTimeToUnixTime( const FILETIME *filetime);
+    /** Convert a FILETIME to ascii printable local time.
+       @param[in]  filetime time structure to be converted
+       @param[out] result   pointer to output buffer, must be at least 30 bytes.
+       @return     result pointer to the output buffer
+     */
+    char* pst_fileTimeToAscii (const FILETIME* filetime, char* result);
+
+    /** Convert a FILETIME to unix struct tm.
+       @param[in]  filetime time structure to be converted
+       @param[out] result   pointer to output struct tm
+     */
+    void pst_fileTimeToStructTM (const FILETIME* filetime, struct tm *result);
+
+    /** Convert a FILETIME to unix time_t value.
+       @param[in]  filetime time structure to be converted
+       @return     result time_t value
+     */
+    time_t pst_fileTimeToUnixTime( const FILETIME* filetime);
 #ifdef __cplusplus
 }
 #endif