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