vtimezone.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <utility>
  19. #include "vtimezone.h"
  20. #include <mapidefs.h>
  21. #include <mapicode.h>
  22. #include <cstdlib>
  23. #include <cmath>
  24. #include <ctime>
  25. using namespace std;
  26. namespace KC {
  27. /**
  28. * Converts icaltimetype to Unix timestamp.
  29. * Here server zone refers to timezone with which the server started,
  30. * not the config file option in ical.cfg
  31. *
  32. * @param[in] tt icaltimetype
  33. * @return Unix timestamp
  34. */
  35. time_t icaltime_as_timet_with_server_zone(const struct icaltimetype tt)
  36. {
  37. struct tm stm;
  38. time_t t;
  39. /* If the time is the special null time, return 0. */
  40. if (icaltime_is_null_time(tt)) {
  41. return 0;
  42. }
  43. /* Copy the icaltimetype to a struct tm. */
  44. memset (&stm, 0, sizeof (struct tm));
  45. if (icaltime_is_date(tt)) {
  46. stm.tm_sec = stm.tm_min = stm.tm_hour = 0;
  47. } else {
  48. stm.tm_sec = tt.second;
  49. stm.tm_min = tt.minute;
  50. stm.tm_hour = tt.hour;
  51. }
  52. stm.tm_mday = tt.day;
  53. stm.tm_mon = tt.month-1;
  54. stm.tm_year = tt.year-1900;
  55. stm.tm_isdst = -1;
  56. t = mktime(&stm);
  57. return t;
  58. }
  59. // time only, not date!
  60. static time_t SystemTimeToUnixTime(const SYSTEMTIME &stime)
  61. {
  62. return stime.wSecond + (stime.wMinute*60) + ((stime.wHour)*60*60);
  63. }
  64. static SYSTEMTIME TMToSystemTime(const struct tm &t)
  65. {
  66. SYSTEMTIME stime = {0};
  67. stime.wYear = t.tm_year;
  68. stime.wMonth = t.tm_mon;
  69. stime.wDayOfWeek = t.tm_wday;
  70. stime.wDay = t.tm_mday;
  71. stime.wHour = t.tm_hour;
  72. stime.wMinute = t.tm_min;
  73. stime.wSecond = t.tm_sec;
  74. stime.wMilliseconds = 0;
  75. return stime;
  76. }
  77. /**
  78. * Converts icaltimetype to UTC Unix timestamp
  79. *
  80. * @param[in] lpicRoot root icalcomponent to get timezone
  81. * @param[in] lpicProp icalproperty containing time
  82. * @return UTC Unix timestamp
  83. */
  84. time_t ICalTimeTypeToUTC(icalcomponent *lpicRoot, icalproperty *lpicProp)
  85. {
  86. time_t tRet = 0;
  87. icalparameter *lpicTZParam = NULL;
  88. const char *lpszTZID = NULL;
  89. icaltimezone *lpicTimeZone = NULL;
  90. lpicTZParam = icalproperty_get_first_parameter(lpicProp, ICAL_TZID_PARAMETER);
  91. if (lpicTZParam) {
  92. lpszTZID = icalparameter_get_tzid(lpicTZParam);
  93. lpicTimeZone = icalcomponent_get_timezone(lpicRoot, lpszTZID);
  94. }
  95. tRet = icaltime_as_timet_with_zone(icalvalue_get_datetime(icalproperty_get_value(lpicProp)), lpicTimeZone);
  96. return tRet;
  97. }
  98. /**
  99. * Converts icaltimetype to local Unix timestamp.
  100. * Here local refers to timezone with which the server started,
  101. * not the config file option in ical.cfg
  102. *
  103. * @param[in] lpicProp icalproperty containing time
  104. * @return local Unix timestamp
  105. */
  106. time_t ICalTimeTypeToLocal(icalproperty *lpicProp)
  107. {
  108. return icaltime_as_timet_with_server_zone(icalvalue_get_datetime(icalproperty_get_value(lpicProp)));
  109. }
  110. /**
  111. * Converts icaltimetype to tm structure
  112. *
  113. * @param[in] tt icaltimetype time
  114. * @return tm structure
  115. */
  116. static struct tm UTC_ICalTime2UnixTime(icaltimetype tt)
  117. {
  118. struct tm stm = {0};
  119. memset(&stm, 0, sizeof(struct tm));
  120. if (icaltime_is_null_time(tt))
  121. return stm;
  122. stm.tm_sec = tt.second;
  123. stm.tm_min = tt.minute;
  124. stm.tm_hour = tt.hour;
  125. stm.tm_mday = tt.day;
  126. stm.tm_mon = tt.month-1;
  127. stm.tm_year = tt.year-1900;
  128. stm.tm_isdst = -1;
  129. return stm;
  130. }
  131. /**
  132. * Converts icaltimetype to TIMEZONE_STRUCT structure
  133. *
  134. * @param[in] kind icalcomponent kind, either STD or DST time component (ICAL_XSTANDARD_COMPONENT, ICAL_XDAYLIGHT_COMPONENT)
  135. * @param[in] lpVTZ vtimezone icalcomponent
  136. * @param[out] lpsTimeZone returned TIMEZONE_STRUCT structure
  137. * @return MAPI error code
  138. * @retval MAPI_E_NOT_FOUND icalcomponent kind not found in vtimezone component, or some part of timezone not found
  139. */
  140. static HRESULT HrZoneToStruct(icalcomponent_kind kind, icalcomponent *lpVTZ,
  141. TIMEZONE_STRUCT *lpsTimeZone)
  142. {
  143. // HRESULT hr = hrSuccess;
  144. icalcomponent *icComp = NULL;
  145. icalcomponent *iterComp = NULL;
  146. icalproperty *tzFrom, *tzTo, *rRule, *dtStart;
  147. icaltimetype icTime;
  148. SYSTEMTIME *lpSysTime = NULL;
  149. SYSTEMTIME stRecurTime;
  150. icalrecurrencetype recur;
  151. /* Assumes that definitions are sorted on dtstart, in ascending order. */
  152. iterComp = icalcomponent_get_first_component(lpVTZ, kind);
  153. while (iterComp != NULL) {
  154. icTime = icalcomponent_get_dtstart(iterComp);
  155. icTime.is_utc = 1;
  156. struct tm start = UTC_ICalTime2UnixTime(icTime);
  157. if (time(NULL) < mktime(&start) && icComp != nullptr)
  158. break;
  159. icComp = iterComp;
  160. iterComp = icalcomponent_get_next_component(lpVTZ, kind);
  161. }
  162. if (icComp == NULL)
  163. return MAPI_E_NOT_FOUND;
  164. dtStart = icalcomponent_get_first_property(icComp, ICAL_DTSTART_PROPERTY);
  165. tzFrom = icalcomponent_get_first_property(icComp, ICAL_TZOFFSETFROM_PROPERTY);
  166. tzTo = icalcomponent_get_first_property(icComp, ICAL_TZOFFSETTO_PROPERTY);
  167. rRule = icalcomponent_get_first_property(icComp, ICAL_RRULE_PROPERTY);
  168. //rDate = icalcomponent_get_first_property(icComp, ICAL_RDATE_PROPERTY);
  169. if (tzFrom == NULL || tzTo == NULL || dtStart == NULL)
  170. return MAPI_E_NOT_FOUND;
  171. icTime = icalcomponent_get_dtstart(icComp);
  172. icTime.is_utc = 1;
  173. if (kind == ICAL_XSTANDARD_COMPONENT) {
  174. // this is set when we request the STD timezone part.
  175. lpsTimeZone->lBias = -(icalproperty_get_tzoffsetto(tzTo) / 60); // STD time is set as bias for timezone
  176. lpsTimeZone->lStdBias = 0;
  177. lpsTimeZone->lDstBias = (icalproperty_get_tzoffsetto(tzTo) - icalproperty_get_tzoffsetfrom(tzFrom)) / 60; // DST bias == standard from
  178. lpsTimeZone->wStdYear = 0;
  179. lpSysTime = &lpsTimeZone->stStdDate;
  180. } else {
  181. lpsTimeZone->wDstYear = 0;
  182. lpSysTime = &lpsTimeZone->stDstDate;
  183. }
  184. memset(lpSysTime, 0, sizeof(SYSTEMTIME));
  185. // eg. japan doesn't have daylight saving switches.
  186. if (!rRule) {
  187. stRecurTime = TMToSystemTime(UTC_ICalTime2UnixTime(icTime));
  188. lpSysTime->wMonth = stRecurTime.wMonth + 1; // fix for -1 in UTC_ICalTime2UnixTime, since TMToSystemTime doesn't do +1
  189. lpSysTime->wDayOfWeek = stRecurTime.wDayOfWeek;
  190. lpSysTime->wDay = int(stRecurTime.wDay / 7.0) + 1;
  191. lpSysTime->wHour = stRecurTime.wHour;
  192. lpSysTime->wMinute = stRecurTime.wMinute;
  193. return hrSuccess;
  194. }
  195. recur = icalproperty_get_rrule(rRule);
  196. // can daylight saving really be !yearly ??
  197. if (recur.freq != ICAL_YEARLY_RECURRENCE ||
  198. recur.by_month[0] == ICAL_RECURRENCE_ARRAY_MAX ||
  199. recur.by_month[1] != ICAL_RECURRENCE_ARRAY_MAX)
  200. return hrSuccess;
  201. stRecurTime = TMToSystemTime(UTC_ICalTime2UnixTime(icTime));
  202. lpSysTime->wHour = stRecurTime.wHour;
  203. lpSysTime->wMinute = stRecurTime.wMinute;
  204. lpSysTime->wMonth = recur.by_month[0];
  205. if (icalrecurrencetype_day_position(recur.by_day[0]) == -1)
  206. lpSysTime->wDay = 5; // last day of month
  207. else
  208. lpSysTime->wDay = icalrecurrencetype_day_position(recur.by_day[0]); // 1..4
  209. lpSysTime->wDayOfWeek = icalrecurrencetype_day_day_of_week(recur.by_day[0]) - 1;
  210. return hrSuccess;
  211. }
  212. /**
  213. * Converts VTIMEZONE block in to TIMEZONE_STRUCT structure
  214. *
  215. * @param[in] lpVTZ VTIMEZONE icalcomponent
  216. * @param[out] lpstrTZID timezone string
  217. * @param[out] lpTimeZone returned TIMEZONE_STRUCT structure
  218. * @return MAPI error code
  219. * @retval MAPI_E_NOT_FOUND standard component not found
  220. * @retval MAPI_E_CALL_FAILED TZID property not found
  221. */
  222. HRESULT HrParseVTimeZone(icalcomponent* lpVTZ, std::string* lpstrTZID, TIMEZONE_STRUCT* lpTimeZone)
  223. {
  224. HRESULT hr = hrSuccess;
  225. std::string strTZID;
  226. TIMEZONE_STRUCT tzRet;
  227. icalproperty *icProp = NULL;
  228. memset(&tzRet, 0, sizeof(TIMEZONE_STRUCT));
  229. icProp = icalcomponent_get_first_property(lpVTZ, ICAL_TZID_PROPERTY);
  230. if (icProp == NULL)
  231. return MAPI_E_CALL_FAILED;
  232. strTZID = icalproperty_get_tzid(icProp);
  233. if (strTZID.at(0) == '\"') {
  234. // strip "" around timezone name
  235. strTZID.erase(0, 1);
  236. strTZID.erase(strTZID.size()-1);
  237. }
  238. hr = HrZoneToStruct(ICAL_XSTANDARD_COMPONENT, lpVTZ, &tzRet);
  239. if (hr != hrSuccess)
  240. return hr;
  241. // if the timezone does no switching, daylight is not given, so we ignore the error (which is only MAPI_E_NOT_FOUND)
  242. HrZoneToStruct(ICAL_XDAYLIGHT_COMPONENT, lpVTZ, &tzRet);
  243. // unsupported case: only exceptions in the timezone switches, and no base rule (eg. very old Asia/Kolkata timezone)
  244. {
  245. icalcomponent *icComp = NULL;
  246. icalproperty *tzSTDRule = NULL, *tzDSTRule = NULL;
  247. icalproperty *tzSTDDate = NULL, *tzDSTDate = NULL;
  248. icComp = icalcomponent_get_first_component(lpVTZ, ICAL_XSTANDARD_COMPONENT);
  249. if (icComp) {
  250. tzSTDRule = icalcomponent_get_first_property(icComp, ICAL_RRULE_PROPERTY);
  251. tzSTDDate = icalcomponent_get_first_property(icComp, ICAL_RDATE_PROPERTY);
  252. }
  253. icComp = icalcomponent_get_first_component(lpVTZ, ICAL_XDAYLIGHT_COMPONENT);
  254. if (icComp) {
  255. tzDSTRule = icalcomponent_get_first_property(icComp, ICAL_RRULE_PROPERTY);
  256. tzDSTDate = icalcomponent_get_first_property(icComp, ICAL_RDATE_PROPERTY);
  257. }
  258. if (tzSTDRule == NULL && tzDSTRule == NULL && tzSTDDate != NULL && tzDSTDate != NULL) {
  259. // clear rule data
  260. memset(&tzRet.stStdDate, 0, sizeof(SYSTEMTIME));
  261. memset(&tzRet.stDstDate, 0, sizeof(SYSTEMTIME));
  262. }
  263. }
  264. if (lpstrTZID)
  265. *lpstrTZID = std::move(strTZID);
  266. if (lpTimeZone)
  267. *lpTimeZone = tzRet;
  268. return hrSuccess;
  269. }
  270. /**
  271. * Converts TIMEZONE_STRUCT structure to VTIMEZONE component
  272. *
  273. * @param[in] strTZID timezone string
  274. * @param[in] tsTimeZone TIMEZONE_STRUCT to be converted
  275. * @param[out] lppVTZComp returned VTIMEZONE component
  276. * @return MAPI error code
  277. * @retval MAPI_E_INVALID_PARAMETER timezone contains invalid data for a yearly daylightsaving
  278. */
  279. HRESULT HrCreateVTimeZone(const std::string &strTZID, TIMEZONE_STRUCT &tsTimeZone, icalcomponent** lppVTZComp)
  280. {
  281. icalcomponent *icTZComp = NULL;
  282. icalcomponent *icComp = NULL;
  283. icaltimetype icTime;
  284. icalrecurrencetype icRec;
  285. // wDay in a timezone context means "week in month", 5 for last week in month
  286. if (tsTimeZone.stStdDate.wYear > 0 || tsTimeZone.stStdDate.wDay > 5 ||
  287. tsTimeZone.stStdDate.wDayOfWeek > 7 ||
  288. tsTimeZone.stDstDate.wYear > 0 || tsTimeZone.stDstDate.wDay > 5 ||
  289. tsTimeZone.stDstDate.wDayOfWeek > 7)
  290. return MAPI_E_INVALID_PARAMETER;
  291. // make a new timezone
  292. icTZComp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
  293. icalcomponent_add_property(icTZComp, icalproperty_new_tzid(strTZID.c_str()));
  294. // STD
  295. icComp = icalcomponent_new_xstandard();
  296. icTime = icaltime_from_timet_with_zone(SystemTimeToUnixTime(tsTimeZone.stStdDate), 0, nullptr);
  297. icalcomponent_add_property(icComp, icalproperty_new_dtstart(icTime));
  298. if (tsTimeZone.lStdBias == tsTimeZone.lDstBias || tsTimeZone.stStdDate.wMonth == 0 || tsTimeZone.stDstDate.wMonth == 0) {
  299. // std == dst
  300. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetfrom(-tsTimeZone.lBias *60));
  301. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetto(-tsTimeZone.lBias *60));
  302. } else {
  303. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetfrom( ((-tsTimeZone.lBias) + (-tsTimeZone.lDstBias)) *60) );
  304. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetto(-tsTimeZone.lBias *60));
  305. // create rrule for STD zone
  306. icalrecurrencetype_clear(&icRec);
  307. icRec.freq = ICAL_YEARLY_RECURRENCE;
  308. icRec.interval = 1;
  309. icRec.by_month[0] = tsTimeZone.stStdDate.wMonth;
  310. icRec.by_month[1] = ICAL_RECURRENCE_ARRAY_MAX;
  311. icRec.week_start = ICAL_SUNDAY_WEEKDAY;
  312. // by_day[0] % 8 = weekday, by_day[0]/8 = Nth week, 0 is 'any', and -1 = last
  313. icRec.by_day[0] = tsTimeZone.stStdDate.wDay == 5 ? -1*(8+tsTimeZone.stStdDate.wDayOfWeek+1) : (tsTimeZone.stStdDate.wDay)*8+tsTimeZone.stStdDate.wDayOfWeek+1;
  314. icRec.by_day[1] = ICAL_RECURRENCE_ARRAY_MAX;
  315. icalcomponent_add_property(icComp, icalproperty_new_rrule(icRec));
  316. }
  317. icalcomponent_add_component(icTZComp, icComp);
  318. // DST, optional
  319. if (tsTimeZone.lStdBias != tsTimeZone.lDstBias && tsTimeZone.stStdDate.wMonth != 0 && tsTimeZone.stDstDate.wMonth != 0) {
  320. icComp = icalcomponent_new_xdaylight();
  321. icTime = icaltime_from_timet_with_zone(SystemTimeToUnixTime(tsTimeZone.stDstDate), 0, nullptr);
  322. icalcomponent_add_property(icComp, icalproperty_new_dtstart(icTime));
  323. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetfrom(-tsTimeZone.lBias *60));
  324. icalcomponent_add_property(icComp, icalproperty_new_tzoffsetto( ((-tsTimeZone.lBias) + (-tsTimeZone.lDstBias)) *60) );
  325. // create rrule for DST zone
  326. icalrecurrencetype_clear(&icRec);
  327. icRec.freq = ICAL_YEARLY_RECURRENCE;
  328. icRec.interval = 1;
  329. icRec.by_month[0] = tsTimeZone.stDstDate.wMonth;
  330. icRec.by_month[1] = ICAL_RECURRENCE_ARRAY_MAX;
  331. icRec.week_start = ICAL_SUNDAY_WEEKDAY;
  332. icRec.by_day[0] = tsTimeZone.stDstDate.wDay == 5 ? -1*(8+tsTimeZone.stDstDate.wDayOfWeek+1) : (tsTimeZone.stDstDate.wDay)*8+tsTimeZone.stDstDate.wDayOfWeek+1;
  333. icRec.by_day[1] = ICAL_RECURRENCE_ARRAY_MAX;
  334. icalcomponent_add_property(icComp, icalproperty_new_rrule(icRec));
  335. icalcomponent_add_component(icTZComp, icComp);
  336. }
  337. *lppVTZComp = icTZComp;
  338. return hrSuccess;
  339. }
  340. /**
  341. * Returns TIMEZONE_STRUCT structure from the string Olson city name.
  342. * Function searches zoneinfo data in linux
  343. *
  344. * @param[in] strTimezone timezone string
  345. * @param[out] ttTimeZone TIMEZONE_STRUCT of the cityname
  346. * @return MAPI error code
  347. * @retval MAPI_E_INVALID_PARAMETER strTimezone is empty
  348. * @retval MAPI_E_NOT_FOUND cannot find the timezone string in zoneinfo
  349. */
  350. HRESULT HrGetTzStruct(const std::string &strTimezone, TIMEZONE_STRUCT *ttTimeZone)
  351. {
  352. icaltimezone *lpicTimeZone = NULL;
  353. icalcomponent *lpicComponent = NULL;
  354. if (strTimezone.empty())
  355. return MAPI_E_INVALID_PARAMETER;
  356. lpicTimeZone = icaltimezone_get_builtin_timezone(strTimezone.c_str());
  357. if (lpicTimeZone == NULL)
  358. return MAPI_E_NOT_FOUND;
  359. lpicComponent = icaltimezone_get_component(lpicTimeZone);
  360. if (lpicComponent == NULL)
  361. return MAPI_E_NOT_FOUND;
  362. return HrParseVTimeZone(lpicComponent, NULL, ttTimeZone);
  363. }
  364. } /* namespace */