mingw: fixed Windows build
[siplcs.git] / src / core / sipe-cal.c
blob9443dcb996a7440e7abdd53a814e955a7dc8da0f
1 /**
2 * @file sipe-cal.c
4 * pidgin-sipe
6 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <glib.h>
33 #include "debug.h"
35 #include "sipe.h"
36 #include "sipe-cal.h"
37 #include "sipe-utils.h"
38 #include "sipe-nls.h"
41 #define TIME_NULL (time_t)-1
42 #define IS(time) (time != TIME_NULL)
45 http://msdn.microsoft.com/en-us/library/aa565001.aspx
47 <?xml version="1.0"?>
48 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
49 <TimeZone>
50 <Bias>480</Bias>
51 <StandardTime>
52 <Bias>0</Bias>
53 <Time>02:00:00</Time>
54 <DayOrder>1</DayOrder>
55 <Month>11</Month>
56 <DayOfWeek>Sunday</DayOfWeek>
57 </StandardTime>
58 <DaylightTime>
59 <Bias>-60</Bias>
60 <Time>02:00:00</Time>
61 <DayOrder>2</DayOrder>
62 <Month>3</Month>
63 <DayOfWeek>Sunday</DayOfWeek>
64 </DaylightTime>
65 </TimeZone>
66 <WorkingPeriodArray>
67 <WorkingPeriod>
68 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
69 <StartTimeInMinutes>600</StartTimeInMinutes>
70 <EndTimeInMinutes>1140</EndTimeInMinutes>
71 </WorkingPeriod>
72 </WorkingPeriodArray>
73 </WorkingHours>
75 Desc:
76 <StandardTime>
77 <Bias>int</Bias>
78 <Time>string</Time>
79 <DayOrder>short</DayOrder>
80 <Month>short</Month>
81 <DayOfWeek>Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday</DayOfWeek>
82 <Year>string</Year>
83 </StandardTime>
86 struct sipe_cal_std_dst {
87 int bias; /* Ex.: -60 */
88 gchar *time; /* hh:mm:ss, 02:00:00 */
89 int day_order; /* 1..5 */
90 int month; /* 1..12 */
91 gchar *day_of_week; /* Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday */
92 gchar *year; /* YYYY */
94 time_t switch_time;
97 struct sipe_cal_working_hours {
98 int bias; /* Ex.: 480 */
99 struct sipe_cal_std_dst std; /* StandardTime */
100 struct sipe_cal_std_dst dst; /* DaylightTime */
101 gchar *days_of_week; /* Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday separated by space */
102 int start_time; /* 0...1440 */
103 int end_time; /* 0...1440 */
105 gchar *tz; /* aggregated timezone string as in TZ environment variable.
106 Ex.: TST+8TDT+7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
107 /** separate simple strings for Windows platform as the proper TZ does not work there.
108 * anyway, dynamic timezones would't work with just TZ
110 gchar *tz_std; /* Ex.: TST8 */
111 gchar *tz_dst; /* Ex.: TDT7 */
114 /* not for translation, a part of XML Schema definitions */
115 static const char *wday_names[] = {"Sunday",
116 "Monday",
117 "Tuesday",
118 "Wednesday",
119 "Thursday",
120 "Friday",
121 "Saturday"};
122 static int
123 sipe_cal_get_wday(char *wday_name)
125 int i;
127 if (!wday_name) return -1;
129 for (i = 0; i < 7; i++) {
130 if (sipe_strequal(wday_names[i], wday_name)) {
131 return i;
135 return -1;
138 void
139 sipe_cal_event_free(struct sipe_cal_event* cal_event)
141 if (!cal_event) return;
143 g_free(cal_event->subject);
144 g_free(cal_event->location);
145 g_free(cal_event);
148 char *
149 sipe_cal_event_describe(struct sipe_cal_event* cal_event)
151 GString* str = g_string_new(NULL);
152 const char *status = "";
154 switch(cal_event->cal_status) {
155 case SIPE_CAL_FREE: status = "SIPE_CAL_FREE"; break;
156 case SIPE_CAL_TENTATIVE: status = "SIPE_CAL_TENTATIVE"; break;
157 case SIPE_CAL_BUSY: status = "SIPE_CAL_BUSY"; break;
158 case SIPE_CAL_OOF: status = "SIPE_CAL_OOF"; break;
159 case SIPE_CAL_NO_DATA: status = "SIPE_CAL_NO_DATA"; break;
162 g_string_append_printf(str, "\t%s: %s", "start_time",
163 IS(cal_event->start_time) ? asctime(localtime(&cal_event->start_time)) : "\n");
164 g_string_append_printf(str, "\t%s: %s", "end_time ",
165 IS(cal_event->end_time) ? asctime(localtime(&cal_event->end_time)) : "\n");
166 g_string_append_printf(str, "\t%s: %s\n", "cal_status", status);
167 g_string_append_printf(str, "\t%s: %s\n", "subject ", cal_event->subject ? cal_event->subject : "");
168 g_string_append_printf(str, "\t%s: %s\n", "location ", cal_event->location ? cal_event->location : "");
169 g_string_append_printf(str, "\t%s: %s\n", "is_meeting", cal_event->is_meeting ? "TRUE" : "FALSE");
171 return g_string_free(str, FALSE);
174 char *
175 sipe_cal_event_hash(struct sipe_cal_event* event)
177 /* no end_time as it dos not get published */
178 /* no cal_status as it can change on publication */
179 return g_strdup_printf("<%d><%s><%s><%d>",
180 (int)event->start_time,
181 event->subject ? event->subject : "",
182 event->location ? event->location : "",
183 event->is_meeting);
186 static void
187 sipe_setenv(const char *name,
188 const char *value)
190 #ifdef HAVE_SETENV
191 setenv(name, value, 1);
192 #else
193 int len = strlen(name) + 1 + strlen(value) + 1;
194 char *str = g_malloc0(len);
195 sprintf(str, "%s=%s", name, value);
196 putenv(str);
197 #endif
200 static void
201 sipe_unsetenv(const char *name)
203 #ifdef HAVE_UNSETENV
204 unsetenv(name);
205 #else
206 int len = strlen(name) + 1 + 1;
207 char *str = g_malloc0(len);
208 sprintf(str, "%s=", name);
209 putenv(str);
210 #endif
214 * Converts struct tm to Epoch time_t considering timezone.
216 * @param tz as defined for TZ environment variable.
218 * Reference: see timegm(3) - Linux man page
220 time_t
221 sipe_mktime_tz(struct tm *tm,
222 const char* tz)
224 time_t ret;
225 char *tz_old;
227 tz_old = getenv("TZ");
228 sipe_setenv("TZ", tz);
229 tzset();
231 ret = mktime(tm);
233 if (tz_old) {
234 sipe_setenv("TZ", tz_old);
235 } else {
236 sipe_unsetenv("TZ");
238 tzset();
240 return ret;
244 * Converts Epoch time_t to struct tm considering timezone.
246 * @param tz as defined for TZ environment variable.
248 * Reference: see timegm(3) - Linux man page
250 static struct tm *
251 sipe_localtime_tz(const time_t *time,
252 const char* tz)
254 struct tm *ret;
255 char *tz_old;
257 tz_old = getenv("TZ");
258 sipe_setenv("TZ", tz);
259 tzset();
261 ret = localtime(time);
263 if (tz_old) {
264 sipe_setenv("TZ", tz_old);
265 } else {
266 sipe_unsetenv("TZ");
268 tzset();
270 return ret;
273 void
274 sipe_cal_free_working_hours(struct sipe_cal_working_hours *wh)
276 if (!wh) return;
278 g_free(wh->std.time);
279 g_free(wh->std.day_of_week);
280 g_free(wh->std.year);
282 g_free(wh->dst.time);
283 g_free(wh->dst.day_of_week);
284 g_free(wh->dst.year);
286 g_free(wh->days_of_week);
287 g_free(wh->tz);
288 g_free(wh->tz_std);
289 g_free(wh->tz_dst);
290 g_free(wh);
294 * Returns time_t of daylight savings time start/end
295 * in the provided timezone or otherwise
296 * (time_t)-1 if no daylight savings time.
298 static time_t
299 sipe_cal_get_std_dst_time(time_t now,
300 int bias,
301 struct sipe_cal_std_dst* std_dst,
302 struct sipe_cal_std_dst* dst_std)
304 struct tm switch_tm;
305 time_t res = TIME_NULL;
306 struct tm *gm_now_tm;
307 gchar **time_arr;
309 if (std_dst->month == 0) return TIME_NULL;
311 gm_now_tm = gmtime(&now);
312 time_arr = g_strsplit(std_dst->time, ":", 0);
314 switch_tm.tm_sec = atoi(time_arr[2]);
315 switch_tm.tm_min = atoi(time_arr[1]);
316 switch_tm.tm_hour = atoi(time_arr[0]);
317 g_strfreev(time_arr);
318 switch_tm.tm_mday = std_dst->year ? std_dst->day_order : 1 /* to adjust later */ ;
319 switch_tm.tm_mon = std_dst->month - 1;
320 switch_tm.tm_year = std_dst->year ? atoi(std_dst->year) - 1900 : gm_now_tm->tm_year;
321 switch_tm.tm_isdst = 0;
322 /* to set tm_wday */
323 res = sipe_mktime_tz(&switch_tm, "UTC");
325 /* if not dynamic, calculate right tm_mday */
326 if (!std_dst->year) {
327 int switch_wday = sipe_cal_get_wday(std_dst->day_of_week);
328 int needed_month;
329 /* get first desired wday in the month */
330 int delta = switch_wday >= switch_tm.tm_wday ? (switch_wday - switch_tm.tm_wday) : (switch_wday + 7 - switch_tm.tm_wday);
331 switch_tm.tm_mday = 1 + delta;
332 /* try nth order */
333 switch_tm.tm_mday += (std_dst->day_order - 1) * 7;
334 needed_month = switch_tm.tm_mon;
335 /* to set settle date if ahead of allowed month dates */
336 res = sipe_mktime_tz(&switch_tm, "UTC");
337 if (needed_month != switch_tm.tm_mon) {
338 /* moving 1 week back to stay within required month */
339 switch_tm.tm_mday -= 7;
340 /* to fix date again */
341 res = sipe_mktime_tz(&switch_tm, "UTC");
344 /* note: bias is taken from "switch to" structure */
345 return res + (bias + dst_std->bias)*60;
348 static void
349 sipe_cal_parse_std_dst(xmlnode *xn_std_dst_time,
350 struct sipe_cal_std_dst* std_dst)
352 xmlnode *node;
353 gchar *tmp;
355 if (!xn_std_dst_time) return;
356 if (!std_dst) return;
358 <StandardTime>
359 <Bias>0</Bias>
360 <Time>02:00:00</Time>
361 <DayOrder>1</DayOrder>
362 <Month>11</Month>
363 <DayOfWeek>Sunday</DayOfWeek>
364 </StandardTime>
367 if ((node = xmlnode_get_child(xn_std_dst_time, "Bias"))) {
368 std_dst->bias = atoi(tmp = xmlnode_get_data(node));
369 g_free(tmp);
372 if ((node = xmlnode_get_child(xn_std_dst_time, "Time"))) {
373 std_dst->time = xmlnode_get_data(node);
376 if ((node = xmlnode_get_child(xn_std_dst_time, "DayOrder"))) {
377 std_dst->day_order = atoi(tmp = xmlnode_get_data(node));
378 g_free(tmp);
381 if ((node = xmlnode_get_child(xn_std_dst_time, "Month"))) {
382 std_dst->month = atoi(tmp = xmlnode_get_data(node));
383 g_free(tmp);
386 if ((node = xmlnode_get_child(xn_std_dst_time, "DayOfWeek"))) {
387 std_dst->day_of_week = xmlnode_get_data(node);
390 if ((node = xmlnode_get_child(xn_std_dst_time, "Year"))) {
391 std_dst->year = xmlnode_get_data(node);
395 void
396 sipe_cal_parse_working_hours(xmlnode *xn_working_hours,
397 struct sipe_buddy *buddy)
399 xmlnode *xn_bias;
400 xmlnode *xn_working_period;
401 xmlnode *xn_standard_time;
402 xmlnode *xn_daylight_time;
403 gchar *tmp;
404 time_t now = time(NULL);
405 struct sipe_cal_std_dst* std;
406 struct sipe_cal_std_dst* dst;
408 if (!xn_working_hours) return;
410 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
411 <TimeZone>
412 <Bias>480</Bias>
414 </TimeZone>
415 <WorkingPeriodArray>
416 <WorkingPeriod>
417 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
418 <StartTimeInMinutes>600</StartTimeInMinutes>
419 <EndTimeInMinutes>1140</EndTimeInMinutes>
420 </WorkingPeriod>
421 </WorkingPeriodArray>
422 </WorkingHours>
424 sipe_cal_free_working_hours(buddy->cal_working_hours);
425 buddy->cal_working_hours = g_new0(struct sipe_cal_working_hours, 1);
427 xn_bias = xmlnode_get_descendant(xn_working_hours, "TimeZone", "Bias", NULL);
428 if (xn_bias) {
429 buddy->cal_working_hours->bias = atoi(tmp = xmlnode_get_data(xn_bias));
430 g_free(tmp);
433 xn_standard_time = xmlnode_get_descendant(xn_working_hours, "TimeZone", "StandardTime", NULL);
434 xn_daylight_time = xmlnode_get_descendant(xn_working_hours, "TimeZone", "DaylightTime", NULL);
436 std = &((*buddy->cal_working_hours).std);
437 dst = &((*buddy->cal_working_hours).dst);
438 sipe_cal_parse_std_dst(xn_standard_time, std);
439 sipe_cal_parse_std_dst(xn_daylight_time, dst);
441 xn_working_period = xmlnode_get_descendant(xn_working_hours, "WorkingPeriodArray", "WorkingPeriod", NULL);
442 if (xn_working_period) {
443 buddy->cal_working_hours->days_of_week =
444 xmlnode_get_data(xmlnode_get_child(xn_working_period, "DayOfWeek"));
446 buddy->cal_working_hours->start_time =
447 atoi(tmp = xmlnode_get_data(xmlnode_get_child(xn_working_period, "StartTimeInMinutes")));
448 g_free(tmp);
450 buddy->cal_working_hours->end_time =
451 atoi(tmp = xmlnode_get_data(xmlnode_get_child(xn_working_period, "EndTimeInMinutes")));
452 g_free(tmp);
455 std->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, std, dst);
456 dst->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, dst, std);
458 /* TST8TDT7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
459 buddy->cal_working_hours->tz =
460 g_strdup_printf("TST%dTDT%d,M%d.%d.%d/%s,M%d.%d.%d/%s",
461 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60,
462 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60,
464 buddy->cal_working_hours->dst.month,
465 buddy->cal_working_hours->dst.day_order,
466 sipe_cal_get_wday(buddy->cal_working_hours->dst.day_of_week),
467 buddy->cal_working_hours->dst.time,
469 buddy->cal_working_hours->std.month,
470 buddy->cal_working_hours->std.day_order,
471 sipe_cal_get_wday(buddy->cal_working_hours->std.day_of_week),
472 buddy->cal_working_hours->std.time
474 /* TST8 */
475 buddy->cal_working_hours->tz_std =
476 g_strdup_printf("TST%d",
477 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60);
478 /* TDT7 */
479 buddy->cal_working_hours->tz_dst =
480 g_strdup_printf("TDT%d",
481 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60);
484 struct sipe_cal_event*
485 sipe_cal_get_event(GSList *cal_events,
486 time_t time_in_question)
488 GSList *entry = cal_events;
489 struct sipe_cal_event* cal_event;
490 struct sipe_cal_event* res = NULL;
492 if (!cal_events || !IS(time_in_question)) return NULL;
494 while (entry) {
495 cal_event = entry->data;
496 /* event is in the past or in the future */
497 if (cal_event->start_time > time_in_question ||
498 cal_event->end_time <= time_in_question)
500 entry = entry->next;
501 continue;
504 if (!res) {
505 res = cal_event;
506 } else {
507 int res_status = (res->cal_status == SIPE_CAL_NO_DATA) ? -1 : res->cal_status;
508 int cal_status = (cal_event->cal_status == SIPE_CAL_NO_DATA) ? -1 : cal_event->cal_status;
509 if (res_status < cal_status) {
510 res = cal_event;
513 entry = entry->next;
515 return res;
518 static int
519 sipe_cal_get_status0(const gchar *free_busy,
520 time_t cal_start,
521 int granularity,
522 time_t time_in_question,
523 int *index)
525 int res = SIPE_CAL_NO_DATA;
526 int shift;
527 time_t cal_end = cal_start + strlen(free_busy)*granularity*60 - 1;
529 if (!(time_in_question >= cal_start && time_in_question <= cal_end)) return res;
531 shift = (time_in_question - cal_start) / (granularity*60);
532 if (index) {
533 *index = shift;
536 res = free_busy[shift] - '0';
538 return res;
542 * Returns time when current calendar state started
544 static time_t
545 sipe_cal_get_since_time(const gchar *free_busy,
546 time_t calStart,
547 int granularity,
548 int index,
549 int current_state)
551 int i;
553 if ((index < 0) || ((size_t)(index + 1) > strlen(free_busy))) return 0;
555 for (i = index; i >= 0; i--) {
556 int temp_status = free_busy[i] - '0';
558 if (current_state != temp_status) {
559 return calStart + (i + 1)*granularity*60;
562 if (i == 0) return calStart;
565 return 0;
567 static char*
568 sipe_cal_get_free_busy(struct sipe_buddy *buddy);
571 sipe_cal_get_status(struct sipe_buddy *buddy,
572 time_t time_in_question,
573 time_t *since)
575 time_t cal_start;
576 const char* free_busy;
577 int ret = SIPE_CAL_NO_DATA;
578 time_t state_since;
579 int index;
581 if (!buddy || !buddy->cal_start_time || !buddy->cal_granularity) {
582 purple_debug_info("sipe", "sipe_cal_get_status: no calendar data1 for %s, exiting\n",
583 buddy ? (buddy->name ? buddy->name : "") : "");
584 return SIPE_CAL_NO_DATA;
587 if (!(free_busy = sipe_cal_get_free_busy(buddy))) {
588 purple_debug_info("sipe", "sipe_cal_get_status: no calendar data2 for %s, exiting\n", buddy->name);
589 return SIPE_CAL_NO_DATA;
591 purple_debug_info("sipe", "sipe_cal_get_description: buddy->cal_free_busy=\n%s\n", free_busy);
593 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
595 ret = sipe_cal_get_status0(free_busy,
596 cal_start,
597 buddy->cal_granularity,
598 time_in_question,
599 &index);
600 state_since = sipe_cal_get_since_time(free_busy,
601 cal_start,
602 buddy->cal_granularity,
603 index,
604 ret);
606 if (since) *since = state_since;
607 return ret;
610 static time_t
611 sipe_cal_get_switch_time(const gchar *free_busy,
612 time_t calStart,
613 int granularity,
614 int index,
615 int current_state,
616 int *to_state)
618 size_t i;
619 time_t ret = TIME_NULL;
621 if ((index < 0) || ((size_t) (index + 1) > strlen(free_busy))) {
622 *to_state = SIPE_CAL_NO_DATA;
623 return ret;
626 for (i = index + 1; i < strlen(free_busy); i++) {
627 int temp_status = free_busy[i] - '0';
629 if (current_state != temp_status) {
630 *to_state = temp_status;
631 return calStart + i*granularity*60;
635 return ret;
638 static const char*
639 sipe_cal_get_tz(struct sipe_cal_working_hours *wh,
640 time_t time_in_question)
642 time_t dst_switch_time = (*wh).dst.switch_time;
643 time_t std_switch_time = (*wh).std.switch_time;
644 gboolean is_dst = FALSE;
646 /* No daylight savings */
647 if (dst_switch_time == TIME_NULL) {
648 return wh->tz_std;
651 if (dst_switch_time < std_switch_time) { /* North hemosphere - Europe, US */
652 if (time_in_question >= dst_switch_time && time_in_question < std_switch_time) {
653 is_dst = TRUE;
655 } else { /* South hemisphere - Australia */
656 if (time_in_question >= dst_switch_time || time_in_question < std_switch_time) {
657 is_dst = TRUE;
661 if (is_dst) {
662 return wh->tz_dst;
663 } else {
664 return wh->tz_std;
668 static time_t
669 sipe_cal_mktime_of_day(struct tm *sample_today_tm,
670 const int shift_minutes,
671 const char *tz)
673 sample_today_tm->tm_sec = 0;
674 sample_today_tm->tm_min = shift_minutes % 60;
675 sample_today_tm->tm_hour = shift_minutes / 60;
677 return sipe_mktime_tz(sample_today_tm, tz);
681 * Returns work day start and end in Epoch time
682 * considering the initial values are provided
683 * in contact's local time zone.
685 static void
686 sipe_cal_get_today_work_hours(struct sipe_cal_working_hours *wh,
687 time_t *start,
688 time_t *end,
689 time_t *next_start)
691 time_t now = time(NULL);
692 const char *tz = sipe_cal_get_tz(wh, now);
693 struct tm *remote_now_tm = sipe_localtime_tz(&now, tz);
695 if (!strstr(wh->days_of_week, wday_names[remote_now_tm->tm_wday])) { /* not a work day */
696 *start = TIME_NULL;
697 *end = TIME_NULL;
698 *next_start = TIME_NULL;
699 return;
702 *end = sipe_cal_mktime_of_day(remote_now_tm, wh->end_time, tz);
704 if (now < *end) {
705 *start = sipe_cal_mktime_of_day(remote_now_tm, wh->start_time, tz);
706 *next_start = TIME_NULL;
707 } else { /* calculate start of tomorrow's work day if any */
708 time_t tom = now + 24*60*60;
709 struct tm *remote_tom_tm = sipe_localtime_tz(&tom, sipe_cal_get_tz(wh, tom));
711 if (!strstr(wh->days_of_week, wday_names[remote_tom_tm->tm_wday])) { /* not a work day */
712 *next_start = TIME_NULL;
715 *next_start = sipe_cal_mktime_of_day(remote_tom_tm, wh->start_time, sipe_cal_get_tz(wh, tom));
716 *start = TIME_NULL;
720 static int
721 sipe_cal_is_in_work_hours(const time_t time_in_question,
722 const time_t start,
723 const time_t end)
725 return !((time_in_question >= end) || (IS(start) && time_in_question < start));
729 * Returns time closest to now. Choses only from times ahead of now.
730 * Returns TIME_NULL otherwise.
732 static time_t
733 sipe_cal_get_until(const time_t now,
734 const time_t switch_time,
735 const time_t start,
736 const time_t end,
737 const time_t next_start)
739 time_t ret = TIME_NULL;
740 int min_diff = now - ret;
742 if (IS(switch_time) && switch_time > now && (switch_time - now) < min_diff) {
743 min_diff = switch_time - now;
744 ret = switch_time;
746 if (IS(start) && start > now && (start - now) < min_diff) {
747 min_diff = start - now;
748 ret = start;
750 if (IS(end) && end > now && (end - now) < min_diff) {
751 min_diff = end - now;
752 ret = end;
754 if (IS(next_start) && next_start > now && (next_start - now) < min_diff) {
755 min_diff = next_start - now;
756 ret = next_start;
758 return ret;
761 static char*
762 sipe_cal_get_free_busy(struct sipe_buddy *buddy)
764 /* do lazy decode if necessary */
765 if (!buddy->cal_free_busy && buddy->cal_free_busy_base64) {
766 gsize cal_dec64_len;
767 guchar *cal_dec64;
768 gsize i;
769 int j = 0;
771 cal_dec64 = purple_base64_decode(buddy->cal_free_busy_base64, &cal_dec64_len);
773 buddy->cal_free_busy = g_malloc0(cal_dec64_len * 4 + 1);
775 http://msdn.microsoft.com/en-us/library/dd941537%28office.13%29.aspx
776 00, Free (Fr)
777 01, Tentative (Te)
778 10, Busy (Bu)
779 11, Out of facility (Oo)
781 http://msdn.microsoft.com/en-us/library/aa566048.aspx
782 0 Free
783 1 Tentative
784 2 Busy
785 3 Out of Office (OOF)
786 4 No data
788 for (i = 0; i < cal_dec64_len; i++) {
789 #define TWO_BIT_MASK 0x03
790 char tmp = cal_dec64[i];
791 buddy->cal_free_busy[j++] = (tmp & TWO_BIT_MASK) + '0';
792 buddy->cal_free_busy[j++] = ((tmp >> 2) & TWO_BIT_MASK) + '0';
793 buddy->cal_free_busy[j++] = ((tmp >> 4) & TWO_BIT_MASK) + '0';
794 buddy->cal_free_busy[j++] = ((tmp >> 6) & TWO_BIT_MASK) + '0';
796 buddy->cal_free_busy[j++] = '\0';
797 g_free(cal_dec64);
800 return buddy->cal_free_busy;
803 char *
804 sipe_cal_get_freebusy_base64(const char* freebusy_hex)
806 guint i = 0;
807 guint j = 0;
808 guint shift_factor = 0;
809 guint len, res_len;
810 guchar *res;
811 gchar *res_base64;
813 if (!freebusy_hex) return NULL;
815 len = strlen(freebusy_hex);
816 res_len = len / 4 + 1;
817 res = g_malloc0(res_len);
818 while (i < len) {
819 res[j] |= (freebusy_hex[i++] - '0') << shift_factor;
820 shift_factor += 2;
821 if (shift_factor == 8) {
822 shift_factor = 0;
823 j++;
827 res_base64 = purple_base64_encode(res, shift_factor ? res_len : res_len - 1);
828 g_free(res);
829 return res_base64;
832 char *
833 sipe_cal_get_description(struct sipe_buddy *buddy)
835 time_t cal_start;
836 time_t cal_end;
837 int current_cal_state;
838 time_t now = time(NULL);
839 time_t start = TIME_NULL;
840 time_t end = TIME_NULL;
841 time_t next_start = TIME_NULL;
842 time_t switch_time;
843 int to_state = SIPE_CAL_NO_DATA;
844 time_t until = TIME_NULL;
845 int index = 0;
846 gboolean has_working_hours = (buddy->cal_working_hours != NULL);
847 const char *free_busy;
848 const char *cal_states[] = {_("Free"),
849 _("Tentative"),
850 _("Busy"),
851 _("Out of office"),
852 _("No data")};
854 if (buddy->cal_granularity != 15) {
855 purple_debug_info("sipe", "sipe_cal_get_description: granularity %d is unsupported, exiting.\n", buddy->cal_granularity);
856 return NULL;
859 /* to lazy load if needed */
860 free_busy = sipe_cal_get_free_busy(buddy);
861 purple_debug_info("sipe", "sipe_cal_get_description: buddy->cal_free_busy=\n%s\n", free_busy ? free_busy : "");
863 if (!buddy->cal_free_busy || !buddy->cal_granularity || !buddy->cal_start_time) {
864 purple_debug_info("sipe", "sipe_cal_get_description: no calendar data, exiting");
865 return NULL;
868 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
869 cal_end = cal_start + 60 * (buddy->cal_granularity) * strlen(buddy->cal_free_busy);
871 current_cal_state = sipe_cal_get_status0(free_busy, cal_start, buddy->cal_granularity, time(NULL), &index);
872 if (current_cal_state == SIPE_CAL_NO_DATA) {
873 purple_debug_info("sipe", "sipe_cal_get_description: calendar is undefined for present moment, exiting.\n");
874 return NULL;
877 switch_time = sipe_cal_get_switch_time(free_busy, cal_start, buddy->cal_granularity, index, current_cal_state, &to_state);
879 purple_debug_info("sipe", "\n* Calendar *\n");
880 if (buddy->cal_working_hours) {
881 sipe_cal_get_today_work_hours(buddy->cal_working_hours, &start, &end, &next_start);
883 purple_debug_info("sipe", "Remote now timezone : %s\n", sipe_cal_get_tz(buddy->cal_working_hours, now));
884 purple_debug_info("sipe", "std.switch_time(GMT): %s",
885 IS((*buddy->cal_working_hours).std.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).std.switch_time))) : "\n");
886 purple_debug_info("sipe", "dst.switch_time(GMT): %s",
887 IS((*buddy->cal_working_hours).dst.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).dst.switch_time))) : "\n");
888 purple_debug_info("sipe", "Remote now time : %s",
889 asctime(sipe_localtime_tz(&now, sipe_cal_get_tz(buddy->cal_working_hours, now))));
890 purple_debug_info("sipe", "Remote start time : %s",
891 IS(start) ? asctime(sipe_localtime_tz(&start, sipe_cal_get_tz(buddy->cal_working_hours, start))) : "\n");
892 purple_debug_info("sipe", "Remote end time : %s",
893 IS(end) ? asctime(sipe_localtime_tz(&end, sipe_cal_get_tz(buddy->cal_working_hours, end))) : "\n");
894 purple_debug_info("sipe", "Rem. next_start time: %s",
895 IS(next_start) ? asctime(sipe_localtime_tz(&next_start, sipe_cal_get_tz(buddy->cal_working_hours, next_start))) : "\n");
896 purple_debug_info("sipe", "Remote switch time : %s",
897 IS(switch_time) ? asctime(sipe_localtime_tz(&switch_time, sipe_cal_get_tz(buddy->cal_working_hours, switch_time))) : "\n");
898 } else {
899 purple_debug_info("sipe", "Local now time : %s",
900 asctime(localtime(&now)));
901 purple_debug_info("sipe", "Local switch time : %s",
902 IS(switch_time) ? asctime(localtime(&switch_time)) : "\n");
904 purple_debug_info("sipe", "Calendar End (GMT) : %s", asctime(gmtime(&cal_end)));
905 purple_debug_info("sipe", "current cal state : %s\n", cal_states[current_cal_state]);
906 purple_debug_info("sipe", "switch cal state : %s\n", cal_states[to_state] );
908 /* Calendar: string calculations */
911 ALGORITHM (don't delete)
912 (c)2009,2010 pier11 <pier11@operamail.com>
914 SOD = Start of Work Day
915 EOD = End of Work Day
916 NSOD = Start of tomorrow's Work Day
917 SW = Calendar status switch time
919 if current_cal_state == Free
920 until = min_t of SOD, EOD, NSOD, SW (min_t(x) = min(x-now) where x>now only)
921 else
922 until = SW
924 if (!until && (cal_period_end > now + 8H))
925 until = cal_period_end
927 if (!until)
928 return "Currently %", current_cal_state
930 if (until - now > 8H)
931 if (current_cal_state == Free && (work_hours && !in work_hours(now)))
932 return "Outside of working hours for next 8 hours"
933 else
934 return "%s for next 8 hours", current_cal_state
936 if (current_cal_state == Free)
937 if (work_hours && until !in work_hours(now))
938 "Not working"
939 else
940 "%s", current_cal_state
941 " until %.2d:%.2d", until
942 else
943 "Currently %", current_cal_state
944 if (work_hours && until !in work_hours(until))
945 ". Outside of working hours at at %.2d:%.2d", until
946 else
947 ". %s at %.2d:%.2d", to_state, until
950 if (current_cal_state < 1) { /* Free */
951 until = sipe_cal_get_until(now, switch_time, start, end, next_start);
952 } else {
953 until = switch_time;
956 if (!IS(until) && (cal_end - now > 8*60*60))
957 until = cal_end;
959 if (!IS(until)) {
960 return g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
963 if (until - now > 8*60*60) {
964 /* Free & outside work hours */
965 if (current_cal_state < 1 && has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
966 return g_strdup(_("Outside of working hours for next 8 hours"));
967 } else {
968 return g_strdup_printf(_("%s for next 8 hours"), cal_states[current_cal_state]);
972 if (current_cal_state < 1) { /* Free */
973 const char *tmp;
974 struct tm *until_tm = localtime(&until);
976 if (has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
977 tmp = _("Not working");
978 } else {
979 tmp = cal_states[current_cal_state];
981 return g_strdup_printf(_("%s until %.2d:%.2d"), tmp, until_tm->tm_hour, until_tm->tm_min);
982 } else { /* Tentative or Busy or OOF */
983 char *tmp;
984 char *res;
985 struct tm *until_tm = localtime(&until);
987 tmp = g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
988 if (has_working_hours && !sipe_cal_is_in_work_hours(until, start, end)) {
989 res = g_strdup_printf(_("%s. Outside of working hours at %.2d:%.2d"),
990 tmp, until_tm->tm_hour, until_tm->tm_min);
991 g_free(tmp);
992 return res;
993 } else {
994 res = g_strdup_printf(_("%s. %s at %.2d:%.2d"), tmp, cal_states[to_state], until_tm->tm_hour, until_tm->tm_min);
995 g_free(tmp);
996 return res;
999 /* End of - Calendar: string calculations */
1003 Local Variables:
1004 mode: c
1005 c-file-style: "bsd"
1006 indent-tabs-mode: t
1007 tab-width: 8
1008 End: