1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "calDateTime.h"
6 #include "calBaseCID.h"
7 
8 #include "nsServiceManagerUtils.h"
9 #include "nsIClassInfoImpl.h"
10 
11 #include "calIErrors.h"
12 #include "calDuration.h"
13 
14 #include "jsapi.h"
15 #include "jsfriendapi.h"
16 #include "js/Wrapper.h"
17 #include "prprf.h"
18 
19 extern "C" {
20 #include "ical.h"
21 }
22 
23 #define CAL_ATTR_SET_PRE \
24   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE)
25 #define CAL_ATTR_SET_POST Normalize()
26 #include "calAttributeHelpers.h"
27 
28 NS_IMPL_CLASSINFO(calDateTime, nullptr, 0, CAL_DATETIME_CID)
NS_IMPL_ISUPPORTS_CI(calDateTime,calIDateTime,calIDateTimeLibical)29 NS_IMPL_ISUPPORTS_CI(calDateTime, calIDateTime, calIDateTimeLibical)
30 
31 calDateTime::calDateTime() : mImmutable(false) { Reset(); }
32 
calDateTime(icaltimetype const * atimeptr,calITimezone * tz)33 calDateTime::calDateTime(icaltimetype const* atimeptr, calITimezone* tz)
34     : mImmutable(false) {
35   FromIcalTime(atimeptr, tz);
36 }
37 
38 NS_IMETHODIMP
GetIsMutable(bool * aResult)39 calDateTime::GetIsMutable(bool* aResult) {
40   NS_ENSURE_ARG_POINTER(aResult);
41   *aResult = !mImmutable;
42   return NS_OK;
43 }
44 
45 NS_IMETHODIMP
MakeImmutable()46 calDateTime::MakeImmutable() {
47   mImmutable = true;
48   return NS_OK;
49 }
50 
51 NS_IMETHODIMP
Clone(calIDateTime ** aResult)52 calDateTime::Clone(calIDateTime** aResult) {
53   NS_ENSURE_ARG_POINTER(aResult);
54   icaltimetype itt;
55   ToIcalTime(&itt);
56   calDateTime* const cdt = new calDateTime(&itt, mTimezone);
57   CAL_ENSURE_MEMORY(cdt);
58   NS_ADDREF(*aResult = cdt);
59   return NS_OK;
60 }
61 
62 NS_IMETHODIMP
ResetTo(int16_t year,int16_t month,int16_t day,int16_t hour,int16_t minute,int16_t second,calITimezone * tz)63 calDateTime::ResetTo(int16_t year, int16_t month, int16_t day, int16_t hour,
64                      int16_t minute, int16_t second, calITimezone* tz) {
65   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
66   NS_ENSURE_ARG_POINTER(tz);
67   mYear = year;
68   mMonth = month;
69   mDay = day;
70   mHour = hour;
71   mMinute = minute;
72   mSecond = second;
73   mIsDate = false;
74   mTimezone = tz;
75   Normalize();
76   return NS_OK;
77 }
78 
79 NS_IMETHODIMP
Reset()80 calDateTime::Reset() {
81   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
82   mYear = 1970;
83   mMonth = 0;
84   mDay = 1;
85   mHour = 0;
86   mMinute = 0;
87   mSecond = 0;
88   mWeekday = 4;
89   mYearday = 1;
90   mIsDate = false;
91   mTimezone = nullptr;
92   mNativeTime = 0;
93   mIsValid = true;
94   return NS_OK;
95 }
96 
CAL_VALUETYPE_ATTR(calDateTime,int16_t,Year)97 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Year)
98 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Month)
99 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Day)
100 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Hour)
101 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Minute)
102 CAL_VALUETYPE_ATTR(calDateTime, int16_t, Second)
103 CAL_VALUETYPE_ATTR(calDateTime, bool, IsDate)
104 CAL_VALUETYPE_ATTR_GETTER(calDateTime, bool, IsValid)
105 CAL_VALUETYPE_ATTR_GETTER(calDateTime, PRTime, NativeTime)
106 CAL_VALUETYPE_ATTR_GETTER(calDateTime, int16_t, Weekday)
107 CAL_VALUETYPE_ATTR_GETTER(calDateTime, int16_t, Yearday)
108 
109 NS_IMETHODIMP
110 calDateTime::GetTimezone(calITimezone** aResult) {
111   NS_ENSURE_ARG_POINTER(aResult);
112   ensureTimezone();
113 
114   NS_IF_ADDREF(*aResult = mTimezone);
115   return NS_OK;
116 }
117 
118 NS_IMETHODIMP
SetTimezone(calITimezone * aValue)119 calDateTime::SetTimezone(calITimezone* aValue) {
120   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
121   NS_ENSURE_ARG_POINTER(aValue);
122   mTimezone = aValue;
123   CAL_ATTR_SET_POST;
124   return NS_OK;
125 }
126 
127 NS_IMETHODIMP
GetTimezoneOffset(int32_t * aResult)128 calDateTime::GetTimezoneOffset(int32_t* aResult) {
129   NS_ENSURE_ARG_POINTER(aResult);
130   icaltimetype icalt;
131   ToIcalTime(&icalt);
132   int dst;
133   *aResult = icaltimezone_get_utc_offset(const_cast<icaltimezone*>(icalt.zone),
134                                          &icalt, &dst);
135   return NS_OK;
136 }
137 
138 NS_IMETHODIMP
SetNativeTime(PRTime aNativeTime)139 calDateTime::SetNativeTime(PRTime aNativeTime) {
140   icaltimetype icalt;
141   PRTimeToIcaltime(aNativeTime, false, icaltimezone_get_utc_timezone(), &icalt);
142   nsCOMPtr<calITimezone> ctz = cal::UTC();
143   FromIcalTime(&icalt, ctz);
144   return NS_OK;
145 }
146 
147 NS_IMETHODIMP
AddDuration(calIDuration * aDuration)148 calDateTime::AddDuration(calIDuration* aDuration) {
149   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
150   NS_ENSURE_ARG_POINTER(aDuration);
151   ensureTimezone();
152 
153   nsresult rv;
154   nsCOMPtr<calIDurationLibical> icaldur = do_QueryInterface(aDuration, &rv);
155   NS_ENSURE_SUCCESS(rv, rv);
156 
157   icaldurationtype idt;
158   icaldur->ToIcalDuration(&idt);
159 
160   icaltimetype itt;
161   ToIcalTime(&itt);
162 
163   icaltimetype const newitt = icaltime_add(itt, idt);
164   FromIcalTime(&newitt, mTimezone);
165 
166   return NS_OK;
167 }
168 
169 NS_IMETHODIMP
SubtractDate(calIDateTime * aDate,calIDuration ** aDuration)170 calDateTime::SubtractDate(calIDateTime* aDate, calIDuration** aDuration) {
171   NS_ENSURE_ARG_POINTER(aDate);
172   NS_ENSURE_ARG_POINTER(aDuration);
173 
174   // same as icaltime_subtract(), but minding timezones:
175   PRTime t2t;
176   aDate->GetNativeTime(&t2t);
177   // for a duration, need to convert the difference in microseconds (prtime)
178   // to seconds (libical), so divide by one million.
179   icaldurationtype const idt = icaldurationtype_from_int(
180       static_cast<int>((mNativeTime - t2t) / int64_t(PR_USEC_PER_SEC)));
181 
182   calDuration* const dur = new calDuration(&idt);
183   CAL_ENSURE_MEMORY(dur);
184   NS_ADDREF(*aDuration = dur);
185   return NS_OK;
186 }
187 
188 NS_IMETHODIMP
ToString(nsACString & aResult)189 calDateTime::ToString(nsACString& aResult) {
190   nsAutoCString tzid;
191   char buffer[256];
192 
193   ensureTimezone();
194   mTimezone->GetTzid(tzid);
195 
196   uint32_t const length = PR_snprintf(
197       buffer, sizeof(buffer),
198       "%04hd/%02hd/%02hd %02hd:%02hd:%02hd %s isDate=%01hd nativeTime=%lld",
199       mYear, mMonth + 1, mDay, mHour, mMinute, mSecond, tzid.get(),
200       static_cast<int16_t>(mIsDate), mNativeTime);
201   if (length != static_cast<uint32_t>(-1)) aResult.Assign(buffer, length);
202   return NS_OK;
203 }
204 
205 NS_IMETHODIMP
GetInTimezone(calITimezone * aTimezone,calIDateTime ** aResult)206 calDateTime::GetInTimezone(calITimezone* aTimezone, calIDateTime** aResult) {
207   NS_ENSURE_ARG_POINTER(aTimezone);
208   NS_ENSURE_ARG_POINTER(aResult);
209 
210   if (mIsDate) {
211     // if it's a date, we really just want to make a copy of this
212     // and set the timezone.
213     nsresult rv = Clone(aResult);
214     if (NS_SUCCEEDED(rv)) {
215       rv = (*aResult)->SetTimezone(aTimezone);
216     }
217     return rv;
218   } else {
219     icaltimetype icalt;
220     ToIcalTime(&icalt);
221 
222     icaltimezone* tz = cal::getIcalTimezone(aTimezone);
223     if (icalt.zone == tz) {
224       return Clone(aResult);
225     }
226 
227     /* If there's a zone, we need to convert; otherwise, we just
228      * assign, since this item is floating */
229     if (icalt.zone && tz) {
230       icaltimezone_convert_time(&icalt, const_cast<icaltimezone*>(icalt.zone),
231                                 tz);
232     }
233     icalt.zone = tz;
234     icalt.is_utc = (tz && tz == icaltimezone_get_utc_timezone());
235 
236     calDateTime* cdt = new calDateTime(&icalt, aTimezone);
237     CAL_ENSURE_MEMORY(cdt);
238     NS_ADDREF(*aResult = cdt);
239     return NS_OK;
240   }
241 }
242 
243 NS_IMETHODIMP
GetStartOfWeek(calIDateTime ** aResult)244 calDateTime::GetStartOfWeek(calIDateTime** aResult) {
245   NS_ENSURE_ARG_POINTER(aResult);
246   ensureTimezone();
247 
248   icaltimetype icalt;
249   ToIcalTime(&icalt);
250   int day_of_week = icaltime_day_of_week(icalt);
251   if (day_of_week > 1) icaltime_adjust(&icalt, -(day_of_week - 1), 0, 0, 0);
252   icalt.is_date = 1;
253 
254   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
255   CAL_ENSURE_MEMORY(cdt);
256   NS_ADDREF(*aResult = cdt);
257   return NS_OK;
258 }
259 
260 NS_IMETHODIMP
GetEndOfWeek(calIDateTime ** aResult)261 calDateTime::GetEndOfWeek(calIDateTime** aResult) {
262   NS_ENSURE_ARG_POINTER(aResult);
263   ensureTimezone();
264 
265   icaltimetype icalt;
266   ToIcalTime(&icalt);
267   int day_of_week = icaltime_day_of_week(icalt);
268   if (day_of_week < 7) icaltime_adjust(&icalt, 7 - day_of_week, 0, 0, 0);
269   icalt.is_date = 1;
270 
271   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
272   CAL_ENSURE_MEMORY(cdt);
273   NS_ADDREF(*aResult = cdt);
274   return NS_OK;
275 }
276 
277 NS_IMETHODIMP
GetStartOfMonth(calIDateTime ** aResult)278 calDateTime::GetStartOfMonth(calIDateTime** aResult) {
279   NS_ENSURE_ARG_POINTER(aResult);
280   ensureTimezone();
281 
282   icaltimetype icalt;
283   ToIcalTime(&icalt);
284   icalt.day = 1;
285   icalt.is_date = 1;
286 
287   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
288   CAL_ENSURE_MEMORY(cdt);
289   NS_ADDREF(*aResult = cdt);
290   return NS_OK;
291 }
292 
293 NS_IMETHODIMP
GetEndOfMonth(calIDateTime ** aResult)294 calDateTime::GetEndOfMonth(calIDateTime** aResult) {
295   NS_ENSURE_ARG_POINTER(aResult);
296   ensureTimezone();
297 
298   icaltimetype icalt;
299   ToIcalTime(&icalt);
300   icalt.day = icaltime_days_in_month(icalt.month, icalt.year);
301   icalt.is_date = 1;
302 
303   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
304   CAL_ENSURE_MEMORY(cdt);
305   NS_ADDREF(*aResult = cdt);
306   return NS_OK;
307 }
308 
309 NS_IMETHODIMP
GetStartOfYear(calIDateTime ** aResult)310 calDateTime::GetStartOfYear(calIDateTime** aResult) {
311   NS_ENSURE_ARG_POINTER(aResult);
312   ensureTimezone();
313 
314   icaltimetype icalt;
315   ToIcalTime(&icalt);
316   icalt.month = 1;
317   icalt.day = 1;
318   icalt.is_date = 1;
319 
320   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
321   CAL_ENSURE_MEMORY(cdt);
322   NS_ADDREF(*aResult = cdt);
323   return NS_OK;
324 }
325 
326 NS_IMETHODIMP
GetEndOfYear(calIDateTime ** aResult)327 calDateTime::GetEndOfYear(calIDateTime** aResult) {
328   NS_ENSURE_ARG_POINTER(aResult);
329   ensureTimezone();
330 
331   icaltimetype icalt;
332   ToIcalTime(&icalt);
333   icalt.month = 12;
334   icalt.day = 31;
335   icalt.is_date = 1;
336 
337   calDateTime* const cdt = new calDateTime(&icalt, mTimezone);
338   CAL_ENSURE_MEMORY(cdt);
339   NS_ADDREF(*aResult = cdt);
340   return NS_OK;
341 }
342 
343 NS_IMETHODIMP
GetIcalString(nsACString & aResult)344 calDateTime::GetIcalString(nsACString& aResult) {
345   icaltimetype t;
346   ToIcalTime(&t);
347 
348   // note that ics is owned by libical, so we don't need to free
349   char const* const ics = icaltime_as_ical_string(t);
350   CAL_ENSURE_MEMORY(ics);
351   aResult.Assign(ics);
352   return NS_OK;
353 }
354 
355 NS_IMETHODIMP
SetIcalString(nsACString const & aIcalString)356 calDateTime::SetIcalString(nsACString const& aIcalString) {
357   NS_ENSURE_FALSE(mImmutable, NS_ERROR_OBJECT_IS_IMMUTABLE);
358   icaltimetype icalt;
359   icalt = icaltime_from_string(PromiseFlatCString(aIcalString).get());
360   if (icaltime_is_null_time(icalt)) {
361     return static_cast<nsresult>(calIErrors::ICS_ERROR_BASE + icalerrno);
362   }
363   FromIcalTime(&icalt, nullptr);
364   return NS_OK;
365 }
366 
367 /**
368  ** utility/protected methods
369  **/
370 
371 // internal Normalize():
Normalize()372 void calDateTime::Normalize() {
373   icaltimetype icalt;
374 
375   ensureTimezone();
376   ToIcalTime(&icalt);
377   FromIcalTime(&icalt, mTimezone);
378 }
379 
ensureTimezone()380 void calDateTime::ensureTimezone() {
381   if (mTimezone == nullptr) {
382     mTimezone = cal::UTC();
383   }
384 }
385 
NS_IMETHODIMP_(void)386 NS_IMETHODIMP_(void)
387 calDateTime::ToIcalTime(struct icaltimetype* icalt) {
388   ensureTimezone();
389 
390   icalt->year = mYear;
391   icalt->month = mMonth + 1;
392   icalt->day = mDay;
393   icalt->hour = mHour;
394   icalt->minute = mMinute;
395   icalt->second = mSecond;
396 
397   icalt->is_date = mIsDate ? 1 : 0;
398   icalt->is_daylight = 0;
399 
400   icaltimezone* tz = cal::getIcalTimezone(mTimezone);
401   icalt->zone = tz;
402   icalt->is_utc = (tz && tz == icaltimezone_get_utc_timezone());
403   icalt->is_daylight = 0;
404   // xxx todo: discuss/investigate is_daylight
405   //     if (tz) {
406   //         icaltimezone_get_utc_offset(tz, icalt, &icalt->is_daylight);
407   //     }
408 }
409 
FromIcalTime(icaltimetype const * icalt,calITimezone * tz)410 void calDateTime::FromIcalTime(icaltimetype const* icalt, calITimezone* tz) {
411   icaltimetype t = *icalt;
412   mIsValid =
413       (icaltime_is_null_time(t) || icaltime_is_valid_time(t) ? true : false);
414 
415   mIsDate = t.is_date ? true : false;
416   if (mIsDate) {
417     t.hour = 0;
418     t.minute = 0;
419     t.second = 0;
420   }
421 
422   if (mIsValid) {
423     t = icaltime_normalize(t);
424   }
425 
426   mYear = static_cast<int16_t>(t.year);
427   mMonth = static_cast<int16_t>(t.month - 1);
428   mDay = static_cast<int16_t>(t.day);
429   mHour = static_cast<int16_t>(t.hour);
430   mMinute = static_cast<int16_t>(t.minute);
431   mSecond = static_cast<int16_t>(t.second);
432 
433   if (tz) {
434     mTimezone = tz;
435   } else {
436     mTimezone = cal::detectTimezone(t, nullptr);
437   }
438 #if defined(DEBUG)
439   if (mTimezone) {
440     if (t.is_utc) {
441       nsCOMPtr<calITimezone> ctz = cal::UTC();
442       NS_ASSERTION(SameCOMIdentity(mTimezone, ctz), "UTC mismatch!");
443     } else if (!t.zone) {
444       nsAutoCString tzid;
445       mTimezone->GetTzid(tzid);
446       if (tzid.EqualsLiteral("floating")) {
447         nsCOMPtr<calITimezone> ctz = cal::floating();
448         NS_ASSERTION(SameCOMIdentity(mTimezone, ctz), "floating mismatch!");
449       }
450     } else {
451       nsAutoCString tzid;
452       mTimezone->GetTzid(tzid);
453       NS_ASSERTION(
454           tzid.Equals(icaltimezone_get_tzid(const_cast<icaltimezone*>(t.zone))),
455           "tzid mismatch!");
456     }
457   }
458 #endif
459 
460   mWeekday = static_cast<int16_t>(icaltime_day_of_week(t) - 1);
461   mYearday = static_cast<int16_t>(icaltime_day_of_year(t));
462 
463   // mNativeTime: not moving the existing date to UTC,
464   // but merely representing it a UTC-based way.
465   t.is_date = 0;
466   mNativeTime = IcaltimeToPRTime(&t, icaltimezone_get_utc_timezone());
467 }
468 
IcaltimeToPRTime(icaltimetype const * icalt,icaltimezone const * tz)469 PRTime calDateTime::IcaltimeToPRTime(icaltimetype const* icalt,
470                                      icaltimezone const* tz) {
471   icaltimetype tt;
472   PRExplodedTime et;
473 
474   /* If the time is the special null time, return 0. */
475   if (icaltime_is_null_time(*icalt)) {
476     return 0;
477   }
478 
479   if (tz) {
480     // use libical for timezone conversion, as it can handle all ics
481     // timezones. having nspr do it is much harder.
482     tt = icaltime_convert_to_zone(*icalt, const_cast<icaltimezone*>(tz));
483   } else {
484     tt = *icalt;
485   }
486 
487   /* Empty the destination */
488   memset(&et, 0, sizeof(struct PRExplodedTime));
489 
490   /* Fill the fields */
491   if (icaltime_is_date(tt)) {
492     et.tm_sec = et.tm_min = et.tm_hour = 0;
493   } else {
494     et.tm_sec = tt.second;
495     et.tm_min = tt.minute;
496     et.tm_hour = tt.hour;
497   }
498   et.tm_mday = static_cast<int16_t>(tt.day);
499   et.tm_month = static_cast<int16_t>(tt.month - 1);
500   et.tm_year = static_cast<int16_t>(tt.year);
501 
502   return PR_ImplodeTime(&et);
503 }
504 
PRTimeToIcaltime(PRTime time,bool isdate,icaltimezone const * tz,icaltimetype * icalt)505 void calDateTime::PRTimeToIcaltime(PRTime time, bool isdate,
506                                    icaltimezone const* tz,
507                                    icaltimetype* icalt) {
508   PRExplodedTime et;
509   PR_ExplodeTime(time, PR_GMTParameters, &et);
510 
511   icalt->year = et.tm_year;
512   icalt->month = et.tm_month + 1;
513   icalt->day = et.tm_mday;
514 
515   if (isdate) {
516     icalt->hour = 0;
517     icalt->minute = 0;
518     icalt->second = 0;
519     icalt->is_date = 1;
520   } else {
521     icalt->hour = et.tm_hour;
522     icalt->minute = et.tm_min;
523     icalt->second = et.tm_sec;
524     icalt->is_date = 0;
525   }
526 
527   icalt->zone = tz;
528   icalt->is_utc = ((tz && tz == icaltimezone_get_utc_timezone()) ? 1 : 0);
529   icalt->is_daylight = 0;
530   // xxx todo: discuss/investigate is_daylight
531   //     if (tz) {
532   //         icaltimezone_get_utc_offset(tz, icalt, &icalt->is_daylight);
533   //     }
534 }
535 
536 NS_IMETHODIMP
Compare(calIDateTime * aOther,int32_t * aResult)537 calDateTime::Compare(calIDateTime* aOther, int32_t* aResult) {
538   NS_ENSURE_ARG_POINTER(aOther);
539   NS_ENSURE_ARG_POINTER(aResult);
540 
541   nsresult rv;
542   nsCOMPtr<calIDateTimeLibical> icalother = do_QueryInterface(aOther, &rv);
543   NS_ENSURE_SUCCESS(rv, rv);
544 
545   bool otherIsDate = false;
546   aOther->GetIsDate(&otherIsDate);
547 
548   icaltimetype a, b;
549   ToIcalTime(&a);
550   icalother->ToIcalTime(&b);
551 
552   // If either this or aOther is floating, both objects are treated
553   // as floating for the comparison.
554   if (!a.zone || !b.zone) {
555     a.zone = nullptr;
556     a.is_utc = 0;
557     b.zone = nullptr;
558     b.is_utc = 0;
559   }
560 
561   if (mIsDate || otherIsDate) {
562     *aResult =
563         icaltime_compare_date_only_tz(a, b, cal::getIcalTimezone(mTimezone));
564   } else {
565     *aResult = icaltime_compare(a, b);
566   }
567 
568   return NS_OK;
569 }
570