]> git.saurik.com Git - wxWidgets.git/blob - src/common/datetime.cpp
901156a569d02ab5d00b29a6c97e70ae9f702545
[wxWidgets.git] / src / common / datetime.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: wx/datetime.h
3 // Purpose: implementation of time/date related classes
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.05.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "datetime.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/string.h"
33 #include "wx/intl.h"
34 #include "wx/log.h"
35 #endif // WX_PRECOMP
36
37 #include "wx/thread.h"
38
39 #define wxDEFINE_TIME_CONSTANTS
40
41 #include "wx/datetime.h"
42
43 // ----------------------------------------------------------------------------
44 // constants
45 // ----------------------------------------------------------------------------
46
47 // note that all these constants should be signed or we'd get some big
48 // surprizes with C integer arithmetics
49 static const int MONTHS_IN_YEAR = 12;
50
51 static const int SECONDS_IN_MINUTE = 60;
52
53 // the number of days in month in Julian/Gregorian calendar: the first line is
54 // for normal years, the second one is for the leap ones
55 static wxDateTime::wxDateTime_t gs_daysInMonth[2][MONTHS_IN_YEAR] =
56 {
57 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
58 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
59 };
60
61 // ----------------------------------------------------------------------------
62 // globals
63 // ----------------------------------------------------------------------------
64
65 // a critical section is needed to protect GetTimeZone() static
66 // variable in MT case
67 #ifdef wxUSE_THREADS
68 wxCriticalSection gs_critsectTimezone;
69 #endif // wxUSE_THREADS
70
71 // ----------------------------------------------------------------------------
72 // private functions
73 // ----------------------------------------------------------------------------
74
75 // get the number of days in the given month of the given year
76 static inline
77 wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
78 {
79 return gs_daysInMonth[wxDateTime::IsLeapYear(year)][month];
80 }
81
82 // ensure that the timezone variable is set by calling localtime
83 static int GetTimeZone()
84 {
85 // set to TRUE when the timezone is set
86 static bool s_timezoneSet = FALSE;
87
88 wxCRIT_SECT_LOCKER(lock, gs_critsectTimezone);
89
90 if ( !s_timezoneSet )
91 {
92 (void)localtime(0);
93
94 s_timezoneSet = TRUE;
95 }
96
97 return (int)timezone;
98 }
99
100 // this function is a wrapper around strftime(3)
101 static wxString CallStrftime(const wxChar *format, const tm* tm)
102 {
103 wxChar buf[1024];
104 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
105 {
106 // is ti really possible that 1024 is too short?
107 wxFAIL_MSG(_T("strftime() failed"));
108 }
109
110 return wxString(buf);
111 }
112
113 // if year and/or month have invalid values, replace them with the current ones
114 static void ReplaceDefaultYearMonthWithCurrent(int *year,
115 wxDateTime::Month *month)
116 {
117 struct tm *tmNow = NULL;
118
119 if ( *year == wxDateTime::Inv_Year )
120 {
121 tmNow = wxDateTime::GetTmNow();
122
123 *year = 1900 + tmNow->tm_year;
124 }
125
126 if ( *month == wxDateTime::Inv_Month )
127 {
128 if ( !tmNow )
129 tmNow = wxDateTime::GetTmNow();
130
131 *month = (wxDateTime::Month)tmNow->tm_mon;
132 }
133 }
134
135 // ============================================================================
136 // implementation of wxDateTime
137 // ============================================================================
138
139 // ----------------------------------------------------------------------------
140 // static data
141 // ----------------------------------------------------------------------------
142
143 wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
144 wxDateTime wxDateTime::ms_InvDateTime;
145
146 // ----------------------------------------------------------------------------
147 // struct Tm
148 // ----------------------------------------------------------------------------
149
150 wxDateTime::Tm::Tm()
151 {
152 year = (wxDateTime_t)wxDateTime::Inv_Year;
153 mon = wxDateTime::Inv_Month;
154 mday = 0;
155 hour = min = sec = 0;
156 wday = wxDateTime::Inv_WeekDay;
157 }
158
159 wxDateTime::Tm::Tm(const struct tm& tm)
160 {
161 sec = tm.tm_sec;
162 min = tm.tm_min;
163 hour = tm.tm_hour;
164 mday = tm.tm_mday;
165 mon = (wxDateTime::Month)tm.tm_mon;
166 year = 1900 + tm.tm_year;
167 wday = tm.tm_wday;
168 yday = tm.tm_yday;
169 }
170
171 bool wxDateTime::Tm::IsValid() const
172 {
173 // we allow for the leap seconds, although we don't use them (yet)
174 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
175 (mday < GetNumOfDaysInMonth(year, mon)) &&
176 (hour < 24) && (min < 60) && (sec < 62);
177 }
178
179 void wxDateTime::Tm::ComputeWeekDay()
180 {
181 wxFAIL_MSG(_T("TODO"));
182 }
183
184 void wxDateTime::Tm::AddMonths(wxDateTime::wxDateTime_t monDiff)
185 {
186 // normalize the months field
187 while ( monDiff < -mon )
188 {
189 year--;
190
191 monDiff += MONTHS_IN_YEAR;
192 }
193
194 while ( monDiff + mon > MONTHS_IN_YEAR )
195 {
196 year++;
197 }
198
199 mon = (wxDateTime::Month)(mon + monDiff);
200
201 wxASSERT_MSG( mon >= 0 && mon < 12, _T("logic error") );
202 }
203
204 void wxDateTime::Tm::AddDays(wxDateTime::wxDateTime_t dayDiff)
205 {
206 // normalize the days field
207 mday += dayDiff;
208 while ( mday < 1 )
209 {
210 AddMonths(-1);
211
212 mday += GetNumOfDaysInMonth(year, mon);
213 }
214
215 while ( mday > GetNumOfDaysInMonth(year, mon) )
216 {
217 mday -= GetNumOfDaysInMonth(year, mon);
218
219 AddMonths(1);
220 }
221
222 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
223 _T("logic error") );
224 }
225
226 // ----------------------------------------------------------------------------
227 // class TimeZone
228 // ----------------------------------------------------------------------------
229
230 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
231 {
232 switch ( tz )
233 {
234 case wxDateTime::Local:
235 // leave offset to be 0
236 break;
237
238 case wxDateTime::GMT_12:
239 case wxDateTime::GMT_11:
240 case wxDateTime::GMT_10:
241 case wxDateTime::GMT_9:
242 case wxDateTime::GMT_8:
243 case wxDateTime::GMT_7:
244 case wxDateTime::GMT_6:
245 case wxDateTime::GMT_5:
246 case wxDateTime::GMT_4:
247 case wxDateTime::GMT_3:
248 case wxDateTime::GMT_2:
249 case wxDateTime::GMT_1:
250 m_offset = -60*(wxDateTime::GMT0 - tz);
251 break;
252
253 case wxDateTime::GMT0:
254 case wxDateTime::GMT1:
255 case wxDateTime::GMT2:
256 case wxDateTime::GMT3:
257 case wxDateTime::GMT4:
258 case wxDateTime::GMT5:
259 case wxDateTime::GMT6:
260 case wxDateTime::GMT7:
261 case wxDateTime::GMT8:
262 case wxDateTime::GMT9:
263 case wxDateTime::GMT10:
264 case wxDateTime::GMT11:
265 case wxDateTime::GMT12:
266 m_offset = 60*(tz - wxDateTime::GMT0);
267 break;
268
269 case wxDateTime::A_CST:
270 // Central Standard Time in use in Australia = UTC + 9.5
271 m_offset = 9*60 + 30;
272 break;
273
274 default:
275 wxFAIL_MSG( _T("unknown time zone") );
276 }
277 }
278
279 // ----------------------------------------------------------------------------
280 // static functions
281 // ----------------------------------------------------------------------------
282
283 /* static */
284 bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
285 {
286 if ( year == Inv_Year )
287 year = GetCurrentYear();
288
289 if ( cal == Gregorian )
290 {
291 // in Gregorian calendar leap years are those divisible by 4 except
292 // those divisible by 100 unless they're also divisible by 400
293 // (in some countries, like Russia and Greece, additional corrections
294 // exist, but they won't manifest themselves until 2700)
295 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
296 }
297 else if ( cal == Julian )
298 {
299 // in Julian calendar the rule is simpler
300 return year % 4 == 0;
301 }
302 else
303 {
304 wxFAIL_MSG(_T("unknown calendar"));
305
306 return FALSE;
307 }
308 }
309
310 /* static */
311 int wxDateTime::GetCentury(int year)
312 {
313 return year > 0 ? year / 100 : year / 100 - 1;
314 }
315
316 /* static */
317 void wxDateTime::SetCountry(wxDateTime::Country country)
318 {
319 ms_country = country;
320 }
321
322 /* static */
323 int wxDateTime::ConvertYearToBC(int year)
324 {
325 // year 0 is BC 1
326 return year > 0 ? year : year - 1;
327 }
328
329 /* static */
330 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
331 {
332 switch ( cal )
333 {
334 case Gregorian:
335 return Now().GetYear();
336
337 case Julian:
338 wxFAIL_MSG(_T("TODO"));
339 break;
340
341 default:
342 wxFAIL_MSG(_T("unsupported calendar"));
343 break;
344 }
345
346 return Inv_Year;
347 }
348
349 /* static */
350 wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
351 {
352 switch ( cal )
353 {
354 case Gregorian:
355 return Now().GetMonth();
356 break;
357
358 case Julian:
359 wxFAIL_MSG(_T("TODO"));
360 break;
361
362 default:
363 wxFAIL_MSG(_T("unsupported calendar"));
364 break;
365 }
366
367 return Inv_Month;
368 }
369
370 /* static */
371 wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
372 {
373 if ( year == Inv_Year )
374 {
375 // take the current year if none given
376 year = GetCurrentYear();
377 }
378
379 switch ( cal )
380 {
381 case Gregorian:
382 case Julian:
383 return IsLeapYear(year) ? 366 : 365;
384 break;
385
386 default:
387 wxFAIL_MSG(_T("unsupported calendar"));
388 break;
389 }
390
391 return 0;
392 }
393
394 /* static */
395 wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
396 int year,
397 wxDateTime::Calendar cal)
398 {
399 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
400
401 if ( cal == Gregorian || cal == Julian )
402 {
403 if ( year == Inv_Year )
404 {
405 // take the current year if none given
406 year = GetCurrentYear();
407 }
408
409 return GetNumOfDaysInMonth(year, month);
410 }
411 else
412 {
413 wxFAIL_MSG(_T("unsupported calendar"));
414
415 return 0;
416 }
417 }
418
419 /* static */
420 wxString wxDateTime::GetMonthName(wxDateTime::Month month, bool abbr)
421 {
422 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
423
424 tm tm = { 0, 0, 0, 1, month, 76 }; // any year will do
425
426 return CallStrftime(abbr ? _T("%b") : _T("%B"), &tm);
427 }
428
429 /* static */
430 wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday, bool abbr)
431 {
432 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
433
434 // take some arbitrary Sunday
435 tm tm = { 0, 0, 0, 28, Nov, 99 };
436
437 // and offset it by the number of days needed to get
438 tm.tm_mday += wday;
439
440 return CallStrftime(abbr ? _T("%a") : _T("%A"), &tm);
441 }
442
443 // ----------------------------------------------------------------------------
444 // constructors and assignment operators
445 // ----------------------------------------------------------------------------
446
447 wxDateTime& wxDateTime::Set(const struct tm& tm1)
448 {
449 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
450
451 tm tm2(tm1);
452 time_t timet = mktime(&tm2);
453 if ( timet == (time_t)(-1) )
454 {
455 wxFAIL_MSG(_T("Invalid time"));
456
457 return ms_InvDateTime;
458 }
459 else
460 {
461 return Set(timet);
462 }
463 }
464
465 wxDateTime& wxDateTime::Set(wxDateTime_t hour,
466 wxDateTime_t minute,
467 wxDateTime_t second,
468 wxDateTime_t millisec)
469 {
470 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
471
472 // we allow seconds to be 61 to account for the leap seconds, even if we
473 // don't use them really
474 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
475 ms_InvDateTime,
476 _T("Invalid time in wxDateTime::Set()") );
477
478 // get the current date from system
479 time_t timet = GetTimeNow();
480 struct tm *tm = localtime(&timet);
481
482 // adjust the time
483 tm->tm_hour = hour;
484 tm->tm_min = minute;
485 tm->tm_sec = second;
486
487 (void)Set(*tm);
488
489 // and finally adjust milliseconds
490 return SetMillisecond(millisec);
491 }
492
493 wxDateTime& wxDateTime::Set(wxDateTime_t day,
494 Month month,
495 int year,
496 wxDateTime_t hour,
497 wxDateTime_t minute,
498 wxDateTime_t second,
499 wxDateTime_t millisec)
500 {
501 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
502
503 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
504 ms_InvDateTime,
505 _T("Invalid time in wxDateTime::Set()") );
506
507 ReplaceDefaultYearMonthWithCurrent(&year, &month);
508
509 wxCHECK_MSG( day <= GetNumberOfDays(month, year), ms_InvDateTime,
510 _T("Invalid date in wxDateTime::Set()") );
511
512 // the range of time_t type (inclusive)
513 static const int yearMinInRange = 1970;
514 static const int yearMaxInRange = 2037;
515
516 // test only the year instead of testing for the exact end of the Unix
517 // time_t range - it doesn't bring anything to do more precise checks
518 if ( year >= yearMinInRange && year <= yearMaxInRange )
519 {
520 // use the standard library version if the date is in range - this is
521 // probably more efficient than our code
522 struct tm tm;
523 tm.tm_year = year - 1900;
524 tm.tm_mon = month;
525 tm.tm_mday = day;
526 tm.tm_hour = hour;
527 tm.tm_min = minute;
528 tm.tm_sec = second;
529
530 (void)Set(tm);
531
532 // and finally adjust milliseconds
533 return SetMillisecond(millisec);
534 }
535 else
536 {
537 // do time calculations ourselves: we want to calculate the number of
538 // milliseconds between the given date and the epoch
539 wxFAIL_MSG(_T("TODO"));
540 }
541
542 return *this;
543 }
544
545 // ----------------------------------------------------------------------------
546 // time_t <-> broken down time conversions
547 // ----------------------------------------------------------------------------
548
549 wxDateTime::Tm wxDateTime::GetTm() const
550 {
551 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
552
553 time_t time = GetTicks();
554 if ( time != (time_t)-1 )
555 {
556 // use C RTL functions
557 tm *tm = localtime(&time);
558
559 // should never happen
560 wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
561
562 return Tm(*tm);
563 }
564 else
565 {
566 wxFAIL_MSG(_T("TODO"));
567
568 return Tm();
569 }
570 }
571
572 wxDateTime& wxDateTime::SetYear(int year)
573 {
574 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
575
576 Tm tm(GetTm());
577 tm.year = year;
578 Set(tm);
579
580 return *this;
581 }
582
583 wxDateTime& wxDateTime::SetMonth(Month month)
584 {
585 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
586
587 Tm tm(GetTm());
588 tm.mon = month;
589 Set(tm);
590
591 return *this;
592 }
593
594 wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
595 {
596 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
597
598 Tm tm(GetTm());
599 tm.mday = mday;
600 Set(tm);
601
602 return *this;
603 }
604
605 wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
606 {
607 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
608
609 Tm tm(GetTm());
610 tm.hour = hour;
611 Set(tm);
612
613 return *this;
614 }
615
616 wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
617 {
618 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
619
620 Tm tm(GetTm());
621 tm.min = min;
622 Set(tm);
623
624 return *this;
625 }
626
627 wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
628 {
629 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
630
631 Tm tm(GetTm());
632 tm.sec = sec;
633 Set(tm);
634
635 return *this;
636 }
637
638 wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
639 {
640 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
641
642 // we don't need to use GetTm() for this one
643 m_time -= m_time % 1000l;
644 m_time += millisecond;
645
646 return *this;
647 }
648
649 // ----------------------------------------------------------------------------
650 // wxDateTime arithmetics
651 // ----------------------------------------------------------------------------
652
653 wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
654 {
655 Tm tm(GetTm());
656
657 tm.year += diff.GetYears();
658 tm.AddMonths(diff.GetMonths());
659 tm.AddDays(diff.GetTotalDays());
660
661 Set(tm);
662
663 return *this;
664 }
665
666 // ----------------------------------------------------------------------------
667 // Weekday and monthday stuff
668 // ----------------------------------------------------------------------------
669
670 wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
671 int year)
672 {
673 // take the current month/year if none specified
674 ReplaceDefaultYearMonthWithCurrent(&year, &month);
675
676 return Set(GetNumOfDaysInMonth(year, month), month, year);
677 }
678
679 bool wxDateTime::SetToWeekDay(WeekDay weekday,
680 int n,
681 Month month,
682 int year)
683 {
684 wxCHECK_MSG( weekday != Inv_WeekDay, FALSE, _T("invalid weekday") );
685
686 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
687 // anyhow in such case - but may be should still give an assert for it?
688
689 // take the current month/year if none specified
690 ReplaceDefaultYearMonthWithCurrent(&year, &month);
691
692 wxDateTime dt;
693
694 // TODO this probably could be optimised somehow...
695
696 if ( n > 0 )
697 {
698 // get the first day of the month
699 dt.Set(1, month, year);
700
701 // get its wday
702 WeekDay wdayFirst = dt.GetWeekDay();
703
704 // go to the first weekday of the month
705 int diff = weekday - wdayFirst;
706 if ( diff < 0 )
707 diff += 7;
708
709 // add advance n-1 weeks more
710 diff += 7*(n - 1);
711
712 dt -= wxDateSpan::Days(diff);
713 }
714 else
715 {
716 // get the last day of the month
717 dt.SetToLastMonthDay(month, year);
718
719 // get its wday
720 WeekDay wdayLast = dt.GetWeekDay();
721
722 // go to the last weekday of the month
723 int diff = wdayLast - weekday;
724 if ( diff < 0 )
725 diff += 7;
726
727 // and rewind n-1 weeks from there
728 diff += 7*(n - 1);
729
730 dt -= wxDateSpan::Days(diff);
731 }
732
733 // check that it is still in the same month
734 if ( dt.GetMonth() == month )
735 {
736 *this = dt;
737
738 return TRUE;
739 }
740 else
741 {
742 // no such day in this month
743 return FALSE;
744 }
745 }
746
747 // ----------------------------------------------------------------------------
748 // timezone stuff
749 // ----------------------------------------------------------------------------
750
751 wxDateTime& wxDateTime::MakeUTC()
752 {
753 return Add(wxTimeSpan::Seconds(GetTimeZone()));
754 }
755
756 wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz)
757 {
758 int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset();
759 return Add(wxTimeSpan::Minutes(minDiff));
760 }
761
762 wxDateTime& wxDateTime::MakeLocalTime(const TimeZone& tz)
763 {
764 int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset();
765 return Substract(wxTimeSpan::Minutes(minDiff));
766 }
767
768 // ----------------------------------------------------------------------------
769 // wxDateTime to/from text representations
770 // ----------------------------------------------------------------------------
771
772 wxString wxDateTime::Format(const wxChar *format) const
773 {
774 time_t time = GetTicks();
775 if ( time != (time_t)-1 )
776 {
777 // use strftime()
778 tm *tm = localtime(&time);
779
780 // should never happen
781 wxCHECK_MSG( tm, _T(""), _T("localtime() failed") );
782
783 return CallStrftime(format, tm);
784 }
785 else
786 {
787 wxFAIL_MSG(_T("TODO"));
788
789 return _T("");
790 }
791 }
792
793 // ============================================================================
794 // wxTimeSpan
795 // ============================================================================
796
797 wxString wxTimeSpan::Format(const wxChar *format) const
798 {
799 wxFAIL_MSG( _T("TODO") );
800
801 wxString str;
802
803 return str;
804 }