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