Merge branch 'mob' of git+ssh://localhost/srv/git/siplcs into mob
[siplcs.git] / src / core / sipe-cal.c
blob4e03771ce0b80b462b4bfa11f747495c8299ffe6
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 #include <string.h>
25 #include <time.h>
26 #include <glib.h>
28 #include "debug.h"
30 #include <sipe.h>
31 #include <sipe-cal.h>
32 #include <sipe-utils.h>
33 #include <sipe-nls.h>
35 #include <stdlib.h>
38 #define TIME_NULL (time_t)-1
39 #define IS(time) (time != TIME_NULL)
42 http://msdn.microsoft.com/en-us/library/aa565001.aspx
44 <?xml version="1.0"?>
45 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
46 <TimeZone>
47 <Bias>480</Bias>
48 <StandardTime>
49 <Bias>0</Bias>
50 <Time>02:00:00</Time>
51 <DayOrder>1</DayOrder>
52 <Month>11</Month>
53 <DayOfWeek>Sunday</DayOfWeek>
54 </StandardTime>
55 <DaylightTime>
56 <Bias>-60</Bias>
57 <Time>02:00:00</Time>
58 <DayOrder>2</DayOrder>
59 <Month>3</Month>
60 <DayOfWeek>Sunday</DayOfWeek>
61 </DaylightTime>
62 </TimeZone>
63 <WorkingPeriodArray>
64 <WorkingPeriod>
65 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
66 <StartTimeInMinutes>600</StartTimeInMinutes>
67 <EndTimeInMinutes>1140</EndTimeInMinutes>
68 </WorkingPeriod>
69 </WorkingPeriodArray>
70 </WorkingHours>
72 Desc:
73 <StandardTime>
74 <Bias>int</Bias>
75 <Time>string</Time>
76 <DayOrder>short</DayOrder>
77 <Month>short</Month>
78 <DayOfWeek>Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday</DayOfWeek>
79 <Year>string</Year>
80 </StandardTime>
83 struct sipe_cal_std_dst {
84 int bias; /* Ex.: -60 */
85 gchar *time; /* hh:mm:ss, 02:00:00 */
86 int day_order; /* 1..5 */
87 int month; /* 1..12 */
88 gchar *day_of_week; /* Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday */
89 gchar *year; /* YYYY */
91 time_t switch_time;
94 struct sipe_cal_working_hours {
95 int bias; /* Ex.: 480 */
96 struct sipe_cal_std_dst std; /* StandardTime */
97 struct sipe_cal_std_dst dst; /* DaylightTime */
98 gchar *days_of_week; /* Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday separated by space */
99 int start_time; /* 0...1440 */
100 int end_time; /* 0...1440 */
102 gchar *tz; /* aggregated timezone string as in TZ environment variable.
103 Ex.: TST+8TDT+7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
104 /** separate simple strings for Windows platform as the proper TZ does not work there.
105 * anyway, dynamic timezones would't work with just TZ
107 gchar *tz_std; /* Ex.: TST8 */
108 gchar *tz_dst; /* Ex.: TDT7 */
111 /* not for translation, a part of XML Schema definitions */
112 static const char *wday_names[] = {"Sunday",
113 "Monday",
114 "Tuesday",
115 "Wednesday",
116 "Thursday",
117 "Friday",
118 "Saturday"};
119 static int
120 sipe_cal_get_wday(char *wday_name)
122 int i;
124 if (!wday_name) return -1;
126 for (i = 0; i < 7; i++) {
127 if (sipe_strequal(wday_names[i], wday_name)) {
128 return i;
132 return -1;
135 void
136 sipe_cal_event_free(struct sipe_cal_event* cal_event)
138 if (!cal_event) return;
140 g_free(cal_event->subject);
141 g_free(cal_event->location);
142 g_free(cal_event);
145 char *
146 sipe_cal_event_describe(struct sipe_cal_event* cal_event)
148 GString* str = g_string_new(NULL);
149 const char *status = "";
151 switch(cal_event->cal_status) {
152 case SIPE_CAL_FREE: status = "SIPE_CAL_FREE"; break;
153 case SIPE_CAL_TENTATIVE: status = "SIPE_CAL_TENTATIVE"; break;
154 case SIPE_CAL_BUSY: status = "SIPE_CAL_BUSY"; break;
155 case SIPE_CAL_OOF: status = "SIPE_CAL_OOF"; break;
156 case SIPE_CAL_NO_DATA: status = "SIPE_CAL_NO_DATA"; break;
159 g_string_append_printf(str, "\t%s: %s", "start_time",
160 IS(cal_event->start_time) ? asctime(localtime(&cal_event->start_time)) : "\n");
161 g_string_append_printf(str, "\t%s: %s", "end_time ",
162 IS(cal_event->end_time) ? asctime(localtime(&cal_event->end_time)) : "\n");
163 g_string_append_printf(str, "\t%s: %s\n", "cal_status", status);
164 g_string_append_printf(str, "\t%s: %s\n", "subject ", cal_event->subject ? cal_event->subject : "");
165 g_string_append_printf(str, "\t%s: %s\n", "location ", cal_event->location ? cal_event->location : "");
166 g_string_append_printf(str, "\t%s: %s\n", "is_meeting", cal_event->is_meeting ? "TRUE" : "FALSE");
168 return g_string_free(str, FALSE);
171 char *
172 sipe_cal_event_hash(struct sipe_cal_event* event)
174 /* no end_time as it dos not get published */
175 /* no cal_status as it can change on publication */
176 return g_strdup_printf("<%d><%s><%s><%d>",
177 (int)event->start_time,
178 event->subject ? event->subject : "",
179 event->location ? event->location : "",
180 event->is_meeting);
183 static void
184 sipe_setenv(const char *name,
185 const char *value)
187 #ifndef _WIN32
188 setenv(name, value, 1);
189 #else
190 int len = strlen(name) + 1 + strlen(value) + 1;
191 char *str = g_malloc0(len);
192 sprintf(str, "%s=%s", name, value);
193 putenv(str);
194 #endif
197 static void
198 sipe_unsetenv(const char *name)
200 #ifndef _WIN32
201 unsetenv(name);
202 #else
203 int len = strlen(name) + 1 + 1;
204 char *str = g_malloc0(len);
205 sprintf(str, "%s=", name);
206 putenv(str);
207 #endif
211 * Converts struct tm to Epoch time_t considering timezone.
213 * @param tz as defined for TZ environment variable.
215 * Reference: see timegm(3) - Linux man page
217 time_t
218 sipe_mktime_tz(struct tm *tm,
219 const char* tz)
221 time_t ret;
222 char *tz_old;
224 tz_old = getenv("TZ");
225 sipe_setenv("TZ", tz);
226 tzset();
228 ret = mktime(tm);
230 if (tz_old) {
231 sipe_setenv("TZ", tz_old);
232 } else {
233 sipe_unsetenv("TZ");
235 tzset();
237 return ret;
241 * Converts Epoch time_t to struct tm considering timezone.
243 * @param tz as defined for TZ environment variable.
245 * Reference: see timegm(3) - Linux man page
247 static struct tm *
248 sipe_localtime_tz(const time_t *time,
249 const char* tz)
251 struct tm *ret;
252 char *tz_old;
254 tz_old = getenv("TZ");
255 sipe_setenv("TZ", tz);
256 tzset();
258 ret = localtime(time);
260 if (tz_old) {
261 sipe_setenv("TZ", tz_old);
262 } else {
263 sipe_unsetenv("TZ");
265 tzset();
267 return ret;
270 void
271 sipe_cal_free_working_hours(struct sipe_cal_working_hours *wh)
273 if (!wh) return;
275 g_free(wh->std.time);
276 g_free(wh->std.day_of_week);
277 g_free(wh->std.year);
279 g_free(wh->dst.time);
280 g_free(wh->dst.day_of_week);
281 g_free(wh->dst.year);
283 g_free(wh->days_of_week);
284 g_free(wh->tz);
285 g_free(wh->tz_std);
286 g_free(wh->tz_dst);
287 g_free(wh);
291 * Returns time_t of daylight savings time start/end
292 * in the provided timezone or otherwise
293 * (time_t)-1 if no daylight savings time.
295 static time_t
296 sipe_cal_get_std_dst_time(time_t now,
297 int bias,
298 struct sipe_cal_std_dst* std_dst,
299 struct sipe_cal_std_dst* dst_std)
301 struct tm switch_tm;
302 time_t res = TIME_NULL;
303 struct tm *gm_now_tm;
304 gchar **time_arr;
306 if (std_dst->month == 0) return TIME_NULL;
308 gm_now_tm = gmtime(&now);
309 time_arr = g_strsplit(std_dst->time, ":", 0);
311 switch_tm.tm_sec = atoi(time_arr[2]);
312 switch_tm.tm_min = atoi(time_arr[1]);
313 switch_tm.tm_hour = atoi(time_arr[0]);
314 g_strfreev(time_arr);
315 switch_tm.tm_mday = std_dst->year ? std_dst->day_order : 1 /* to adjust later */ ;
316 switch_tm.tm_mon = std_dst->month - 1;
317 switch_tm.tm_year = std_dst->year ? atoi(std_dst->year) - 1900 : gm_now_tm->tm_year;
318 switch_tm.tm_isdst = 0;
319 /* to set tm_wday */
320 res = sipe_mktime_tz(&switch_tm, "UTC");
322 /* if not dynamic, calculate right tm_mday */
323 if (!std_dst->year) {
324 int switch_wday = sipe_cal_get_wday(std_dst->day_of_week);
325 int needed_month;
326 /* get first desired wday in the month */
327 int delta = switch_wday >= switch_tm.tm_wday ? (switch_wday - switch_tm.tm_wday) : (switch_wday + 7 - switch_tm.tm_wday);
328 switch_tm.tm_mday = 1 + delta;
329 /* try nth order */
330 switch_tm.tm_mday += (std_dst->day_order - 1) * 7;
331 needed_month = switch_tm.tm_mon;
332 /* to set settle date if ahead of allowed month dates */
333 res = sipe_mktime_tz(&switch_tm, "UTC");
334 if (needed_month != switch_tm.tm_mon) {
335 /* moving 1 week back to stay within required month */
336 switch_tm.tm_mday -= 7;
337 /* to fix date again */
338 res = sipe_mktime_tz(&switch_tm, "UTC");
341 /* note: bias is taken from "switch to" structure */
342 return res + (bias + dst_std->bias)*60;
345 static void
346 sipe_cal_parse_std_dst(xmlnode *xn_std_dst_time,
347 struct sipe_cal_std_dst* std_dst)
349 xmlnode *node;
350 gchar *tmp;
352 if (!xn_std_dst_time) return;
353 if (!std_dst) return;
355 <StandardTime>
356 <Bias>0</Bias>
357 <Time>02:00:00</Time>
358 <DayOrder>1</DayOrder>
359 <Month>11</Month>
360 <DayOfWeek>Sunday</DayOfWeek>
361 </StandardTime>
364 if ((node = xmlnode_get_child(xn_std_dst_time, "Bias"))) {
365 std_dst->bias = atoi(tmp = xmlnode_get_data(node));
366 g_free(tmp);
369 if ((node = xmlnode_get_child(xn_std_dst_time, "Time"))) {
370 std_dst->time = xmlnode_get_data(node);
373 if ((node = xmlnode_get_child(xn_std_dst_time, "DayOrder"))) {
374 std_dst->day_order = atoi(tmp = xmlnode_get_data(node));
375 g_free(tmp);
378 if ((node = xmlnode_get_child(xn_std_dst_time, "Month"))) {
379 std_dst->month = atoi(tmp = xmlnode_get_data(node));
380 g_free(tmp);
383 if ((node = xmlnode_get_child(xn_std_dst_time, "DayOfWeek"))) {
384 std_dst->day_of_week = xmlnode_get_data(node);
387 if ((node = xmlnode_get_child(xn_std_dst_time, "Year"))) {
388 std_dst->year = xmlnode_get_data(node);
392 void
393 sipe_cal_parse_working_hours(xmlnode *xn_working_hours,
394 struct sipe_buddy *buddy)
396 xmlnode *xn_bias;
397 xmlnode *xn_working_period;
398 xmlnode *xn_standard_time;
399 xmlnode *xn_daylight_time;
400 gchar *tmp;
401 time_t now = time(NULL);
402 struct sipe_cal_std_dst* std;
403 struct sipe_cal_std_dst* dst;
405 if (!xn_working_hours) return;
407 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
408 <TimeZone>
409 <Bias>480</Bias>
411 </TimeZone>
412 <WorkingPeriodArray>
413 <WorkingPeriod>
414 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
415 <StartTimeInMinutes>600</StartTimeInMinutes>
416 <EndTimeInMinutes>1140</EndTimeInMinutes>
417 </WorkingPeriod>
418 </WorkingPeriodArray>
419 </WorkingHours>
421 sipe_cal_free_working_hours(buddy->cal_working_hours);
422 buddy->cal_working_hours = g_new0(struct sipe_cal_working_hours, 1);
424 xn_bias = xmlnode_get_descendant(xn_working_hours, "TimeZone", "Bias", NULL);
425 if (xn_bias) {
426 buddy->cal_working_hours->bias = atoi(tmp = xmlnode_get_data(xn_bias));
427 g_free(tmp);
430 xn_standard_time = xmlnode_get_descendant(xn_working_hours, "TimeZone", "StandardTime", NULL);
431 xn_daylight_time = xmlnode_get_descendant(xn_working_hours, "TimeZone", "DaylightTime", NULL);
433 std = &((*buddy->cal_working_hours).std);
434 dst = &((*buddy->cal_working_hours).dst);
435 sipe_cal_parse_std_dst(xn_standard_time, std);
436 sipe_cal_parse_std_dst(xn_daylight_time, dst);
438 xn_working_period = xmlnode_get_descendant(xn_working_hours, "WorkingPeriodArray", "WorkingPeriod", NULL);
439 if (xn_working_period) {
440 buddy->cal_working_hours->days_of_week =
441 xmlnode_get_data(xmlnode_get_child(xn_working_period, "DayOfWeek"));
443 buddy->cal_working_hours->start_time =
444 atoi(tmp = xmlnode_get_data(xmlnode_get_child(xn_working_period, "StartTimeInMinutes")));
445 g_free(tmp);
447 buddy->cal_working_hours->end_time =
448 atoi(tmp = xmlnode_get_data(xmlnode_get_child(xn_working_period, "EndTimeInMinutes")));
449 g_free(tmp);
452 std->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, std, dst);
453 dst->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, dst, std);
455 /* TST8TDT7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
456 buddy->cal_working_hours->tz =
457 g_strdup_printf("TST%dTDT%d,M%d.%d.%d/%s,M%d.%d.%d/%s",
458 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60,
459 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60,
461 buddy->cal_working_hours->dst.month,
462 buddy->cal_working_hours->dst.day_order,
463 sipe_cal_get_wday(buddy->cal_working_hours->dst.day_of_week),
464 buddy->cal_working_hours->dst.time,
466 buddy->cal_working_hours->std.month,
467 buddy->cal_working_hours->std.day_order,
468 sipe_cal_get_wday(buddy->cal_working_hours->std.day_of_week),
469 buddy->cal_working_hours->std.time
471 /* TST8 */
472 buddy->cal_working_hours->tz_std =
473 g_strdup_printf("TST%d",
474 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60);
475 /* TDT7 */
476 buddy->cal_working_hours->tz_dst =
477 g_strdup_printf("TDT%d",
478 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60);
481 struct sipe_cal_event*
482 sipe_cal_get_event(GSList *cal_events,
483 time_t time_in_question)
485 GSList *entry = cal_events;
486 struct sipe_cal_event* cal_event;
487 struct sipe_cal_event* res = NULL;
489 if (!cal_events || !IS(time_in_question)) return NULL;
491 while (entry) {
492 cal_event = entry->data;
493 /* event is in the past or in the future */
494 if (cal_event->start_time > time_in_question ||
495 cal_event->end_time <= time_in_question)
497 entry = entry->next;
498 continue;
501 if (!res) {
502 res = cal_event;
503 } else {
504 int res_status = (res->cal_status == SIPE_CAL_NO_DATA) ? -1 : res->cal_status;
505 int cal_status = (cal_event->cal_status == SIPE_CAL_NO_DATA) ? -1 : cal_event->cal_status;
506 if (res_status < cal_status) {
507 res = cal_event;
510 entry = entry->next;
512 return res;
515 static int
516 sipe_cal_get_status0(const gchar *free_busy,
517 time_t cal_start,
518 int granularity,
519 time_t time_in_question,
520 int *index)
522 int res = SIPE_CAL_NO_DATA;
523 int shift;
524 time_t cal_end = cal_start + strlen(free_busy)*granularity*60 - 1;
526 if (!(time_in_question >= cal_start && time_in_question <= cal_end)) return res;
528 shift = (time_in_question - cal_start) / (granularity*60);
529 if (index) {
530 *index = shift;
533 res = free_busy[shift] - '0';
535 return res;
539 * Returns time when current calendar state started
541 static time_t
542 sipe_cal_get_since_time(const gchar *free_busy,
543 time_t calStart,
544 int granularity,
545 int index,
546 int current_state)
548 int i;
550 if ((index < 0) || ((size_t)(index + 1) > strlen(free_busy))) return 0;
552 for (i = index; i >= 0; i--) {
553 int temp_status = free_busy[i] - '0';
555 if (current_state != temp_status) {
556 return calStart + (i + 1)*granularity*60;
559 if (i == 0) return calStart;
562 return 0;
564 static char*
565 sipe_cal_get_free_busy(struct sipe_buddy *buddy);
568 sipe_cal_get_status(struct sipe_buddy *buddy,
569 time_t time_in_question,
570 time_t *since)
572 time_t cal_start;
573 const char* free_busy;
574 int ret = SIPE_CAL_NO_DATA;
575 time_t state_since;
576 int index;
578 if (!buddy || !buddy->cal_start_time || !buddy->cal_granularity) {
579 purple_debug_info("sipe", "sipe_cal_get_status: no calendar data1 for %s, exiting\n",
580 buddy ? (buddy->name ? buddy->name : "") : "");
581 return SIPE_CAL_NO_DATA;
584 if (!(free_busy = sipe_cal_get_free_busy(buddy))) {
585 purple_debug_info("sipe", "sipe_cal_get_status: no calendar data2 for %s, exiting\n", buddy->name);
586 return SIPE_CAL_NO_DATA;
588 purple_debug_info("sipe", "sipe_cal_get_description: buddy->cal_free_busy=\n%s\n", free_busy);
590 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
592 ret = sipe_cal_get_status0(free_busy,
593 cal_start,
594 buddy->cal_granularity,
595 time_in_question,
596 &index);
597 state_since = sipe_cal_get_since_time(free_busy,
598 cal_start,
599 buddy->cal_granularity,
600 index,
601 ret);
603 if (since) *since = state_since;
604 return ret;
607 static time_t
608 sipe_cal_get_switch_time(const gchar *free_busy,
609 time_t calStart,
610 int granularity,
611 int index,
612 int current_state,
613 int *to_state)
615 size_t i;
616 time_t ret = TIME_NULL;
618 if ((index < 0) || ((size_t) (index + 1) > strlen(free_busy))) {
619 *to_state = SIPE_CAL_NO_DATA;
620 return ret;
623 for (i = index + 1; i < strlen(free_busy); i++) {
624 int temp_status = free_busy[i] - '0';
626 if (current_state != temp_status) {
627 *to_state = temp_status;
628 return calStart + i*granularity*60;
632 return ret;
635 static const char*
636 sipe_cal_get_tz(struct sipe_cal_working_hours *wh,
637 time_t time_in_question)
639 time_t dst_switch_time = (*wh).dst.switch_time;
640 time_t std_switch_time = (*wh).std.switch_time;
641 gboolean is_dst = FALSE;
643 /* No daylight savings */
644 if (dst_switch_time == TIME_NULL) {
645 return wh->tz_std;
648 if (dst_switch_time < std_switch_time) { /* North hemosphere - Europe, US */
649 if (time_in_question >= dst_switch_time && time_in_question < std_switch_time) {
650 is_dst = TRUE;
652 } else { /* South hemisphere - Australia */
653 if (time_in_question >= dst_switch_time || time_in_question < std_switch_time) {
654 is_dst = TRUE;
658 if (is_dst) {
659 return wh->tz_dst;
660 } else {
661 return wh->tz_std;
665 static time_t
666 sipe_cal_mktime_of_day(struct tm *sample_today_tm,
667 const int shift_minutes,
668 const char *tz)
670 sample_today_tm->tm_sec = 0;
671 sample_today_tm->tm_min = shift_minutes % 60;
672 sample_today_tm->tm_hour = shift_minutes / 60;
674 return sipe_mktime_tz(sample_today_tm, tz);
678 * Returns work day start and end in Epoch time
679 * considering the initial values are provided
680 * in contact's local time zone.
682 static void
683 sipe_cal_get_today_work_hours(struct sipe_cal_working_hours *wh,
684 time_t *start,
685 time_t *end,
686 time_t *next_start)
688 time_t now = time(NULL);
689 const char *tz = sipe_cal_get_tz(wh, now);
690 struct tm *remote_now_tm = sipe_localtime_tz(&now, tz);
692 if (!strstr(wh->days_of_week, wday_names[remote_now_tm->tm_wday])) { /* not a work day */
693 *start = TIME_NULL;
694 *end = TIME_NULL;
695 *next_start = TIME_NULL;
696 return;
699 *end = sipe_cal_mktime_of_day(remote_now_tm, wh->end_time, tz);
701 if (now < *end) {
702 *start = sipe_cal_mktime_of_day(remote_now_tm, wh->start_time, tz);
703 *next_start = TIME_NULL;
704 } else { /* calculate start of tomorrow's work day if any */
705 time_t tom = now + 24*60*60;
706 struct tm *remote_tom_tm = sipe_localtime_tz(&tom, sipe_cal_get_tz(wh, tom));
708 if (!strstr(wh->days_of_week, wday_names[remote_tom_tm->tm_wday])) { /* not a work day */
709 *next_start = TIME_NULL;
712 *next_start = sipe_cal_mktime_of_day(remote_tom_tm, wh->start_time, sipe_cal_get_tz(wh, tom));
713 *start = TIME_NULL;
717 static int
718 sipe_cal_is_in_work_hours(const time_t time_in_question,
719 const time_t start,
720 const time_t end)
722 return !((time_in_question >= end) || (IS(start) && time_in_question < start));
726 * Returns time closest to now. Choses only from times ahead of now.
727 * Returns TIME_NULL otherwise.
729 static time_t
730 sipe_cal_get_until(const time_t now,
731 const time_t switch_time,
732 const time_t start,
733 const time_t end,
734 const time_t next_start)
736 time_t ret = TIME_NULL;
737 int min_diff = now - ret;
739 if (IS(switch_time) && switch_time > now && (switch_time - now) < min_diff) {
740 min_diff = switch_time - now;
741 ret = switch_time;
743 if (IS(start) && start > now && (start - now) < min_diff) {
744 min_diff = start - now;
745 ret = start;
747 if (IS(end) && end > now && (end - now) < min_diff) {
748 min_diff = end - now;
749 ret = end;
751 if (IS(next_start) && next_start > now && (next_start - now) < min_diff) {
752 min_diff = next_start - now;
753 ret = next_start;
755 return ret;
758 static char*
759 sipe_cal_get_free_busy(struct sipe_buddy *buddy)
761 /* do lazy decode if necessary */
762 if (!buddy->cal_free_busy && buddy->cal_free_busy_base64) {
763 gsize cal_dec64_len;
764 guchar *cal_dec64;
765 gsize i;
766 int j = 0;
768 cal_dec64 = purple_base64_decode(buddy->cal_free_busy_base64, &cal_dec64_len);
770 buddy->cal_free_busy = g_malloc0(cal_dec64_len * 4 + 1);
772 http://msdn.microsoft.com/en-us/library/dd941537%28office.13%29.aspx
773 00, Free (Fr)
774 01, Tentative (Te)
775 10, Busy (Bu)
776 11, Out of facility (Oo)
778 http://msdn.microsoft.com/en-us/library/aa566048.aspx
779 0 Free
780 1 Tentative
781 2 Busy
782 3 Out of Office (OOF)
783 4 No data
785 for (i = 0; i < cal_dec64_len; i++) {
786 #define TWO_BIT_MASK 0x03
787 char tmp = cal_dec64[i];
788 buddy->cal_free_busy[j++] = (tmp & TWO_BIT_MASK) + '0';
789 buddy->cal_free_busy[j++] = ((tmp >> 2) & TWO_BIT_MASK) + '0';
790 buddy->cal_free_busy[j++] = ((tmp >> 4) & TWO_BIT_MASK) + '0';
791 buddy->cal_free_busy[j++] = ((tmp >> 6) & TWO_BIT_MASK) + '0';
793 buddy->cal_free_busy[j++] = '\0';
794 g_free(cal_dec64);
797 return buddy->cal_free_busy;
800 char *
801 sipe_cal_get_freebusy_base64(const char* freebusy_hex)
803 guint i = 0;
804 guint j = 0;
805 guint shift_factor = 0;
806 guint len, res_len;
807 guchar *res;
808 gchar *res_base64;
810 if (!freebusy_hex) return NULL;
812 len = strlen(freebusy_hex);
813 res_len = len / 4 + 1;
814 res = g_malloc0(res_len);
815 while (i < len) {
816 res[j] |= (freebusy_hex[i++] - '0') << shift_factor;
817 shift_factor += 2;
818 if (shift_factor == 8) {
819 shift_factor = 0;
820 j++;
824 res_base64 = purple_base64_encode(res, shift_factor ? res_len : res_len - 1);
825 g_free(res);
826 return res_base64;
829 char *
830 sipe_cal_get_description(struct sipe_buddy *buddy)
832 time_t cal_start;
833 time_t cal_end;
834 int current_cal_state;
835 time_t now = time(NULL);
836 time_t start = TIME_NULL;
837 time_t end = TIME_NULL;
838 time_t next_start = TIME_NULL;
839 time_t switch_time;
840 int to_state = SIPE_CAL_NO_DATA;
841 time_t until = TIME_NULL;
842 int index = 0;
843 gboolean has_working_hours = (buddy->cal_working_hours != NULL);
844 const char *free_busy;
845 const char *cal_states[] = {_("Free"),
846 _("Tentative"),
847 _("Busy"),
848 _("Out of office"),
849 _("No data")};
851 if (buddy->cal_granularity != 15) {
852 purple_debug_info("sipe", "sipe_cal_get_description: granularity %d is unsupported, exiting.\n", buddy->cal_granularity);
853 return NULL;
856 /* to lazy load if needed */
857 free_busy = sipe_cal_get_free_busy(buddy);
858 purple_debug_info("sipe", "sipe_cal_get_description: buddy->cal_free_busy=\n%s\n", free_busy ? free_busy : "");
860 if (!buddy->cal_free_busy || !buddy->cal_granularity || !buddy->cal_start_time) {
861 purple_debug_info("sipe", "sipe_cal_get_description: no calendar data, exiting");
862 return NULL;
865 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
866 cal_end = cal_start + 60 * (buddy->cal_granularity) * strlen(buddy->cal_free_busy);
868 current_cal_state = sipe_cal_get_status0(free_busy, cal_start, buddy->cal_granularity, time(NULL), &index);
869 if (current_cal_state == SIPE_CAL_NO_DATA) {
870 purple_debug_info("sipe", "sipe_cal_get_description: calendar is undefined for present moment, exiting.\n");
871 return NULL;
874 switch_time = sipe_cal_get_switch_time(free_busy, cal_start, buddy->cal_granularity, index, current_cal_state, &to_state);
876 purple_debug_info("sipe", "\n* Calendar *\n");
877 if (buddy->cal_working_hours) {
878 sipe_cal_get_today_work_hours(buddy->cal_working_hours, &start, &end, &next_start);
880 purple_debug_info("sipe", "Remote now timezone : %s\n", sipe_cal_get_tz(buddy->cal_working_hours, now));
881 purple_debug_info("sipe", "std.switch_time(GMT): %s",
882 IS((*buddy->cal_working_hours).std.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).std.switch_time))) : "\n");
883 purple_debug_info("sipe", "dst.switch_time(GMT): %s",
884 IS((*buddy->cal_working_hours).dst.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).dst.switch_time))) : "\n");
885 purple_debug_info("sipe", "Remote now time : %s",
886 asctime(sipe_localtime_tz(&now, sipe_cal_get_tz(buddy->cal_working_hours, now))));
887 purple_debug_info("sipe", "Remote start time : %s",
888 IS(start) ? asctime(sipe_localtime_tz(&start, sipe_cal_get_tz(buddy->cal_working_hours, start))) : "\n");
889 purple_debug_info("sipe", "Remote end time : %s",
890 IS(end) ? asctime(sipe_localtime_tz(&end, sipe_cal_get_tz(buddy->cal_working_hours, end))) : "\n");
891 purple_debug_info("sipe", "Rem. next_start time: %s",
892 IS(next_start) ? asctime(sipe_localtime_tz(&next_start, sipe_cal_get_tz(buddy->cal_working_hours, next_start))) : "\n");
893 purple_debug_info("sipe", "Remote switch time : %s",
894 IS(switch_time) ? asctime(sipe_localtime_tz(&switch_time, sipe_cal_get_tz(buddy->cal_working_hours, switch_time))) : "\n");
895 } else {
896 purple_debug_info("sipe", "Local now time : %s",
897 asctime(localtime(&now)));
898 purple_debug_info("sipe", "Local switch time : %s",
899 IS(switch_time) ? asctime(localtime(&switch_time)) : "\n");
901 purple_debug_info("sipe", "Calendar End (GMT) : %s", asctime(gmtime(&cal_end)));
902 purple_debug_info("sipe", "current cal state : %s\n", cal_states[current_cal_state]);
903 purple_debug_info("sipe", "switch cal state : %s\n", cal_states[to_state] );
905 /* Calendar: string calculations */
908 ALGORITHM (don't delete)
909 (c)2009,2010 pier11 <pier11@operamail.com>
911 SOD = Start of Work Day
912 EOD = End of Work Day
913 NSOD = Start of tomorrow's Work Day
914 SW = Calendar status switch time
916 if current_cal_state == Free
917 until = min_t of SOD, EOD, NSOD, SW (min_t(x) = min(x-now) where x>now only)
918 else
919 until = SW
921 if (!until && (cal_period_end > now + 8H))
922 until = cal_period_end
924 if (!until)
925 return "Currently %", current_cal_state
927 if (until - now > 8H)
928 if (current_cal_state == Free && (work_hours && !in work_hours(now)))
929 return "Outside of working hours for next 8 hours"
930 else
931 return "%s for next 8 hours", current_cal_state
933 if (current_cal_state == Free)
934 if (work_hours && until !in work_hours(now))
935 "Not working"
936 else
937 "%s", current_cal_state
938 " until %.2d:%.2d", until
939 else
940 "Currently %", current_cal_state
941 if (work_hours && until !in work_hours(until))
942 ". Outside of working hours at at %.2d:%.2d", until
943 else
944 ". %s at %.2d:%.2d", to_state, until
947 if (current_cal_state < 1) { /* Free */
948 until = sipe_cal_get_until(now, switch_time, start, end, next_start);
949 } else {
950 until = switch_time;
953 if (!IS(until) && (cal_end - now > 8*60*60))
954 until = cal_end;
956 if (!IS(until)) {
957 return g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
960 if (until - now > 8*60*60) {
961 /* Free & outside work hours */
962 if (current_cal_state < 1 && has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
963 return g_strdup(_("Outside of working hours for next 8 hours"));
964 } else {
965 return g_strdup_printf(_("%s for next 8 hours"), cal_states[current_cal_state]);
969 if (current_cal_state < 1) { /* Free */
970 const char *tmp;
971 struct tm *until_tm = localtime(&until);
973 if (has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
974 tmp = _("Not working");
975 } else {
976 tmp = cal_states[current_cal_state];
978 return g_strdup_printf(_("%s until %.2d:%.2d"), tmp, until_tm->tm_hour, until_tm->tm_min);
979 } else { /* Tentative or Busy or OOF */
980 char *tmp;
981 char *res;
982 struct tm *until_tm = localtime(&until);
984 tmp = g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
985 if (has_working_hours && !sipe_cal_is_in_work_hours(until, start, end)) {
986 res = g_strdup_printf(_("%s. Outside of working hours at %.2d:%.2d"),
987 tmp, until_tm->tm_hour, until_tm->tm_min);
988 g_free(tmp);
989 return res;
990 } else {
991 res = g_strdup_printf(_("%s. %s at %.2d:%.2d"), tmp, cal_states[to_state], until_tm->tm_hour, until_tm->tm_min);
992 g_free(tmp);
993 return res;
996 /* End of - Calendar: string calculations */
1000 Local Variables:
1001 mode: c
1002 c-file-style: "bsd"
1003 indent-tabs-mode: t
1004 tab-width: 8
1005 End: