]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
wxExecute may only be called from the main thread
[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
299fcbfe
VZ
23 * milliseconds since Jan 1. 1970 (the Unix Epoch) - so it is always
24 * expressed in GMT.
1ef54dcf
VZ
25 *
26 * 2. the range is thus something about 580 million years, but due to current
27 * algorithms limitations, only dates from Nov 24, 4714BC are handled
28 *
29 * 3. standard ANSI C functions are used to do time calculations whenever
30 * possible, i.e. when the date is in the range Jan 1, 1970 to 2038
31 *
32 * 4. otherwise, the calculations are done by converting the date to/from JDN
33 * first (the range limitation mentioned above comes from here: the
34 * algorithm used by Scott E. Lee's code only works for positive JDNs, more
35 * or less)
36 *
299fcbfe
VZ
37 * 5. the object constructed for the given DD-MM-YYYY HH:MM:SS corresponds to
38 * this moment in local time and may be converted to the object
39 * corresponding to the same date/time in another time zone by using
40 * ToTimezone()
41 *
42 * 6. the conversions to the current (or any other) timezone are done when the
43 * internal time representation is converted to the broken-down one in
44 * wxDateTime::Tm.
1ef54dcf 45 */
0979c962
VZ
46
47// ============================================================================
48// declarations
49// ============================================================================
50
51// ----------------------------------------------------------------------------
52// headers
53// ----------------------------------------------------------------------------
54
55#ifdef __GNUG__
56 #pragma implementation "datetime.h"
57#endif
58
59// For compilers that support precompilation, includes "wx.h".
60#include "wx/wxprec.h"
61
62#ifdef __BORLANDC__
63 #pragma hdrstop
64#endif
65
1e6feb95
VZ
66#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
67
0979c962
VZ
68#ifndef WX_PRECOMP
69 #include "wx/string.h"
0979c962
VZ
70 #include "wx/log.h"
71#endif // WX_PRECOMP
72
54800df8 73#include "wx/intl.h"
fcc3d7cb 74#include "wx/thread.h"
cd0b1709 75#include "wx/tokenzr.h"
7281ab04 76#include "wx/module.h"
fcc3d7cb 77
f0f951fa 78#define wxDEFINE_TIME_CONSTANTS // before including datetime.h
b76b015e 79
ff0ea71c
GT
80#include <ctype.h>
81
0979c962 82#include "wx/datetime.h"
f6bcfd97 83#include "wx/timer.h" // for wxGetLocalTimeMillis()
0979c962 84
f0f951fa
VZ
85// ----------------------------------------------------------------------------
86// conditional compilation
87// ----------------------------------------------------------------------------
6de20863 88
f0f951fa
VZ
89#if defined(HAVE_STRPTIME) && defined(__LINUX__)
90 // glibc 2.0.7 strptime() is broken - the following snippet causes it to
91 // crash (instead of just failing):
92 //
93 // strncpy(buf, "Tue Dec 21 20:25:40 1999", 128);
94 // strptime(buf, "%x", &tm);
95 //
96 // so don't use it
97 #undef HAVE_STRPTIME
98#endif // broken strptime()
99
0779cc9a 100#if !defined(WX_TIMEZONE) && !defined(WX_GMTOFF_IN_TM)
ad0dc53b 101 #if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
f0f951fa 102 #define WX_TIMEZONE _timezone
8208e181 103 #elif defined(__MWERKS__)
f6bcfd97 104 long wxmw_timezone = 28800;
c4e1b7f2 105 #define WX_TIMEZONE wxmw_timezone
5283098e 106 #elif defined(__DJGPP__) || defined(__WINE__)
713a0efc 107 #include <sys/timeb.h>
c4e1b7f2 108 #include <values.h>
713a0efc
VS
109 static long wxGetTimeZone()
110 {
c4e1b7f2
VS
111 static long timezone = MAXLONG; // invalid timezone
112 if (timezone == MAXLONG)
113 {
114 struct timeb tb;
115 ftime(&tb);
116 timezone = tb.timezone;
117 }
118 return timezone;
713a0efc
VS
119 }
120 #define WX_TIMEZONE wxGetTimeZone()
0779cc9a
GD
121 #elif defined(__DARWIN__)
122 #define WX_GMTOFF_IN_TM
f0f951fa
VZ
123 #else // unknown platform - try timezone
124 #define WX_TIMEZONE timezone
125 #endif
0779cc9a 126#endif // !WX_TIMEZONE && !WX_GMTOFF_IN_TM
c25a510b 127
384223b3
VZ
128// ----------------------------------------------------------------------------
129// macros
130// ----------------------------------------------------------------------------
131
132// debugging helper: just a convenient replacement of wxCHECK()
133#define wxDATETIME_CHECK(expr, msg) \
134 if ( !(expr) ) \
135 { \
136 wxFAIL_MSG(msg); \
137 *this = wxInvalidDateTime; \
138 return *this; \
139 }
140
4f6aed9c
VZ
141// ----------------------------------------------------------------------------
142// private classes
143// ----------------------------------------------------------------------------
144
145class wxDateTimeHolidaysModule : public wxModule
146{
147public:
148 virtual bool OnInit()
149 {
150 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays);
151
152 return TRUE;
153 }
154
155 virtual void OnExit()
156 {
157 wxDateTimeHolidayAuthority::ClearAllAuthorities();
f6081a04 158 wxDateTimeHolidayAuthority::ms_authorities.Clear();
4f6aed9c
VZ
159 }
160
161private:
162 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule)
163};
164
165IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule, wxModule)
166
b76b015e
VZ
167// ----------------------------------------------------------------------------
168// constants
169// ----------------------------------------------------------------------------
170
e6ec579c 171// some trivial ones
fcc3d7cb
VZ
172static const int MONTHS_IN_YEAR = 12;
173
df05cdc5
VZ
174static const int SEC_PER_MIN = 60;
175
176static const int MIN_PER_HOUR = 60;
177
178static const int HOURS_PER_DAY = 24;
fcc3d7cb 179
e6ec579c
VZ
180static const long SECONDS_PER_DAY = 86400l;
181
df05cdc5
VZ
182static const int DAYS_PER_WEEK = 7;
183
e6ec579c
VZ
184static const long MILLISECONDS_PER_DAY = 86400000l;
185
186// this is the integral part of JDN of the midnight of Jan 1, 1970
187// (i.e. JDN(Jan 1, 1970) = 2440587.5)
cd0b1709 188static const long EPOCH_JDN = 2440587l;
b76b015e 189
1ef54dcf
VZ
190// the date of JDN -0.5 (as we don't work with fractional parts, this is the
191// reference date for us) is Nov 24, 4714BC
192static const int JDN_0_YEAR = -4713;
193static const int JDN_0_MONTH = wxDateTime::Nov;
194static const int JDN_0_DAY = 24;
195
196// the constants used for JDN calculations
cd0b1709
VZ
197static const long JDN_OFFSET = 32046l;
198static const long DAYS_PER_5_MONTHS = 153l;
199static const long DAYS_PER_4_YEARS = 1461l;
200static const long DAYS_PER_400_YEARS = 146097l;
1ef54dcf 201
f0f951fa
VZ
202// this array contains the cumulated number of days in all previous months for
203// normal and leap years
204static const wxDateTime::wxDateTime_t gs_cumulatedDays[2][MONTHS_IN_YEAR] =
205{
206 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
207 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
208};
209
fcc3d7cb 210// ----------------------------------------------------------------------------
2ef31e80
VZ
211// global data
212// ----------------------------------------------------------------------------
213
384223b3
VZ
214// in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
215// indicate an invalid wxDateTime object
56d0c5a6 216const wxDateTime wxDefaultDateTime;
2ef31e80
VZ
217
218wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
219
220// ----------------------------------------------------------------------------
221// private globals
fcc3d7cb
VZ
222// ----------------------------------------------------------------------------
223
224// a critical section is needed to protect GetTimeZone() static
225// variable in MT case
c25a510b 226#if wxUSE_THREADS
2ef31e80 227 static wxCriticalSection gs_critsectTimezone;
fcc3d7cb
VZ
228#endif // wxUSE_THREADS
229
b76b015e
VZ
230// ----------------------------------------------------------------------------
231// private functions
232// ----------------------------------------------------------------------------
233
4f6aed9c
VZ
234// debugger helper: shows what the date really is
235#ifdef __WXDEBUG__
236extern const wxChar *wxDumpDate(const wxDateTime* dt)
237{
0de868d9 238 static wxChar buf[128];
4f6aed9c
VZ
239
240 wxStrcpy(buf, dt->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
241
242 return buf;
243}
244#endif // Debug
245
fcc3d7cb
VZ
246// get the number of days in the given month of the given year
247static inline
248wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
249{
e6ec579c
VZ
250 // the number of days in month in Julian/Gregorian calendar: the first line
251 // is for normal years, the second one is for the leap ones
252 static wxDateTime::wxDateTime_t daysInMonth[2][MONTHS_IN_YEAR] =
253 {
254 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
255 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
256 };
257
258 return daysInMonth[wxDateTime::IsLeapYear(year)][month];
fcc3d7cb
VZ
259}
260
261// ensure that the timezone variable is set by calling localtime
262static int GetTimeZone()
263{
264 // set to TRUE when the timezone is set
265 static bool s_timezoneSet = FALSE;
0779cc9a
GD
266#ifdef WX_GMTOFF_IN_TM
267 static long gmtoffset = LONG_MAX; // invalid timezone
268#endif
269
fcc3d7cb
VZ
270 wxCRIT_SECT_LOCKER(lock, gs_critsectTimezone);
271
272 if ( !s_timezoneSet )
273 {
1ef54dcf 274 // just call localtime() instead of figuring out whether this system
e6ec579c 275 // supports tzset(), _tzset() or something else
0779cc9a
GD
276 time_t t = 0;
277 struct tm *tm;
fcc3d7cb 278
0779cc9a 279 tm = localtime(&t);
fcc3d7cb 280 s_timezoneSet = TRUE;
0779cc9a
GD
281#ifdef WX_GMTOFF_IN_TM
282 gmtoffset = tm->tm_gmtoff;
283#endif
fcc3d7cb
VZ
284 }
285
0779cc9a
GD
286#ifdef WX_GMTOFF_IN_TM
287 return (int)gmtoffset;
288#else
6de20863 289 return (int)WX_TIMEZONE;
0779cc9a 290#endif
fcc3d7cb
VZ
291}
292
e6ec579c 293// return the integral part of the JDN for the midnight of the given date (to
1ef54dcf
VZ
294// get the real JDN you need to add 0.5, this is, in fact, JDN of the
295// noon of the previous day)
e6ec579c
VZ
296static long GetTruncatedJDN(wxDateTime::wxDateTime_t day,
297 wxDateTime::Month mon,
298 int year)
299{
1ef54dcf
VZ
300 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
301
302 // check the date validity
303 wxASSERT_MSG(
304 (year > JDN_0_YEAR) ||
305 ((year == JDN_0_YEAR) && (mon > JDN_0_MONTH)) ||
306 ((year == JDN_0_YEAR) && (mon == JDN_0_MONTH) && (day >= JDN_0_DAY)),
307 _T("date out of range - can't convert to JDN")
308 );
309
310 // make the year positive to avoid problems with negative numbers division
311 year += 4800;
312
313 // months are counted from March here
314 int month;
315 if ( mon >= wxDateTime::Mar )
e6ec579c 316 {
1ef54dcf 317 month = mon - 2;
e6ec579c 318 }
1ef54dcf 319 else
e6ec579c 320 {
1ef54dcf
VZ
321 month = mon + 10;
322 year--;
323 }
e6ec579c 324
1ef54dcf
VZ
325 // now we can simply add all the contributions together
326 return ((year / 100) * DAYS_PER_400_YEARS) / 4
327 + ((year % 100) * DAYS_PER_4_YEARS) / 4
328 + (month * DAYS_PER_5_MONTHS + 2) / 5
329 + day
330 - JDN_OFFSET;
e6ec579c
VZ
331}
332
2f02cb89 333// this function is a wrapper around strftime(3)
b76b015e
VZ
334static wxString CallStrftime(const wxChar *format, const tm* tm)
335{
68ee7c47 336 wxChar buf[4096];
b76b015e
VZ
337 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
338 {
68ee7c47 339 // buffer is too small?
b76b015e
VZ
340 wxFAIL_MSG(_T("strftime() failed"));
341 }
342
343 return wxString(buf);
344}
345
2f02cb89
VZ
346// if year and/or month have invalid values, replace them with the current ones
347static void ReplaceDefaultYearMonthWithCurrent(int *year,
348 wxDateTime::Month *month)
349{
350 struct tm *tmNow = NULL;
351
352 if ( *year == wxDateTime::Inv_Year )
353 {
354 tmNow = wxDateTime::GetTmNow();
355
356 *year = 1900 + tmNow->tm_year;
357 }
358
359 if ( *month == wxDateTime::Inv_Month )
360 {
361 if ( !tmNow )
362 tmNow = wxDateTime::GetTmNow();
363
364 *month = (wxDateTime::Month)tmNow->tm_mon;
365 }
366}
367
f0f951fa
VZ
368// fll the struct tm with default values
369static void InitTm(struct tm& tm)
370{
371 // struct tm may have etxra fields (undocumented and with unportable
372 // names) which, nevertheless, must be set to 0
373 memset(&tm, 0, sizeof(struct tm));
374
375 tm.tm_mday = 1; // mday 0 is invalid
376 tm.tm_year = 76; // any valid year
377 tm.tm_isdst = -1; // auto determine
378}
379
cd0b1709
VZ
380// parsing helpers
381// ---------------
382
383// return the month if the string is a month name or Inv_Month otherwise
f0f951fa 384static wxDateTime::Month GetMonthFromName(const wxString& name, int flags)
cd0b1709
VZ
385{
386 wxDateTime::Month mon;
387 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
388 {
f0f951fa
VZ
389 // case-insensitive comparison either one of or with both abbreviated
390 // and not versions
391 if ( flags & wxDateTime::Name_Full )
cd0b1709 392 {
f0f951fa
VZ
393 if ( name.CmpNoCase(wxDateTime::
394 GetMonthName(mon, wxDateTime::Name_Full)) == 0 )
395 {
396 break;
397 }
398 }
399
400 if ( flags & wxDateTime::Name_Abbr )
401 {
402 if ( name.CmpNoCase(wxDateTime::
403 GetMonthName(mon, wxDateTime::Name_Abbr)) == 0 )
404 {
405 break;
406 }
cd0b1709
VZ
407 }
408 }
409
410 return mon;
411}
412
413// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
f0f951fa 414static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name, int flags)
cd0b1709
VZ
415{
416 wxDateTime::WeekDay wd;
417 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
418 {
f0f951fa
VZ
419 // case-insensitive comparison either one of or with both abbreviated
420 // and not versions
421 if ( flags & wxDateTime::Name_Full )
cd0b1709 422 {
f0f951fa
VZ
423 if ( name.CmpNoCase(wxDateTime::
424 GetWeekDayName(wd, wxDateTime::Name_Full)) == 0 )
425 {
426 break;
427 }
428 }
429
430 if ( flags & wxDateTime::Name_Abbr )
431 {
432 if ( name.CmpNoCase(wxDateTime::
433 GetWeekDayName(wd, wxDateTime::Name_Abbr)) == 0 )
434 {
435 break;
436 }
cd0b1709
VZ
437 }
438 }
439
440 return wd;
441}
442
f6bcfd97
BP
443// scans all digits (but no more than len) and returns the resulting number
444static bool GetNumericToken(size_t len, const wxChar*& p, unsigned long *number)
f0f951fa 445{
f6bcfd97 446 size_t n = 1;
f0f951fa
VZ
447 wxString s;
448 while ( wxIsdigit(*p) )
449 {
450 s += *p++;
f6bcfd97
BP
451
452 if ( len && ++n > len )
453 break;
f0f951fa
VZ
454 }
455
fde5a86b 456 return !!s && s.ToULong(number);
f0f951fa
VZ
457}
458
459// scans all alphabetic characters and returns the resulting string
460static wxString GetAlphaToken(const wxChar*& p)
461{
462 wxString s;
463 while ( wxIsalpha(*p) )
464 {
465 s += *p++;
466 }
467
468 return s;
469}
470
0979c962
VZ
471// ============================================================================
472// implementation of wxDateTime
473// ============================================================================
474
b76b015e
VZ
475// ----------------------------------------------------------------------------
476// struct Tm
477// ----------------------------------------------------------------------------
478
479wxDateTime::Tm::Tm()
480{
481 year = (wxDateTime_t)wxDateTime::Inv_Year;
482 mon = wxDateTime::Inv_Month;
483 mday = 0;
e6ec579c 484 hour = min = sec = msec = 0;
b76b015e
VZ
485 wday = wxDateTime::Inv_WeekDay;
486}
487
299fcbfe
VZ
488wxDateTime::Tm::Tm(const struct tm& tm, const TimeZone& tz)
489 : m_tz(tz)
b76b015e 490{
e6ec579c 491 msec = 0;
b76b015e
VZ
492 sec = tm.tm_sec;
493 min = tm.tm_min;
494 hour = tm.tm_hour;
495 mday = tm.tm_mday;
fcc3d7cb 496 mon = (wxDateTime::Month)tm.tm_mon;
b76b015e
VZ
497 year = 1900 + tm.tm_year;
498 wday = tm.tm_wday;
499 yday = tm.tm_yday;
500}
501
502bool wxDateTime::Tm::IsValid() const
503{
504 // we allow for the leap seconds, although we don't use them (yet)
fcc3d7cb 505 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
c5a1681b 506 (mday <= GetNumOfDaysInMonth(year, mon)) &&
e6ec579c 507 (hour < 24) && (min < 60) && (sec < 62) && (msec < 1000);
b76b015e
VZ
508}
509
510void wxDateTime::Tm::ComputeWeekDay()
511{
c5a1681b
VZ
512 // compute the week day from day/month/year: we use the dumbest algorithm
513 // possible: just compute our JDN and then use the (simple to derive)
514 // formula: weekday = (JDN + 1.5) % 7
515 wday = (wxDateTime::WeekDay)(GetTruncatedJDN(mday, mon, year) + 2) % 7;
b76b015e
VZ
516}
517
e6ec579c 518void wxDateTime::Tm::AddMonths(int monDiff)
fcc3d7cb
VZ
519{
520 // normalize the months field
521 while ( monDiff < -mon )
522 {
523 year--;
524
525 monDiff += MONTHS_IN_YEAR;
526 }
527
2ef31e80 528 while ( monDiff + mon >= MONTHS_IN_YEAR )
fcc3d7cb
VZ
529 {
530 year++;
239446b4
VZ
531
532 monDiff -= MONTHS_IN_YEAR;
fcc3d7cb
VZ
533 }
534
535 mon = (wxDateTime::Month)(mon + monDiff);
536
e6ec579c 537 wxASSERT_MSG( mon >= 0 && mon < MONTHS_IN_YEAR, _T("logic error") );
4f6aed9c
VZ
538
539 // NB: we don't check here that the resulting date is valid, this function
540 // is private and the caller must check it if needed
fcc3d7cb
VZ
541}
542
e6ec579c 543void wxDateTime::Tm::AddDays(int dayDiff)
fcc3d7cb
VZ
544{
545 // normalize the days field
9d9b7755 546 while ( dayDiff + mday < 1 )
fcc3d7cb
VZ
547 {
548 AddMonths(-1);
549
9d9b7755 550 dayDiff += GetNumOfDaysInMonth(year, mon);
fcc3d7cb
VZ
551 }
552
9d9b7755 553 mday += dayDiff;
fcc3d7cb
VZ
554 while ( mday > GetNumOfDaysInMonth(year, mon) )
555 {
556 mday -= GetNumOfDaysInMonth(year, mon);
557
558 AddMonths(1);
559 }
560
561 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
562 _T("logic error") );
563}
564
565// ----------------------------------------------------------------------------
566// class TimeZone
567// ----------------------------------------------------------------------------
568
569wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
570{
571 switch ( tz )
572 {
573 case wxDateTime::Local:
299fcbfe
VZ
574 // get the offset from C RTL: it returns the difference GMT-local
575 // while we want to have the offset _from_ GMT, hence the '-'
576 m_offset = -GetTimeZone();
fcc3d7cb
VZ
577 break;
578
579 case wxDateTime::GMT_12:
580 case wxDateTime::GMT_11:
581 case wxDateTime::GMT_10:
582 case wxDateTime::GMT_9:
583 case wxDateTime::GMT_8:
584 case wxDateTime::GMT_7:
585 case wxDateTime::GMT_6:
586 case wxDateTime::GMT_5:
587 case wxDateTime::GMT_4:
588 case wxDateTime::GMT_3:
589 case wxDateTime::GMT_2:
590 case wxDateTime::GMT_1:
299fcbfe 591 m_offset = -3600*(wxDateTime::GMT0 - tz);
fcc3d7cb
VZ
592 break;
593
594 case wxDateTime::GMT0:
595 case wxDateTime::GMT1:
596 case wxDateTime::GMT2:
597 case wxDateTime::GMT3:
598 case wxDateTime::GMT4:
599 case wxDateTime::GMT5:
600 case wxDateTime::GMT6:
601 case wxDateTime::GMT7:
602 case wxDateTime::GMT8:
603 case wxDateTime::GMT9:
604 case wxDateTime::GMT10:
605 case wxDateTime::GMT11:
606 case wxDateTime::GMT12:
299fcbfe 607 m_offset = 3600*(tz - wxDateTime::GMT0);
fcc3d7cb
VZ
608 break;
609
610 case wxDateTime::A_CST:
611 // Central Standard Time in use in Australia = UTC + 9.5
cd0b1709 612 m_offset = 60l*(9*60 + 30);
fcc3d7cb
VZ
613 break;
614
615 default:
616 wxFAIL_MSG( _T("unknown time zone") );
617 }
618}
619
b76b015e
VZ
620// ----------------------------------------------------------------------------
621// static functions
622// ----------------------------------------------------------------------------
623
624/* static */
625bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
626{
2f02cb89
VZ
627 if ( year == Inv_Year )
628 year = GetCurrentYear();
629
b76b015e
VZ
630 if ( cal == Gregorian )
631 {
632 // in Gregorian calendar leap years are those divisible by 4 except
633 // those divisible by 100 unless they're also divisible by 400
634 // (in some countries, like Russia and Greece, additional corrections
635 // exist, but they won't manifest themselves until 2700)
636 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
637 }
638 else if ( cal == Julian )
639 {
640 // in Julian calendar the rule is simpler
641 return year % 4 == 0;
642 }
643 else
644 {
645 wxFAIL_MSG(_T("unknown calendar"));
646
647 return FALSE;
648 }
649}
650
fcc3d7cb
VZ
651/* static */
652int wxDateTime::GetCentury(int year)
653{
654 return year > 0 ? year / 100 : year / 100 - 1;
655}
656
b76b015e
VZ
657/* static */
658int wxDateTime::ConvertYearToBC(int year)
659{
660 // year 0 is BC 1
661 return year > 0 ? year : year - 1;
662}
663
664/* static */
665int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
666{
667 switch ( cal )
668 {
669 case Gregorian:
670 return Now().GetYear();
671
672 case Julian:
673 wxFAIL_MSG(_T("TODO"));
674 break;
675
676 default:
677 wxFAIL_MSG(_T("unsupported calendar"));
678 break;
679 }
680
681 return Inv_Year;
682}
683
684/* static */
685wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
686{
687 switch ( cal )
688 {
689 case Gregorian:
690 return Now().GetMonth();
b76b015e
VZ
691
692 case Julian:
693 wxFAIL_MSG(_T("TODO"));
694 break;
695
696 default:
697 wxFAIL_MSG(_T("unsupported calendar"));
698 break;
699 }
700
701 return Inv_Month;
702}
703
2f02cb89
VZ
704/* static */
705wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
706{
707 if ( year == Inv_Year )
708 {
709 // take the current year if none given
710 year = GetCurrentYear();
711 }
712
713 switch ( cal )
714 {
715 case Gregorian:
716 case Julian:
717 return IsLeapYear(year) ? 366 : 365;
2f02cb89
VZ
718
719 default:
720 wxFAIL_MSG(_T("unsupported calendar"));
721 break;
722 }
723
724 return 0;
725}
726
b76b015e
VZ
727/* static */
728wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
729 int year,
730 wxDateTime::Calendar cal)
731{
fcc3d7cb 732 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
b76b015e
VZ
733
734 if ( cal == Gregorian || cal == Julian )
735 {
736 if ( year == Inv_Year )
737 {
738 // take the current year if none given
739 year = GetCurrentYear();
740 }
741
fcc3d7cb 742 return GetNumOfDaysInMonth(year, month);
b76b015e
VZ
743 }
744 else
745 {
746 wxFAIL_MSG(_T("unsupported calendar"));
747
748 return 0;
749 }
750}
751
752/* static */
f0f951fa
VZ
753wxString wxDateTime::GetMonthName(wxDateTime::Month month,
754 wxDateTime::NameFlags flags)
b76b015e
VZ
755{
756 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
757
68ee7c47
VZ
758 // notice that we must set all the fields to avoid confusing libc (GNU one
759 // gets confused to a crash if we don't do this)
cd0b1709 760 tm tm;
f0f951fa 761 InitTm(tm);
cd0b1709 762 tm.tm_mon = month;
b76b015e 763
f0f951fa 764 return CallStrftime(flags == Name_Abbr ? _T("%b") : _T("%B"), &tm);
b76b015e
VZ
765}
766
767/* static */
f0f951fa
VZ
768wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday,
769 wxDateTime::NameFlags flags)
b76b015e
VZ
770{
771 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
772
773 // take some arbitrary Sunday
f0f951fa
VZ
774 tm tm;
775 InitTm(tm);
776 tm.tm_mday = 28;
777 tm.tm_mon = Nov;
778 tm.tm_year = 99;
b76b015e 779
1ef54dcf 780 // and offset it by the number of days needed to get the correct wday
b76b015e
VZ
781 tm.tm_mday += wday;
782
c5a1681b
VZ
783 // call mktime() to normalize it...
784 (void)mktime(&tm);
785
786 // ... and call strftime()
f0f951fa
VZ
787 return CallStrftime(flags == Name_Abbr ? _T("%a") : _T("%A"), &tm);
788}
789
790/* static */
791void wxDateTime::GetAmPmStrings(wxString *am, wxString *pm)
792{
793 tm tm;
794 InitTm(tm);
795 if ( am )
796 {
797 *am = CallStrftime(_T("%p"), &tm);
798 }
799 if ( pm )
800 {
801 tm.tm_hour = 13;
802 *pm = CallStrftime(_T("%p"), &tm);
803 }
b76b015e
VZ
804}
805
239446b4
VZ
806// ----------------------------------------------------------------------------
807// Country stuff: date calculations depend on the country (DST, work days,
808// ...), so we need to know which rules to follow.
809// ----------------------------------------------------------------------------
810
811/* static */
812wxDateTime::Country wxDateTime::GetCountry()
813{
f0f951fa
VZ
814 // TODO use LOCALE_ICOUNTRY setting under Win32
815
239446b4
VZ
816 if ( ms_country == Country_Unknown )
817 {
818 // try to guess from the time zone name
819 time_t t = time(NULL);
820 struct tm *tm = localtime(&t);
821
822 wxString tz = CallStrftime(_T("%Z"), tm);
823 if ( tz == _T("WET") || tz == _T("WEST") )
824 {
825 ms_country = UK;
826 }
827 else if ( tz == _T("CET") || tz == _T("CEST") )
828 {
829 ms_country = Country_EEC;
830 }
831 else if ( tz == _T("MSK") || tz == _T("MSD") )
832 {
833 ms_country = Russia;
834 }
835 else if ( tz == _T("AST") || tz == _T("ADT") ||
836 tz == _T("EST") || tz == _T("EDT") ||
837 tz == _T("CST") || tz == _T("CDT") ||
838 tz == _T("MST") || tz == _T("MDT") ||
839 tz == _T("PST") || tz == _T("PDT") )
840 {
841 ms_country = USA;
842 }
843 else
844 {
845 // well, choose a default one
846 ms_country = USA;
847 }
848 }
849
850 return ms_country;
851}
852
853/* static */
854void wxDateTime::SetCountry(wxDateTime::Country country)
855{
856 ms_country = country;
857}
858
859/* static */
860bool wxDateTime::IsWestEuropeanCountry(Country country)
861{
862 if ( country == Country_Default )
863 {
864 country = GetCountry();
865 }
866
867 return (Country_WesternEurope_Start <= country) &&
868 (country <= Country_WesternEurope_End);
869}
870
871// ----------------------------------------------------------------------------
872// DST calculations: we use 3 different rules for the West European countries,
873// USA and for the rest of the world. This is undoubtedly false for many
874// countries, but I lack the necessary info (and the time to gather it),
875// please add the other rules here!
876// ----------------------------------------------------------------------------
877
878/* static */
879bool wxDateTime::IsDSTApplicable(int year, Country country)
880{
881 if ( year == Inv_Year )
882 {
883 // take the current year if none given
884 year = GetCurrentYear();
885 }
886
887 if ( country == Country_Default )
888 {
889 country = GetCountry();
890 }
891
892 switch ( country )
893 {
894 case USA:
895 case UK:
896 // DST was first observed in the US and UK during WWI, reused
897 // during WWII and used again since 1966
898 return year >= 1966 ||
899 (year >= 1942 && year <= 1945) ||
900 (year == 1918 || year == 1919);
901
902 default:
903 // assume that it started after WWII
904 return year > 1950;
905 }
906}
907
908/* static */
909wxDateTime wxDateTime::GetBeginDST(int year, Country country)
910{
911 if ( year == Inv_Year )
912 {
913 // take the current year if none given
914 year = GetCurrentYear();
915 }
916
917 if ( country == Country_Default )
918 {
919 country = GetCountry();
920 }
921
922 if ( !IsDSTApplicable(year, country) )
923 {
2ef31e80 924 return wxInvalidDateTime;
239446b4
VZ
925 }
926
927 wxDateTime dt;
928
929 if ( IsWestEuropeanCountry(country) || (country == Russia) )
930 {
931 // DST begins at 1 a.m. GMT on the last Sunday of March
932 if ( !dt.SetToLastWeekDay(Sun, Mar, year) )
933 {
934 // weird...
935 wxFAIL_MSG( _T("no last Sunday in March?") );
936 }
937
938 dt += wxTimeSpan::Hours(1);
939
41acf5c0
VZ
940 // disable DST tests because it could result in an infinite recursion!
941 dt.MakeGMT(TRUE);
239446b4
VZ
942 }
943 else switch ( country )
944 {
945 case USA:
946 switch ( year )
947 {
948 case 1918:
949 case 1919:
950 // don't know for sure - assume it was in effect all year
951
952 case 1943:
953 case 1944:
954 case 1945:
955 dt.Set(1, Jan, year);
956 break;
957
958 case 1942:
959 // DST was installed Feb 2, 1942 by the Congress
960 dt.Set(2, Feb, year);
961 break;
962
963 // Oil embargo changed the DST period in the US
964 case 1974:
965 dt.Set(6, Jan, 1974);
966 break;
967
968 case 1975:
969 dt.Set(23, Feb, 1975);
970 break;
971
972 default:
973 // before 1986, DST begun on the last Sunday of April, but
974 // in 1986 Reagan changed it to begin at 2 a.m. of the
975 // first Sunday in April
976 if ( year < 1986 )
977 {
978 if ( !dt.SetToLastWeekDay(Sun, Apr, year) )
979 {
980 // weird...
981 wxFAIL_MSG( _T("no first Sunday in April?") );
982 }
983 }
984 else
985 {
986 if ( !dt.SetToWeekDay(Sun, 1, Apr, year) )
987 {
988 // weird...
989 wxFAIL_MSG( _T("no first Sunday in April?") );
990 }
991 }
992
993 dt += wxTimeSpan::Hours(2);
994
995 // TODO what about timezone??
996 }
997
998 break;
999
1000 default:
1001 // assume Mar 30 as the start of the DST for the rest of the world
1002 // - totally bogus, of course
1003 dt.Set(30, Mar, year);
1004 }
1005
1006 return dt;
1007}
1008
1009/* static */
1010wxDateTime wxDateTime::GetEndDST(int year, Country country)
1011{
1012 if ( year == Inv_Year )
1013 {
1014 // take the current year if none given
1015 year = GetCurrentYear();
1016 }
1017
1018 if ( country == Country_Default )
1019 {
1020 country = GetCountry();
1021 }
1022
1023 if ( !IsDSTApplicable(year, country) )
1024 {
2ef31e80 1025 return wxInvalidDateTime;
239446b4
VZ
1026 }
1027
1028 wxDateTime dt;
1029
1030 if ( IsWestEuropeanCountry(country) || (country == Russia) )
1031 {
5f287370 1032 // DST ends at 1 a.m. GMT on the last Sunday of October
239446b4
VZ
1033 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1034 {
1035 // weirder and weirder...
1036 wxFAIL_MSG( _T("no last Sunday in October?") );
1037 }
1038
1039 dt += wxTimeSpan::Hours(1);
1040
41acf5c0
VZ
1041 // disable DST tests because it could result in an infinite recursion!
1042 dt.MakeGMT(TRUE);
239446b4
VZ
1043 }
1044 else switch ( country )
1045 {
1046 case USA:
1047 switch ( year )
1048 {
1049 case 1918:
1050 case 1919:
1051 // don't know for sure - assume it was in effect all year
1052
1053 case 1943:
1054 case 1944:
1055 dt.Set(31, Dec, year);
1056 break;
1057
1058 case 1945:
1059 // the time was reset after the end of the WWII
1060 dt.Set(30, Sep, year);
1061 break;
1062
1063 default:
5f287370 1064 // DST ends at 2 a.m. on the last Sunday of October
239446b4
VZ
1065 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1066 {
1067 // weirder and weirder...
1068 wxFAIL_MSG( _T("no last Sunday in October?") );
1069 }
1070
1071 dt += wxTimeSpan::Hours(2);
1072
1073 // TODO what about timezone??
1074 }
1075 break;
1076
1077 default:
1078 // assume October 26th as the end of the DST - totally bogus too
1079 dt.Set(26, Oct, year);
1080 }
1081
1082 return dt;
1083}
1084
0979c962
VZ
1085// ----------------------------------------------------------------------------
1086// constructors and assignment operators
1087// ----------------------------------------------------------------------------
1088
f6bcfd97
BP
1089// return the current time with ms precision
1090/* static */ wxDateTime wxDateTime::UNow()
1091{
1092 return wxDateTime(wxGetLocalTimeMillis());
1093}
1094
299fcbfe
VZ
1095// the values in the tm structure contain the local time
1096wxDateTime& wxDateTime::Set(const struct tm& tm)
0979c962 1097{
299fcbfe 1098 struct tm tm2(tm);
b76b015e 1099 time_t timet = mktime(&tm2);
1ef54dcf 1100
4afd7529 1101 if ( timet == (time_t)-1 )
0979c962 1102 {
4afd7529
VZ
1103 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1104 // less than timezone - try to make it work for this case
1105 if ( tm2.tm_year == 70 && tm2.tm_mon == 0 && tm2.tm_mday == 1 )
1106 {
1107 // add timezone to make sure that date is in range
1108 tm2.tm_sec -= GetTimeZone();
1109
1110 timet = mktime(&tm2);
1111 if ( timet != (time_t)-1 )
1112 {
1113 timet += GetTimeZone();
1114
1115 return Set(timet);
1116 }
1117 }
1118
1119 wxFAIL_MSG( _T("mktime() failed") );
0979c962 1120
384223b3
VZ
1121 *this = wxInvalidDateTime;
1122
1123 return *this;
0979c962
VZ
1124 }
1125 else
1126 {
1127 return Set(timet);
1128 }
1129}
1130
1131wxDateTime& wxDateTime::Set(wxDateTime_t hour,
1132 wxDateTime_t minute,
1133 wxDateTime_t second,
1134 wxDateTime_t millisec)
1135{
1136 // we allow seconds to be 61 to account for the leap seconds, even if we
1137 // don't use them really
384223b3
VZ
1138 wxDATETIME_CHECK( hour < 24 &&
1139 second < 62 &&
1140 minute < 60 &&
1141 millisec < 1000,
1142 _T("Invalid time in wxDateTime::Set()") );
0979c962
VZ
1143
1144 // get the current date from system
9d9b7755 1145 struct tm *tm = GetTmNow();
299fcbfe 1146
384223b3 1147 wxDATETIME_CHECK( tm, _T("localtime() failed") );
0979c962
VZ
1148
1149 // adjust the time
1150 tm->tm_hour = hour;
1151 tm->tm_min = minute;
1152 tm->tm_sec = second;
1153
b76b015e 1154 (void)Set(*tm);
0979c962
VZ
1155
1156 // and finally adjust milliseconds
1157 return SetMillisecond(millisec);
1158}
1159
1160wxDateTime& wxDateTime::Set(wxDateTime_t day,
1161 Month month,
1162 int year,
1163 wxDateTime_t hour,
1164 wxDateTime_t minute,
1165 wxDateTime_t second,
1166 wxDateTime_t millisec)
1167{
384223b3
VZ
1168 wxDATETIME_CHECK( hour < 24 &&
1169 second < 62 &&
1170 minute < 60 &&
1171 millisec < 1000,
1172 _T("Invalid time in wxDateTime::Set()") );
0979c962 1173
2f02cb89 1174 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 1175
384223b3
VZ
1176 wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
1177 _T("Invalid date in wxDateTime::Set()") );
0979c962
VZ
1178
1179 // the range of time_t type (inclusive)
1180 static const int yearMinInRange = 1970;
1181 static const int yearMaxInRange = 2037;
1182
1183 // test only the year instead of testing for the exact end of the Unix
1184 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 1185 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
1186 {
1187 // use the standard library version if the date is in range - this is
b76b015e 1188 // probably more efficient than our code
0979c962 1189 struct tm tm;
b76b015e 1190 tm.tm_year = year - 1900;
0979c962
VZ
1191 tm.tm_mon = month;
1192 tm.tm_mday = day;
1193 tm.tm_hour = hour;
1194 tm.tm_min = minute;
1195 tm.tm_sec = second;
299fcbfe 1196 tm.tm_isdst = -1; // mktime() will guess it
0979c962
VZ
1197
1198 (void)Set(tm);
1199
1200 // and finally adjust milliseconds
1201 return SetMillisecond(millisec);
1202 }
1203 else
1204 {
1205 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 1206 // milliseconds between the given date and the epoch
e6ec579c
VZ
1207
1208 // get the JDN for the midnight of this day
1209 m_time = GetTruncatedJDN(day, month, year);
1210 m_time -= EPOCH_JDN;
1211 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1212
299fcbfe
VZ
1213 // JDN corresponds to GMT, we take localtime
1214 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
b76b015e
VZ
1215 }
1216
1217 return *this;
1218}
1219
e6ec579c
VZ
1220wxDateTime& wxDateTime::Set(double jdn)
1221{
1ef54dcf
VZ
1222 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1223 // EPOCH_JDN + 0.5
1224 jdn -= EPOCH_JDN + 0.5;
1225
cd0b1709
VZ
1226 jdn *= MILLISECONDS_PER_DAY;
1227
9c2882d9 1228 m_time.Assign(jdn);
e6ec579c
VZ
1229
1230 return *this;
1231}
1232
9d9b7755
VZ
1233wxDateTime& wxDateTime::ResetTime()
1234{
1235 Tm tm = GetTm();
1236
1237 if ( tm.hour || tm.min || tm.sec || tm.msec )
1238 {
1239 tm.msec =
1240 tm.sec =
1241 tm.min =
1242 tm.hour = 0;
1243
1244 Set(tm);
1245 }
1246
1247 return *this;
1248}
1249
2b5f62a0
VZ
1250// ----------------------------------------------------------------------------
1251// DOS Date and Time Format functions
1252// ----------------------------------------------------------------------------
1253// the dos date and time value is an unsigned 32 bit value in the format:
1254// YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1255//
1256// Y = year offset from 1980 (0-127)
1257// M = month (1-12)
1258// D = day of month (1-31)
1259// h = hour (0-23)
1260// m = minute (0-59)
1261// s = bisecond (0-29) each bisecond indicates two seconds
1262// ----------------------------------------------------------------------------
1263
1264wxDateTime& wxDateTime::SetFromDOS(unsigned long ddt)
1265{
1266 struct tm tm;
1267
1268 long year = ddt & 0xFE000000;
1269 year >>= 25;
1270 year += 80;
1271 tm.tm_year = year;
1272
1273 long month = ddt & 0x1E00000;
1274 month >>= 21;
1275 month -= 1;
1276 tm.tm_mon = month;
1277
1278 long day = ddt & 0x1F0000;
1279 day >>= 16;
1280 tm.tm_mday = day;
1281
1282 long hour = ddt & 0xF800;
1283 hour >>= 11;
1284 tm.tm_hour = hour;
1285
1286 long minute = ddt & 0x7E0;
1287 minute >>= 5;
1288 tm.tm_min = minute;
1289
1290 long second = ddt & 0x1F;
1291 tm.tm_sec = second * 2;
1292
1293 return Set(mktime(&tm));
1294}
1295
1296unsigned long wxDateTime::GetAsDOS() const
1297{
1298 unsigned long ddt;
1299 time_t ticks = GetTicks();
1300 struct tm *tm = localtime(&ticks);
1301
1302 long year = tm->tm_year;
1303 year -= 80;
1304 year <<= 25;
1305
1306 long month = tm->tm_mon;
1307 month += 1;
1308 month <<= 21;
1309
1310 long day = tm->tm_mday;
1311 day <<= 16;
1312
1313 long hour = tm->tm_hour;
1314 hour <<= 11;
1315
1316 long minute = tm->tm_min;
1317 minute <<= 5;
1318
1319 long second = tm->tm_sec;
1320 second /= 2;
1321
1322 ddt = year | month | day | hour | minute | second;
1323 return ddt;
1324}
1325
b76b015e
VZ
1326// ----------------------------------------------------------------------------
1327// time_t <-> broken down time conversions
1328// ----------------------------------------------------------------------------
1329
299fcbfe 1330wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
b76b015e
VZ
1331{
1332 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1333
1334 time_t time = GetTicks();
1335 if ( time != (time_t)-1 )
1336 {
1337 // use C RTL functions
299fcbfe
VZ
1338 tm *tm;
1339 if ( tz.GetOffset() == -GetTimeZone() )
1340 {
1341 // we are working with local time
1342 tm = localtime(&time);
c5a1681b
VZ
1343
1344 // should never happen
f6bcfd97 1345 wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
299fcbfe
VZ
1346 }
1347 else
1348 {
13111b2a 1349 time += (time_t)tz.GetOffset();
33ac7e6f 1350#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1351 int time2 = (int) time;
9d9b7755 1352 if ( time2 >= 0 )
fb10f04c 1353#else
9d9b7755 1354 if ( time >= 0 )
fb10f04c 1355#endif
c5a1681b
VZ
1356 {
1357 tm = gmtime(&time);
b76b015e 1358
c5a1681b
VZ
1359 // should never happen
1360 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1361 }
1362 else
1363 {
1364 tm = (struct tm *)NULL;
1365 }
1366 }
b76b015e 1367
c5a1681b
VZ
1368 if ( tm )
1369 {
f6bcfd97
BP
1370 // adjust the milliseconds
1371 Tm tm2(*tm, tz);
1372 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
1373 tm2.msec = (wxDateTime_t)(timeOnly % 1000);
1374 return tm2;
c5a1681b
VZ
1375 }
1376 //else: use generic code below
b76b015e 1377 }
e6ec579c 1378
c5a1681b
VZ
1379 // remember the time and do the calculations with the date only - this
1380 // eliminates rounding errors of the floating point arithmetics
299fcbfe 1381
c5a1681b 1382 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1ef54dcf 1383
c5a1681b 1384 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1ef54dcf 1385
c5a1681b
VZ
1386 // we want to always have positive time and timeMidnight to be really
1387 // the midnight before it
1388 if ( timeOnly < 0 )
1389 {
1390 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1391 }
e6ec579c 1392
c5a1681b 1393 timeMidnight -= timeOnly;
1ef54dcf 1394
c5a1681b
VZ
1395 // calculate the Gregorian date from JDN for the midnight of our date:
1396 // this will yield day, month (in 1..12 range) and year
1ef54dcf 1397
c5a1681b
VZ
1398 // actually, this is the JDN for the noon of the previous day
1399 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1ef54dcf 1400
c5a1681b 1401 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1ef54dcf 1402
c5a1681b 1403 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1ef54dcf 1404
c5a1681b 1405 // calculate the century
479cd5de
VZ
1406 long temp = (jdn + JDN_OFFSET) * 4 - 1;
1407 long century = temp / DAYS_PER_400_YEARS;
1ef54dcf 1408
c5a1681b
VZ
1409 // then the year and day of year (1 <= dayOfYear <= 366)
1410 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
479cd5de
VZ
1411 long year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1412 long dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1ef54dcf 1413
c5a1681b
VZ
1414 // and finally the month and day of the month
1415 temp = dayOfYear * 5 - 3;
479cd5de
VZ
1416 long month = temp / DAYS_PER_5_MONTHS;
1417 long day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
c5a1681b
VZ
1418
1419 // month is counted from March - convert to normal
1420 if ( month < 10 )
1421 {
1422 month += 3;
1423 }
1424 else
1425 {
1426 year += 1;
1427 month -= 9;
1428 }
1ef54dcf 1429
c5a1681b
VZ
1430 // year is offset by 4800
1431 year -= 4800;
1ef54dcf 1432
c5a1681b
VZ
1433 // check that the algorithm gave us something reasonable
1434 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1435 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
e6ec579c 1436
c5a1681b
VZ
1437 // construct Tm from these values
1438 Tm tm;
1439 tm.year = (int)year;
1440 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1441 tm.mday = (wxDateTime_t)day;
479cd5de 1442 tm.msec = (wxDateTime_t)(timeOnly % 1000);
c5a1681b
VZ
1443 timeOnly -= tm.msec;
1444 timeOnly /= 1000; // now we have time in seconds
e6ec579c 1445
479cd5de 1446 tm.sec = (wxDateTime_t)(timeOnly % 60);
c5a1681b
VZ
1447 timeOnly -= tm.sec;
1448 timeOnly /= 60; // now we have time in minutes
e6ec579c 1449
479cd5de 1450 tm.min = (wxDateTime_t)(timeOnly % 60);
c5a1681b 1451 timeOnly -= tm.min;
e6ec579c 1452
479cd5de 1453 tm.hour = (wxDateTime_t)(timeOnly / 60);
b76b015e 1454
c5a1681b 1455 return tm;
b76b015e
VZ
1456}
1457
1458wxDateTime& wxDateTime::SetYear(int year)
1459{
1460 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1461
1462 Tm tm(GetTm());
1463 tm.year = year;
1464 Set(tm);
1465
1466 return *this;
1467}
1468
1469wxDateTime& wxDateTime::SetMonth(Month month)
1470{
1471 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1472
1473 Tm tm(GetTm());
1474 tm.mon = month;
1475 Set(tm);
1476
1477 return *this;
1478}
1479
1480wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1481{
1482 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1483
1484 Tm tm(GetTm());
1485 tm.mday = mday;
1486 Set(tm);
1487
1488 return *this;
1489}
1490
1491wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1492{
1493 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1494
1495 Tm tm(GetTm());
1496 tm.hour = hour;
1497 Set(tm);
1498
1499 return *this;
1500}
1501
1502wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1503{
1504 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1505
1506 Tm tm(GetTm());
1507 tm.min = min;
1508 Set(tm);
1509
1510 return *this;
1511}
1512
1513wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1514{
1515 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1516
1517 Tm tm(GetTm());
1518 tm.sec = sec;
1519 Set(tm);
0979c962
VZ
1520
1521 return *this;
1522}
b76b015e
VZ
1523
1524wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1525{
1526 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1527
1528 // we don't need to use GetTm() for this one
1529 m_time -= m_time % 1000l;
1530 m_time += millisecond;
1531
1532 return *this;
1533}
1534
1535// ----------------------------------------------------------------------------
1536// wxDateTime arithmetics
1537// ----------------------------------------------------------------------------
1538
1539wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1540{
1541 Tm tm(GetTm());
1542
1543 tm.year += diff.GetYears();
fcc3d7cb 1544 tm.AddMonths(diff.GetMonths());
4f6aed9c
VZ
1545
1546 // check that the resulting date is valid
1547 if ( tm.mday > GetNumOfDaysInMonth(tm.year, tm.mon) )
1548 {
1549 // We suppose that when adding one month to Jan 31 we want to get Feb
1550 // 28 (or 29), i.e. adding a month to the last day of the month should
1551 // give the last day of the next month which is quite logical.
1552 //
1553 // Unfortunately, there is no logic way to understand what should
1554 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1555 // We make it Feb 28 (last day too), but it is highly questionable.
1556 tm.mday = GetNumOfDaysInMonth(tm.year, tm.mon);
1557 }
1558
fcc3d7cb 1559 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
1560
1561 Set(tm);
1562
9d9b7755
VZ
1563 wxASSERT_MSG( IsSameTime(tm),
1564 _T("Add(wxDateSpan) shouldn't modify time") );
1565
b76b015e
VZ
1566 return *this;
1567}
1568
2f02cb89
VZ
1569// ----------------------------------------------------------------------------
1570// Weekday and monthday stuff
1571// ----------------------------------------------------------------------------
1572
2b5f62a0
VZ
1573bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1574 WeekDay weekday,
1575 WeekFlags flags)
4f6aed9c 1576{
2b5f62a0
VZ
1577 wxASSERT_MSG( numWeek > 0,
1578 _T("invalid week number: weeks are counted from 1") );
1579
4f6aed9c
VZ
1580 int year = GetYear();
1581
1582 // Jan 4 always lies in the 1st week of the year
1583 Set(4, Jan, year);
2b5f62a0 1584 SetToWeekDayInSameWeek(weekday, flags) += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c
VZ
1585
1586 if ( GetYear() != year )
1587 {
1588 // oops... numWeek was too big
1589 return FALSE;
1590 }
1591
1592 return TRUE;
1593}
1594
2f02cb89
VZ
1595wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1596 int year)
1597{
1598 // take the current month/year if none specified
1a8557b1
VZ
1599 if ( year == Inv_Year )
1600 year = GetYear();
1601 if ( month == Inv_Month )
1602 month = GetMonth();
2f02cb89 1603
fcc3d7cb 1604 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1605}
1606
2b5f62a0 1607wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1608{
384223b3 1609 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1610
2b5f62a0 1611 int wdayThis = GetWeekDay();
cd0b1709
VZ
1612 if ( weekday == wdayThis )
1613 {
1614 // nothing to do
1615 return *this;
1616 }
2b5f62a0
VZ
1617
1618 if ( flags == Default_First )
1619 {
1620 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1621 }
1622
1623 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1624 // is the first day in the week, but breaks down for Monday_First case so
1625 // we adjust the week days in this case
1626 if( flags == Monday_First )
1627 {
1628 if ( wdayThis == Sun )
1629 wdayThis += 7;
1630 }
1631 //else: Sunday_First, nothing to do
1632
1633 // go forward or back in time to the day we want
1634 if ( weekday < wdayThis )
cd0b1709 1635 {
f6bcfd97 1636 return Subtract(wxDateSpan::Days(wdayThis - weekday));
cd0b1709
VZ
1637 }
1638 else // weekday > wdayThis
1639 {
4f6aed9c 1640 return Add(wxDateSpan::Days(weekday - wdayThis));
cd0b1709
VZ
1641 }
1642}
1643
1644wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1645{
384223b3 1646 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1647
1648 int diff;
1649 WeekDay wdayThis = GetWeekDay();
1650 if ( weekday == wdayThis )
1651 {
1652 // nothing to do
1653 return *this;
1654 }
1655 else if ( weekday < wdayThis )
1656 {
1657 // need to advance a week
1658 diff = 7 - (wdayThis - weekday);
1659 }
1660 else // weekday > wdayThis
1661 {
1662 diff = weekday - wdayThis;
1663 }
1664
4f6aed9c 1665 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1666}
1667
1668wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1669{
384223b3 1670 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1671
1672 int diff;
1673 WeekDay wdayThis = GetWeekDay();
1674 if ( weekday == wdayThis )
1675 {
1676 // nothing to do
1677 return *this;
1678 }
1679 else if ( weekday > wdayThis )
1680 {
1681 // need to go to previous week
1682 diff = 7 - (weekday - wdayThis);
1683 }
1684 else // weekday < wdayThis
1685 {
1686 diff = wdayThis - weekday;
1687 }
1688
f6bcfd97 1689 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1690}
1691
2f02cb89
VZ
1692bool wxDateTime::SetToWeekDay(WeekDay weekday,
1693 int n,
1694 Month month,
1695 int year)
1696{
1697 wxCHECK_MSG( weekday != Inv_WeekDay, FALSE, _T("invalid weekday") );
1698
1699 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1700 // anyhow in such case - but may be should still give an assert for it?
1701
1702 // take the current month/year if none specified
1703 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1704
1705 wxDateTime dt;
1706
1707 // TODO this probably could be optimised somehow...
1708
1709 if ( n > 0 )
1710 {
1711 // get the first day of the month
1712 dt.Set(1, month, year);
1713
1714 // get its wday
1715 WeekDay wdayFirst = dt.GetWeekDay();
1716
1717 // go to the first weekday of the month
1718 int diff = weekday - wdayFirst;
1719 if ( diff < 0 )
1720 diff += 7;
1721
1722 // add advance n-1 weeks more
1723 diff += 7*(n - 1);
1724
239446b4 1725 dt += wxDateSpan::Days(diff);
2f02cb89 1726 }
239446b4 1727 else // count from the end of the month
2f02cb89
VZ
1728 {
1729 // get the last day of the month
1730 dt.SetToLastMonthDay(month, year);
1731
1732 // get its wday
1733 WeekDay wdayLast = dt.GetWeekDay();
1734
1735 // go to the last weekday of the month
1736 int diff = wdayLast - weekday;
1737 if ( diff < 0 )
1738 diff += 7;
1739
1740 // and rewind n-1 weeks from there
239446b4 1741 diff += 7*(-n - 1);
2f02cb89
VZ
1742
1743 dt -= wxDateSpan::Days(diff);
1744 }
1745
1746 // check that it is still in the same month
1747 if ( dt.GetMonth() == month )
1748 {
1749 *this = dt;
1750
1751 return TRUE;
1752 }
1753 else
1754 {
1755 // no such day in this month
1756 return FALSE;
1757 }
1758}
1759
239446b4
VZ
1760wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
1761{
239446b4
VZ
1762 Tm tm(GetTm(tz));
1763
f0f951fa 1764 return gs_cumulatedDays[IsLeapYear(tm.year)][tm.mon] + tm.mday;
239446b4
VZ
1765}
1766
9d9b7755
VZ
1767wxDateTime::wxDateTime_t wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags,
1768 const TimeZone& tz) const
239446b4 1769{
9d9b7755
VZ
1770 if ( flags == Default_First )
1771 {
1772 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1773 }
239446b4
VZ
1774
1775 wxDateTime_t nDayInYear = GetDayOfYear(tz);
9d9b7755 1776 wxDateTime_t week;
239446b4 1777
9d9b7755
VZ
1778 WeekDay wd = GetWeekDay(tz);
1779 if ( flags == Sunday_First )
1780 {
1781 week = (nDayInYear - wd + 7) / 7;
1782 }
1783 else
1784 {
1785 // have to shift the week days values
1786 week = (nDayInYear - (wd - 1 + 7) % 7 + 7) / 7;
1787 }
239446b4 1788
9d9b7755
VZ
1789 // FIXME some more elegant way??
1790 WeekDay wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
1791 if ( wdYearStart == Wed || wdYearStart == Thu )
239446b4
VZ
1792 {
1793 week++;
1794 }
1795
1796 return week;
68ee7c47
VZ
1797}
1798
9d9b7755
VZ
1799wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
1800 const TimeZone& tz) const
68ee7c47 1801{
9d9b7755
VZ
1802 Tm tm = GetTm(tz);
1803 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 1804 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 1805 if ( nWeek < 0 )
68ee7c47 1806 {
9d9b7755
VZ
1807 // this may happen for January when Jan, 1 is the last week of the
1808 // previous year
1809 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 1810 }
68ee7c47 1811
6dc6fda6 1812 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
1813}
1814
f0f951fa
VZ
1815wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
1816{
1817 int year = GetYear();
384223b3
VZ
1818 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
1819 _T("invalid year day") );
f0f951fa
VZ
1820
1821 bool isLeap = IsLeapYear(year);
1822 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
1823 {
1824 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1825 // don't need it neither - because of the CHECK above we know that
1826 // yday lies in December then
1827 if ( (mon == Dec) || (yday < gs_cumulatedDays[isLeap][mon + 1]) )
1828 {
1829 Set(yday - gs_cumulatedDays[isLeap][mon], mon, year);
1830
1831 break;
1832 }
1833 }
1834
1835 return *this;
1836}
1837
e6ec579c
VZ
1838// ----------------------------------------------------------------------------
1839// Julian day number conversion and related stuff
1840// ----------------------------------------------------------------------------
1841
1842double wxDateTime::GetJulianDayNumber() const
1843{
299fcbfe
VZ
1844 // JDN are always expressed for the GMT dates
1845 Tm tm(ToTimezone(GMT0).GetTm(GMT0));
e6ec579c
VZ
1846
1847 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1848
1849 // add the part GetTruncatedJDN() neglected
1850 result += 0.5;
1851
1852 // and now add the time: 86400 sec = 1 JDN
1853 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
1854}
1855
1856double wxDateTime::GetRataDie() const
1857{
1858 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1859 return GetJulianDayNumber() - 1721119.5 - 306;
1860}
1861
fcc3d7cb 1862// ----------------------------------------------------------------------------
299fcbfe 1863// timezone and DST stuff
fcc3d7cb
VZ
1864// ----------------------------------------------------------------------------
1865
299fcbfe 1866int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 1867{
299fcbfe
VZ
1868 wxCHECK_MSG( country == Country_Default, -1,
1869 _T("country support not implemented") );
1870
1871 // use the C RTL for the dates in the standard range
1872 time_t timet = GetTicks();
1873 if ( timet != (time_t)-1 )
1874 {
1875 tm *tm = localtime(&timet);
1876
1877 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
1878
1879 return tm->tm_isdst;
1880 }
1881 else
1882 {
239446b4
VZ
1883 int year = GetYear();
1884
1885 if ( !IsDSTApplicable(year, country) )
1886 {
1887 // no DST time in this year in this country
1888 return -1;
1889 }
299fcbfe 1890
239446b4 1891 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 1892 }
fcc3d7cb
VZ
1893}
1894
41acf5c0 1895wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 1896{
479cd5de 1897 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 1898
41acf5c0
VZ
1899 // we need to know whether DST is or not in effect for this date unless
1900 // the test disabled by the caller
1901 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
1902 {
1903 // FIXME we assume that the DST is always shifted by 1 hour
1904 secDiff -= 3600;
1905 }
1906
f6bcfd97 1907 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
1908}
1909
b76b015e
VZ
1910// ----------------------------------------------------------------------------
1911// wxDateTime to/from text representations
1912// ----------------------------------------------------------------------------
1913
299fcbfe 1914wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 1915{
e6ec579c
VZ
1916 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
1917
f6bcfd97
BP
1918 // we have to use our own implementation if the date is out of range of
1919 // strftime() or if we use non standard specificators
b76b015e 1920 time_t time = GetTicks();
f6bcfd97 1921 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
1922 {
1923 // use strftime()
299fcbfe
VZ
1924 tm *tm;
1925 if ( tz.GetOffset() == -GetTimeZone() )
1926 {
1927 // we are working with local time
1928 tm = localtime(&time);
c5a1681b
VZ
1929
1930 // should never happen
1931 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
299fcbfe
VZ
1932 }
1933 else
1934 {
479cd5de 1935 time += (int)tz.GetOffset();
299fcbfe 1936
33ac7e6f 1937#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1938 int time2 = (int) time;
9c2882d9 1939 if ( time2 >= 0 )
fb10f04c 1940#else
9c2882d9 1941 if ( time >= 0 )
fb10f04c 1942#endif
c5a1681b
VZ
1943 {
1944 tm = gmtime(&time);
b76b015e 1945
c5a1681b
VZ
1946 // should never happen
1947 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
1948 }
1949 else
1950 {
1951 tm = (struct tm *)NULL;
1952 }
1953 }
b76b015e 1954
c5a1681b 1955 if ( tm )
e6ec579c 1956 {
c5a1681b 1957 return CallStrftime(format, tm);
e6ec579c 1958 }
c5a1681b
VZ
1959 //else: use generic code below
1960 }
1961
68ee7c47 1962 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
1963 // complications, no GNU extensions but we do add support for a "%l" format
1964 // specifier allowing to get the number of milliseconds
68ee7c47 1965 Tm tm = GetTm(tz);
e6ec579c 1966
68ee7c47
VZ
1967 // used for calls to strftime() when we only deal with time
1968 struct tm tmTimeOnly;
1969 tmTimeOnly.tm_hour = tm.hour;
1970 tmTimeOnly.tm_min = tm.min;
1971 tmTimeOnly.tm_sec = tm.sec;
1972 tmTimeOnly.tm_wday = 0;
1973 tmTimeOnly.tm_yday = 0;
1974 tmTimeOnly.tm_mday = 1; // any date will do
1975 tmTimeOnly.tm_mon = 0;
1976 tmTimeOnly.tm_year = 76;
1977 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 1978
77c3e48a 1979 wxString tmp, res, fmt;
68ee7c47
VZ
1980 for ( const wxChar *p = format; *p; p++ )
1981 {
1982 if ( *p != _T('%') )
1983 {
1984 // copy as is
1985 res += *p;
b76b015e 1986
68ee7c47
VZ
1987 continue;
1988 }
e6ec579c 1989
77c3e48a 1990 // set the default format
68ee7c47 1991 switch ( *++p )
77c3e48a
VZ
1992 {
1993 case _T('Y'): // year has 4 digits
1994 fmt = _T("%04d");
1995 break;
1996
1997 case _T('j'): // day of year has 3 digits
f6bcfd97 1998 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
1999 fmt = _T("%03d");
2000 break;
2001
59d04dff
VZ
2002 case _T('w'): // week day as number has only one
2003 fmt = _T("%d");
2004 break;
2005
77c3e48a
VZ
2006 default:
2007 // it's either another valid format specifier in which case
2008 // the format is "%02d" (for all the rest) or we have the
2009 // field width preceding the format in which case it will
2010 // override the default format anyhow
2011 fmt = _T("%02d");
2012 }
2013
fc3398f8
VZ
2014 bool restart = TRUE;
2015 while ( restart )
68ee7c47 2016 {
fc3398f8 2017 restart = FALSE;
68ee7c47 2018
fc3398f8
VZ
2019 // start of the format specification
2020 switch ( *p )
2021 {
2022 case _T('a'): // a weekday name
2023 case _T('A'):
2024 // second parameter should be TRUE for abbreviated names
2025 res += GetWeekDayName(tm.GetWeekDay(),
2026 *p == _T('a') ? Name_Abbr : Name_Full);
2027 break;
68ee7c47 2028
fc3398f8
VZ
2029 case _T('b'): // a month name
2030 case _T('B'):
2031 res += GetMonthName(tm.mon,
2032 *p == _T('b') ? Name_Abbr : Name_Full);
2033 break;
2034
2035 case _T('c'): // locale default date and time representation
2036 case _T('x'): // locale default date representation
2037 //
2038 // the problem: there is no way to know what do these format
2039 // specifications correspond to for the current locale.
2040 //
2041 // the solution: use a hack and still use strftime(): first
2042 // find the YEAR which is a year in the strftime() range (1970
2043 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2044 // of the real year. Then make a copy of the format and
2045 // replace all occurences of YEAR in it with some unique
2046 // string not appearing anywhere else in it, then use
2047 // strftime() to format the date in year YEAR and then replace
2048 // YEAR back by the real year and the unique replacement
2049 // string back with YEAR. Notice that "all occurences of YEAR"
2050 // means all occurences of 4 digit as well as 2 digit form!
2051 //
2052 // the bugs: we assume that neither of %c nor %x contains any
2053 // fields which may change between the YEAR and real year. For
2054 // example, the week number (%U, %W) and the day number (%j)
2055 // will change if one of these years is leap and the other one
2056 // is not!
68ee7c47 2057 {
fc3398f8
VZ
2058 // find the YEAR: normally, for any year X, Jan 1 or the
2059 // year X + 28 is the same weekday as Jan 1 of X (because
2060 // the weekday advances by 1 for each normal X and by 2
2061 // for each leap X, hence by 5 every 4 years or by 35
2062 // which is 0 mod 7 every 28 years) but this rule breaks
2063 // down if there are years between X and Y which are
2064 // divisible by 4 but not leap (i.e. divisible by 100 but
2065 // not 400), hence the correction.
2066
2067 int yearReal = GetYear(tz);
2068 int mod28 = yearReal % 28;
2069
2070 // be careful to not go too far - we risk to leave the
2071 // supported range
2072 int year;
2073 if ( mod28 < 10 )
2074 {
2075 year = 1988 + mod28; // 1988 == 0 (mod 28)
2076 }
2077 else
2078 {
2079 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2080 }
e6ec579c 2081
fc3398f8
VZ
2082 int nCentury = year / 100,
2083 nCenturyReal = yearReal / 100;
c5a1681b 2084
fc3398f8
VZ
2085 // need to adjust for the years divisble by 400 which are
2086 // not leap but are counted like leap ones if we just take
2087 // the number of centuries in between for nLostWeekDays
2088 int nLostWeekDays = (nCentury - nCenturyReal) -
2089 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2090
fc3398f8
VZ
2091 // we have to gain back the "lost" weekdays: note that the
2092 // effect of this loop is to not do anything to
2093 // nLostWeekDays (which we won't use any more), but to
2094 // (indirectly) set the year correctly
2095 while ( (nLostWeekDays % 7) != 0 )
2096 {
2097 nLostWeekDays += year++ % 4 ? 1 : 2;
2098 }
c5a1681b 2099
fc3398f8
VZ
2100 // at any rate, we couldn't go further than 1988 + 9 + 28!
2101 wxASSERT_MSG( year < 2030,
2102 _T("logic error in wxDateTime::Format") );
c5a1681b 2103
fc3398f8
VZ
2104 wxString strYear, strYear2;
2105 strYear.Printf(_T("%d"), year);
2106 strYear2.Printf(_T("%d"), year % 100);
c5a1681b 2107
fc3398f8
VZ
2108 // find two strings not occuring in format (this is surely
2109 // not optimal way of doing it... improvements welcome!)
2110 wxString fmt = format;
2111 wxString replacement = (wxChar)-1;
2112 while ( fmt.Find(replacement) != wxNOT_FOUND )
2113 {
2114 replacement << (wxChar)-1;
2115 }
c5a1681b 2116
fc3398f8
VZ
2117 wxString replacement2 = (wxChar)-2;
2118 while ( fmt.Find(replacement) != wxNOT_FOUND )
2119 {
2120 replacement << (wxChar)-2;
2121 }
2122
2123 // replace all occurences of year with it
2124 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
2125 if ( !wasReplaced )
2126 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
2127
2128 // use strftime() to format the same date but in supported
2129 // year
2130 //
2131 // NB: we assume that strftime() doesn't check for the
2132 // date validity and will happily format the date
2133 // corresponding to Feb 29 of a non leap year (which
2134 // may happen if yearReal was leap and year is not)
2135 struct tm tmAdjusted;
2136 InitTm(tmAdjusted);
2137 tmAdjusted.tm_hour = tm.hour;
2138 tmAdjusted.tm_min = tm.min;
2139 tmAdjusted.tm_sec = tm.sec;
2140 tmAdjusted.tm_wday = tm.GetWeekDay();
2141 tmAdjusted.tm_yday = GetDayOfYear();
2142 tmAdjusted.tm_mday = tm.mday;
2143 tmAdjusted.tm_mon = tm.mon;
2144 tmAdjusted.tm_year = year - 1900;
2145 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2146 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2147 : _T("%x"),
2148 &tmAdjusted);
2149
2150 // now replace the occurence of 1999 with the real year
2151 wxString strYearReal, strYearReal2;
2152 strYearReal.Printf(_T("%04d"), yearReal);
2153 strYearReal2.Printf(_T("%02d"), yearReal % 100);
2154 str.Replace(strYear, strYearReal);
2155 str.Replace(strYear2, strYearReal2);
2156
2157 // and replace back all occurences of replacement string
2158 if ( wasReplaced )
2159 {
2160 str.Replace(replacement2, strYear2);
2161 str.Replace(replacement, strYear);
2162 }
2163
2164 res += str;
68ee7c47 2165 }
fc3398f8 2166 break;
c5a1681b 2167
fc3398f8
VZ
2168 case _T('d'): // day of a month (01-31)
2169 res += wxString::Format(fmt, tm.mday);
2170 break;
68ee7c47 2171
fc3398f8
VZ
2172 case _T('H'): // hour in 24h format (00-23)
2173 res += wxString::Format(fmt, tm.hour);
2174 break;
2175
2176 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2177 {
fc3398f8
VZ
2178 // 24h -> 12h, 0h -> 12h too
2179 int hour12 = tm.hour > 12 ? tm.hour - 12
2180 : tm.hour ? tm.hour : 12;
2181 res += wxString::Format(fmt, hour12);
68ee7c47 2182 }
fc3398f8 2183 break;
c5a1681b 2184
fc3398f8
VZ
2185 case _T('j'): // day of the year
2186 res += wxString::Format(fmt, GetDayOfYear(tz));
2187 break;
68ee7c47 2188
f6bcfd97
BP
2189 case _T('l'): // milliseconds (NOT STANDARD)
2190 res += wxString::Format(fmt, GetMillisecond(tz));
2191 break;
2192
fc3398f8
VZ
2193 case _T('m'): // month as a number (01-12)
2194 res += wxString::Format(fmt, tm.mon + 1);
2195 break;
68ee7c47 2196
fc3398f8
VZ
2197 case _T('M'): // minute as a decimal number (00-59)
2198 res += wxString::Format(fmt, tm.min);
2199 break;
68ee7c47 2200
fc3398f8
VZ
2201 case _T('p'): // AM or PM string
2202 res += CallStrftime(_T("%p"), &tmTimeOnly);
2203 break;
68ee7c47 2204
fc3398f8
VZ
2205 case _T('S'): // second as a decimal number (00-61)
2206 res += wxString::Format(fmt, tm.sec);
2207 break;
68ee7c47 2208
fc3398f8
VZ
2209 case _T('U'): // week number in the year (Sunday 1st week day)
2210 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2211 break;
68ee7c47 2212
fc3398f8
VZ
2213 case _T('W'): // week number in the year (Monday 1st week day)
2214 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2215 break;
68ee7c47 2216
fc3398f8
VZ
2217 case _T('w'): // weekday as a number (0-6), Sunday = 0
2218 res += wxString::Format(fmt, tm.GetWeekDay());
2219 break;
68ee7c47 2220
fc3398f8 2221 // case _T('x'): -- handled with "%c"
68ee7c47 2222
fc3398f8
VZ
2223 case _T('X'): // locale default time representation
2224 // just use strftime() to format the time for us
2225 res += CallStrftime(_T("%X"), &tmTimeOnly);
2226 break;
68ee7c47 2227
fc3398f8
VZ
2228 case _T('y'): // year without century (00-99)
2229 res += wxString::Format(fmt, tm.year % 100);
2230 break;
68ee7c47 2231
fc3398f8
VZ
2232 case _T('Y'): // year with century
2233 res += wxString::Format(fmt, tm.year);
2234 break;
68ee7c47 2235
fc3398f8
VZ
2236 case _T('Z'): // timezone name
2237 res += CallStrftime(_T("%Z"), &tmTimeOnly);
2238 break;
68ee7c47 2239
fc3398f8
VZ
2240 default:
2241 // is it the format width?
2242 fmt.Empty();
2243 while ( *p == _T('-') || *p == _T('+') ||
2244 *p == _T(' ') || wxIsdigit(*p) )
2245 {
2246 fmt += *p;
2247 }
68ee7c47 2248
fc3398f8
VZ
2249 if ( !fmt.IsEmpty() )
2250 {
2251 // we've only got the flags and width so far in fmt
2252 fmt.Prepend(_T('%'));
2253 fmt.Append(_T('d'));
68ee7c47 2254
fc3398f8 2255 restart = TRUE;
68ee7c47 2256
fc3398f8
VZ
2257 break;
2258 }
68ee7c47 2259
fc3398f8
VZ
2260 // no, it wasn't the width
2261 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2262
fc3398f8 2263 // fall through and just copy it nevertheless
68ee7c47 2264
fc3398f8
VZ
2265 case _T('%'): // a percent sign
2266 res += *p;
2267 break;
2268
2269 case 0: // the end of string
2270 wxFAIL_MSG(_T("missing format at the end of string"));
2271
2272 // just put the '%' which was the last char in format
2273 res += _T('%');
2274 break;
2275 }
68ee7c47 2276 }
c5a1681b
VZ
2277 }
2278
68ee7c47 2279 return res;
b76b015e 2280}
fcc3d7cb 2281
cd0b1709
VZ
2282// this function parses a string in (strict) RFC 822 format: see the section 5
2283// of the RFC for the detailed description, but briefly it's something of the
2284// form "Sat, 18 Dec 1999 00:48:30 +0100"
2285//
2286// this function is "strict" by design - it must reject anything except true
2287// RFC822 time specs.
2288//
2289// TODO a great candidate for using reg exps
2290const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2291{
2292 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2293
2294 const wxChar *p = date;
2295 const wxChar *comma = wxStrchr(p, _T(','));
2296 if ( comma )
2297 {
2298 // the part before comma is the weekday
2299
2300 // skip it for now - we don't use but might check that it really
2301 // corresponds to the specfied date
2302 p = comma + 1;
2303
2304 if ( *p != _T(' ') )
2305 {
2306 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2307
2308 return (wxChar *)NULL;
2309 }
2310
2311 p++; // skip space
2312 }
2313
2314 // the following 1 or 2 digits are the day number
2315 if ( !wxIsdigit(*p) )
2316 {
2317 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2318
2319 return (wxChar *)NULL;
2320 }
2321
2322 wxDateTime_t day = *p++ - _T('0');
2323 if ( wxIsdigit(*p) )
2324 {
2325 day *= 10;
2326 day += *p++ - _T('0');
2327 }
2328
2329 if ( *p++ != _T(' ') )
2330 {
2331 return (wxChar *)NULL;
2332 }
2333
2334 // the following 3 letters specify the month
2335 wxString monName(p, 3);
2336 Month mon;
2337 if ( monName == _T("Jan") )
2338 mon = Jan;
2339 else if ( monName == _T("Feb") )
2340 mon = Feb;
2341 else if ( monName == _T("Mar") )
2342 mon = Mar;
2343 else if ( monName == _T("Apr") )
2344 mon = Apr;
2345 else if ( monName == _T("May") )
2346 mon = May;
2347 else if ( monName == _T("Jun") )
2348 mon = Jun;
2349 else if ( monName == _T("Jul") )
2350 mon = Jul;
2351 else if ( monName == _T("Aug") )
2352 mon = Aug;
2353 else if ( monName == _T("Sep") )
2354 mon = Sep;
2355 else if ( monName == _T("Oct") )
2356 mon = Oct;
2357 else if ( monName == _T("Nov") )
2358 mon = Nov;
2359 else if ( monName == _T("Dec") )
2360 mon = Dec;
2361 else
2362 {
2363 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2364
2365 return (wxChar *)NULL;
2366 }
2367
2368 p += 3;
2369
2370 if ( *p++ != _T(' ') )
2371 {
2372 return (wxChar *)NULL;
2373 }
2374
2375 // next is the year
2376 if ( !wxIsdigit(*p) )
2377 {
2378 // no year?
2379 return (wxChar *)NULL;
2380 }
2381
2382 int year = *p++ - _T('0');
2383
2384 if ( !wxIsdigit(*p) )
2385 {
2386 // should have at least 2 digits in the year
2387 return (wxChar *)NULL;
2388 }
2389
2390 year *= 10;
2391 year += *p++ - _T('0');
2392
2393 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2394 if ( wxIsdigit(*p) )
2395 {
2396 year *= 10;
2397 year += *p++ - _T('0');
2398
2399 if ( !wxIsdigit(*p) )
2400 {
2401 // no 3 digit years please
2402 return (wxChar *)NULL;
2403 }
2404
2405 year *= 10;
2406 year += *p++ - _T('0');
2407 }
2408
2409 if ( *p++ != _T(' ') )
2410 {
2411 return (wxChar *)NULL;
2412 }
2413
2414 // time is in the format hh:mm:ss and seconds are optional
2415 if ( !wxIsdigit(*p) )
2416 {
2417 return (wxChar *)NULL;
2418 }
2419
2420 wxDateTime_t hour = *p++ - _T('0');
2421
2422 if ( !wxIsdigit(*p) )
2423 {
2424 return (wxChar *)NULL;
2425 }
2426
2427 hour *= 10;
2428 hour += *p++ - _T('0');
2429
2430 if ( *p++ != _T(':') )
2431 {
2432 return (wxChar *)NULL;
2433 }
2434
2435 if ( !wxIsdigit(*p) )
2436 {
2437 return (wxChar *)NULL;
2438 }
2439
2440 wxDateTime_t min = *p++ - _T('0');
2441
2442 if ( !wxIsdigit(*p) )
2443 {
2444 return (wxChar *)NULL;
2445 }
2446
2447 min *= 10;
2448 min += *p++ - _T('0');
2449
2450 wxDateTime_t sec = 0;
2451 if ( *p++ == _T(':') )
2452 {
2453 if ( !wxIsdigit(*p) )
2454 {
2455 return (wxChar *)NULL;
2456 }
2457
2458 sec = *p++ - _T('0');
2459
2460 if ( !wxIsdigit(*p) )
2461 {
2462 return (wxChar *)NULL;
2463 }
2464
2465 sec *= 10;
2466 sec += *p++ - _T('0');
2467 }
2468
2469 if ( *p++ != _T(' ') )
2470 {
2471 return (wxChar *)NULL;
2472 }
2473
2474 // and now the interesting part: the timezone
40973ea5 2475 int offset;
cd0b1709
VZ
2476 if ( *p == _T('-') || *p == _T('+') )
2477 {
2478 // the explicit offset given: it has the form of hhmm
2479 bool plus = *p++ == _T('+');
2480
2481 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2482 {
2483 return (wxChar *)NULL;
2484 }
2485
2486 // hours
2487 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2488
2489 p += 2;
2490
2491 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2492 {
2493 return (wxChar *)NULL;
2494 }
2495
2496 // minutes
2497 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2498
2499 if ( !plus )
2500 {
2501 offset = -offset;
2502 }
2503
2504 p += 2;
2505 }
2506 else
2507 {
2508 // the symbolic timezone given: may be either military timezone or one
2509 // of standard abbreviations
2510 if ( !*(p + 1) )
2511 {
2512 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2513 static const int offsets[26] =
2514 {
2515 //A B C D E F G H I J K L M
2516 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2517 //N O P R Q S T U V W Z Y Z
2518 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2519 };
2520
2521 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2522 {
2523 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2524
2525 return (wxChar *)NULL;
2526 }
2527
2528 offset = offsets[*p++ - _T('A')];
2529 }
2530 else
2531 {
2532 // abbreviation
2533 wxString tz = p;
2534 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2535 offset = 0;
2536 else if ( tz == _T("AST") )
2537 offset = AST - GMT0;
2538 else if ( tz == _T("ADT") )
2539 offset = ADT - GMT0;
2540 else if ( tz == _T("EST") )
2541 offset = EST - GMT0;
2542 else if ( tz == _T("EDT") )
2543 offset = EDT - GMT0;
2544 else if ( tz == _T("CST") )
2545 offset = CST - GMT0;
2546 else if ( tz == _T("CDT") )
2547 offset = CDT - GMT0;
2548 else if ( tz == _T("MST") )
2549 offset = MST - GMT0;
2550 else if ( tz == _T("MDT") )
2551 offset = MDT - GMT0;
2552 else if ( tz == _T("PST") )
2553 offset = PST - GMT0;
2554 else if ( tz == _T("PDT") )
2555 offset = PDT - GMT0;
2556 else
2557 {
2558 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2559
2560 return (wxChar *)NULL;
2561 }
2562
2563 p += tz.length();
2564 }
2565
2566 // make it minutes
2567 offset *= 60;
2568 }
2569
2570 // the spec was correct
2571 Set(day, mon, year, hour, min, sec);
487c1f7e 2572 MakeTimezone((wxDateTime_t)(60*offset));
cd0b1709
VZ
2573
2574 return p;
2575}
2576
f0f951fa
VZ
2577const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2578 const wxChar *format,
2579 const wxDateTime& dateDef)
cd0b1709
VZ
2580{
2581 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 2582 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 2583
f0f951fa
VZ
2584 wxString str;
2585 unsigned long num;
cd0b1709 2586
f0f951fa
VZ
2587 // what fields have we found?
2588 bool haveWDay = FALSE,
2589 haveYDay = FALSE,
2590 haveDay = FALSE,
2591 haveMon = FALSE,
2592 haveYear = FALSE,
2593 haveHour = FALSE,
2594 haveMin = FALSE,
2595 haveSec = FALSE;
2596
2597 bool hourIsIn12hFormat = FALSE, // or in 24h one?
2598 isPM = FALSE; // AM by default
2599
2600 // and the value of the items we have (init them to get rid of warnings)
2601 wxDateTime_t sec = 0,
2602 min = 0,
2603 hour = 0;
2604 WeekDay wday = Inv_WeekDay;
2605 wxDateTime_t yday = 0,
2606 mday = 0;
2607 wxDateTime::Month mon = Inv_Month;
2608 int year = 0;
2609
2610 const wxChar *input = date;
2611 for ( const wxChar *fmt = format; *fmt; fmt++ )
2612 {
2613 if ( *fmt != _T('%') )
2614 {
2615 if ( wxIsspace(*fmt) )
2616 {
2617 // a white space in the format string matches 0 or more white
2618 // spaces in the input
2619 while ( wxIsspace(*input) )
2620 {
2621 input++;
2622 }
2623 }
2624 else // !space
2625 {
2626 // any other character (not whitespace, not '%') must be
2627 // matched by itself in the input
2628 if ( *input++ != *fmt )
2629 {
2630 // no match
2631 return (wxChar *)NULL;
2632 }
2633 }
2634
2635 // done with this format char
2636 continue;
2637 }
2638
2639 // start of a format specification
f6bcfd97
BP
2640
2641 // parse the optional width
2642 size_t width = 0;
2643 while ( isdigit(*++fmt) )
2644 {
2645 width *= 10;
2646 width += *fmt - _T('0');
2647 }
2648
4147efe9
VZ
2649 // the default widths for the various fields
2650 if ( !width )
2651 {
2652 switch ( *fmt )
2653 {
2654 case _T('Y'): // year has 4 digits
2655 width = 4;
2656 break;
2657
2658 case _T('j'): // day of year has 3 digits
2659 case _T('l'): // milliseconds have 3 digits
2660 width = 3;
2661 break;
2662
2663 case _T('w'): // week day as number has only one
2664 width = 1;
2665 break;
2666
2667 default:
2668 // default for all other fields
2669 width = 2;
2670 }
2671 }
2672
f6bcfd97
BP
2673 // then the format itself
2674 switch ( *fmt )
f0f951fa
VZ
2675 {
2676 case _T('a'): // a weekday name
2677 case _T('A'):
2678 {
2679 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2680 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2681 if ( wday == Inv_WeekDay )
2682 {
2683 // no match
2684 return (wxChar *)NULL;
2685 }
2686 }
2687 haveWDay = TRUE;
2688 break;
2689
2690 case _T('b'): // a month name
2691 case _T('B'):
2692 {
2693 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
2694 mon = GetMonthFromName(GetAlphaToken(input), flag);
2695 if ( mon == Inv_Month )
2696 {
2697 // no match
2698 return (wxChar *)NULL;
2699 }
2700 }
2701 haveMon = TRUE;
2702 break;
2703
2704 case _T('c'): // locale default date and time representation
2705 {
2706 wxDateTime dt;
2707
2708 // this is the format which corresponds to ctime() output
2709 // and strptime("%c") should parse it, so try it first
41acf5c0 2710 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
2711
2712 const wxChar *result = dt.ParseFormat(input, fmtCtime);
2713 if ( !result )
2714 {
2715 result = dt.ParseFormat(input, _T("%x %X"));
2716 }
2717
2718 if ( !result )
2719 {
2720 result = dt.ParseFormat(input, _T("%X %x"));
2721 }
2722
2723 if ( !result )
2724 {
2725 // we've tried everything and still no match
2726 return (wxChar *)NULL;
2727 }
2728
be4017f8
VZ
2729 Tm tm = dt.GetTm();
2730
2731 haveDay = haveMon = haveYear =
2732 haveHour = haveMin = haveSec = TRUE;
2733
2734 hour = tm.hour;
2735 min = tm.min;
2736 sec = tm.sec;
2737
2738 year = tm.year;
2739 mon = tm.mon;
2740 mday = tm.mday;
2741
f0f951fa
VZ
2742 input = result;
2743 }
2744 break;
2745
2746 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
2747 if ( !GetNumericToken(width, input, &num) ||
2748 (num > 31) || (num < 1) )
f0f951fa
VZ
2749 {
2750 // no match
2751 return (wxChar *)NULL;
2752 }
2753
2754 // we can't check whether the day range is correct yet, will
2755 // do it later - assume ok for now
2756 haveDay = TRUE;
2757 mday = (wxDateTime_t)num;
2758 break;
5f287370 2759
f0f951fa 2760 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 2761 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
2762 {
2763 // no match
2764 return (wxChar *)NULL;
2765 }
2766
2767 haveHour = TRUE;
2768 hour = (wxDateTime_t)num;
2769 break;
2770
2771 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 2772 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2773 {
2774 // no match
2775 return (wxChar *)NULL;
2776 }
2777
2778 haveHour = TRUE;
2779 hourIsIn12hFormat = TRUE;
479cd5de 2780 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
2781 break;
2782
2783 case _T('j'): // day of the year
f6bcfd97 2784 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
2785 {
2786 // no match
2787 return (wxChar *)NULL;
2788 }
2789
2790 haveYDay = TRUE;
2791 yday = (wxDateTime_t)num;
2792 break;
2793
2794 case _T('m'): // month as a number (01-12)
f6bcfd97 2795 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2796 {
2797 // no match
2798 return (wxChar *)NULL;
2799 }
2800
2801 haveMon = TRUE;
be4017f8 2802 mon = (Month)(num - 1);
f0f951fa
VZ
2803 break;
2804
2805 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 2806 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
2807 {
2808 // no match
2809 return (wxChar *)NULL;
2810 }
2811
2812 haveMin = TRUE;
2813 min = (wxDateTime_t)num;
2814 break;
2815
2816 case _T('p'): // AM or PM string
2817 {
2818 wxString am, pm, token = GetAlphaToken(input);
2819
2820 GetAmPmStrings(&am, &pm);
2821 if ( token.CmpNoCase(pm) == 0 )
2822 {
2823 isPM = TRUE;
2824 }
2825 else if ( token.CmpNoCase(am) != 0 )
2826 {
2827 // no match
2828 return (wxChar *)NULL;
2829 }
2830 }
2831 break;
2832
2833 case _T('r'): // time as %I:%M:%S %p
2834 {
2835 wxDateTime dt;
2836 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
2837 if ( !input )
2838 {
2839 // no match
2840 return (wxChar *)NULL;
2841 }
2842
2843 haveHour = haveMin = haveSec = TRUE;
2844
2845 Tm tm = dt.GetTm();
2846 hour = tm.hour;
2847 min = tm.min;
2848 sec = tm.sec;
2849 }
2850 break;
2851
2852 case _T('R'): // time as %H:%M
2853 {
2854 wxDateTime dt;
2855 input = dt.ParseFormat(input, _T("%H:%M"));
2856 if ( !input )
2857 {
2858 // no match
2859 return (wxChar *)NULL;
2860 }
2861
2862 haveHour = haveMin = TRUE;
2863
2864 Tm tm = dt.GetTm();
2865 hour = tm.hour;
2866 min = tm.min;
2867 }
2868
2869 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 2870 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
2871 {
2872 // no match
2873 return (wxChar *)NULL;
2874 }
2875
2876 haveSec = TRUE;
2877 sec = (wxDateTime_t)num;
2878 break;
2879
2880 case _T('T'): // time as %H:%M:%S
2881 {
2882 wxDateTime dt;
2883 input = dt.ParseFormat(input, _T("%H:%M:%S"));
2884 if ( !input )
2885 {
2886 // no match
2887 return (wxChar *)NULL;
2888 }
2889
2890 haveHour = haveMin = haveSec = TRUE;
2891
2892 Tm tm = dt.GetTm();
2893 hour = tm.hour;
2894 min = tm.min;
2895 sec = tm.sec;
2896 }
be4017f8 2897 break;
f0f951fa
VZ
2898
2899 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 2900 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
2901 {
2902 // no match
2903 return (wxChar *)NULL;
2904 }
2905
2906 haveWDay = TRUE;
2907 wday = (WeekDay)num;
2908 break;
2909
2910 case _T('x'): // locale default date representation
2911#ifdef HAVE_STRPTIME
2912 // try using strptime() - it may fail even if the input is
2913 // correct but the date is out of range, so we will fall back
2914 // to our generic code anyhow (FIXME !Unicode friendly)
2915 {
2916 struct tm tm;
2917 const wxChar *result = strptime(input, "%x", &tm);
2918 if ( result )
2919 {
2920 input = result;
2921
2922 haveDay = haveMon = haveYear = TRUE;
2923
2924 year = 1900 + tm.tm_year;
2925 mon = (Month)tm.tm_mon;
2926 mday = tm.tm_mday;
2927
2928 break;
2929 }
2930 }
2931#endif // HAVE_STRPTIME
2932
2933 // TODO query the LOCALE_IDATE setting under Win32
2934 {
2935 wxDateTime dt;
2936
41acf5c0 2937 wxString fmtDate, fmtDateAlt;
f0f951fa
VZ
2938 if ( IsWestEuropeanCountry(GetCountry()) ||
2939 GetCountry() == Russia )
2940 {
2941 fmtDate = _T("%d/%m/%y");
41acf5c0 2942 fmtDateAlt = _T("%m/%d/%y");
f0f951fa
VZ
2943 }
2944 else // assume USA
2945 {
2946 fmtDate = _T("%m/%d/%y");
41acf5c0 2947 fmtDateAlt = _T("%d/%m/%y");
f0f951fa
VZ
2948 }
2949
2950 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0
VZ
2951
2952 if ( !result )
2953 {
2954 // ok, be nice and try another one
2955 result = dt.ParseFormat(input, fmtDateAlt);
2956 }
2957
f0f951fa
VZ
2958 if ( !result )
2959 {
2960 // bad luck
2961 return (wxChar *)NULL;
2962 }
2963
2964 Tm tm = dt.GetTm();
2965
2966 haveDay = haveMon = haveYear = TRUE;
2967
2968 year = tm.year;
2969 mon = tm.mon;
2970 mday = tm.mday;
2971
2972 input = result;
2973 }
2974
2975 break;
2976
2977 case _T('X'): // locale default time representation
2978#ifdef HAVE_STRPTIME
2979 {
2980 // use strptime() to do it for us (FIXME !Unicode friendly)
2981 struct tm tm;
2982 input = strptime(input, "%X", &tm);
2983 if ( !input )
2984 {
2985 return (wxChar *)NULL;
2986 }
2987
2988 haveHour = haveMin = haveSec = TRUE;
2989
2990 hour = tm.tm_hour;
2991 min = tm.tm_min;
2992 sec = tm.tm_sec;
2993 }
2994#else // !HAVE_STRPTIME
2995 // TODO under Win32 we can query the LOCALE_ITIME system
2996 // setting which says whether the default time format is
2997 // 24 or 12 hour
2998 {
2999 // try to parse what follows as "%H:%M:%S" and, if this
3000 // fails, as "%I:%M:%S %p" - this should catch the most
3001 // common cases
3002 wxDateTime dt;
3003
3004 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3005 if ( !result )
3006 {
3007 result = dt.ParseFormat(input, _T("%r"));
3008 }
3009
3010 if ( !result )
3011 {
3012 // no match
3013 return (wxChar *)NULL;
3014 }
3015
3016 haveHour = haveMin = haveSec = TRUE;
3017
3018 Tm tm = dt.GetTm();
3019 hour = tm.hour;
3020 min = tm.min;
3021 sec = tm.sec;
3022
3023 input = result;
3024 }
3025#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3026 break;
3027
3028 case _T('y'): // year without century (00-99)
f6bcfd97 3029 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3030 {
3031 // no match
3032 return (wxChar *)NULL;
3033 }
3034
3035 haveYear = TRUE;
f6bcfd97
BP
3036
3037 // TODO should have an option for roll over date instead of
3038 // hard coding it here
3039 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3040 break;
3041
3042 case _T('Y'): // year with century
f6bcfd97 3043 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3044 {
3045 // no match
3046 return (wxChar *)NULL;
3047 }
3048
be4017f8
VZ
3049 haveYear = TRUE;
3050 year = (wxDateTime_t)num;
f0f951fa
VZ
3051 break;
3052
3053 case _T('Z'): // timezone name
3054 wxFAIL_MSG(_T("TODO"));
3055 break;
3056
3057 case _T('%'): // a percent sign
3058 if ( *input++ != _T('%') )
3059 {
3060 // no match
3061 return (wxChar *)NULL;
3062 }
3063 break;
3064
3065 case 0: // the end of string
3066 wxFAIL_MSG(_T("unexpected format end"));
3067
3068 // fall through
3069
3070 default: // not a known format spec
3071 return (wxChar *)NULL;
3072 }
3073 }
3074
3075 // format matched, try to construct a date from what we have now
3076 Tm tmDef;
3077 if ( dateDef.IsValid() )
3078 {
3079 // take this date as default
3080 tmDef = dateDef.GetTm();
3081 }
c3302e7e 3082 else if ( IsValid() )
f0f951fa
VZ
3083 {
3084 // if this date is valid, don't change it
3085 tmDef = GetTm();
3086 }
3087 else
3088 {
3089 // no default and this date is invalid - fall back to Today()
3090 tmDef = Today().GetTm();
3091 }
3092
3093 Tm tm = tmDef;
3094
3095 // set the date
3096 if ( haveYear )
3097 {
3098 tm.year = year;
3099 }
3100
3101 // TODO we don't check here that the values are consistent, if both year
3102 // day and month/day were found, we just ignore the year day and we
3103 // also always ignore the week day
3104 if ( haveMon && haveDay )
3105 {
be4017f8
VZ
3106 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3107 {
3108 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3109
3110 return (wxChar *)NULL;
3111 }
3112
f0f951fa
VZ
3113 tm.mon = mon;
3114 tm.mday = mday;
3115 }
3116 else if ( haveYDay )
3117 {
be4017f8
VZ
3118 if ( yday > GetNumberOfDays(tm.year) )
3119 {
3120 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3121
3122 return (wxChar *)NULL;
3123 }
3124
f0f951fa
VZ
3125 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3126
3127 tm.mon = tm2.mon;
3128 tm.mday = tm2.mday;
3129 }
3130
3131 // deal with AM/PM
3132 if ( haveHour && hourIsIn12hFormat && isPM )
3133 {
3134 // translate to 24hour format
3135 hour += 12;
3136 }
3137 //else: either already in 24h format or no translation needed
3138
3139 // set the time
3140 if ( haveHour )
3141 {
5f287370 3142 tm.hour = hour;
f0f951fa
VZ
3143 }
3144
3145 if ( haveMin )
3146 {
3147 tm.min = min;
3148 }
3149
3150 if ( haveSec )
3151 {
3152 tm.sec = sec;
3153 }
3154
3155 Set(tm);
3156
3157 return input;
cd0b1709
VZ
3158}
3159
3160const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3161{
3162 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3163
f0f951fa
VZ
3164 // there is a public domain version of getdate.y, but it only works for
3165 // English...
cd0b1709
VZ
3166 wxFAIL_MSG(_T("TODO"));
3167
3168 return (wxChar *)NULL;
3169}
3170
3171const wxChar *wxDateTime::ParseDate(const wxChar *date)
3172{
3173 // this is a simplified version of ParseDateTime() which understands only
3174 // "today" (for wxDate compatibility) and digits only otherwise (and not
3175 // all esoteric constructions ParseDateTime() knows about)
3176
3177 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3178
3179 const wxChar *p = date;
3180 while ( wxIsspace(*p) )
3181 p++;
3182
fd9f9f4c
VZ
3183 // some special cases
3184 static struct
cd0b1709 3185 {
fd9f9f4c
VZ
3186 const wxChar *str;
3187 int dayDiffFromToday;
3188 } literalDates[] =
3189 {
3190 { wxTRANSLATE("today"), 0 },
3191 { wxTRANSLATE("yesterday"), -1 },
3192 { wxTRANSLATE("tomorrow"), 1 },
3193 };
3194
3195 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3196 {
3197 wxString date = wxGetTranslation(literalDates[n].str);
3198 size_t len = date.length();
3b415ba4 3199 if ( wxStrlen(p) >= len && (wxString(p, len).CmpNoCase(date) == 0) )
fd9f9f4c
VZ
3200 {
3201 // nothing can follow this, so stop here
3202 p += len;
cd0b1709 3203
fd9f9f4c
VZ
3204 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3205 *this = Today();
3206 if ( dayDiffFromToday )
3207 {
3208 *this += wxDateSpan::Days(dayDiffFromToday);
3209 }
cd0b1709 3210
fd9f9f4c
VZ
3211 return p;
3212 }
cd0b1709
VZ
3213 }
3214
3ca6a5f0
BP
3215 // We try to guess what we have here: for each new (numeric) token, we
3216 // determine if it can be a month, day or a year. Of course, there is an
3217 // ambiguity as some numbers may be days as well as months, so we also
3218 // have the ability to back track.
3219
cd0b1709
VZ
3220 // what do we have?
3221 bool haveDay = FALSE, // the months day?
3222 haveWDay = FALSE, // the day of week?
3223 haveMon = FALSE, // the month?
3224 haveYear = FALSE; // the year?
3225
3226 // and the value of the items we have (init them to get rid of warnings)
3227 WeekDay wday = Inv_WeekDay;
3228 wxDateTime_t day = 0;
3229 wxDateTime::Month mon = Inv_Month;
3230 int year = 0;
3231
3232 // tokenize the string
f6bcfd97
BP
3233 size_t nPosCur = 0;
3234 static const wxChar *dateDelimiters = _T(".,/-\t\n ");
3235 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3236 while ( tok.HasMoreTokens() )
3237 {
3238 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3239 if ( !token )
3240 continue;
cd0b1709
VZ
3241
3242 // is it a number?
3243 unsigned long val;
fde5a86b 3244 if ( token.ToULong(&val) )
cd0b1709
VZ
3245 {
3246 // guess what this number is
3247
3248 bool isDay = FALSE,
3249 isMonth = FALSE,
3ca6a5f0
BP
3250 isYear = FALSE;
3251
3252 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3253 {
3ca6a5f0
BP
3254 // assume it is month
3255 isMonth = TRUE;
3256 }
3257 else // not the month
3258 {
3259 wxDateTime_t maxDays = haveMon
3260 ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
3261 : 31;
3262
3263 // can it be day?
3897b707 3264 if ( (val == 0) || (val > (unsigned long)maxDays) ) // cast to shut up compiler warning in BCC
cd0b1709 3265 {
3ca6a5f0 3266 isYear = TRUE;
cd0b1709 3267 }
3ca6a5f0 3268 else
cd0b1709
VZ
3269 {
3270 isDay = TRUE;
cd0b1709
VZ
3271 }
3272 }
3273
cd0b1709
VZ
3274 if ( isYear )
3275 {
3276 if ( haveYear )
cd0b1709 3277 break;
cd0b1709
VZ
3278
3279 haveYear = TRUE;
3280
479cd5de 3281 year = (wxDateTime_t)val;
cd0b1709 3282 }
3ca6a5f0 3283 else if ( isDay )
cd0b1709 3284 {
3ca6a5f0 3285 if ( haveDay )
cd0b1709 3286 break;
cd0b1709 3287
3ca6a5f0 3288 haveDay = TRUE;
cd0b1709 3289
3ca6a5f0 3290 day = (wxDateTime_t)val;
cd0b1709 3291 }
3ca6a5f0 3292 else if ( isMonth )
cd0b1709 3293 {
3ca6a5f0 3294 haveMon = TRUE;
cd0b1709 3295
3ca6a5f0 3296 mon = (Month)(val - 1);
cd0b1709
VZ
3297 }
3298 }
3299 else // not a number
3300 {
f6bcfd97
BP
3301 // be careful not to overwrite the current mon value
3302 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3303 if ( mon2 != Inv_Month )
cd0b1709
VZ
3304 {
3305 // it's a month
3306 if ( haveMon )
3307 {
6411a32c
VZ
3308 // but we already have a month - maybe we guessed wrong?
3309 if ( !haveDay )
3310 {
f5cd9787 3311 // no need to check in month range as always < 12, but
6411a32c
VZ
3312 // the days are counted from 1 unlike the months
3313 day = (wxDateTime_t)mon + 1;
3314 haveDay = TRUE;
3315 }
3316 else
3317 {
3318 // could possible be the year (doesn't the year come
3319 // before the month in the japanese format?) (FIXME)
3320 break;
f5cd9787 3321 }
cd0b1709
VZ
3322 }
3323
f6bcfd97
BP
3324 mon = mon2;
3325
cd0b1709
VZ
3326 haveMon = TRUE;
3327 }
6411a32c 3328 else // not a valid month name
cd0b1709 3329 {
f0f951fa 3330 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3331 if ( wday != Inv_WeekDay )
3332 {
3333 // a week day
3334 if ( haveWDay )
3335 {
3336 break;
3337 }
3338
3339 haveWDay = TRUE;
3340 }
6411a32c 3341 else // not a valid weekday name
cd0b1709
VZ
3342 {
3343 // try the ordinals
3344 static const wxChar *ordinals[] =
3345 {
3346 wxTRANSLATE("first"),
3347 wxTRANSLATE("second"),
3348 wxTRANSLATE("third"),
3349 wxTRANSLATE("fourth"),
3350 wxTRANSLATE("fifth"),
3351 wxTRANSLATE("sixth"),
3352 wxTRANSLATE("seventh"),
3353 wxTRANSLATE("eighth"),
3354 wxTRANSLATE("ninth"),
3355 wxTRANSLATE("tenth"),
3356 wxTRANSLATE("eleventh"),
3357 wxTRANSLATE("twelfth"),
3358 wxTRANSLATE("thirteenth"),
3359 wxTRANSLATE("fourteenth"),
3360 wxTRANSLATE("fifteenth"),
3361 wxTRANSLATE("sixteenth"),
3362 wxTRANSLATE("seventeenth"),
3363 wxTRANSLATE("eighteenth"),
3364 wxTRANSLATE("nineteenth"),
3365 wxTRANSLATE("twentieth"),
3366 // that's enough - otherwise we'd have problems with
6411a32c 3367 // composite (or not) ordinals
cd0b1709
VZ
3368 };
3369
3370 size_t n;
3371 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3372 {
3373 if ( token.CmpNoCase(ordinals[n]) == 0 )
3374 {
3375 break;
3376 }
3377 }
3378
3379 if ( n == WXSIZEOF(ordinals) )
3380 {
3381 // stop here - something unknown
3382 break;
3383 }
3384
3385 // it's a day
3386 if ( haveDay )
3387 {
3388 // don't try anything here (as in case of numeric day
3389 // above) - the symbolic day spec should always
3390 // precede the month/year
3391 break;
3392 }
3393
3394 haveDay = TRUE;
3395
33ac7e6f 3396 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3397 }
3398 }
3399 }
f6bcfd97
BP
3400
3401 nPosCur = tok.GetPosition();
cd0b1709
VZ
3402 }
3403
3404 // either no more tokens or the scan was stopped by something we couldn't
3405 // parse - in any case, see if we can construct a date from what we have
3406 if ( !haveDay && !haveWDay )
3407 {
f6bcfd97 3408 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709
VZ
3409
3410 return (wxChar *)NULL;
3411 }
3412
3413 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3414 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3415 {
3416 // without adjectives (which we don't support here) the week day only
3417 // makes sense completely separately or with the full date
3418 // specification (what would "Wed 1999" mean?)
3419 return (wxChar *)NULL;
3420 }
3421
f6bcfd97
BP
3422 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3423 {
3424 // may be we have month and day instead of day and year?
3425 if ( haveDay && !haveMon )
3426 {
3427 if ( day <= 12 )
3428 {
3429 // exchange day and month
3430 mon = (wxDateTime::Month)(day - 1);
3431
3432 // we're in the current year then
f5cd9787
VZ
3433 if ( (year > 0) &&
3434 (unsigned)year <= GetNumOfDaysInMonth(Inv_Year, mon) )
f6bcfd97
BP
3435 {
3436 day = year;
3437
3438 haveMon = TRUE;
3439 haveYear = FALSE;
3440 }
3441 //else: no, can't exchange, leave haveMon == FALSE
3442 }
3443 }
3444
3445 if ( !haveMon )
3446 {
3447 // if we give the year, month and day must be given too
3448 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3449
3450 return (wxChar *)NULL;
3451 }
3452 }
3453
cd0b1709
VZ
3454 if ( !haveMon )
3455 {
3456 mon = GetCurrentMonth();
3457 }
3458
3459 if ( !haveYear )
3460 {
3461 year = GetCurrentYear();
3462 }
3463
3464 if ( haveDay )
3465 {
3466 Set(day, mon, year);
3467
3468 if ( haveWDay )
3469 {
3470 // check that it is really the same
3471 if ( GetWeekDay() != wday )
3472 {
3473 // inconsistency detected
f6bcfd97
BP
3474 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3475
cd0b1709
VZ
3476 return (wxChar *)NULL;
3477 }
3478 }
3479 }
3480 else // haveWDay
3481 {
3482 *this = Today();
3483
3484 SetToWeekDayInSameWeek(wday);
3485 }
3486
f6bcfd97
BP
3487 // return the pointer to the first unparsed char
3488 p += nPosCur;
3489 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
3490 {
3491 // if we couldn't parse the token after the delimiter, put back the
3492 // delimiter as well
3493 p--;
3494 }
3495
3496 return p;
cd0b1709
VZ
3497}
3498
3499const wxChar *wxDateTime::ParseTime(const wxChar *time)
3500{
3501 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3502
f0f951fa
VZ
3503 // first try some extra things
3504 static const struct
3505 {
3506 const wxChar *name;
3507 wxDateTime_t hour;
3508 } stdTimes[] =
3509 {
3510 { wxTRANSLATE("noon"), 12 },
3511 { wxTRANSLATE("midnight"), 00 },
3512 // anything else?
3513 };
cd0b1709 3514
f0f951fa
VZ
3515 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3516 {
3517 wxString timeString = wxGetTranslation(stdTimes[n].name);
3518 size_t len = timeString.length();
3519 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3520 {
3521 Set(stdTimes[n].hour, 0, 0);
3522
3523 return time + len;
3524 }
3525 }
3526
c7f9a482
VZ
3527 // try all time formats we may think about in the order from longest to
3528 // shortest
3529
3530 // 12hour with AM/PM?
3531 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
3532
f0f951fa
VZ
3533 if ( !result )
3534 {
3535 // normally, it's the same, but why not try it?
3536 result = ParseFormat(time, _T("%H:%M:%S"));
3537 }
3538
3539 if ( !result )
3540 {
c7f9a482
VZ
3541 // 12hour with AM/PM but without seconds?
3542 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
3543 }
3544
3545 if ( !result )
3546 {
3547 // without seconds?
3548 result = ParseFormat(time, _T("%H:%M"));
3549 }
3550
3551 if ( !result )
3552 {
c7f9a482
VZ
3553 // just the hour and AM/PM?
3554 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
3555 }
3556
3557 if ( !result )
3558 {
3559 // just the hour?
3560 result = ParseFormat(time, _T("%H"));
3561 }
3562
3563 if ( !result )
3564 {
c7f9a482
VZ
3565 // parse the standard format: normally it is one of the formats above
3566 // but it may be set to something completely different by the user
3567 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
3568 }
3569
3570 // TODO: parse timezones
3571
3572 return result;
cd0b1709
VZ
3573}
3574
4f6aed9c
VZ
3575// ----------------------------------------------------------------------------
3576// Workdays and holidays support
3577// ----------------------------------------------------------------------------
3578
3579bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3580{
3581 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3582}
3583
fcc3d7cb
VZ
3584// ============================================================================
3585// wxTimeSpan
3586// ============================================================================
3587
033400eb
VZ
3588// this enum is only used in wxTimeSpan::Format() below but we can't declare
3589// it locally to the method as it provokes an internal compiler error in egcs
3590// 2.91.60 when building with -O2
3591enum TimeSpanPart
3592{
3593 Part_Week,
3594 Part_Day,
3595 Part_Hour,
3596 Part_Min,
3597 Part_Sec,
3598 Part_MSec
3599};
3600
e6ec579c
VZ
3601// not all strftime(3) format specifiers make sense here because, for example,
3602// a time span doesn't have a year nor a timezone
3603//
3604// Here are the ones which are supported (all of them are supported by strftime
3605// as well):
3606// %H hour in 24 hour format
3607// %M minute (00 - 59)
3608// %S second (00 - 59)
3609// %% percent sign
3610//
3611// Also, for MFC CTimeSpan compatibility, we support
3612// %D number of days
3613//
3614// And, to be better than MFC :-), we also have
3615// %E number of wEeks
3616// %l milliseconds (000 - 999)
fcc3d7cb
VZ
3617wxString wxTimeSpan::Format(const wxChar *format) const
3618{
e6ec579c 3619 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
3620
3621 wxString str;
5b735202 3622 str.Alloc(wxStrlen(format));
e6ec579c 3623
df05cdc5
VZ
3624 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3625 //
3626 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3627 // question is what should ts.Format("%S") do? The code here returns "3273"
3628 // in this case (i.e. the total number of seconds, not just seconds % 60)
3629 // because, for me, this call means "give me entire time interval in
3630 // seconds" and not "give me the seconds part of the time interval"
3631 //
3632 // If we agree that it should behave like this, it is clear that the
3633 // interpretation of each format specifier depends on the presence of the
3634 // other format specs in the string: if there was "%H" before "%M", we
3635 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3636
3637 // we remember the most important unit found so far
033400eb 3638 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 3639
f6bcfd97 3640 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
3641 {
3642 wxChar ch = *pch;
3643
f6bcfd97 3644 if ( ch == _T('%') )
e6ec579c 3645 {
df05cdc5
VZ
3646 // the start of the format specification of the printf() below
3647 wxString fmtPrefix = _T('%');
3648
3649 // the number
3650 long n;
e6ec579c 3651
f6bcfd97 3652 ch = *++pch; // get the format spec char
e6ec579c
VZ
3653 switch ( ch )
3654 {
3655 default:
3656 wxFAIL_MSG( _T("invalid format character") );
3657 // fall through
3658
f6bcfd97 3659 case _T('%'):
df05cdc5
VZ
3660 str += ch;
3661
3662 // skip the part below switch
3663 continue;
e6ec579c 3664
f6bcfd97 3665 case _T('D'):
df05cdc5
VZ
3666 n = GetDays();
3667 if ( partBiggest < Part_Day )
3668 {
3669 n %= DAYS_PER_WEEK;
3670 }
3671 else
3672 {
3673 partBiggest = Part_Day;
3674 }
e6ec579c
VZ
3675 break;
3676
f6bcfd97 3677 case _T('E'):
df05cdc5
VZ
3678 partBiggest = Part_Week;
3679 n = GetWeeks();
e6ec579c
VZ
3680 break;
3681
f6bcfd97 3682 case _T('H'):
df05cdc5
VZ
3683 n = GetHours();
3684 if ( partBiggest < Part_Hour )
3685 {
3686 n %= HOURS_PER_DAY;
3687 }
3688 else
3689 {
3690 partBiggest = Part_Hour;
3691 }
3692
3693 fmtPrefix += _T("02");
e6ec579c
VZ
3694 break;
3695
f6bcfd97 3696 case _T('l'):
df05cdc5
VZ
3697 n = GetMilliseconds().ToLong();
3698 if ( partBiggest < Part_MSec )
3699 {
3700 n %= 1000;
3701 }
3702 //else: no need to reset partBiggest to Part_MSec, it is
3703 // the least significant one anyhow
3704
3705 fmtPrefix += _T("03");
e6ec579c
VZ
3706 break;
3707
f6bcfd97 3708 case _T('M'):
df05cdc5
VZ
3709 n = GetMinutes();
3710 if ( partBiggest < Part_Min )
3711 {
3712 n %= MIN_PER_HOUR;
3713 }
3714 else
3715 {
3716 partBiggest = Part_Min;
3717 }
3718
3719 fmtPrefix += _T("02");
e6ec579c
VZ
3720 break;
3721
f6bcfd97 3722 case _T('S'):
df05cdc5
VZ
3723 n = GetSeconds().ToLong();
3724 if ( partBiggest < Part_Sec )
3725 {
3726 n %= SEC_PER_MIN;
3727 }
3728 else
3729 {
3730 partBiggest = Part_Sec;
3731 }
3732
3733 fmtPrefix += _T("02");
e6ec579c
VZ
3734 break;
3735 }
3736
df05cdc5
VZ
3737 str += wxString::Format(fmtPrefix + _T("ld"), n);
3738 }
3739 else
3740 {
3741 // normal character, just copy
3742 str += ch;
e6ec579c 3743 }
e6ec579c 3744 }
fcc3d7cb
VZ
3745
3746 return str;
3747}
4f6aed9c
VZ
3748
3749// ============================================================================
3750// wxDateTimeHolidayAuthority and related classes
3751// ============================================================================
3752
3753#include "wx/arrimpl.cpp"
3754
f6bcfd97 3755WX_DEFINE_OBJARRAY(wxDateTimeArray);
4f6aed9c
VZ
3756
3757static int wxCMPFUNC_CONV
3758wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
3759{
3760 wxDateTime dt1 = **first,
3761 dt2 = **second;
3762
3763 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
3764}
3765
3766// ----------------------------------------------------------------------------
3767// wxDateTimeHolidayAuthority
3768// ----------------------------------------------------------------------------
3769
3770wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
3771
3772/* static */
3773bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
3774{
3775 size_t count = ms_authorities.GetCount();
3776 for ( size_t n = 0; n < count; n++ )
3777 {
3778 if ( ms_authorities[n]->DoIsHoliday(dt) )
3779 {
3780 return TRUE;
3781 }
3782 }
3783
3784 return FALSE;
3785}
3786
3787/* static */
3788size_t
3789wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
3790 const wxDateTime& dtEnd,
3791 wxDateTimeArray& holidays)
3792{
3793 wxDateTimeArray hol;
3794
3795 holidays.Empty();
3796
3797 size_t count = ms_authorities.GetCount();
6dc6fda6 3798 for ( size_t nAuth = 0; nAuth < count; nAuth++ )
4f6aed9c 3799 {
6dc6fda6 3800 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
3801
3802 WX_APPEND_ARRAY(holidays, hol);
3803 }
3804
3805 holidays.Sort(wxDateTimeCompareFunc);
3806
3807 return holidays.GetCount();
3808}
3809
3810/* static */
3811void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3812{
3813 WX_CLEAR_ARRAY(ms_authorities);
3814}
3815
3816/* static */
3817void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
3818{
3819 ms_authorities.Add(auth);
3820}
3821
3822// ----------------------------------------------------------------------------
3823// wxDateTimeWorkDays
3824// ----------------------------------------------------------------------------
3825
3826bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
3827{
3828 wxDateTime::WeekDay wd = dt.GetWeekDay();
3829
3830 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
3831}
3832
3833size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
3834 const wxDateTime& dtEnd,
3835 wxDateTimeArray& holidays) const
3836{
3837 if ( dtStart > dtEnd )
3838 {
3839 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3840
3841 return 0u;
3842 }
3843
3844 holidays.Empty();
3845
3846 // instead of checking all days, start with the first Sat after dtStart and
3847 // end with the last Sun before dtEnd
3848 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
3849 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
3850 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
3851 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
3852 dt;
3853
3854 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
3855 {
3856 holidays.Add(dt);
3857 }
3858
3859 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
3860 {
3861 holidays.Add(dt);
3862 }
3863
3864 return holidays.GetCount();
3865}
3e0b743f 3866
1e6feb95 3867#endif // wxUSE_DATETIME