]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
wxChoice/wxComboBox background colour change (to get back to where we were);
[wxWidgets.git] / src / common / datetime.cpp
CommitLineData
1ef54dcf 1///////////////////////////////////////////////////////////////////////////////
0979c962
VZ
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$
1ef54dcf
VZ
8// Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// parts of code taken from sndcal library by Scott E. Lee:
10//
11// Copyright 1993-1995, Scott E. Lee, all rights reserved.
12// Permission granted to use, copy, modify, distribute and sell
13// so long as the above copyright and this permission statement
14// are retained in all copies.
15//
0979c962 16// Licence: wxWindows license
1ef54dcf
VZ
17///////////////////////////////////////////////////////////////////////////////
18
19/*
20 * Implementation notes:
21 *
22 * 1. the time is stored as a 64bit integer containing the signed number of
23 * milliseconds since Jan 1. 1970 (the Unix Epoch)
24 *
25 * 2. the range is thus something about 580 million years, but due to current
26 * algorithms limitations, only dates from Nov 24, 4714BC are handled
27 *
28 * 3. standard ANSI C functions are used to do time calculations whenever
29 * possible, i.e. when the date is in the range Jan 1, 1970 to 2038
30 *
31 * 4. otherwise, the calculations are done by converting the date to/from JDN
32 * first (the range limitation mentioned above comes from here: the
33 * algorithm used by Scott E. Lee's code only works for positive JDNs, more
34 * or less)
35 *
36 */
0979c962
VZ
37
38// ============================================================================
39// declarations
40// ============================================================================
41
42// ----------------------------------------------------------------------------
43// headers
44// ----------------------------------------------------------------------------
45
46#ifdef __GNUG__
47 #pragma implementation "datetime.h"
48#endif
49
50// For compilers that support precompilation, includes "wx.h".
51#include "wx/wxprec.h"
52
53#ifdef __BORLANDC__
54 #pragma hdrstop
55#endif
56
57#ifndef WX_PRECOMP
58 #include "wx/string.h"
59 #include "wx/intl.h"
60 #include "wx/log.h"
61#endif // WX_PRECOMP
62
fcc3d7cb
VZ
63#include "wx/thread.h"
64
b76b015e
VZ
65#define wxDEFINE_TIME_CONSTANTS
66
0979c962
VZ
67#include "wx/datetime.h"
68
b76b015e
VZ
69// ----------------------------------------------------------------------------
70// constants
71// ----------------------------------------------------------------------------
72
e6ec579c 73// some trivial ones
fcc3d7cb
VZ
74static const int MONTHS_IN_YEAR = 12;
75
76static const int SECONDS_IN_MINUTE = 60;
77
e6ec579c
VZ
78static const long SECONDS_PER_DAY = 86400l;
79
80static const long MILLISECONDS_PER_DAY = 86400000l;
81
82// this is the integral part of JDN of the midnight of Jan 1, 1970
83// (i.e. JDN(Jan 1, 1970) = 2440587.5)
84static const int EPOCH_JDN = 2440587;
b76b015e 85
1ef54dcf
VZ
86// the date of JDN -0.5 (as we don't work with fractional parts, this is the
87// reference date for us) is Nov 24, 4714BC
88static const int JDN_0_YEAR = -4713;
89static const int JDN_0_MONTH = wxDateTime::Nov;
90static const int JDN_0_DAY = 24;
91
92// the constants used for JDN calculations
93static const int JDN_OFFSET = 32046;
94static const int DAYS_PER_5_MONTHS = 153;
95static const int DAYS_PER_4_YEARS = 1461;
96static const int DAYS_PER_400_YEARS = 146097;
97
fcc3d7cb
VZ
98// ----------------------------------------------------------------------------
99// globals
100// ----------------------------------------------------------------------------
101
102// a critical section is needed to protect GetTimeZone() static
103// variable in MT case
104#ifdef wxUSE_THREADS
105 wxCriticalSection gs_critsectTimezone;
106#endif // wxUSE_THREADS
107
b76b015e
VZ
108// ----------------------------------------------------------------------------
109// private functions
110// ----------------------------------------------------------------------------
111
fcc3d7cb
VZ
112// get the number of days in the given month of the given year
113static inline
114wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
115{
e6ec579c
VZ
116 // the number of days in month in Julian/Gregorian calendar: the first line
117 // is for normal years, the second one is for the leap ones
118 static wxDateTime::wxDateTime_t daysInMonth[2][MONTHS_IN_YEAR] =
119 {
120 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
121 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
122 };
123
124 return daysInMonth[wxDateTime::IsLeapYear(year)][month];
fcc3d7cb
VZ
125}
126
127// ensure that the timezone variable is set by calling localtime
128static int GetTimeZone()
129{
130 // set to TRUE when the timezone is set
131 static bool s_timezoneSet = FALSE;
132
133 wxCRIT_SECT_LOCKER(lock, gs_critsectTimezone);
134
135 if ( !s_timezoneSet )
136 {
1ef54dcf 137 // just call localtime() instead of figuring out whether this system
e6ec579c
VZ
138 // supports tzset(), _tzset() or something else
139 time_t t;
140 (void)localtime(&t);
fcc3d7cb
VZ
141
142 s_timezoneSet = TRUE;
143 }
144
145 return (int)timezone;
146}
147
e6ec579c 148// return the integral part of the JDN for the midnight of the given date (to
1ef54dcf
VZ
149// get the real JDN you need to add 0.5, this is, in fact, JDN of the
150// noon of the previous day)
e6ec579c
VZ
151static long GetTruncatedJDN(wxDateTime::wxDateTime_t day,
152 wxDateTime::Month mon,
153 int year)
154{
1ef54dcf
VZ
155 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
156
157 // check the date validity
158 wxASSERT_MSG(
159 (year > JDN_0_YEAR) ||
160 ((year == JDN_0_YEAR) && (mon > JDN_0_MONTH)) ||
161 ((year == JDN_0_YEAR) && (mon == JDN_0_MONTH) && (day >= JDN_0_DAY)),
162 _T("date out of range - can't convert to JDN")
163 );
164
165 // make the year positive to avoid problems with negative numbers division
166 year += 4800;
167
168 // months are counted from March here
169 int month;
170 if ( mon >= wxDateTime::Mar )
e6ec579c 171 {
1ef54dcf 172 month = mon - 2;
e6ec579c 173 }
1ef54dcf 174 else
e6ec579c 175 {
1ef54dcf
VZ
176 month = mon + 10;
177 year--;
178 }
e6ec579c 179
1ef54dcf
VZ
180 // now we can simply add all the contributions together
181 return ((year / 100) * DAYS_PER_400_YEARS) / 4
182 + ((year % 100) * DAYS_PER_4_YEARS) / 4
183 + (month * DAYS_PER_5_MONTHS + 2) / 5
184 + day
185 - JDN_OFFSET;
e6ec579c
VZ
186}
187
2f02cb89 188// this function is a wrapper around strftime(3)
b76b015e
VZ
189static wxString CallStrftime(const wxChar *format, const tm* tm)
190{
191 wxChar buf[1024];
192 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
193 {
194 // is ti really possible that 1024 is too short?
195 wxFAIL_MSG(_T("strftime() failed"));
196 }
197
198 return wxString(buf);
199}
200
2f02cb89
VZ
201// if year and/or month have invalid values, replace them with the current ones
202static void ReplaceDefaultYearMonthWithCurrent(int *year,
203 wxDateTime::Month *month)
204{
205 struct tm *tmNow = NULL;
206
207 if ( *year == wxDateTime::Inv_Year )
208 {
209 tmNow = wxDateTime::GetTmNow();
210
211 *year = 1900 + tmNow->tm_year;
212 }
213
214 if ( *month == wxDateTime::Inv_Month )
215 {
216 if ( !tmNow )
217 tmNow = wxDateTime::GetTmNow();
218
219 *month = (wxDateTime::Month)tmNow->tm_mon;
220 }
221}
222
0979c962
VZ
223// ============================================================================
224// implementation of wxDateTime
225// ============================================================================
226
227// ----------------------------------------------------------------------------
228// static data
229// ----------------------------------------------------------------------------
230
b76b015e 231wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
0979c962
VZ
232wxDateTime wxDateTime::ms_InvDateTime;
233
b76b015e
VZ
234// ----------------------------------------------------------------------------
235// struct Tm
236// ----------------------------------------------------------------------------
237
238wxDateTime::Tm::Tm()
239{
240 year = (wxDateTime_t)wxDateTime::Inv_Year;
241 mon = wxDateTime::Inv_Month;
242 mday = 0;
e6ec579c 243 hour = min = sec = msec = 0;
b76b015e
VZ
244 wday = wxDateTime::Inv_WeekDay;
245}
246
247wxDateTime::Tm::Tm(const struct tm& tm)
248{
e6ec579c 249 msec = 0;
b76b015e
VZ
250 sec = tm.tm_sec;
251 min = tm.tm_min;
252 hour = tm.tm_hour;
253 mday = tm.tm_mday;
fcc3d7cb 254 mon = (wxDateTime::Month)tm.tm_mon;
b76b015e
VZ
255 year = 1900 + tm.tm_year;
256 wday = tm.tm_wday;
257 yday = tm.tm_yday;
258}
259
260bool wxDateTime::Tm::IsValid() const
261{
262 // we allow for the leap seconds, although we don't use them (yet)
fcc3d7cb
VZ
263 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
264 (mday < GetNumOfDaysInMonth(year, mon)) &&
e6ec579c 265 (hour < 24) && (min < 60) && (sec < 62) && (msec < 1000);
b76b015e
VZ
266}
267
268void wxDateTime::Tm::ComputeWeekDay()
269{
270 wxFAIL_MSG(_T("TODO"));
271}
272
e6ec579c 273void wxDateTime::Tm::AddMonths(int monDiff)
fcc3d7cb
VZ
274{
275 // normalize the months field
276 while ( monDiff < -mon )
277 {
278 year--;
279
280 monDiff += MONTHS_IN_YEAR;
281 }
282
283 while ( monDiff + mon > MONTHS_IN_YEAR )
284 {
285 year++;
286 }
287
288 mon = (wxDateTime::Month)(mon + monDiff);
289
e6ec579c 290 wxASSERT_MSG( mon >= 0 && mon < MONTHS_IN_YEAR, _T("logic error") );
fcc3d7cb
VZ
291}
292
e6ec579c 293void wxDateTime::Tm::AddDays(int dayDiff)
fcc3d7cb
VZ
294{
295 // normalize the days field
296 mday += dayDiff;
297 while ( mday < 1 )
298 {
299 AddMonths(-1);
300
301 mday += GetNumOfDaysInMonth(year, mon);
302 }
303
304 while ( mday > GetNumOfDaysInMonth(year, mon) )
305 {
306 mday -= GetNumOfDaysInMonth(year, mon);
307
308 AddMonths(1);
309 }
310
311 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
312 _T("logic error") );
313}
314
315// ----------------------------------------------------------------------------
316// class TimeZone
317// ----------------------------------------------------------------------------
318
319wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
320{
321 switch ( tz )
322 {
323 case wxDateTime::Local:
324 // leave offset to be 0
325 break;
326
327 case wxDateTime::GMT_12:
328 case wxDateTime::GMT_11:
329 case wxDateTime::GMT_10:
330 case wxDateTime::GMT_9:
331 case wxDateTime::GMT_8:
332 case wxDateTime::GMT_7:
333 case wxDateTime::GMT_6:
334 case wxDateTime::GMT_5:
335 case wxDateTime::GMT_4:
336 case wxDateTime::GMT_3:
337 case wxDateTime::GMT_2:
338 case wxDateTime::GMT_1:
339 m_offset = -60*(wxDateTime::GMT0 - tz);
340 break;
341
342 case wxDateTime::GMT0:
343 case wxDateTime::GMT1:
344 case wxDateTime::GMT2:
345 case wxDateTime::GMT3:
346 case wxDateTime::GMT4:
347 case wxDateTime::GMT5:
348 case wxDateTime::GMT6:
349 case wxDateTime::GMT7:
350 case wxDateTime::GMT8:
351 case wxDateTime::GMT9:
352 case wxDateTime::GMT10:
353 case wxDateTime::GMT11:
354 case wxDateTime::GMT12:
355 m_offset = 60*(tz - wxDateTime::GMT0);
356 break;
357
358 case wxDateTime::A_CST:
359 // Central Standard Time in use in Australia = UTC + 9.5
360 m_offset = 9*60 + 30;
361 break;
362
363 default:
364 wxFAIL_MSG( _T("unknown time zone") );
365 }
366}
367
b76b015e
VZ
368// ----------------------------------------------------------------------------
369// static functions
370// ----------------------------------------------------------------------------
371
372/* static */
373bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
374{
2f02cb89
VZ
375 if ( year == Inv_Year )
376 year = GetCurrentYear();
377
b76b015e
VZ
378 if ( cal == Gregorian )
379 {
380 // in Gregorian calendar leap years are those divisible by 4 except
381 // those divisible by 100 unless they're also divisible by 400
382 // (in some countries, like Russia and Greece, additional corrections
383 // exist, but they won't manifest themselves until 2700)
384 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
385 }
386 else if ( cal == Julian )
387 {
388 // in Julian calendar the rule is simpler
389 return year % 4 == 0;
390 }
391 else
392 {
393 wxFAIL_MSG(_T("unknown calendar"));
394
395 return FALSE;
396 }
397}
398
fcc3d7cb
VZ
399/* static */
400int wxDateTime::GetCentury(int year)
401{
402 return year > 0 ? year / 100 : year / 100 - 1;
403}
404
b76b015e
VZ
405/* static */
406void wxDateTime::SetCountry(wxDateTime::Country country)
407{
408 ms_country = country;
409}
410
411/* static */
412int wxDateTime::ConvertYearToBC(int year)
413{
414 // year 0 is BC 1
415 return year > 0 ? year : year - 1;
416}
417
418/* static */
419int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
420{
421 switch ( cal )
422 {
423 case Gregorian:
424 return Now().GetYear();
425
426 case Julian:
427 wxFAIL_MSG(_T("TODO"));
428 break;
429
430 default:
431 wxFAIL_MSG(_T("unsupported calendar"));
432 break;
433 }
434
435 return Inv_Year;
436}
437
438/* static */
439wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
440{
441 switch ( cal )
442 {
443 case Gregorian:
444 return Now().GetMonth();
445 break;
446
447 case Julian:
448 wxFAIL_MSG(_T("TODO"));
449 break;
450
451 default:
452 wxFAIL_MSG(_T("unsupported calendar"));
453 break;
454 }
455
456 return Inv_Month;
457}
458
2f02cb89
VZ
459/* static */
460wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
461{
462 if ( year == Inv_Year )
463 {
464 // take the current year if none given
465 year = GetCurrentYear();
466 }
467
468 switch ( cal )
469 {
470 case Gregorian:
471 case Julian:
472 return IsLeapYear(year) ? 366 : 365;
473 break;
474
475 default:
476 wxFAIL_MSG(_T("unsupported calendar"));
477 break;
478 }
479
480 return 0;
481}
482
b76b015e
VZ
483/* static */
484wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
485 int year,
486 wxDateTime::Calendar cal)
487{
fcc3d7cb 488 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
b76b015e
VZ
489
490 if ( cal == Gregorian || cal == Julian )
491 {
492 if ( year == Inv_Year )
493 {
494 // take the current year if none given
495 year = GetCurrentYear();
496 }
497
fcc3d7cb 498 return GetNumOfDaysInMonth(year, month);
b76b015e
VZ
499 }
500 else
501 {
502 wxFAIL_MSG(_T("unsupported calendar"));
503
504 return 0;
505 }
506}
507
508/* static */
509wxString wxDateTime::GetMonthName(wxDateTime::Month month, bool abbr)
510{
511 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
512
513 tm tm = { 0, 0, 0, 1, month, 76 }; // any year will do
514
515 return CallStrftime(abbr ? _T("%b") : _T("%B"), &tm);
516}
517
518/* static */
519wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday, bool abbr)
520{
521 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
522
523 // take some arbitrary Sunday
524 tm tm = { 0, 0, 0, 28, Nov, 99 };
525
1ef54dcf 526 // and offset it by the number of days needed to get the correct wday
b76b015e
VZ
527 tm.tm_mday += wday;
528
529 return CallStrftime(abbr ? _T("%a") : _T("%A"), &tm);
530}
531
0979c962
VZ
532// ----------------------------------------------------------------------------
533// constructors and assignment operators
534// ----------------------------------------------------------------------------
535
b76b015e 536wxDateTime& wxDateTime::Set(const struct tm& tm1)
0979c962 537{
b76b015e
VZ
538 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
539
540 tm tm2(tm1);
1ef54dcf
VZ
541
542 // we want the time in GMT, mktime() takes the local time, so use timegm()
543 // if it's available
544#ifdef HAVE_TIMEGM
545 time_t timet = timegm(&tm2);
546#else // !HAVE_TIMEGM
547 // FIXME this almost surely doesn't work
548 tm2.tm_sec -= GetTimeZone();
549
b76b015e 550 time_t timet = mktime(&tm2);
1ef54dcf
VZ
551
552 if ( tm2.tm_isdst )
553 {
554 tm2.tm_hour += 1;
555
556 timet = mktime(&tm2);
557 }
558#endif // HAVE_TIMEGM/!HAVE_TIMEGM
559
0979c962
VZ
560 if ( timet == (time_t)(-1) )
561 {
562 wxFAIL_MSG(_T("Invalid time"));
563
564 return ms_InvDateTime;
565 }
566 else
567 {
568 return Set(timet);
569 }
570}
571
572wxDateTime& wxDateTime::Set(wxDateTime_t hour,
573 wxDateTime_t minute,
574 wxDateTime_t second,
575 wxDateTime_t millisec)
576{
b76b015e
VZ
577 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
578
0979c962
VZ
579 // we allow seconds to be 61 to account for the leap seconds, even if we
580 // don't use them really
581 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
582 ms_InvDateTime,
583 _T("Invalid time in wxDateTime::Set()") );
584
585 // get the current date from system
586 time_t timet = GetTimeNow();
1ef54dcf 587 struct tm *tm = gmtime(&timet);
0979c962
VZ
588
589 // adjust the time
590 tm->tm_hour = hour;
591 tm->tm_min = minute;
592 tm->tm_sec = second;
593
b76b015e 594 (void)Set(*tm);
0979c962
VZ
595
596 // and finally adjust milliseconds
597 return SetMillisecond(millisec);
598}
599
600wxDateTime& wxDateTime::Set(wxDateTime_t day,
601 Month month,
602 int year,
603 wxDateTime_t hour,
604 wxDateTime_t minute,
605 wxDateTime_t second,
606 wxDateTime_t millisec)
607{
b76b015e
VZ
608 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
609
0979c962
VZ
610 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
611 ms_InvDateTime,
612 _T("Invalid time in wxDateTime::Set()") );
613
2f02cb89 614 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 615
1ef54dcf
VZ
616 wxCHECK_MSG( (0 < day) && (day <= GetNumberOfDays(month, year)),
617 ms_InvDateTime,
0979c962
VZ
618 _T("Invalid date in wxDateTime::Set()") );
619
620 // the range of time_t type (inclusive)
621 static const int yearMinInRange = 1970;
622 static const int yearMaxInRange = 2037;
623
624 // test only the year instead of testing for the exact end of the Unix
625 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 626 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
627 {
628 // use the standard library version if the date is in range - this is
b76b015e 629 // probably more efficient than our code
0979c962 630 struct tm tm;
b76b015e 631 tm.tm_year = year - 1900;
0979c962
VZ
632 tm.tm_mon = month;
633 tm.tm_mday = day;
634 tm.tm_hour = hour;
635 tm.tm_min = minute;
636 tm.tm_sec = second;
1ef54dcf 637 tm.tm_isdst = -1; // mktime() will guess it (wrongly, probably)
0979c962
VZ
638
639 (void)Set(tm);
640
641 // and finally adjust milliseconds
642 return SetMillisecond(millisec);
643 }
644 else
645 {
646 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 647 // milliseconds between the given date and the epoch
e6ec579c
VZ
648
649 // get the JDN for the midnight of this day
650 m_time = GetTruncatedJDN(day, month, year);
651 m_time -= EPOCH_JDN;
652 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
653
654 Add(wxTimeSpan(hour, minute, second, millisec));
b76b015e
VZ
655 }
656
657 return *this;
658}
659
e6ec579c
VZ
660wxDateTime& wxDateTime::Set(double jdn)
661{
1ef54dcf
VZ
662 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
663 // EPOCH_JDN + 0.5
664 jdn -= EPOCH_JDN + 0.5;
665
666 m_time = jdn;
667 m_time *= MILLISECONDS_PER_DAY;
e6ec579c
VZ
668
669 return *this;
670}
671
b76b015e
VZ
672// ----------------------------------------------------------------------------
673// time_t <-> broken down time conversions
674// ----------------------------------------------------------------------------
675
676wxDateTime::Tm wxDateTime::GetTm() const
677{
678 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
679
680 time_t time = GetTicks();
681 if ( time != (time_t)-1 )
682 {
683 // use C RTL functions
1ef54dcf 684 tm *tm = gmtime(&time);
b76b015e
VZ
685
686 // should never happen
1ef54dcf 687 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
b76b015e
VZ
688
689 return Tm(*tm);
690 }
691 else
692 {
1ef54dcf
VZ
693 // remember the time and do the calculations with the date only - this
694 // eliminates rounding errors of the floating point arithmetics
e6ec579c 695
e6ec579c 696 wxLongLong timeMidnight = m_time;
1ef54dcf
VZ
697
698 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
699 if ( timeOnly < 0 )
700 {
701 timeOnly = MILLISECONDS_PER_DAY - timeOnly;
702 }
703
e6ec579c
VZ
704 timeMidnight -= timeOnly;
705
1ef54dcf
VZ
706 // calculate the Gregorian date from JDN for the midnight of our date:
707 // this will yield day, month (in 1..12 range) and year
708
709 // actually, this is the JDN for the noon of the previous day
710 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
711
712 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
713
714 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
715
716 // calculate the century
717 int temp = (jdn + JDN_OFFSET) * 4 - 1;
718 int century = temp / DAYS_PER_400_YEARS;
719
720 // then the year and day of year (1 <= dayOfYear <= 366)
721 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
722 int year = (century * 100) + (temp / DAYS_PER_4_YEARS);
723 int dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
724
725 // and finally the month and day of the month
726 temp = dayOfYear * 5 - 3;
727 int month = temp / DAYS_PER_5_MONTHS;
728 int day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
729
730 // month is counted from March - convert to normal
731 if ( month < 10 )
e6ec579c 732 {
1ef54dcf 733 month += 3;
e6ec579c 734 }
1ef54dcf
VZ
735 else
736 {
737 year += 1;
738 month -= 9;
739 }
740
741 // year is offset by 4800
742 year -= 4800;
743
744 // check that the algorithm gave us something reasonable
745 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
746 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
747 wxASSERT_MSG( (INT_MIN <= year) && (year <= INT_MAX),
748 _T("year range overflow") );
e6ec579c 749
1ef54dcf 750 // construct Tm from these values
e6ec579c 751 Tm tm;
1ef54dcf 752 tm.year = (int)year;
e6ec579c 753 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1ef54dcf 754 tm.mday = (wxDateTime_t)day;
e6ec579c
VZ
755 tm.msec = timeOnly % 1000;
756 timeOnly -= tm.msec;
757 timeOnly /= 1000; // now we have time in seconds
758
759 tm.sec = timeOnly % 60;
760 timeOnly -= tm.sec;
761 timeOnly /= 60; // now we have time in minutes
762
763 tm.min = timeOnly % 60;
764 timeOnly -= tm.min;
765
766 tm.hour = timeOnly / 60;
b76b015e 767
e6ec579c 768 return tm;
0979c962 769 }
b76b015e
VZ
770}
771
772wxDateTime& wxDateTime::SetYear(int year)
773{
774 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
775
776 Tm tm(GetTm());
777 tm.year = year;
778 Set(tm);
779
780 return *this;
781}
782
783wxDateTime& wxDateTime::SetMonth(Month month)
784{
785 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
786
787 Tm tm(GetTm());
788 tm.mon = month;
789 Set(tm);
790
791 return *this;
792}
793
794wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
795{
796 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
797
798 Tm tm(GetTm());
799 tm.mday = mday;
800 Set(tm);
801
802 return *this;
803}
804
805wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
806{
807 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
808
809 Tm tm(GetTm());
810 tm.hour = hour;
811 Set(tm);
812
813 return *this;
814}
815
816wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
817{
818 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
819
820 Tm tm(GetTm());
821 tm.min = min;
822 Set(tm);
823
824 return *this;
825}
826
827wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
828{
829 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
830
831 Tm tm(GetTm());
832 tm.sec = sec;
833 Set(tm);
0979c962
VZ
834
835 return *this;
836}
b76b015e
VZ
837
838wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
839{
840 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
841
842 // we don't need to use GetTm() for this one
843 m_time -= m_time % 1000l;
844 m_time += millisecond;
845
846 return *this;
847}
848
849// ----------------------------------------------------------------------------
850// wxDateTime arithmetics
851// ----------------------------------------------------------------------------
852
853wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
854{
855 Tm tm(GetTm());
856
857 tm.year += diff.GetYears();
fcc3d7cb
VZ
858 tm.AddMonths(diff.GetMonths());
859 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
860
861 Set(tm);
862
863 return *this;
864}
865
2f02cb89
VZ
866// ----------------------------------------------------------------------------
867// Weekday and monthday stuff
868// ----------------------------------------------------------------------------
869
870wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
871 int year)
872{
873 // take the current month/year if none specified
874 ReplaceDefaultYearMonthWithCurrent(&year, &month);
875
fcc3d7cb 876 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
877}
878
879bool wxDateTime::SetToWeekDay(WeekDay weekday,
880 int n,
881 Month month,
882 int year)
883{
884 wxCHECK_MSG( weekday != Inv_WeekDay, FALSE, _T("invalid weekday") );
885
886 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
887 // anyhow in such case - but may be should still give an assert for it?
888
889 // take the current month/year if none specified
890 ReplaceDefaultYearMonthWithCurrent(&year, &month);
891
892 wxDateTime dt;
893
894 // TODO this probably could be optimised somehow...
895
896 if ( n > 0 )
897 {
898 // get the first day of the month
899 dt.Set(1, month, year);
900
901 // get its wday
902 WeekDay wdayFirst = dt.GetWeekDay();
903
904 // go to the first weekday of the month
905 int diff = weekday - wdayFirst;
906 if ( diff < 0 )
907 diff += 7;
908
909 // add advance n-1 weeks more
910 diff += 7*(n - 1);
911
912 dt -= wxDateSpan::Days(diff);
913 }
914 else
915 {
916 // get the last day of the month
917 dt.SetToLastMonthDay(month, year);
918
919 // get its wday
920 WeekDay wdayLast = dt.GetWeekDay();
921
922 // go to the last weekday of the month
923 int diff = wdayLast - weekday;
924 if ( diff < 0 )
925 diff += 7;
926
927 // and rewind n-1 weeks from there
928 diff += 7*(n - 1);
929
930 dt -= wxDateSpan::Days(diff);
931 }
932
933 // check that it is still in the same month
934 if ( dt.GetMonth() == month )
935 {
936 *this = dt;
937
938 return TRUE;
939 }
940 else
941 {
942 // no such day in this month
943 return FALSE;
944 }
945}
946
e6ec579c
VZ
947// ----------------------------------------------------------------------------
948// Julian day number conversion and related stuff
949// ----------------------------------------------------------------------------
950
951double wxDateTime::GetJulianDayNumber() const
952{
953 Tm tm(GetTm());
954
955 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
956
957 // add the part GetTruncatedJDN() neglected
958 result += 0.5;
959
960 // and now add the time: 86400 sec = 1 JDN
961 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
962}
963
964double wxDateTime::GetRataDie() const
965{
966 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
967 return GetJulianDayNumber() - 1721119.5 - 306;
968}
969
fcc3d7cb
VZ
970// ----------------------------------------------------------------------------
971// timezone stuff
972// ----------------------------------------------------------------------------
973
974wxDateTime& wxDateTime::MakeUTC()
975{
976 return Add(wxTimeSpan::Seconds(GetTimeZone()));
977}
978
979wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz)
980{
981 int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset();
982 return Add(wxTimeSpan::Minutes(minDiff));
983}
984
985wxDateTime& wxDateTime::MakeLocalTime(const TimeZone& tz)
986{
987 int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset();
988 return Substract(wxTimeSpan::Minutes(minDiff));
989}
990
b76b015e
VZ
991// ----------------------------------------------------------------------------
992// wxDateTime to/from text representations
993// ----------------------------------------------------------------------------
994
995wxString wxDateTime::Format(const wxChar *format) const
996{
e6ec579c
VZ
997 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
998
b76b015e
VZ
999 time_t time = GetTicks();
1000 if ( time != (time_t)-1 )
1001 {
1002 // use strftime()
1ef54dcf 1003 tm *tm = gmtime(&time);
b76b015e
VZ
1004
1005 // should never happen
1ef54dcf 1006 wxCHECK_MSG( tm, _T(""), _T("gmtime() failed") );
b76b015e
VZ
1007
1008 return CallStrftime(format, tm);
1009 }
1010 else
1011 {
e6ec579c
VZ
1012 // use a hack and still use strftime(): make a copy of the format and
1013 // replace all occurences of YEAR in it with some unique string not
1014 // appearing anywhere else in it, then use strftime() to format the
1015 // date in year YEAR and then replace YEAR back by the real year and
1016 // the unique replacement string back with YEAR where YEAR is any year
1017 // in the range supported by strftime() (1970 - 2037) which is equal to
1018 // the real year modulo 28 (so the week days coincide for them)
1019
1020 // find the YEAR
1021 int yearReal = GetYear();
1022 int year = 1970 + yearReal % 28;
1023
1024 wxString strYear;
1025 strYear.Printf(_T("%d"), year);
1026
1027 // find a string not occuring in format (this is surely not optimal way
1028 // of doing it... improvements welcome!)
1029 wxString fmt = format;
1030 wxString replacement = (wxChar)-1;
1031 while ( fmt.Find(replacement) != wxNOT_FOUND )
1032 {
1033 replacement << (wxChar)-1;
1034 }
1035
1036 // replace all occurences of year with it
1037 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
1038
1039 // use strftime() to format the same date but in supported year
1040 wxDateTime dt(*this);
1041 dt.SetYear(year);
1042 wxString str = dt.Format(format);
b76b015e 1043
e6ec579c
VZ
1044 // now replace the occurence of 1999 with the real year
1045 wxString strYearReal;
1046 strYearReal.Printf(_T("%d"), yearReal);
1047 str.Replace(strYear, strYearReal);
1048
1049 // and replace back all occurences of replacement string
1050 if ( wasReplaced )
1051 str.Replace(replacement, strYear);
1052
1053 return str;
b76b015e
VZ
1054 }
1055}
fcc3d7cb
VZ
1056
1057// ============================================================================
1058// wxTimeSpan
1059// ============================================================================
1060
e6ec579c
VZ
1061// not all strftime(3) format specifiers make sense here because, for example,
1062// a time span doesn't have a year nor a timezone
1063//
1064// Here are the ones which are supported (all of them are supported by strftime
1065// as well):
1066// %H hour in 24 hour format
1067// %M minute (00 - 59)
1068// %S second (00 - 59)
1069// %% percent sign
1070//
1071// Also, for MFC CTimeSpan compatibility, we support
1072// %D number of days
1073//
1074// And, to be better than MFC :-), we also have
1075// %E number of wEeks
1076// %l milliseconds (000 - 999)
fcc3d7cb
VZ
1077wxString wxTimeSpan::Format(const wxChar *format) const
1078{
e6ec579c 1079 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
1080
1081 wxString str;
e6ec579c
VZ
1082 str.Alloc(strlen(format));
1083
1084 for ( const wxChar *pch = format; pch; pch++ )
1085 {
1086 wxChar ch = *pch;
1087
1088 if ( ch == '%' )
1089 {
1090 wxString tmp;
1091
1092 ch = *pch++;
1093 switch ( ch )
1094 {
1095 default:
1096 wxFAIL_MSG( _T("invalid format character") );
1097 // fall through
1098
1099 case '%':
1100 // will get to str << ch below
1101 break;
1102
1103 case 'D':
1104 tmp.Printf(_T("%d"), GetDays());
1105 break;
1106
1107 case 'E':
1108 tmp.Printf(_T("%d"), GetWeeks());
1109 break;
1110
1111 case 'H':
1112 tmp.Printf(_T("%02d"), GetHours());
1113 break;
1114
1115 case 'l':
1116 tmp.Printf(_T("%03d"), GetMilliseconds());
1117 break;
1118
1119 case 'M':
1120 tmp.Printf(_T("%02d"), GetMinutes());
1121 break;
1122
1123 case 'S':
1124 tmp.Printf(_T("%02d"), GetSeconds());
1125 break;
1126 }
1127
1128 if ( !!tmp )
1129 {
1130 str += tmp;
1131
1132 // skip str += ch below
1133 continue;
1134 }
1135 }
1136
1137 str += ch;
1138 }
fcc3d7cb
VZ
1139
1140 return str;
1141}