Fix #3399007: Crash when sipe_cal_working_hours->days_of_week is NULL
[siplcs.git] / src / core / sipe-cal.c
blobd2d24bcf8b26ac03406ec349124266665a2fdf29
1 /**
2 * @file sipe-cal.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include <glib.h>
35 #include "http-conn.h"
36 #include "sipe-backend.h"
37 #include "sipe-buddy.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-cal.h"
41 #include "sipe-nls.h"
42 #include "sipe-utils.h"
43 #include "sipe-xml.h"
44 #include "sipe.h"
46 #define TIME_NULL (time_t)-1
47 #define IS(time) (time != TIME_NULL)
50 http://msdn.microsoft.com/en-us/library/aa565001.aspx
52 <?xml version="1.0"?>
53 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
54 <TimeZone>
55 <Bias>480</Bias>
56 <StandardTime>
57 <Bias>0</Bias>
58 <Time>02:00:00</Time>
59 <DayOrder>1</DayOrder>
60 <Month>11</Month>
61 <DayOfWeek>Sunday</DayOfWeek>
62 </StandardTime>
63 <DaylightTime>
64 <Bias>-60</Bias>
65 <Time>02:00:00</Time>
66 <DayOrder>2</DayOrder>
67 <Month>3</Month>
68 <DayOfWeek>Sunday</DayOfWeek>
69 </DaylightTime>
70 </TimeZone>
71 <WorkingPeriodArray>
72 <WorkingPeriod>
73 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
74 <StartTimeInMinutes>600</StartTimeInMinutes>
75 <EndTimeInMinutes>1140</EndTimeInMinutes>
76 </WorkingPeriod>
77 </WorkingPeriodArray>
78 </WorkingHours>
80 Desc:
81 <StandardTime>
82 <Bias>int</Bias>
83 <Time>string</Time>
84 <DayOrder>short</DayOrder>
85 <Month>short</Month>
86 <DayOfWeek>Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday</DayOfWeek>
87 <Year>string</Year>
88 </StandardTime>
91 struct sipe_cal_std_dst {
92 int bias; /* Ex.: -60 */
93 gchar *time; /* hh:mm:ss, 02:00:00 */
94 int day_order; /* 1..5 */
95 int month; /* 1..12 */
96 gchar *day_of_week; /* Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday */
97 gchar *year; /* YYYY */
99 time_t switch_time;
102 struct sipe_cal_working_hours {
103 int bias; /* Ex.: 480 */
104 struct sipe_cal_std_dst std; /* StandardTime */
105 struct sipe_cal_std_dst dst; /* DaylightTime */
106 gchar *days_of_week; /* Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday separated by space */
107 int start_time; /* 0...1440 */
108 int end_time; /* 0...1440 */
110 gchar *tz; /* aggregated timezone string as in TZ environment variable.
111 Ex.: TST+8TDT+7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
112 /** separate simple strings for Windows platform as the proper TZ does not work there.
113 * anyway, dynamic timezones would't work with just TZ
115 gchar *tz_std; /* Ex.: TST8 */
116 gchar *tz_dst; /* Ex.: TDT7 */
119 /* not for translation, a part of XML Schema definitions */
120 static const char *wday_names[] = {"Sunday",
121 "Monday",
122 "Tuesday",
123 "Wednesday",
124 "Thursday",
125 "Friday",
126 "Saturday"};
127 static int
128 sipe_cal_get_wday(char *wday_name)
130 int i;
132 if (!wday_name) return -1;
134 for (i = 0; i < 7; i++) {
135 if (sipe_strequal(wday_names[i], wday_name)) {
136 return i;
140 return -1;
143 void
144 sipe_cal_event_free(struct sipe_cal_event* cal_event)
146 if (!cal_event) return;
148 g_free(cal_event->subject);
149 g_free(cal_event->location);
150 g_free(cal_event);
153 void
154 sipe_cal_events_free(GSList *cal_events)
156 GSList *entry = cal_events;
158 if (!cal_events) return;
160 while (entry) {
161 struct sipe_cal_event *cal_event = entry->data;
162 sipe_cal_event_free(cal_event);
163 entry = entry->next;
166 g_slist_free(cal_events);
169 void
170 sipe_cal_calendar_free(struct sipe_calendar *cal)
172 g_free(cal->email);
173 g_free(cal->legacy_dn);
174 if (cal->auth) {
175 g_free(cal->auth->domain);
176 g_free(cal->auth->user);
177 g_free(cal->auth->password);
179 g_free(cal->auth);
180 g_free(cal->as_url);
181 g_free(cal->oof_url);
182 g_free(cal->oab_url);
183 g_free(cal->domino_url);
184 g_free(cal->oof_state);
185 g_free(cal->oof_note);
186 g_free(cal->free_busy);
187 g_free(cal->working_hours_xml_str);
189 sipe_cal_events_free(cal->cal_events);
191 if (cal->http_conn) {
192 http_conn_free(cal->http_conn);
195 if (cal->http_session) {
196 http_conn_session_free(cal->http_session);
199 g_free(cal);
202 gboolean
203 sipe_cal_calendar_init(struct sipe_core_private *sipe_private,
204 gboolean *has_url)
206 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
207 if (!sip->cal) {
208 const char *value;
210 sip->cal = g_new0(struct sipe_calendar, 1);
211 sip->cal->sipe_private = sipe_private;
213 sip->cal->email = g_strdup(sip->email);
215 /* user specified a service URL? */
216 value = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
217 if (has_url) *has_url = !is_empty(value);
218 if (!is_empty(value)) {
219 sip->cal->as_url = g_strdup(value);
220 sip->cal->oof_url = g_strdup(value);
221 sip->cal->domino_url = g_strdup(value);
224 sip->cal->auth = g_new0(HttpConnAuth, 1);
225 sip->cal->auth->use_negotiate = SIPE_CORE_PUBLIC_FLAG_IS(KRB5);
227 /* user specified email login? */
228 value = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_LOGIN);
229 if (!is_empty(value)) {
231 /* user specified email login domain? */
232 const char *tmp = strstr(value, "\\");
233 if (tmp) {
234 sip->cal->auth->domain = g_strndup(value, tmp - value);
235 sip->cal->auth->user = g_strdup(tmp + 1);
236 } else {
237 sip->cal->auth->user = g_strdup(value);
239 sip->cal->auth->password = g_strdup(sipe_backend_setting(SIPE_CORE_PUBLIC,
240 SIPE_SETTING_EMAIL_PASSWORD));
242 } else {
243 /* re-use SIPE credentials */
244 sip->cal->auth->domain = g_strdup(sip->authdomain);
245 sip->cal->auth->user = g_strdup(sip->authuser);
246 sip->cal->auth->password = g_strdup(sip->password);
248 return TRUE;
250 return FALSE;
254 char *
255 sipe_cal_event_describe(struct sipe_cal_event* cal_event)
257 GString* str = g_string_new(NULL);
258 const char *status = "";
260 switch(cal_event->cal_status) {
261 case SIPE_CAL_FREE: status = "SIPE_CAL_FREE"; break;
262 case SIPE_CAL_TENTATIVE: status = "SIPE_CAL_TENTATIVE"; break;
263 case SIPE_CAL_BUSY: status = "SIPE_CAL_BUSY"; break;
264 case SIPE_CAL_OOF: status = "SIPE_CAL_OOF"; break;
265 case SIPE_CAL_NO_DATA: status = "SIPE_CAL_NO_DATA"; break;
268 g_string_append_printf(str, "\t%s: %s", "start_time",
269 IS(cal_event->start_time) ? asctime(localtime(&cal_event->start_time)) : "\n");
270 g_string_append_printf(str, "\t%s: %s", "end_time ",
271 IS(cal_event->end_time) ? asctime(localtime(&cal_event->end_time)) : "\n");
272 g_string_append_printf(str, "\t%s: %s\n", "cal_status", status);
273 g_string_append_printf(str, "\t%s: %s\n", "subject ", cal_event->subject ? cal_event->subject : "");
274 g_string_append_printf(str, "\t%s: %s\n", "location ", cal_event->location ? cal_event->location : "");
275 g_string_append_printf(str, "\t%s: %s\n", "is_meeting", cal_event->is_meeting ? "TRUE" : "FALSE");
277 return g_string_free(str, FALSE);
280 char *
281 sipe_cal_event_hash(struct sipe_cal_event* event)
283 /* no end_time as it dos not get published */
284 /* no cal_status as it can change on publication */
285 return g_strdup_printf("<%d><%s><%s><%d>",
286 (int)event->start_time,
287 event->subject ? event->subject : "",
288 event->location ? event->location : "",
289 event->is_meeting);
292 #define ENVIRONMENT_TIMEZONE "TZ"
294 static gchar *
295 sipe_switch_tz(const char *tz)
297 gchar *tz_orig;
299 tz_orig = g_strdup(g_getenv(ENVIRONMENT_TIMEZONE));
300 g_setenv(ENVIRONMENT_TIMEZONE, tz, TRUE);
301 tzset();
302 return(tz_orig);
305 static void
306 sipe_reset_tz(gchar *tz_orig)
308 if (tz_orig) {
309 g_setenv(ENVIRONMENT_TIMEZONE, tz_orig, TRUE);
310 g_free(tz_orig);
311 } else {
312 g_unsetenv(ENVIRONMENT_TIMEZONE);
314 tzset();
318 * Converts struct tm to Epoch time_t considering timezone.
320 * @param tz as defined for TZ environment variable.
322 * Reference: see timegm(3) - Linux man page
324 time_t
325 sipe_mktime_tz(struct tm *tm,
326 const char* tz)
328 time_t ret;
329 gchar *tz_orig;
331 tz_orig = sipe_switch_tz(tz);
332 ret = mktime(tm);
333 sipe_reset_tz(tz_orig);
335 return ret;
339 * Converts Epoch time_t to struct tm considering timezone.
341 * @param tz as defined for TZ environment variable.
343 * Reference: see timegm(3) - Linux man page
345 static struct tm *
346 sipe_localtime_tz(const time_t *time,
347 const char* tz)
349 struct tm *ret;
350 gchar *tz_orig;
352 tz_orig = sipe_switch_tz(tz);
353 ret = localtime(time);
354 sipe_reset_tz(tz_orig);
356 return ret;
359 void
360 sipe_cal_free_working_hours(struct sipe_cal_working_hours *wh)
362 if (!wh) return;
364 g_free(wh->std.time);
365 g_free(wh->std.day_of_week);
366 g_free(wh->std.year);
368 g_free(wh->dst.time);
369 g_free(wh->dst.day_of_week);
370 g_free(wh->dst.year);
372 g_free(wh->days_of_week);
373 g_free(wh->tz);
374 g_free(wh->tz_std);
375 g_free(wh->tz_dst);
376 g_free(wh);
380 * Returns time_t of daylight savings time start/end
381 * in the provided timezone or otherwise
382 * (time_t)-1 if no daylight savings time.
384 static time_t
385 sipe_cal_get_std_dst_time(time_t now,
386 int bias,
387 struct sipe_cal_std_dst* std_dst,
388 struct sipe_cal_std_dst* dst_std)
390 struct tm switch_tm;
391 time_t res = TIME_NULL;
392 struct tm *gm_now_tm;
393 gchar **time_arr;
395 if (std_dst->month == 0) return TIME_NULL;
397 gm_now_tm = gmtime(&now);
398 time_arr = g_strsplit(std_dst->time, ":", 0);
400 switch_tm.tm_sec = atoi(time_arr[2]);
401 switch_tm.tm_min = atoi(time_arr[1]);
402 switch_tm.tm_hour = atoi(time_arr[0]);
403 g_strfreev(time_arr);
404 switch_tm.tm_mday = std_dst->year ? std_dst->day_order : 1 /* to adjust later */ ;
405 switch_tm.tm_mon = std_dst->month - 1;
406 switch_tm.tm_year = std_dst->year ? atoi(std_dst->year) - 1900 : gm_now_tm->tm_year;
407 switch_tm.tm_isdst = 0;
408 /* to set tm_wday */
409 res = sipe_mktime_tz(&switch_tm, "UTC");
411 /* if not dynamic, calculate right tm_mday */
412 if (!std_dst->year) {
413 int switch_wday = sipe_cal_get_wday(std_dst->day_of_week);
414 int needed_month;
415 /* get first desired wday in the month */
416 int delta = switch_wday >= switch_tm.tm_wday ? (switch_wday - switch_tm.tm_wday) : (switch_wday + 7 - switch_tm.tm_wday);
417 switch_tm.tm_mday = 1 + delta;
418 /* try nth order */
419 switch_tm.tm_mday += (std_dst->day_order - 1) * 7;
420 needed_month = switch_tm.tm_mon;
421 /* to set settle date if ahead of allowed month dates */
422 res = sipe_mktime_tz(&switch_tm, "UTC");
423 if (needed_month != switch_tm.tm_mon) {
424 /* moving 1 week back to stay within required month */
425 switch_tm.tm_mday -= 7;
426 /* to fix date again */
427 res = sipe_mktime_tz(&switch_tm, "UTC");
430 /* note: bias is taken from "switch to" structure */
431 return res + (bias + dst_std->bias)*60;
434 static void
435 sipe_cal_parse_std_dst(const sipe_xml *xn_std_dst_time,
436 struct sipe_cal_std_dst *std_dst)
438 const sipe_xml *node;
439 gchar *tmp;
441 if (!xn_std_dst_time) return;
442 if (!std_dst) return;
444 <StandardTime>
445 <Bias>0</Bias>
446 <Time>02:00:00</Time>
447 <DayOrder>1</DayOrder>
448 <Month>11</Month>
449 <DayOfWeek>Sunday</DayOfWeek>
450 </StandardTime>
453 if ((node = sipe_xml_child(xn_std_dst_time, "Bias"))) {
454 std_dst->bias = atoi(tmp = sipe_xml_data(node));
455 g_free(tmp);
458 if ((node = sipe_xml_child(xn_std_dst_time, "Time"))) {
459 std_dst->time = sipe_xml_data(node);
462 if ((node = sipe_xml_child(xn_std_dst_time, "DayOrder"))) {
463 std_dst->day_order = atoi(tmp = sipe_xml_data(node));
464 g_free(tmp);
467 if ((node = sipe_xml_child(xn_std_dst_time, "Month"))) {
468 std_dst->month = atoi(tmp = sipe_xml_data(node));
469 g_free(tmp);
472 if ((node = sipe_xml_child(xn_std_dst_time, "DayOfWeek"))) {
473 std_dst->day_of_week = sipe_xml_data(node);
476 if ((node = sipe_xml_child(xn_std_dst_time, "Year"))) {
477 std_dst->year = sipe_xml_data(node);
481 void
482 sipe_cal_parse_working_hours(const sipe_xml *xn_working_hours,
483 struct sipe_buddy *buddy)
485 const sipe_xml *xn_bias;
486 const sipe_xml *xn_timezone;
487 const sipe_xml *xn_working_period;
488 const sipe_xml *xn_standard_time;
489 const sipe_xml *xn_daylight_time;
490 gchar *tmp;
491 time_t now = time(NULL);
492 struct sipe_cal_std_dst* std;
493 struct sipe_cal_std_dst* dst;
495 if (!xn_working_hours) return;
497 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
498 <TimeZone>
499 <Bias>480</Bias>
501 </TimeZone>
502 <WorkingPeriodArray>
503 <WorkingPeriod>
504 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
505 <StartTimeInMinutes>600</StartTimeInMinutes>
506 <EndTimeInMinutes>1140</EndTimeInMinutes>
507 </WorkingPeriod>
508 </WorkingPeriodArray>
509 </WorkingHours>
511 sipe_cal_free_working_hours(buddy->cal_working_hours);
512 buddy->cal_working_hours = g_new0(struct sipe_cal_working_hours, 1);
514 xn_timezone = sipe_xml_child(xn_working_hours, "TimeZone");
515 xn_bias = sipe_xml_child(xn_timezone, "Bias");
516 if (xn_bias) {
517 buddy->cal_working_hours->bias = atoi(tmp = sipe_xml_data(xn_bias));
518 g_free(tmp);
521 xn_standard_time = sipe_xml_child(xn_timezone, "StandardTime");
522 xn_daylight_time = sipe_xml_child(xn_timezone, "DaylightTime");
524 std = &((*buddy->cal_working_hours).std);
525 dst = &((*buddy->cal_working_hours).dst);
526 sipe_cal_parse_std_dst(xn_standard_time, std);
527 sipe_cal_parse_std_dst(xn_daylight_time, dst);
529 xn_working_period = sipe_xml_child(xn_working_hours, "WorkingPeriodArray/WorkingPeriod");
530 if (xn_working_period) {
531 /* NOTE: this can be NULL! */
532 buddy->cal_working_hours->days_of_week =
533 sipe_xml_data(sipe_xml_child(xn_working_period, "DayOfWeek"));
535 buddy->cal_working_hours->start_time =
536 atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "StartTimeInMinutes")));
537 g_free(tmp);
539 buddy->cal_working_hours->end_time =
540 atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "EndTimeInMinutes")));
541 g_free(tmp);
544 std->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, std, dst);
545 dst->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, dst, std);
547 /* TST8TDT7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
548 buddy->cal_working_hours->tz =
549 g_strdup_printf("TST%dTDT%d,M%d.%d.%d/%s,M%d.%d.%d/%s",
550 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60,
551 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60,
553 buddy->cal_working_hours->dst.month,
554 buddy->cal_working_hours->dst.day_order,
555 sipe_cal_get_wday(buddy->cal_working_hours->dst.day_of_week),
556 buddy->cal_working_hours->dst.time,
558 buddy->cal_working_hours->std.month,
559 buddy->cal_working_hours->std.day_order,
560 sipe_cal_get_wday(buddy->cal_working_hours->std.day_of_week),
561 buddy->cal_working_hours->std.time
563 /* TST8 */
564 buddy->cal_working_hours->tz_std =
565 g_strdup_printf("TST%d",
566 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60);
567 /* TDT7 */
568 buddy->cal_working_hours->tz_dst =
569 g_strdup_printf("TDT%d",
570 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60);
573 struct sipe_cal_event*
574 sipe_cal_get_event(GSList *cal_events,
575 time_t time_in_question)
577 GSList *entry = cal_events;
578 struct sipe_cal_event* cal_event;
579 struct sipe_cal_event* res = NULL;
581 if (!cal_events || !IS(time_in_question)) return NULL;
583 while (entry) {
584 cal_event = entry->data;
585 /* event is in the past or in the future */
586 if (cal_event->start_time > time_in_question ||
587 cal_event->end_time <= time_in_question)
589 entry = entry->next;
590 continue;
593 if (!res) {
594 res = cal_event;
595 } else {
596 int res_status = (res->cal_status == SIPE_CAL_NO_DATA) ? -1 : res->cal_status;
597 int cal_status = (cal_event->cal_status == SIPE_CAL_NO_DATA) ? -1 : cal_event->cal_status;
598 if (res_status < cal_status) {
599 res = cal_event;
602 entry = entry->next;
604 return res;
607 static int
608 sipe_cal_get_status0(const gchar *free_busy,
609 time_t cal_start,
610 int granularity,
611 time_t time_in_question,
612 int *index)
614 int res = SIPE_CAL_NO_DATA;
615 int shift;
616 time_t cal_end = cal_start + strlen(free_busy)*granularity*60 - 1;
618 if (!(time_in_question >= cal_start && time_in_question <= cal_end)) return res;
620 shift = (time_in_question - cal_start) / (granularity*60);
621 if (index) {
622 *index = shift;
625 res = free_busy[shift] - '0';
627 return res;
631 * Returns time when current calendar state started
633 static time_t
634 sipe_cal_get_since_time(const gchar *free_busy,
635 time_t calStart,
636 int granularity,
637 int index,
638 int current_state)
640 int i;
642 if ((index < 0) || ((size_t)(index + 1) > strlen(free_busy))) return 0;
644 for (i = index; i >= 0; i--) {
645 int temp_status = free_busy[i] - '0';
647 if (current_state != temp_status) {
648 return calStart + (i + 1)*granularity*60;
651 if (i == 0) return calStart;
654 return 0;
656 static char*
657 sipe_cal_get_free_busy(struct sipe_buddy *buddy);
660 sipe_cal_get_status(struct sipe_buddy *buddy,
661 time_t time_in_question,
662 time_t *since)
664 time_t cal_start;
665 const char* free_busy;
666 int ret = SIPE_CAL_NO_DATA;
667 time_t state_since;
668 int index;
670 if (!buddy || !buddy->cal_start_time || !buddy->cal_granularity) {
671 SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data1 for %s, exiting",
672 buddy ? (buddy->name ? buddy->name : "") : "");
673 return SIPE_CAL_NO_DATA;
676 if (!(free_busy = sipe_cal_get_free_busy(buddy))) {
677 SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data2 for %s, exiting", buddy->name);
678 return SIPE_CAL_NO_DATA;
680 SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy);
682 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
684 ret = sipe_cal_get_status0(free_busy,
685 cal_start,
686 buddy->cal_granularity,
687 time_in_question,
688 &index);
689 state_since = sipe_cal_get_since_time(free_busy,
690 cal_start,
691 buddy->cal_granularity,
692 index,
693 ret);
695 if (since) *since = state_since;
696 return ret;
699 static time_t
700 sipe_cal_get_switch_time(const gchar *free_busy,
701 time_t calStart,
702 int granularity,
703 int index,
704 int current_state,
705 int *to_state)
707 size_t i;
708 time_t ret = TIME_NULL;
710 if ((index < 0) || ((size_t) (index + 1) > strlen(free_busy))) {
711 *to_state = SIPE_CAL_NO_DATA;
712 return ret;
715 for (i = index + 1; i < strlen(free_busy); i++) {
716 int temp_status = free_busy[i] - '0';
718 if (current_state != temp_status) {
719 *to_state = temp_status;
720 return calStart + i*granularity*60;
724 return ret;
727 static const char*
728 sipe_cal_get_tz(struct sipe_cal_working_hours *wh,
729 time_t time_in_question)
731 time_t dst_switch_time = (*wh).dst.switch_time;
732 time_t std_switch_time = (*wh).std.switch_time;
733 gboolean is_dst = FALSE;
735 /* No daylight savings */
736 if (dst_switch_time == TIME_NULL) {
737 return wh->tz_std;
740 if (dst_switch_time < std_switch_time) { /* North hemosphere - Europe, US */
741 if (time_in_question >= dst_switch_time && time_in_question < std_switch_time) {
742 is_dst = TRUE;
744 } else { /* South hemisphere - Australia */
745 if (time_in_question >= dst_switch_time || time_in_question < std_switch_time) {
746 is_dst = TRUE;
750 if (is_dst) {
751 return wh->tz_dst;
752 } else {
753 return wh->tz_std;
757 static time_t
758 sipe_cal_mktime_of_day(struct tm *sample_today_tm,
759 const int shift_minutes,
760 const char *tz)
762 sample_today_tm->tm_sec = 0;
763 sample_today_tm->tm_min = shift_minutes % 60;
764 sample_today_tm->tm_hour = shift_minutes / 60;
766 return sipe_mktime_tz(sample_today_tm, tz);
770 * Returns work day start and end in Epoch time
771 * considering the initial values are provided
772 * in contact's local time zone.
774 static void
775 sipe_cal_get_today_work_hours(struct sipe_cal_working_hours *wh,
776 time_t *start,
777 time_t *end,
778 time_t *next_start)
780 time_t now = time(NULL);
781 const char *tz = sipe_cal_get_tz(wh, now);
782 struct tm *remote_now_tm = sipe_localtime_tz(&now, tz);
784 if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_now_tm->tm_wday]))) {
785 /* not a work day */
786 *start = TIME_NULL;
787 *end = TIME_NULL;
788 *next_start = TIME_NULL;
789 return;
792 *end = sipe_cal_mktime_of_day(remote_now_tm, wh->end_time, tz);
794 if (now < *end) {
795 *start = sipe_cal_mktime_of_day(remote_now_tm, wh->start_time, tz);
796 *next_start = TIME_NULL;
797 } else { /* calculate start of tomorrow's work day if any */
798 time_t tom = now + 24*60*60;
799 struct tm *remote_tom_tm = sipe_localtime_tz(&tom, sipe_cal_get_tz(wh, tom));
801 if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_tom_tm->tm_wday]))) {
802 /* not a work day */
803 *next_start = TIME_NULL;
806 *next_start = sipe_cal_mktime_of_day(remote_tom_tm, wh->start_time, sipe_cal_get_tz(wh, tom));
807 *start = TIME_NULL;
811 static int
812 sipe_cal_is_in_work_hours(const time_t time_in_question,
813 const time_t start,
814 const time_t end)
816 return !((time_in_question >= end) || (IS(start) && time_in_question < start));
820 * Returns time closest to now. Choses only from times ahead of now.
821 * Returns TIME_NULL otherwise.
823 static time_t
824 sipe_cal_get_until(const time_t now,
825 const time_t switch_time,
826 const time_t start,
827 const time_t end,
828 const time_t next_start)
830 time_t ret = TIME_NULL;
831 int min_diff = now - ret;
833 if (IS(switch_time) && switch_time > now && (switch_time - now) < min_diff) {
834 min_diff = switch_time - now;
835 ret = switch_time;
837 if (IS(start) && start > now && (start - now) < min_diff) {
838 min_diff = start - now;
839 ret = start;
841 if (IS(end) && end > now && (end - now) < min_diff) {
842 min_diff = end - now;
843 ret = end;
845 if (IS(next_start) && next_start > now && (next_start - now) < min_diff) {
846 min_diff = next_start - now;
847 ret = next_start;
849 return ret;
852 static char*
853 sipe_cal_get_free_busy(struct sipe_buddy *buddy)
855 /* do lazy decode if necessary */
856 if (!buddy->cal_free_busy && buddy->cal_free_busy_base64) {
857 gsize cal_dec64_len;
858 guchar *cal_dec64;
859 gsize i;
860 int j = 0;
862 cal_dec64 = g_base64_decode(buddy->cal_free_busy_base64, &cal_dec64_len);
864 buddy->cal_free_busy = g_malloc0(cal_dec64_len * 4 + 1);
866 http://msdn.microsoft.com/en-us/library/dd941537%28office.13%29.aspx
867 00, Free (Fr)
868 01, Tentative (Te)
869 10, Busy (Bu)
870 11, Out of facility (Oo)
872 http://msdn.microsoft.com/en-us/library/aa566048.aspx
873 0 Free
874 1 Tentative
875 2 Busy
876 3 Out of Office (OOF)
877 4 No data
879 for (i = 0; i < cal_dec64_len; i++) {
880 #define TWO_BIT_MASK 0x03
881 char tmp = cal_dec64[i];
882 buddy->cal_free_busy[j++] = (tmp & TWO_BIT_MASK) + '0';
883 buddy->cal_free_busy[j++] = ((tmp >> 2) & TWO_BIT_MASK) + '0';
884 buddy->cal_free_busy[j++] = ((tmp >> 4) & TWO_BIT_MASK) + '0';
885 buddy->cal_free_busy[j++] = ((tmp >> 6) & TWO_BIT_MASK) + '0';
887 buddy->cal_free_busy[j++] = '\0';
888 g_free(cal_dec64);
891 return buddy->cal_free_busy;
894 char *
895 sipe_cal_get_freebusy_base64(const char* freebusy_hex)
897 guint i = 0;
898 guint j = 0;
899 guint shift_factor = 0;
900 guint len, res_len;
901 guchar *res;
902 gchar *res_base64;
904 if (!freebusy_hex) return NULL;
906 len = strlen(freebusy_hex);
907 res_len = len / 4 + 1;
908 res = g_malloc0(res_len);
909 while (i < len) {
910 res[j] |= (freebusy_hex[i++] - '0') << shift_factor;
911 shift_factor += 2;
912 if (shift_factor == 8) {
913 shift_factor = 0;
914 j++;
918 res_base64 = g_base64_encode(res, shift_factor ? res_len : res_len - 1);
919 g_free(res);
920 return res_base64;
923 char *
924 sipe_cal_get_description(struct sipe_buddy *buddy)
926 time_t cal_start;
927 time_t cal_end;
928 int current_cal_state;
929 time_t now = time(NULL);
930 time_t start = TIME_NULL;
931 time_t end = TIME_NULL;
932 time_t next_start = TIME_NULL;
933 time_t switch_time;
934 int to_state = SIPE_CAL_NO_DATA;
935 time_t until = TIME_NULL;
936 int index = 0;
937 gboolean has_working_hours = (buddy->cal_working_hours != NULL);
938 const char *free_busy;
939 const char *cal_states[] = {_("Free"),
940 _("Tentative"),
941 _("Busy"),
942 _("Out of office"),
943 _("No data")};
945 if (buddy->cal_granularity != 15) {
946 SIPE_DEBUG_INFO("sipe_cal_get_description: granularity %d is unsupported, exiting.", buddy->cal_granularity);
947 return NULL;
950 /* to lazy load if needed */
951 free_busy = sipe_cal_get_free_busy(buddy);
952 SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy ? free_busy : "");
954 if (!buddy->cal_free_busy || !buddy->cal_granularity || !buddy->cal_start_time) {
955 SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: no calendar data, exiting");
956 return NULL;
959 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
960 cal_end = cal_start + 60 * (buddy->cal_granularity) * strlen(buddy->cal_free_busy);
962 current_cal_state = sipe_cal_get_status0(free_busy, cal_start, buddy->cal_granularity, time(NULL), &index);
963 if (current_cal_state == SIPE_CAL_NO_DATA) {
964 SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: calendar is undefined for present moment, exiting.");
965 return NULL;
968 switch_time = sipe_cal_get_switch_time(free_busy, cal_start, buddy->cal_granularity, index, current_cal_state, &to_state);
970 SIPE_DEBUG_INFO_NOFORMAT("\n* Calendar *");
971 if (buddy->cal_working_hours) {
972 sipe_cal_get_today_work_hours(buddy->cal_working_hours, &start, &end, &next_start);
974 SIPE_DEBUG_INFO("Remote now timezone : %s", sipe_cal_get_tz(buddy->cal_working_hours, now));
975 SIPE_DEBUG_INFO("std.switch_time(GMT): %s",
976 IS((*buddy->cal_working_hours).std.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).std.switch_time))) : "");
977 SIPE_DEBUG_INFO("dst.switch_time(GMT): %s",
978 IS((*buddy->cal_working_hours).dst.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).dst.switch_time))) : "");
979 SIPE_DEBUG_INFO("Remote now time : %s",
980 asctime(sipe_localtime_tz(&now, sipe_cal_get_tz(buddy->cal_working_hours, now))));
981 SIPE_DEBUG_INFO("Remote start time : %s",
982 IS(start) ? asctime(sipe_localtime_tz(&start, sipe_cal_get_tz(buddy->cal_working_hours, start))) : "");
983 SIPE_DEBUG_INFO("Remote end time : %s",
984 IS(end) ? asctime(sipe_localtime_tz(&end, sipe_cal_get_tz(buddy->cal_working_hours, end))) : "");
985 SIPE_DEBUG_INFO("Rem. next_start time: %s",
986 IS(next_start) ? asctime(sipe_localtime_tz(&next_start, sipe_cal_get_tz(buddy->cal_working_hours, next_start))) : "");
987 SIPE_DEBUG_INFO("Remote switch time : %s",
988 IS(switch_time) ? asctime(sipe_localtime_tz(&switch_time, sipe_cal_get_tz(buddy->cal_working_hours, switch_time))) : "");
989 } else {
990 SIPE_DEBUG_INFO("Local now time : %s",
991 asctime(localtime(&now)));
992 SIPE_DEBUG_INFO("Local switch time : %s",
993 IS(switch_time) ? asctime(localtime(&switch_time)) : "");
995 SIPE_DEBUG_INFO("Calendar End (GMT) : %s", asctime(gmtime(&cal_end)));
996 SIPE_DEBUG_INFO("current cal state : %s", cal_states[current_cal_state]);
997 SIPE_DEBUG_INFO("switch cal state : %s", cal_states[to_state] );
999 /* Calendar: string calculations */
1002 ALGORITHM (don't delete)
1003 (c)2009,2010 pier11 <pier11@operamail.com>
1005 SOD = Start of Work Day
1006 EOD = End of Work Day
1007 NSOD = Start of tomorrow's Work Day
1008 SW = Calendar status switch time
1010 if current_cal_state == Free
1011 until = min_t of SOD, EOD, NSOD, SW (min_t(x) = min(x-now) where x>now only)
1012 else
1013 until = SW
1015 if (!until && (cal_period_end > now + 8H))
1016 until = cal_period_end
1018 if (!until)
1019 return "Currently %", current_cal_state
1021 if (until - now > 8H)
1022 if (current_cal_state == Free && (work_hours && !in work_hours(now)))
1023 return "Outside of working hours for next 8 hours"
1024 else
1025 return "%s for next 8 hours", current_cal_state
1027 if (current_cal_state == Free)
1028 if (work_hours && until !in work_hours(now))
1029 "Not working"
1030 else
1031 "%s", current_cal_state
1032 " until %.2d:%.2d", until
1033 else
1034 "Currently %", current_cal_state
1035 if (work_hours && until !in work_hours(until))
1036 ". Outside of working hours at at %.2d:%.2d", until
1037 else
1038 ". %s at %.2d:%.2d", to_state, until
1041 if (current_cal_state < 1) { /* Free */
1042 until = sipe_cal_get_until(now, switch_time, start, end, next_start);
1043 } else {
1044 until = switch_time;
1047 if (!IS(until) && (cal_end - now > 8*60*60))
1048 until = cal_end;
1050 if (!IS(until)) {
1051 return g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1054 if (until - now > 8*60*60) {
1055 /* Free & outside work hours */
1056 if (current_cal_state < 1 && has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1057 return g_strdup(_("Outside of working hours for next 8 hours"));
1058 } else {
1059 return g_strdup_printf(_("%s for next 8 hours"), cal_states[current_cal_state]);
1063 if (current_cal_state < 1) { /* Free */
1064 const char *tmp;
1065 struct tm *until_tm = localtime(&until);
1067 if (has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1068 tmp = _("Not working");
1069 } else {
1070 tmp = cal_states[current_cal_state];
1072 return g_strdup_printf(_("%s until %.2d:%.2d"), tmp, until_tm->tm_hour, until_tm->tm_min);
1073 } else { /* Tentative or Busy or OOF */
1074 char *tmp;
1075 char *res;
1076 struct tm *until_tm = localtime(&until);
1078 tmp = g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1079 if (has_working_hours && !sipe_cal_is_in_work_hours(until, start, end)) {
1080 res = g_strdup_printf(_("%s. Outside of working hours at %.2d:%.2d"),
1081 tmp, until_tm->tm_hour, until_tm->tm_min);
1082 g_free(tmp);
1083 return res;
1084 } else {
1085 res = g_strdup_printf(_("%s. %s at %.2d:%.2d"), tmp, cal_states[to_state], until_tm->tm_hour, until_tm->tm_min);
1086 g_free(tmp);
1087 return res;
1090 /* End of - Calendar: string calculations */
1094 Local Variables:
1095 mode: c
1096 c-file-style: "bsd"
1097 indent-tabs-mode: t
1098 tab-width: 8
1099 End: