]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
Compilation fix for wxUSE_STL==1.
[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);
842 if ( am )
843 {
844 *am = CallStrftime(_T("%p"), &tm);
845 }
846 if ( pm )
847 {
848 tm.tm_hour = 13;
849 *pm = CallStrftime(_T("%p"), &tm);
850 }
b76b015e
VZ
851}
852
239446b4
VZ
853// ----------------------------------------------------------------------------
854// Country stuff: date calculations depend on the country (DST, work days,
855// ...), so we need to know which rules to follow.
856// ----------------------------------------------------------------------------
857
858/* static */
859wxDateTime::Country wxDateTime::GetCountry()
860{
f0f951fa
VZ
861 // TODO use LOCALE_ICOUNTRY setting under Win32
862
239446b4
VZ
863 if ( ms_country == Country_Unknown )
864 {
865 // try to guess from the time zone name
866 time_t t = time(NULL);
867 struct tm *tm = localtime(&t);
868
869 wxString tz = CallStrftime(_T("%Z"), tm);
870 if ( tz == _T("WET") || tz == _T("WEST") )
871 {
872 ms_country = UK;
873 }
874 else if ( tz == _T("CET") || tz == _T("CEST") )
875 {
876 ms_country = Country_EEC;
877 }
878 else if ( tz == _T("MSK") || tz == _T("MSD") )
879 {
880 ms_country = Russia;
881 }
882 else if ( tz == _T("AST") || tz == _T("ADT") ||
883 tz == _T("EST") || tz == _T("EDT") ||
884 tz == _T("CST") || tz == _T("CDT") ||
885 tz == _T("MST") || tz == _T("MDT") ||
886 tz == _T("PST") || tz == _T("PDT") )
887 {
888 ms_country = USA;
889 }
890 else
891 {
892 // well, choose a default one
893 ms_country = USA;
894 }
895 }
896
897 return ms_country;
898}
899
900/* static */
901void wxDateTime::SetCountry(wxDateTime::Country country)
902{
903 ms_country = country;
904}
905
906/* static */
907bool wxDateTime::IsWestEuropeanCountry(Country country)
908{
909 if ( country == Country_Default )
910 {
911 country = GetCountry();
912 }
913
914 return (Country_WesternEurope_Start <= country) &&
915 (country <= Country_WesternEurope_End);
916}
917
918// ----------------------------------------------------------------------------
919// DST calculations: we use 3 different rules for the West European countries,
920// USA and for the rest of the world. This is undoubtedly false for many
921// countries, but I lack the necessary info (and the time to gather it),
922// please add the other rules here!
923// ----------------------------------------------------------------------------
924
925/* static */
926bool wxDateTime::IsDSTApplicable(int year, Country country)
927{
928 if ( year == Inv_Year )
929 {
930 // take the current year if none given
931 year = GetCurrentYear();
932 }
933
934 if ( country == Country_Default )
935 {
936 country = GetCountry();
937 }
938
939 switch ( country )
940 {
941 case USA:
942 case UK:
943 // DST was first observed in the US and UK during WWI, reused
944 // during WWII and used again since 1966
945 return year >= 1966 ||
946 (year >= 1942 && year <= 1945) ||
947 (year == 1918 || year == 1919);
948
949 default:
950 // assume that it started after WWII
951 return year > 1950;
952 }
953}
954
955/* static */
956wxDateTime wxDateTime::GetBeginDST(int year, Country country)
957{
958 if ( year == Inv_Year )
959 {
960 // take the current year if none given
961 year = GetCurrentYear();
962 }
963
964 if ( country == Country_Default )
965 {
966 country = GetCountry();
967 }
968
969 if ( !IsDSTApplicable(year, country) )
970 {
2ef31e80 971 return wxInvalidDateTime;
239446b4
VZ
972 }
973
974 wxDateTime dt;
975
976 if ( IsWestEuropeanCountry(country) || (country == Russia) )
977 {
978 // DST begins at 1 a.m. GMT on the last Sunday of March
979 if ( !dt.SetToLastWeekDay(Sun, Mar, year) )
980 {
981 // weird...
982 wxFAIL_MSG( _T("no last Sunday in March?") );
983 }
984
985 dt += wxTimeSpan::Hours(1);
986
41acf5c0
VZ
987 // disable DST tests because it could result in an infinite recursion!
988 dt.MakeGMT(TRUE);
239446b4
VZ
989 }
990 else switch ( country )
991 {
992 case USA:
993 switch ( year )
994 {
995 case 1918:
996 case 1919:
997 // don't know for sure - assume it was in effect all year
998
999 case 1943:
1000 case 1944:
1001 case 1945:
1002 dt.Set(1, Jan, year);
1003 break;
1004
1005 case 1942:
1006 // DST was installed Feb 2, 1942 by the Congress
1007 dt.Set(2, Feb, year);
1008 break;
1009
1010 // Oil embargo changed the DST period in the US
1011 case 1974:
1012 dt.Set(6, Jan, 1974);
1013 break;
1014
1015 case 1975:
1016 dt.Set(23, Feb, 1975);
1017 break;
1018
1019 default:
1020 // before 1986, DST begun on the last Sunday of April, but
1021 // in 1986 Reagan changed it to begin at 2 a.m. of the
1022 // first Sunday in April
1023 if ( year < 1986 )
1024 {
1025 if ( !dt.SetToLastWeekDay(Sun, Apr, year) )
1026 {
1027 // weird...
1028 wxFAIL_MSG( _T("no first Sunday in April?") );
1029 }
1030 }
1031 else
1032 {
1033 if ( !dt.SetToWeekDay(Sun, 1, Apr, year) )
1034 {
1035 // weird...
1036 wxFAIL_MSG( _T("no first Sunday in April?") );
1037 }
1038 }
1039
1040 dt += wxTimeSpan::Hours(2);
1041
1042 // TODO what about timezone??
1043 }
1044
1045 break;
1046
1047 default:
1048 // assume Mar 30 as the start of the DST for the rest of the world
1049 // - totally bogus, of course
1050 dt.Set(30, Mar, year);
1051 }
1052
1053 return dt;
1054}
1055
1056/* static */
1057wxDateTime wxDateTime::GetEndDST(int year, Country country)
1058{
1059 if ( year == Inv_Year )
1060 {
1061 // take the current year if none given
1062 year = GetCurrentYear();
1063 }
1064
1065 if ( country == Country_Default )
1066 {
1067 country = GetCountry();
1068 }
1069
1070 if ( !IsDSTApplicable(year, country) )
1071 {
2ef31e80 1072 return wxInvalidDateTime;
239446b4
VZ
1073 }
1074
1075 wxDateTime dt;
1076
1077 if ( IsWestEuropeanCountry(country) || (country == Russia) )
1078 {
5f287370 1079 // DST ends at 1 a.m. GMT on the last Sunday of October
239446b4
VZ
1080 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1081 {
1082 // weirder and weirder...
1083 wxFAIL_MSG( _T("no last Sunday in October?") );
1084 }
1085
1086 dt += wxTimeSpan::Hours(1);
1087
41acf5c0
VZ
1088 // disable DST tests because it could result in an infinite recursion!
1089 dt.MakeGMT(TRUE);
239446b4
VZ
1090 }
1091 else switch ( country )
1092 {
1093 case USA:
1094 switch ( year )
1095 {
1096 case 1918:
1097 case 1919:
1098 // don't know for sure - assume it was in effect all year
1099
1100 case 1943:
1101 case 1944:
1102 dt.Set(31, Dec, year);
1103 break;
1104
1105 case 1945:
1106 // the time was reset after the end of the WWII
1107 dt.Set(30, Sep, year);
1108 break;
1109
1110 default:
5f287370 1111 // DST ends at 2 a.m. on the last Sunday of October
239446b4
VZ
1112 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1113 {
1114 // weirder and weirder...
1115 wxFAIL_MSG( _T("no last Sunday in October?") );
1116 }
1117
1118 dt += wxTimeSpan::Hours(2);
1119
1120 // TODO what about timezone??
1121 }
1122 break;
1123
1124 default:
1125 // assume October 26th as the end of the DST - totally bogus too
1126 dt.Set(26, Oct, year);
1127 }
1128
1129 return dt;
1130}
1131
0979c962
VZ
1132// ----------------------------------------------------------------------------
1133// constructors and assignment operators
1134// ----------------------------------------------------------------------------
1135
f6bcfd97
BP
1136// return the current time with ms precision
1137/* static */ wxDateTime wxDateTime::UNow()
1138{
1139 return wxDateTime(wxGetLocalTimeMillis());
1140}
1141
299fcbfe
VZ
1142// the values in the tm structure contain the local time
1143wxDateTime& wxDateTime::Set(const struct tm& tm)
0979c962 1144{
299fcbfe 1145 struct tm tm2(tm);
b76b015e 1146 time_t timet = mktime(&tm2);
1ef54dcf 1147
4afd7529 1148 if ( timet == (time_t)-1 )
0979c962 1149 {
4afd7529
VZ
1150 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1151 // less than timezone - try to make it work for this case
1152 if ( tm2.tm_year == 70 && tm2.tm_mon == 0 && tm2.tm_mday == 1 )
1153 {
1154 // add timezone to make sure that date is in range
1155 tm2.tm_sec -= GetTimeZone();
1156
1157 timet = mktime(&tm2);
1158 if ( timet != (time_t)-1 )
1159 {
1160 timet += GetTimeZone();
1161
1162 return Set(timet);
1163 }
1164 }
1165
1166 wxFAIL_MSG( _T("mktime() failed") );
0979c962 1167
384223b3
VZ
1168 *this = wxInvalidDateTime;
1169
1170 return *this;
0979c962
VZ
1171 }
1172 else
1173 {
1174 return Set(timet);
1175 }
1176}
1177
1178wxDateTime& wxDateTime::Set(wxDateTime_t hour,
1179 wxDateTime_t minute,
1180 wxDateTime_t second,
1181 wxDateTime_t millisec)
1182{
1183 // we allow seconds to be 61 to account for the leap seconds, even if we
1184 // don't use them really
384223b3
VZ
1185 wxDATETIME_CHECK( hour < 24 &&
1186 second < 62 &&
1187 minute < 60 &&
1188 millisec < 1000,
1189 _T("Invalid time in wxDateTime::Set()") );
0979c962
VZ
1190
1191 // get the current date from system
9d9b7755 1192 struct tm *tm = GetTmNow();
299fcbfe 1193
384223b3 1194 wxDATETIME_CHECK( tm, _T("localtime() failed") );
0979c962
VZ
1195
1196 // adjust the time
1197 tm->tm_hour = hour;
1198 tm->tm_min = minute;
1199 tm->tm_sec = second;
1200
b76b015e 1201 (void)Set(*tm);
0979c962
VZ
1202
1203 // and finally adjust milliseconds
1204 return SetMillisecond(millisec);
1205}
1206
1207wxDateTime& wxDateTime::Set(wxDateTime_t day,
1208 Month month,
1209 int year,
1210 wxDateTime_t hour,
1211 wxDateTime_t minute,
1212 wxDateTime_t second,
1213 wxDateTime_t millisec)
1214{
384223b3
VZ
1215 wxDATETIME_CHECK( hour < 24 &&
1216 second < 62 &&
1217 minute < 60 &&
1218 millisec < 1000,
1219 _T("Invalid time in wxDateTime::Set()") );
0979c962 1220
2f02cb89 1221 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 1222
384223b3
VZ
1223 wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
1224 _T("Invalid date in wxDateTime::Set()") );
0979c962
VZ
1225
1226 // the range of time_t type (inclusive)
1227 static const int yearMinInRange = 1970;
1228 static const int yearMaxInRange = 2037;
1229
1230 // test only the year instead of testing for the exact end of the Unix
1231 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 1232 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
1233 {
1234 // use the standard library version if the date is in range - this is
b76b015e 1235 // probably more efficient than our code
0979c962 1236 struct tm tm;
b76b015e 1237 tm.tm_year = year - 1900;
0979c962
VZ
1238 tm.tm_mon = month;
1239 tm.tm_mday = day;
1240 tm.tm_hour = hour;
1241 tm.tm_min = minute;
1242 tm.tm_sec = second;
299fcbfe 1243 tm.tm_isdst = -1; // mktime() will guess it
0979c962
VZ
1244
1245 (void)Set(tm);
1246
1247 // and finally adjust milliseconds
1248 return SetMillisecond(millisec);
1249 }
1250 else
1251 {
1252 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 1253 // milliseconds between the given date and the epoch
e6ec579c
VZ
1254
1255 // get the JDN for the midnight of this day
1256 m_time = GetTruncatedJDN(day, month, year);
1257 m_time -= EPOCH_JDN;
1258 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1259
299fcbfe
VZ
1260 // JDN corresponds to GMT, we take localtime
1261 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
b76b015e
VZ
1262 }
1263
1264 return *this;
1265}
1266
e6ec579c
VZ
1267wxDateTime& wxDateTime::Set(double jdn)
1268{
1ef54dcf
VZ
1269 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1270 // EPOCH_JDN + 0.5
1271 jdn -= EPOCH_JDN + 0.5;
1272
cd0b1709
VZ
1273 jdn *= MILLISECONDS_PER_DAY;
1274
9c2882d9 1275 m_time.Assign(jdn);
e6ec579c
VZ
1276
1277 return *this;
1278}
1279
9d9b7755
VZ
1280wxDateTime& wxDateTime::ResetTime()
1281{
1282 Tm tm = GetTm();
1283
1284 if ( tm.hour || tm.min || tm.sec || tm.msec )
1285 {
1286 tm.msec =
1287 tm.sec =
1288 tm.min =
1289 tm.hour = 0;
1290
1291 Set(tm);
1292 }
1293
1294 return *this;
1295}
1296
2b5f62a0
VZ
1297// ----------------------------------------------------------------------------
1298// DOS Date and Time Format functions
1299// ----------------------------------------------------------------------------
1300// the dos date and time value is an unsigned 32 bit value in the format:
1301// YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1302//
1303// Y = year offset from 1980 (0-127)
1304// M = month (1-12)
1305// D = day of month (1-31)
1306// h = hour (0-23)
1307// m = minute (0-59)
1308// s = bisecond (0-29) each bisecond indicates two seconds
1309// ----------------------------------------------------------------------------
1310
1311wxDateTime& wxDateTime::SetFromDOS(unsigned long ddt)
1312{
1313 struct tm tm;
1314
1315 long year = ddt & 0xFE000000;
1316 year >>= 25;
1317 year += 80;
1318 tm.tm_year = year;
1319
1320 long month = ddt & 0x1E00000;
1321 month >>= 21;
1322 month -= 1;
1323 tm.tm_mon = month;
1324
1325 long day = ddt & 0x1F0000;
1326 day >>= 16;
1327 tm.tm_mday = day;
1328
1329 long hour = ddt & 0xF800;
1330 hour >>= 11;
1331 tm.tm_hour = hour;
1332
1333 long minute = ddt & 0x7E0;
1334 minute >>= 5;
1335 tm.tm_min = minute;
1336
1337 long second = ddt & 0x1F;
1338 tm.tm_sec = second * 2;
1339
1340 return Set(mktime(&tm));
1341}
1342
1343unsigned long wxDateTime::GetAsDOS() const
1344{
1345 unsigned long ddt;
1346 time_t ticks = GetTicks();
1347 struct tm *tm = localtime(&ticks);
1348
1349 long year = tm->tm_year;
1350 year -= 80;
1351 year <<= 25;
1352
1353 long month = tm->tm_mon;
1354 month += 1;
1355 month <<= 21;
1356
1357 long day = tm->tm_mday;
1358 day <<= 16;
1359
1360 long hour = tm->tm_hour;
1361 hour <<= 11;
1362
1363 long minute = tm->tm_min;
1364 minute <<= 5;
1365
1366 long second = tm->tm_sec;
1367 second /= 2;
1368
1369 ddt = year | month | day | hour | minute | second;
1370 return ddt;
1371}
1372
b76b015e
VZ
1373// ----------------------------------------------------------------------------
1374// time_t <-> broken down time conversions
1375// ----------------------------------------------------------------------------
1376
299fcbfe 1377wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
b76b015e
VZ
1378{
1379 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1380
1381 time_t time = GetTicks();
1382 if ( time != (time_t)-1 )
1383 {
1384 // use C RTL functions
299fcbfe
VZ
1385 tm *tm;
1386 if ( tz.GetOffset() == -GetTimeZone() )
1387 {
1388 // we are working with local time
1389 tm = localtime(&time);
c5a1681b
VZ
1390
1391 // should never happen
f6bcfd97 1392 wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
299fcbfe
VZ
1393 }
1394 else
1395 {
13111b2a 1396 time += (time_t)tz.GetOffset();
33ac7e6f 1397#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1398 int time2 = (int) time;
9d9b7755 1399 if ( time2 >= 0 )
fb10f04c 1400#else
9d9b7755 1401 if ( time >= 0 )
fb10f04c 1402#endif
c5a1681b
VZ
1403 {
1404 tm = gmtime(&time);
b76b015e 1405
c5a1681b
VZ
1406 // should never happen
1407 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1408 }
1409 else
1410 {
1411 tm = (struct tm *)NULL;
1412 }
1413 }
b76b015e 1414
c5a1681b
VZ
1415 if ( tm )
1416 {
f6bcfd97
BP
1417 // adjust the milliseconds
1418 Tm tm2(*tm, tz);
1419 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
1420 tm2.msec = (wxDateTime_t)(timeOnly % 1000);
1421 return tm2;
c5a1681b
VZ
1422 }
1423 //else: use generic code below
b76b015e 1424 }
e6ec579c 1425
c5a1681b
VZ
1426 // remember the time and do the calculations with the date only - this
1427 // eliminates rounding errors of the floating point arithmetics
299fcbfe 1428
c5a1681b 1429 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1ef54dcf 1430
c5a1681b 1431 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1ef54dcf 1432
c5a1681b
VZ
1433 // we want to always have positive time and timeMidnight to be really
1434 // the midnight before it
1435 if ( timeOnly < 0 )
1436 {
1437 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1438 }
e6ec579c 1439
c5a1681b 1440 timeMidnight -= timeOnly;
1ef54dcf 1441
c5a1681b
VZ
1442 // calculate the Gregorian date from JDN for the midnight of our date:
1443 // this will yield day, month (in 1..12 range) and year
1ef54dcf 1444
c5a1681b
VZ
1445 // actually, this is the JDN for the noon of the previous day
1446 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1ef54dcf 1447
c5a1681b 1448 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1ef54dcf 1449
c5a1681b 1450 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1ef54dcf 1451
c5a1681b 1452 // calculate the century
479cd5de
VZ
1453 long temp = (jdn + JDN_OFFSET) * 4 - 1;
1454 long century = temp / DAYS_PER_400_YEARS;
1ef54dcf 1455
c5a1681b
VZ
1456 // then the year and day of year (1 <= dayOfYear <= 366)
1457 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
479cd5de
VZ
1458 long year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1459 long dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1ef54dcf 1460
c5a1681b
VZ
1461 // and finally the month and day of the month
1462 temp = dayOfYear * 5 - 3;
479cd5de
VZ
1463 long month = temp / DAYS_PER_5_MONTHS;
1464 long day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
c5a1681b
VZ
1465
1466 // month is counted from March - convert to normal
1467 if ( month < 10 )
1468 {
1469 month += 3;
1470 }
1471 else
1472 {
1473 year += 1;
1474 month -= 9;
1475 }
1ef54dcf 1476
c5a1681b
VZ
1477 // year is offset by 4800
1478 year -= 4800;
1ef54dcf 1479
c5a1681b
VZ
1480 // check that the algorithm gave us something reasonable
1481 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1482 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
e6ec579c 1483
c5a1681b
VZ
1484 // construct Tm from these values
1485 Tm tm;
1486 tm.year = (int)year;
1487 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1488 tm.mday = (wxDateTime_t)day;
479cd5de 1489 tm.msec = (wxDateTime_t)(timeOnly % 1000);
c5a1681b
VZ
1490 timeOnly -= tm.msec;
1491 timeOnly /= 1000; // now we have time in seconds
e6ec579c 1492
479cd5de 1493 tm.sec = (wxDateTime_t)(timeOnly % 60);
c5a1681b
VZ
1494 timeOnly -= tm.sec;
1495 timeOnly /= 60; // now we have time in minutes
e6ec579c 1496
479cd5de 1497 tm.min = (wxDateTime_t)(timeOnly % 60);
c5a1681b 1498 timeOnly -= tm.min;
e6ec579c 1499
479cd5de 1500 tm.hour = (wxDateTime_t)(timeOnly / 60);
b76b015e 1501
c5a1681b 1502 return tm;
b76b015e
VZ
1503}
1504
1505wxDateTime& wxDateTime::SetYear(int year)
1506{
1507 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1508
1509 Tm tm(GetTm());
1510 tm.year = year;
1511 Set(tm);
1512
1513 return *this;
1514}
1515
1516wxDateTime& wxDateTime::SetMonth(Month month)
1517{
1518 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1519
1520 Tm tm(GetTm());
1521 tm.mon = month;
1522 Set(tm);
1523
1524 return *this;
1525}
1526
1527wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1528{
1529 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1530
1531 Tm tm(GetTm());
1532 tm.mday = mday;
1533 Set(tm);
1534
1535 return *this;
1536}
1537
1538wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1539{
1540 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1541
1542 Tm tm(GetTm());
1543 tm.hour = hour;
1544 Set(tm);
1545
1546 return *this;
1547}
1548
1549wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1550{
1551 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1552
1553 Tm tm(GetTm());
1554 tm.min = min;
1555 Set(tm);
1556
1557 return *this;
1558}
1559
1560wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1561{
1562 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1563
1564 Tm tm(GetTm());
1565 tm.sec = sec;
1566 Set(tm);
0979c962
VZ
1567
1568 return *this;
1569}
b76b015e
VZ
1570
1571wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1572{
1573 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1574
1575 // we don't need to use GetTm() for this one
1576 m_time -= m_time % 1000l;
1577 m_time += millisecond;
1578
1579 return *this;
1580}
1581
1582// ----------------------------------------------------------------------------
1583// wxDateTime arithmetics
1584// ----------------------------------------------------------------------------
1585
1586wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1587{
1588 Tm tm(GetTm());
1589
1590 tm.year += diff.GetYears();
fcc3d7cb 1591 tm.AddMonths(diff.GetMonths());
4f6aed9c
VZ
1592
1593 // check that the resulting date is valid
1594 if ( tm.mday > GetNumOfDaysInMonth(tm.year, tm.mon) )
1595 {
1596 // We suppose that when adding one month to Jan 31 we want to get Feb
1597 // 28 (or 29), i.e. adding a month to the last day of the month should
1598 // give the last day of the next month which is quite logical.
1599 //
1600 // Unfortunately, there is no logic way to understand what should
1601 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1602 // We make it Feb 28 (last day too), but it is highly questionable.
1603 tm.mday = GetNumOfDaysInMonth(tm.year, tm.mon);
1604 }
1605
fcc3d7cb 1606 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
1607
1608 Set(tm);
1609
9d9b7755
VZ
1610 wxASSERT_MSG( IsSameTime(tm),
1611 _T("Add(wxDateSpan) shouldn't modify time") );
1612
b76b015e
VZ
1613 return *this;
1614}
1615
2f02cb89
VZ
1616// ----------------------------------------------------------------------------
1617// Weekday and monthday stuff
1618// ----------------------------------------------------------------------------
1619
2b5f62a0
VZ
1620bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1621 WeekDay weekday,
1622 WeekFlags flags)
4f6aed9c 1623{
2b5f62a0
VZ
1624 wxASSERT_MSG( numWeek > 0,
1625 _T("invalid week number: weeks are counted from 1") );
1626
4f6aed9c
VZ
1627 int year = GetYear();
1628
1629 // Jan 4 always lies in the 1st week of the year
1630 Set(4, Jan, year);
2b5f62a0 1631 SetToWeekDayInSameWeek(weekday, flags) += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c
VZ
1632
1633 if ( GetYear() != year )
1634 {
1635 // oops... numWeek was too big
1636 return FALSE;
1637 }
1638
1639 return TRUE;
1640}
1641
2f02cb89
VZ
1642wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1643 int year)
1644{
1645 // take the current month/year if none specified
1a8557b1
VZ
1646 if ( year == Inv_Year )
1647 year = GetYear();
1648 if ( month == Inv_Month )
1649 month = GetMonth();
2f02cb89 1650
fcc3d7cb 1651 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1652}
1653
2b5f62a0 1654wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1655{
384223b3 1656 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1657
2b5f62a0 1658 int wdayThis = GetWeekDay();
cd0b1709
VZ
1659 if ( weekday == wdayThis )
1660 {
1661 // nothing to do
1662 return *this;
1663 }
2b5f62a0
VZ
1664
1665 if ( flags == Default_First )
1666 {
1667 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1668 }
1669
1670 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1671 // is the first day in the week, but breaks down for Monday_First case so
1672 // we adjust the week days in this case
1673 if( flags == Monday_First )
1674 {
1675 if ( wdayThis == Sun )
1676 wdayThis += 7;
1677 }
1678 //else: Sunday_First, nothing to do
1679
1680 // go forward or back in time to the day we want
1681 if ( weekday < wdayThis )
cd0b1709 1682 {
f6bcfd97 1683 return Subtract(wxDateSpan::Days(wdayThis - weekday));
cd0b1709
VZ
1684 }
1685 else // weekday > wdayThis
1686 {
4f6aed9c 1687 return Add(wxDateSpan::Days(weekday - wdayThis));
cd0b1709
VZ
1688 }
1689}
1690
1691wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1692{
384223b3 1693 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1694
1695 int diff;
1696 WeekDay wdayThis = GetWeekDay();
1697 if ( weekday == wdayThis )
1698 {
1699 // nothing to do
1700 return *this;
1701 }
1702 else if ( weekday < wdayThis )
1703 {
1704 // need to advance a week
1705 diff = 7 - (wdayThis - weekday);
1706 }
1707 else // weekday > wdayThis
1708 {
1709 diff = weekday - wdayThis;
1710 }
1711
4f6aed9c 1712 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1713}
1714
1715wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1716{
384223b3 1717 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1718
1719 int diff;
1720 WeekDay wdayThis = GetWeekDay();
1721 if ( weekday == wdayThis )
1722 {
1723 // nothing to do
1724 return *this;
1725 }
1726 else if ( weekday > wdayThis )
1727 {
1728 // need to go to previous week
1729 diff = 7 - (weekday - wdayThis);
1730 }
1731 else // weekday < wdayThis
1732 {
1733 diff = wdayThis - weekday;
1734 }
1735
f6bcfd97 1736 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1737}
1738
2f02cb89
VZ
1739bool wxDateTime::SetToWeekDay(WeekDay weekday,
1740 int n,
1741 Month month,
1742 int year)
1743{
1744 wxCHECK_MSG( weekday != Inv_WeekDay, FALSE, _T("invalid weekday") );
1745
1746 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1747 // anyhow in such case - but may be should still give an assert for it?
1748
1749 // take the current month/year if none specified
1750 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1751
1752 wxDateTime dt;
1753
1754 // TODO this probably could be optimised somehow...
1755
1756 if ( n > 0 )
1757 {
1758 // get the first day of the month
1759 dt.Set(1, month, year);
1760
1761 // get its wday
1762 WeekDay wdayFirst = dt.GetWeekDay();
1763
1764 // go to the first weekday of the month
1765 int diff = weekday - wdayFirst;
1766 if ( diff < 0 )
1767 diff += 7;
1768
1769 // add advance n-1 weeks more
1770 diff += 7*(n - 1);
1771
239446b4 1772 dt += wxDateSpan::Days(diff);
2f02cb89 1773 }
239446b4 1774 else // count from the end of the month
2f02cb89
VZ
1775 {
1776 // get the last day of the month
1777 dt.SetToLastMonthDay(month, year);
1778
1779 // get its wday
1780 WeekDay wdayLast = dt.GetWeekDay();
1781
1782 // go to the last weekday of the month
1783 int diff = wdayLast - weekday;
1784 if ( diff < 0 )
1785 diff += 7;
1786
1787 // and rewind n-1 weeks from there
239446b4 1788 diff += 7*(-n - 1);
2f02cb89
VZ
1789
1790 dt -= wxDateSpan::Days(diff);
1791 }
1792
1793 // check that it is still in the same month
1794 if ( dt.GetMonth() == month )
1795 {
1796 *this = dt;
1797
1798 return TRUE;
1799 }
1800 else
1801 {
1802 // no such day in this month
1803 return FALSE;
1804 }
1805}
1806
239446b4
VZ
1807wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
1808{
239446b4
VZ
1809 Tm tm(GetTm(tz));
1810
f0f951fa 1811 return gs_cumulatedDays[IsLeapYear(tm.year)][tm.mon] + tm.mday;
239446b4
VZ
1812}
1813
9d9b7755
VZ
1814wxDateTime::wxDateTime_t wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags,
1815 const TimeZone& tz) const
239446b4 1816{
9d9b7755
VZ
1817 if ( flags == Default_First )
1818 {
1819 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1820 }
239446b4
VZ
1821
1822 wxDateTime_t nDayInYear = GetDayOfYear(tz);
9d9b7755 1823 wxDateTime_t week;
239446b4 1824
9d9b7755
VZ
1825 WeekDay wd = GetWeekDay(tz);
1826 if ( flags == Sunday_First )
1827 {
1828 week = (nDayInYear - wd + 7) / 7;
1829 }
1830 else
1831 {
1832 // have to shift the week days values
1833 week = (nDayInYear - (wd - 1 + 7) % 7 + 7) / 7;
1834 }
239446b4 1835
9d9b7755
VZ
1836 // FIXME some more elegant way??
1837 WeekDay wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
1838 if ( wdYearStart == Wed || wdYearStart == Thu )
239446b4
VZ
1839 {
1840 week++;
1841 }
1842
1843 return week;
68ee7c47
VZ
1844}
1845
9d9b7755
VZ
1846wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
1847 const TimeZone& tz) const
68ee7c47 1848{
9d9b7755
VZ
1849 Tm tm = GetTm(tz);
1850 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 1851 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 1852 if ( nWeek < 0 )
68ee7c47 1853 {
9d9b7755
VZ
1854 // this may happen for January when Jan, 1 is the last week of the
1855 // previous year
1856 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 1857 }
68ee7c47 1858
6dc6fda6 1859 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
1860}
1861
f0f951fa
VZ
1862wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
1863{
1864 int year = GetYear();
384223b3
VZ
1865 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
1866 _T("invalid year day") );
f0f951fa
VZ
1867
1868 bool isLeap = IsLeapYear(year);
1869 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
1870 {
1871 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1872 // don't need it neither - because of the CHECK above we know that
1873 // yday lies in December then
1874 if ( (mon == Dec) || (yday < gs_cumulatedDays[isLeap][mon + 1]) )
1875 {
1876 Set(yday - gs_cumulatedDays[isLeap][mon], mon, year);
1877
1878 break;
1879 }
1880 }
1881
1882 return *this;
1883}
1884
e6ec579c
VZ
1885// ----------------------------------------------------------------------------
1886// Julian day number conversion and related stuff
1887// ----------------------------------------------------------------------------
1888
1889double wxDateTime::GetJulianDayNumber() const
1890{
299fcbfe
VZ
1891 // JDN are always expressed for the GMT dates
1892 Tm tm(ToTimezone(GMT0).GetTm(GMT0));
e6ec579c
VZ
1893
1894 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1895
1896 // add the part GetTruncatedJDN() neglected
1897 result += 0.5;
1898
1899 // and now add the time: 86400 sec = 1 JDN
1900 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
1901}
1902
1903double wxDateTime::GetRataDie() const
1904{
1905 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1906 return GetJulianDayNumber() - 1721119.5 - 306;
1907}
1908
fcc3d7cb 1909// ----------------------------------------------------------------------------
299fcbfe 1910// timezone and DST stuff
fcc3d7cb
VZ
1911// ----------------------------------------------------------------------------
1912
299fcbfe 1913int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 1914{
299fcbfe
VZ
1915 wxCHECK_MSG( country == Country_Default, -1,
1916 _T("country support not implemented") );
1917
1918 // use the C RTL for the dates in the standard range
1919 time_t timet = GetTicks();
1920 if ( timet != (time_t)-1 )
1921 {
1922 tm *tm = localtime(&timet);
1923
1924 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
1925
1926 return tm->tm_isdst;
1927 }
1928 else
1929 {
239446b4
VZ
1930 int year = GetYear();
1931
1932 if ( !IsDSTApplicable(year, country) )
1933 {
1934 // no DST time in this year in this country
1935 return -1;
1936 }
299fcbfe 1937
239446b4 1938 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 1939 }
fcc3d7cb
VZ
1940}
1941
41acf5c0 1942wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 1943{
479cd5de 1944 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 1945
41acf5c0
VZ
1946 // we need to know whether DST is or not in effect for this date unless
1947 // the test disabled by the caller
1948 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
1949 {
1950 // FIXME we assume that the DST is always shifted by 1 hour
1951 secDiff -= 3600;
1952 }
1953
f6bcfd97 1954 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
1955}
1956
b76b015e
VZ
1957// ----------------------------------------------------------------------------
1958// wxDateTime to/from text representations
1959// ----------------------------------------------------------------------------
1960
299fcbfe 1961wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 1962{
e6ec579c
VZ
1963 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
1964
f6bcfd97
BP
1965 // we have to use our own implementation if the date is out of range of
1966 // strftime() or if we use non standard specificators
b76b015e 1967 time_t time = GetTicks();
f6bcfd97 1968 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
1969 {
1970 // use strftime()
299fcbfe
VZ
1971 tm *tm;
1972 if ( tz.GetOffset() == -GetTimeZone() )
1973 {
1974 // we are working with local time
1975 tm = localtime(&time);
c5a1681b
VZ
1976
1977 // should never happen
1978 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
299fcbfe
VZ
1979 }
1980 else
1981 {
479cd5de 1982 time += (int)tz.GetOffset();
299fcbfe 1983
33ac7e6f 1984#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1985 int time2 = (int) time;
9c2882d9 1986 if ( time2 >= 0 )
fb10f04c 1987#else
9c2882d9 1988 if ( time >= 0 )
fb10f04c 1989#endif
c5a1681b
VZ
1990 {
1991 tm = gmtime(&time);
b76b015e 1992
c5a1681b
VZ
1993 // should never happen
1994 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
1995 }
1996 else
1997 {
1998 tm = (struct tm *)NULL;
1999 }
2000 }
b76b015e 2001
c5a1681b 2002 if ( tm )
e6ec579c 2003 {
c5a1681b 2004 return CallStrftime(format, tm);
e6ec579c 2005 }
c5a1681b
VZ
2006 //else: use generic code below
2007 }
2008
68ee7c47 2009 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
2010 // complications, no GNU extensions but we do add support for a "%l" format
2011 // specifier allowing to get the number of milliseconds
68ee7c47 2012 Tm tm = GetTm(tz);
e6ec579c 2013
68ee7c47
VZ
2014 // used for calls to strftime() when we only deal with time
2015 struct tm tmTimeOnly;
2016 tmTimeOnly.tm_hour = tm.hour;
2017 tmTimeOnly.tm_min = tm.min;
2018 tmTimeOnly.tm_sec = tm.sec;
2019 tmTimeOnly.tm_wday = 0;
2020 tmTimeOnly.tm_yday = 0;
2021 tmTimeOnly.tm_mday = 1; // any date will do
2022 tmTimeOnly.tm_mon = 0;
2023 tmTimeOnly.tm_year = 76;
2024 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 2025
77c3e48a 2026 wxString tmp, res, fmt;
68ee7c47
VZ
2027 for ( const wxChar *p = format; *p; p++ )
2028 {
2029 if ( *p != _T('%') )
2030 {
2031 // copy as is
2032 res += *p;
b76b015e 2033
68ee7c47
VZ
2034 continue;
2035 }
e6ec579c 2036
77c3e48a 2037 // set the default format
68ee7c47 2038 switch ( *++p )
77c3e48a
VZ
2039 {
2040 case _T('Y'): // year has 4 digits
2041 fmt = _T("%04d");
2042 break;
2043
2044 case _T('j'): // day of year has 3 digits
f6bcfd97 2045 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
2046 fmt = _T("%03d");
2047 break;
2048
59d04dff
VZ
2049 case _T('w'): // week day as number has only one
2050 fmt = _T("%d");
2051 break;
2052
77c3e48a
VZ
2053 default:
2054 // it's either another valid format specifier in which case
2055 // the format is "%02d" (for all the rest) or we have the
2056 // field width preceding the format in which case it will
2057 // override the default format anyhow
2058 fmt = _T("%02d");
2059 }
2060
fc3398f8
VZ
2061 bool restart = TRUE;
2062 while ( restart )
68ee7c47 2063 {
fc3398f8 2064 restart = FALSE;
68ee7c47 2065
fc3398f8
VZ
2066 // start of the format specification
2067 switch ( *p )
2068 {
2069 case _T('a'): // a weekday name
2070 case _T('A'):
2071 // second parameter should be TRUE for abbreviated names
2072 res += GetWeekDayName(tm.GetWeekDay(),
2073 *p == _T('a') ? Name_Abbr : Name_Full);
2074 break;
68ee7c47 2075
fc3398f8
VZ
2076 case _T('b'): // a month name
2077 case _T('B'):
2078 res += GetMonthName(tm.mon,
2079 *p == _T('b') ? Name_Abbr : Name_Full);
2080 break;
2081
2082 case _T('c'): // locale default date and time representation
2083 case _T('x'): // locale default date representation
2084 //
2085 // the problem: there is no way to know what do these format
2086 // specifications correspond to for the current locale.
2087 //
2088 // the solution: use a hack and still use strftime(): first
2089 // find the YEAR which is a year in the strftime() range (1970
2090 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2091 // of the real year. Then make a copy of the format and
2092 // replace all occurences of YEAR in it with some unique
2093 // string not appearing anywhere else in it, then use
2094 // strftime() to format the date in year YEAR and then replace
2095 // YEAR back by the real year and the unique replacement
2096 // string back with YEAR. Notice that "all occurences of YEAR"
2097 // means all occurences of 4 digit as well as 2 digit form!
2098 //
2099 // the bugs: we assume that neither of %c nor %x contains any
2100 // fields which may change between the YEAR and real year. For
2101 // example, the week number (%U, %W) and the day number (%j)
2102 // will change if one of these years is leap and the other one
2103 // is not!
68ee7c47 2104 {
fc3398f8
VZ
2105 // find the YEAR: normally, for any year X, Jan 1 or the
2106 // year X + 28 is the same weekday as Jan 1 of X (because
2107 // the weekday advances by 1 for each normal X and by 2
2108 // for each leap X, hence by 5 every 4 years or by 35
2109 // which is 0 mod 7 every 28 years) but this rule breaks
2110 // down if there are years between X and Y which are
2111 // divisible by 4 but not leap (i.e. divisible by 100 but
2112 // not 400), hence the correction.
2113
2114 int yearReal = GetYear(tz);
2115 int mod28 = yearReal % 28;
2116
2117 // be careful to not go too far - we risk to leave the
2118 // supported range
2119 int year;
2120 if ( mod28 < 10 )
2121 {
2122 year = 1988 + mod28; // 1988 == 0 (mod 28)
2123 }
2124 else
2125 {
2126 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2127 }
e6ec579c 2128
fc3398f8
VZ
2129 int nCentury = year / 100,
2130 nCenturyReal = yearReal / 100;
c5a1681b 2131
fc3398f8
VZ
2132 // need to adjust for the years divisble by 400 which are
2133 // not leap but are counted like leap ones if we just take
2134 // the number of centuries in between for nLostWeekDays
2135 int nLostWeekDays = (nCentury - nCenturyReal) -
2136 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2137
fc3398f8
VZ
2138 // we have to gain back the "lost" weekdays: note that the
2139 // effect of this loop is to not do anything to
2140 // nLostWeekDays (which we won't use any more), but to
2141 // (indirectly) set the year correctly
2142 while ( (nLostWeekDays % 7) != 0 )
2143 {
2144 nLostWeekDays += year++ % 4 ? 1 : 2;
2145 }
c5a1681b 2146
fc3398f8
VZ
2147 // at any rate, we couldn't go further than 1988 + 9 + 28!
2148 wxASSERT_MSG( year < 2030,
2149 _T("logic error in wxDateTime::Format") );
c5a1681b 2150
fc3398f8
VZ
2151 wxString strYear, strYear2;
2152 strYear.Printf(_T("%d"), year);
2153 strYear2.Printf(_T("%d"), year % 100);
c5a1681b 2154
fc3398f8
VZ
2155 // find two strings not occuring in format (this is surely
2156 // not optimal way of doing it... improvements welcome!)
2157 wxString fmt = format;
2158 wxString replacement = (wxChar)-1;
2159 while ( fmt.Find(replacement) != wxNOT_FOUND )
2160 {
2161 replacement << (wxChar)-1;
2162 }
c5a1681b 2163
fc3398f8
VZ
2164 wxString replacement2 = (wxChar)-2;
2165 while ( fmt.Find(replacement) != wxNOT_FOUND )
2166 {
2167 replacement << (wxChar)-2;
2168 }
2169
2170 // replace all occurences of year with it
2171 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
2172 if ( !wasReplaced )
2173 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
2174
2175 // use strftime() to format the same date but in supported
2176 // year
2177 //
2178 // NB: we assume that strftime() doesn't check for the
2179 // date validity and will happily format the date
2180 // corresponding to Feb 29 of a non leap year (which
2181 // may happen if yearReal was leap and year is not)
2182 struct tm tmAdjusted;
2183 InitTm(tmAdjusted);
2184 tmAdjusted.tm_hour = tm.hour;
2185 tmAdjusted.tm_min = tm.min;
2186 tmAdjusted.tm_sec = tm.sec;
2187 tmAdjusted.tm_wday = tm.GetWeekDay();
2188 tmAdjusted.tm_yday = GetDayOfYear();
2189 tmAdjusted.tm_mday = tm.mday;
2190 tmAdjusted.tm_mon = tm.mon;
2191 tmAdjusted.tm_year = year - 1900;
2192 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2193 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2194 : _T("%x"),
2195 &tmAdjusted);
2196
2197 // now replace the occurence of 1999 with the real year
2198 wxString strYearReal, strYearReal2;
2199 strYearReal.Printf(_T("%04d"), yearReal);
2200 strYearReal2.Printf(_T("%02d"), yearReal % 100);
2201 str.Replace(strYear, strYearReal);
2202 str.Replace(strYear2, strYearReal2);
2203
2204 // and replace back all occurences of replacement string
2205 if ( wasReplaced )
2206 {
2207 str.Replace(replacement2, strYear2);
2208 str.Replace(replacement, strYear);
2209 }
2210
2211 res += str;
68ee7c47 2212 }
fc3398f8 2213 break;
c5a1681b 2214
fc3398f8
VZ
2215 case _T('d'): // day of a month (01-31)
2216 res += wxString::Format(fmt, tm.mday);
2217 break;
68ee7c47 2218
fc3398f8
VZ
2219 case _T('H'): // hour in 24h format (00-23)
2220 res += wxString::Format(fmt, tm.hour);
2221 break;
2222
2223 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2224 {
fc3398f8
VZ
2225 // 24h -> 12h, 0h -> 12h too
2226 int hour12 = tm.hour > 12 ? tm.hour - 12
2227 : tm.hour ? tm.hour : 12;
2228 res += wxString::Format(fmt, hour12);
68ee7c47 2229 }
fc3398f8 2230 break;
c5a1681b 2231
fc3398f8
VZ
2232 case _T('j'): // day of the year
2233 res += wxString::Format(fmt, GetDayOfYear(tz));
2234 break;
68ee7c47 2235
f6bcfd97
BP
2236 case _T('l'): // milliseconds (NOT STANDARD)
2237 res += wxString::Format(fmt, GetMillisecond(tz));
2238 break;
2239
fc3398f8
VZ
2240 case _T('m'): // month as a number (01-12)
2241 res += wxString::Format(fmt, tm.mon + 1);
2242 break;
68ee7c47 2243
fc3398f8
VZ
2244 case _T('M'): // minute as a decimal number (00-59)
2245 res += wxString::Format(fmt, tm.min);
2246 break;
68ee7c47 2247
fc3398f8
VZ
2248 case _T('p'): // AM or PM string
2249 res += CallStrftime(_T("%p"), &tmTimeOnly);
2250 break;
68ee7c47 2251
fc3398f8
VZ
2252 case _T('S'): // second as a decimal number (00-61)
2253 res += wxString::Format(fmt, tm.sec);
2254 break;
68ee7c47 2255
fc3398f8
VZ
2256 case _T('U'): // week number in the year (Sunday 1st week day)
2257 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2258 break;
68ee7c47 2259
fc3398f8
VZ
2260 case _T('W'): // week number in the year (Monday 1st week day)
2261 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2262 break;
68ee7c47 2263
fc3398f8
VZ
2264 case _T('w'): // weekday as a number (0-6), Sunday = 0
2265 res += wxString::Format(fmt, tm.GetWeekDay());
2266 break;
68ee7c47 2267
fc3398f8 2268 // case _T('x'): -- handled with "%c"
68ee7c47 2269
fc3398f8
VZ
2270 case _T('X'): // locale default time representation
2271 // just use strftime() to format the time for us
2272 res += CallStrftime(_T("%X"), &tmTimeOnly);
2273 break;
68ee7c47 2274
fc3398f8
VZ
2275 case _T('y'): // year without century (00-99)
2276 res += wxString::Format(fmt, tm.year % 100);
2277 break;
68ee7c47 2278
fc3398f8
VZ
2279 case _T('Y'): // year with century
2280 res += wxString::Format(fmt, tm.year);
2281 break;
68ee7c47 2282
fc3398f8
VZ
2283 case _T('Z'): // timezone name
2284 res += CallStrftime(_T("%Z"), &tmTimeOnly);
2285 break;
68ee7c47 2286
fc3398f8
VZ
2287 default:
2288 // is it the format width?
2289 fmt.Empty();
2290 while ( *p == _T('-') || *p == _T('+') ||
2291 *p == _T(' ') || wxIsdigit(*p) )
2292 {
2293 fmt += *p;
2294 }
68ee7c47 2295
fc3398f8
VZ
2296 if ( !fmt.IsEmpty() )
2297 {
2298 // we've only got the flags and width so far in fmt
2299 fmt.Prepend(_T('%'));
2300 fmt.Append(_T('d'));
68ee7c47 2301
fc3398f8 2302 restart = TRUE;
68ee7c47 2303
fc3398f8
VZ
2304 break;
2305 }
68ee7c47 2306
fc3398f8
VZ
2307 // no, it wasn't the width
2308 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2309
fc3398f8 2310 // fall through and just copy it nevertheless
68ee7c47 2311
fc3398f8
VZ
2312 case _T('%'): // a percent sign
2313 res += *p;
2314 break;
2315
2316 case 0: // the end of string
2317 wxFAIL_MSG(_T("missing format at the end of string"));
2318
2319 // just put the '%' which was the last char in format
2320 res += _T('%');
2321 break;
2322 }
68ee7c47 2323 }
c5a1681b
VZ
2324 }
2325
68ee7c47 2326 return res;
b76b015e 2327}
fcc3d7cb 2328
cd0b1709
VZ
2329// this function parses a string in (strict) RFC 822 format: see the section 5
2330// of the RFC for the detailed description, but briefly it's something of the
2331// form "Sat, 18 Dec 1999 00:48:30 +0100"
2332//
2333// this function is "strict" by design - it must reject anything except true
2334// RFC822 time specs.
2335//
2336// TODO a great candidate for using reg exps
2337const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2338{
2339 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2340
2341 const wxChar *p = date;
2342 const wxChar *comma = wxStrchr(p, _T(','));
2343 if ( comma )
2344 {
2345 // the part before comma is the weekday
2346
2347 // skip it for now - we don't use but might check that it really
2348 // corresponds to the specfied date
2349 p = comma + 1;
2350
2351 if ( *p != _T(' ') )
2352 {
2353 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2354
2355 return (wxChar *)NULL;
2356 }
2357
2358 p++; // skip space
2359 }
2360
2361 // the following 1 or 2 digits are the day number
2362 if ( !wxIsdigit(*p) )
2363 {
2364 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2365
2366 return (wxChar *)NULL;
2367 }
2368
2369 wxDateTime_t day = *p++ - _T('0');
2370 if ( wxIsdigit(*p) )
2371 {
2372 day *= 10;
2373 day += *p++ - _T('0');
2374 }
2375
2376 if ( *p++ != _T(' ') )
2377 {
2378 return (wxChar *)NULL;
2379 }
2380
2381 // the following 3 letters specify the month
2382 wxString monName(p, 3);
2383 Month mon;
2384 if ( monName == _T("Jan") )
2385 mon = Jan;
2386 else if ( monName == _T("Feb") )
2387 mon = Feb;
2388 else if ( monName == _T("Mar") )
2389 mon = Mar;
2390 else if ( monName == _T("Apr") )
2391 mon = Apr;
2392 else if ( monName == _T("May") )
2393 mon = May;
2394 else if ( monName == _T("Jun") )
2395 mon = Jun;
2396 else if ( monName == _T("Jul") )
2397 mon = Jul;
2398 else if ( monName == _T("Aug") )
2399 mon = Aug;
2400 else if ( monName == _T("Sep") )
2401 mon = Sep;
2402 else if ( monName == _T("Oct") )
2403 mon = Oct;
2404 else if ( monName == _T("Nov") )
2405 mon = Nov;
2406 else if ( monName == _T("Dec") )
2407 mon = Dec;
2408 else
2409 {
2410 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2411
2412 return (wxChar *)NULL;
2413 }
2414
2415 p += 3;
2416
2417 if ( *p++ != _T(' ') )
2418 {
2419 return (wxChar *)NULL;
2420 }
2421
2422 // next is the year
2423 if ( !wxIsdigit(*p) )
2424 {
2425 // no year?
2426 return (wxChar *)NULL;
2427 }
2428
2429 int year = *p++ - _T('0');
2430
2431 if ( !wxIsdigit(*p) )
2432 {
2433 // should have at least 2 digits in the year
2434 return (wxChar *)NULL;
2435 }
2436
2437 year *= 10;
2438 year += *p++ - _T('0');
2439
2440 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2441 if ( wxIsdigit(*p) )
2442 {
2443 year *= 10;
2444 year += *p++ - _T('0');
2445
2446 if ( !wxIsdigit(*p) )
2447 {
2448 // no 3 digit years please
2449 return (wxChar *)NULL;
2450 }
2451
2452 year *= 10;
2453 year += *p++ - _T('0');
2454 }
2455
2456 if ( *p++ != _T(' ') )
2457 {
2458 return (wxChar *)NULL;
2459 }
2460
2461 // time is in the format hh:mm:ss and seconds are optional
2462 if ( !wxIsdigit(*p) )
2463 {
2464 return (wxChar *)NULL;
2465 }
2466
2467 wxDateTime_t hour = *p++ - _T('0');
2468
2469 if ( !wxIsdigit(*p) )
2470 {
2471 return (wxChar *)NULL;
2472 }
2473
2474 hour *= 10;
2475 hour += *p++ - _T('0');
2476
2477 if ( *p++ != _T(':') )
2478 {
2479 return (wxChar *)NULL;
2480 }
2481
2482 if ( !wxIsdigit(*p) )
2483 {
2484 return (wxChar *)NULL;
2485 }
2486
2487 wxDateTime_t min = *p++ - _T('0');
2488
2489 if ( !wxIsdigit(*p) )
2490 {
2491 return (wxChar *)NULL;
2492 }
2493
2494 min *= 10;
2495 min += *p++ - _T('0');
2496
2497 wxDateTime_t sec = 0;
2498 if ( *p++ == _T(':') )
2499 {
2500 if ( !wxIsdigit(*p) )
2501 {
2502 return (wxChar *)NULL;
2503 }
2504
2505 sec = *p++ - _T('0');
2506
2507 if ( !wxIsdigit(*p) )
2508 {
2509 return (wxChar *)NULL;
2510 }
2511
2512 sec *= 10;
2513 sec += *p++ - _T('0');
2514 }
2515
2516 if ( *p++ != _T(' ') )
2517 {
2518 return (wxChar *)NULL;
2519 }
2520
2521 // and now the interesting part: the timezone
40973ea5 2522 int offset;
cd0b1709
VZ
2523 if ( *p == _T('-') || *p == _T('+') )
2524 {
2525 // the explicit offset given: it has the form of hhmm
2526 bool plus = *p++ == _T('+');
2527
2528 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2529 {
2530 return (wxChar *)NULL;
2531 }
2532
2533 // hours
2534 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2535
2536 p += 2;
2537
2538 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2539 {
2540 return (wxChar *)NULL;
2541 }
2542
2543 // minutes
2544 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2545
2546 if ( !plus )
2547 {
2548 offset = -offset;
2549 }
2550
2551 p += 2;
2552 }
2553 else
2554 {
2555 // the symbolic timezone given: may be either military timezone or one
2556 // of standard abbreviations
2557 if ( !*(p + 1) )
2558 {
2559 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2560 static const int offsets[26] =
2561 {
2562 //A B C D E F G H I J K L M
2563 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2564 //N O P R Q S T U V W Z Y Z
2565 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2566 };
2567
2568 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2569 {
2570 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2571
2572 return (wxChar *)NULL;
2573 }
2574
2575 offset = offsets[*p++ - _T('A')];
2576 }
2577 else
2578 {
2579 // abbreviation
2580 wxString tz = p;
2581 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2582 offset = 0;
2583 else if ( tz == _T("AST") )
2584 offset = AST - GMT0;
2585 else if ( tz == _T("ADT") )
2586 offset = ADT - GMT0;
2587 else if ( tz == _T("EST") )
2588 offset = EST - GMT0;
2589 else if ( tz == _T("EDT") )
2590 offset = EDT - GMT0;
2591 else if ( tz == _T("CST") )
2592 offset = CST - GMT0;
2593 else if ( tz == _T("CDT") )
2594 offset = CDT - GMT0;
2595 else if ( tz == _T("MST") )
2596 offset = MST - GMT0;
2597 else if ( tz == _T("MDT") )
2598 offset = MDT - GMT0;
2599 else if ( tz == _T("PST") )
2600 offset = PST - GMT0;
2601 else if ( tz == _T("PDT") )
2602 offset = PDT - GMT0;
2603 else
2604 {
2605 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2606
2607 return (wxChar *)NULL;
2608 }
2609
2610 p += tz.length();
2611 }
2612
2613 // make it minutes
2614 offset *= 60;
2615 }
2616
2617 // the spec was correct
2618 Set(day, mon, year, hour, min, sec);
487c1f7e 2619 MakeTimezone((wxDateTime_t)(60*offset));
cd0b1709
VZ
2620
2621 return p;
2622}
2623
f0f951fa
VZ
2624const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2625 const wxChar *format,
2626 const wxDateTime& dateDef)
cd0b1709
VZ
2627{
2628 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 2629 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 2630
f0f951fa
VZ
2631 wxString str;
2632 unsigned long num;
cd0b1709 2633
f0f951fa
VZ
2634 // what fields have we found?
2635 bool haveWDay = FALSE,
2636 haveYDay = FALSE,
2637 haveDay = FALSE,
2638 haveMon = FALSE,
2639 haveYear = FALSE,
2640 haveHour = FALSE,
2641 haveMin = FALSE,
2642 haveSec = FALSE;
2643
2644 bool hourIsIn12hFormat = FALSE, // or in 24h one?
2645 isPM = FALSE; // AM by default
2646
2647 // and the value of the items we have (init them to get rid of warnings)
2648 wxDateTime_t sec = 0,
2649 min = 0,
2650 hour = 0;
2651 WeekDay wday = Inv_WeekDay;
2652 wxDateTime_t yday = 0,
2653 mday = 0;
2654 wxDateTime::Month mon = Inv_Month;
2655 int year = 0;
2656
2657 const wxChar *input = date;
2658 for ( const wxChar *fmt = format; *fmt; fmt++ )
2659 {
2660 if ( *fmt != _T('%') )
2661 {
2662 if ( wxIsspace(*fmt) )
2663 {
2664 // a white space in the format string matches 0 or more white
2665 // spaces in the input
2666 while ( wxIsspace(*input) )
2667 {
2668 input++;
2669 }
2670 }
2671 else // !space
2672 {
2673 // any other character (not whitespace, not '%') must be
2674 // matched by itself in the input
2675 if ( *input++ != *fmt )
2676 {
2677 // no match
2678 return (wxChar *)NULL;
2679 }
2680 }
2681
2682 // done with this format char
2683 continue;
2684 }
2685
2686 // start of a format specification
f6bcfd97
BP
2687
2688 // parse the optional width
2689 size_t width = 0;
1c193821 2690 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
2691 {
2692 width *= 10;
2693 width += *fmt - _T('0');
2694 }
2695
4147efe9
VZ
2696 // the default widths for the various fields
2697 if ( !width )
2698 {
2699 switch ( *fmt )
2700 {
2701 case _T('Y'): // year has 4 digits
2702 width = 4;
2703 break;
2704
2705 case _T('j'): // day of year has 3 digits
2706 case _T('l'): // milliseconds have 3 digits
2707 width = 3;
2708 break;
2709
2710 case _T('w'): // week day as number has only one
2711 width = 1;
2712 break;
2713
2714 default:
2715 // default for all other fields
2716 width = 2;
2717 }
2718 }
2719
f6bcfd97
BP
2720 // then the format itself
2721 switch ( *fmt )
f0f951fa
VZ
2722 {
2723 case _T('a'): // a weekday name
2724 case _T('A'):
2725 {
2726 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2727 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2728 if ( wday == Inv_WeekDay )
2729 {
2730 // no match
2731 return (wxChar *)NULL;
2732 }
2733 }
2734 haveWDay = TRUE;
2735 break;
2736
2737 case _T('b'): // a month name
2738 case _T('B'):
2739 {
2740 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
2741 mon = GetMonthFromName(GetAlphaToken(input), flag);
2742 if ( mon == Inv_Month )
2743 {
2744 // no match
2745 return (wxChar *)NULL;
2746 }
2747 }
2748 haveMon = TRUE;
2749 break;
2750
2751 case _T('c'): // locale default date and time representation
2752 {
2753 wxDateTime dt;
2754
2755 // this is the format which corresponds to ctime() output
2756 // and strptime("%c") should parse it, so try it first
41acf5c0 2757 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
2758
2759 const wxChar *result = dt.ParseFormat(input, fmtCtime);
2760 if ( !result )
2761 {
2762 result = dt.ParseFormat(input, _T("%x %X"));
2763 }
2764
2765 if ( !result )
2766 {
2767 result = dt.ParseFormat(input, _T("%X %x"));
2768 }
2769
2770 if ( !result )
2771 {
2772 // we've tried everything and still no match
2773 return (wxChar *)NULL;
2774 }
2775
be4017f8
VZ
2776 Tm tm = dt.GetTm();
2777
2778 haveDay = haveMon = haveYear =
2779 haveHour = haveMin = haveSec = TRUE;
2780
2781 hour = tm.hour;
2782 min = tm.min;
2783 sec = tm.sec;
2784
2785 year = tm.year;
2786 mon = tm.mon;
2787 mday = tm.mday;
2788
f0f951fa
VZ
2789 input = result;
2790 }
2791 break;
2792
2793 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
2794 if ( !GetNumericToken(width, input, &num) ||
2795 (num > 31) || (num < 1) )
f0f951fa
VZ
2796 {
2797 // no match
2798 return (wxChar *)NULL;
2799 }
2800
2801 // we can't check whether the day range is correct yet, will
2802 // do it later - assume ok for now
2803 haveDay = TRUE;
2804 mday = (wxDateTime_t)num;
2805 break;
5f287370 2806
f0f951fa 2807 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 2808 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
2809 {
2810 // no match
2811 return (wxChar *)NULL;
2812 }
2813
2814 haveHour = TRUE;
2815 hour = (wxDateTime_t)num;
2816 break;
2817
2818 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 2819 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2820 {
2821 // no match
2822 return (wxChar *)NULL;
2823 }
2824
2825 haveHour = TRUE;
2826 hourIsIn12hFormat = TRUE;
479cd5de 2827 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
2828 break;
2829
2830 case _T('j'): // day of the year
f6bcfd97 2831 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
2832 {
2833 // no match
2834 return (wxChar *)NULL;
2835 }
2836
2837 haveYDay = TRUE;
2838 yday = (wxDateTime_t)num;
2839 break;
2840
2841 case _T('m'): // month as a number (01-12)
f6bcfd97 2842 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2843 {
2844 // no match
2845 return (wxChar *)NULL;
2846 }
2847
2848 haveMon = TRUE;
be4017f8 2849 mon = (Month)(num - 1);
f0f951fa
VZ
2850 break;
2851
2852 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 2853 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
2854 {
2855 // no match
2856 return (wxChar *)NULL;
2857 }
2858
2859 haveMin = TRUE;
2860 min = (wxDateTime_t)num;
2861 break;
2862
2863 case _T('p'): // AM or PM string
2864 {
2865 wxString am, pm, token = GetAlphaToken(input);
2866
2867 GetAmPmStrings(&am, &pm);
2868 if ( token.CmpNoCase(pm) == 0 )
2869 {
2870 isPM = TRUE;
2871 }
2872 else if ( token.CmpNoCase(am) != 0 )
2873 {
2874 // no match
2875 return (wxChar *)NULL;
2876 }
2877 }
2878 break;
2879
2880 case _T('r'): // time as %I:%M:%S %p
2881 {
2882 wxDateTime dt;
2883 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
2884 if ( !input )
2885 {
2886 // no match
2887 return (wxChar *)NULL;
2888 }
2889
2890 haveHour = haveMin = haveSec = TRUE;
2891
2892 Tm tm = dt.GetTm();
2893 hour = tm.hour;
2894 min = tm.min;
2895 sec = tm.sec;
2896 }
2897 break;
2898
2899 case _T('R'): // time as %H:%M
2900 {
2901 wxDateTime dt;
2902 input = dt.ParseFormat(input, _T("%H:%M"));
2903 if ( !input )
2904 {
2905 // no match
2906 return (wxChar *)NULL;
2907 }
2908
2909 haveHour = haveMin = TRUE;
2910
2911 Tm tm = dt.GetTm();
2912 hour = tm.hour;
2913 min = tm.min;
2914 }
2915
2916 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 2917 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
2918 {
2919 // no match
2920 return (wxChar *)NULL;
2921 }
2922
2923 haveSec = TRUE;
2924 sec = (wxDateTime_t)num;
2925 break;
2926
2927 case _T('T'): // time as %H:%M:%S
2928 {
2929 wxDateTime dt;
2930 input = dt.ParseFormat(input, _T("%H:%M:%S"));
2931 if ( !input )
2932 {
2933 // no match
2934 return (wxChar *)NULL;
2935 }
2936
2937 haveHour = haveMin = haveSec = TRUE;
2938
2939 Tm tm = dt.GetTm();
2940 hour = tm.hour;
2941 min = tm.min;
2942 sec = tm.sec;
2943 }
be4017f8 2944 break;
f0f951fa
VZ
2945
2946 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 2947 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
2948 {
2949 // no match
2950 return (wxChar *)NULL;
2951 }
2952
2953 haveWDay = TRUE;
2954 wday = (WeekDay)num;
2955 break;
2956
2957 case _T('x'): // locale default date representation
2958#ifdef HAVE_STRPTIME
2423ef22 2959 // try using strptime() -- it may fail even if the input is
f0f951fa 2960 // correct but the date is out of range, so we will fall back
2423ef22 2961 // to our generic code anyhow
f0f951fa
VZ
2962 {
2963 struct tm tm;
2423ef22
VZ
2964
2965 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
2966 if ( result )
2967 {
2968 input = result;
2969
2970 haveDay = haveMon = haveYear = TRUE;
2971
2972 year = 1900 + tm.tm_year;
2973 mon = (Month)tm.tm_mon;
2974 mday = tm.tm_mday;
2975
2976 break;
2977 }
2978 }
2979#endif // HAVE_STRPTIME
2980
2981 // TODO query the LOCALE_IDATE setting under Win32
2982 {
2983 wxDateTime dt;
2984
41acf5c0 2985 wxString fmtDate, fmtDateAlt;
f0f951fa
VZ
2986 if ( IsWestEuropeanCountry(GetCountry()) ||
2987 GetCountry() == Russia )
2988 {
2989 fmtDate = _T("%d/%m/%y");
41acf5c0 2990 fmtDateAlt = _T("%m/%d/%y");
f0f951fa
VZ
2991 }
2992 else // assume USA
2993 {
2994 fmtDate = _T("%m/%d/%y");
41acf5c0 2995 fmtDateAlt = _T("%d/%m/%y");
f0f951fa
VZ
2996 }
2997
2998 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0
VZ
2999
3000 if ( !result )
3001 {
3002 // ok, be nice and try another one
3003 result = dt.ParseFormat(input, fmtDateAlt);
3004 }
3005
f0f951fa
VZ
3006 if ( !result )
3007 {
3008 // bad luck
3009 return (wxChar *)NULL;
3010 }
3011
3012 Tm tm = dt.GetTm();
3013
3014 haveDay = haveMon = haveYear = TRUE;
3015
3016 year = tm.year;
3017 mon = tm.mon;
3018 mday = tm.mday;
3019
3020 input = result;
3021 }
3022
3023 break;
3024
3025 case _T('X'): // locale default time representation
3026#ifdef HAVE_STRPTIME
3027 {
3028 // use strptime() to do it for us (FIXME !Unicode friendly)
3029 struct tm tm;
2423ef22 3030 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3031 if ( !input )
3032 {
3033 return (wxChar *)NULL;
3034 }
3035
3036 haveHour = haveMin = haveSec = TRUE;
3037
3038 hour = tm.tm_hour;
3039 min = tm.tm_min;
3040 sec = tm.tm_sec;
3041 }
3042#else // !HAVE_STRPTIME
3043 // TODO under Win32 we can query the LOCALE_ITIME system
3044 // setting which says whether the default time format is
3045 // 24 or 12 hour
3046 {
3047 // try to parse what follows as "%H:%M:%S" and, if this
3048 // fails, as "%I:%M:%S %p" - this should catch the most
3049 // common cases
3050 wxDateTime dt;
3051
3052 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3053 if ( !result )
3054 {
3055 result = dt.ParseFormat(input, _T("%r"));
3056 }
3057
3058 if ( !result )
3059 {
3060 // no match
3061 return (wxChar *)NULL;
3062 }
3063
3064 haveHour = haveMin = haveSec = TRUE;
3065
3066 Tm tm = dt.GetTm();
3067 hour = tm.hour;
3068 min = tm.min;
3069 sec = tm.sec;
3070
3071 input = result;
3072 }
3073#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3074 break;
3075
3076 case _T('y'): // year without century (00-99)
f6bcfd97 3077 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3078 {
3079 // no match
3080 return (wxChar *)NULL;
3081 }
3082
3083 haveYear = TRUE;
f6bcfd97
BP
3084
3085 // TODO should have an option for roll over date instead of
3086 // hard coding it here
3087 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3088 break;
3089
3090 case _T('Y'): // year with century
f6bcfd97 3091 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3092 {
3093 // no match
3094 return (wxChar *)NULL;
3095 }
3096
be4017f8
VZ
3097 haveYear = TRUE;
3098 year = (wxDateTime_t)num;
f0f951fa
VZ
3099 break;
3100
3101 case _T('Z'): // timezone name
3102 wxFAIL_MSG(_T("TODO"));
3103 break;
3104
3105 case _T('%'): // a percent sign
3106 if ( *input++ != _T('%') )
3107 {
3108 // no match
3109 return (wxChar *)NULL;
3110 }
3111 break;
3112
3113 case 0: // the end of string
3114 wxFAIL_MSG(_T("unexpected format end"));
3115
3116 // fall through
3117
3118 default: // not a known format spec
3119 return (wxChar *)NULL;
3120 }
3121 }
3122
3123 // format matched, try to construct a date from what we have now
3124 Tm tmDef;
3125 if ( dateDef.IsValid() )
3126 {
3127 // take this date as default
3128 tmDef = dateDef.GetTm();
3129 }
c3302e7e 3130 else if ( IsValid() )
f0f951fa
VZ
3131 {
3132 // if this date is valid, don't change it
3133 tmDef = GetTm();
3134 }
3135 else
3136 {
3137 // no default and this date is invalid - fall back to Today()
3138 tmDef = Today().GetTm();
3139 }
3140
3141 Tm tm = tmDef;
3142
3143 // set the date
3144 if ( haveYear )
3145 {
3146 tm.year = year;
3147 }
3148
3149 // TODO we don't check here that the values are consistent, if both year
3150 // day and month/day were found, we just ignore the year day and we
3151 // also always ignore the week day
3152 if ( haveMon && haveDay )
3153 {
be4017f8
VZ
3154 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3155 {
3156 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3157
3158 return (wxChar *)NULL;
3159 }
3160
f0f951fa
VZ
3161 tm.mon = mon;
3162 tm.mday = mday;
3163 }
3164 else if ( haveYDay )
3165 {
be4017f8
VZ
3166 if ( yday > GetNumberOfDays(tm.year) )
3167 {
3168 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3169
3170 return (wxChar *)NULL;
3171 }
3172
f0f951fa
VZ
3173 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3174
3175 tm.mon = tm2.mon;
3176 tm.mday = tm2.mday;
3177 }
3178
3179 // deal with AM/PM
3180 if ( haveHour && hourIsIn12hFormat && isPM )
3181 {
3182 // translate to 24hour format
3183 hour += 12;
3184 }
3185 //else: either already in 24h format or no translation needed
3186
3187 // set the time
3188 if ( haveHour )
3189 {
5f287370 3190 tm.hour = hour;
f0f951fa
VZ
3191 }
3192
3193 if ( haveMin )
3194 {
3195 tm.min = min;
3196 }
3197
3198 if ( haveSec )
3199 {
3200 tm.sec = sec;
3201 }
3202
3203 Set(tm);
3204
88a036ce
VZ
3205 // finally check that the week day is consistent -- if we had it
3206 if ( haveWDay && GetWeekDay() != wday )
3207 {
3208 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3209
3210 return NULL;
3211 }
3212
f0f951fa 3213 return input;
cd0b1709
VZ
3214}
3215
3216const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3217{
3218 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3219
f0f951fa
VZ
3220 // there is a public domain version of getdate.y, but it only works for
3221 // English...
cd0b1709
VZ
3222 wxFAIL_MSG(_T("TODO"));
3223
3224 return (wxChar *)NULL;
3225}
3226
3227const wxChar *wxDateTime::ParseDate(const wxChar *date)
3228{
3229 // this is a simplified version of ParseDateTime() which understands only
3230 // "today" (for wxDate compatibility) and digits only otherwise (and not
3231 // all esoteric constructions ParseDateTime() knows about)
3232
3233 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3234
3235 const wxChar *p = date;
3236 while ( wxIsspace(*p) )
3237 p++;
3238
fd9f9f4c
VZ
3239 // some special cases
3240 static struct
cd0b1709 3241 {
fd9f9f4c
VZ
3242 const wxChar *str;
3243 int dayDiffFromToday;
3244 } literalDates[] =
3245 {
3246 { wxTRANSLATE("today"), 0 },
3247 { wxTRANSLATE("yesterday"), -1 },
3248 { wxTRANSLATE("tomorrow"), 1 },
3249 };
3250
3251 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3252 {
3253 wxString date = wxGetTranslation(literalDates[n].str);
3254 size_t len = date.length();
3b415ba4 3255 if ( wxStrlen(p) >= len && (wxString(p, len).CmpNoCase(date) == 0) )
fd9f9f4c
VZ
3256 {
3257 // nothing can follow this, so stop here
3258 p += len;
cd0b1709 3259
fd9f9f4c
VZ
3260 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3261 *this = Today();
3262 if ( dayDiffFromToday )
3263 {
3264 *this += wxDateSpan::Days(dayDiffFromToday);
3265 }
cd0b1709 3266
fd9f9f4c
VZ
3267 return p;
3268 }
cd0b1709
VZ
3269 }
3270
3ca6a5f0
BP
3271 // We try to guess what we have here: for each new (numeric) token, we
3272 // determine if it can be a month, day or a year. Of course, there is an
3273 // ambiguity as some numbers may be days as well as months, so we also
3274 // have the ability to back track.
3275
cd0b1709
VZ
3276 // what do we have?
3277 bool haveDay = FALSE, // the months day?
3278 haveWDay = FALSE, // the day of week?
3279 haveMon = FALSE, // the month?
3280 haveYear = FALSE; // the year?
3281
3282 // and the value of the items we have (init them to get rid of warnings)
3283 WeekDay wday = Inv_WeekDay;
3284 wxDateTime_t day = 0;
3285 wxDateTime::Month mon = Inv_Month;
3286 int year = 0;
3287
3288 // tokenize the string
f6bcfd97 3289 size_t nPosCur = 0;
62ae2780 3290 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3291 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3292 while ( tok.HasMoreTokens() )
3293 {
3294 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3295 if ( !token )
3296 continue;
cd0b1709
VZ
3297
3298 // is it a number?
3299 unsigned long val;
fde5a86b 3300 if ( token.ToULong(&val) )
cd0b1709
VZ
3301 {
3302 // guess what this number is
3303
3304 bool isDay = FALSE,
3305 isMonth = FALSE,
3ca6a5f0
BP
3306 isYear = FALSE;
3307
3308 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3309 {
3ca6a5f0
BP
3310 // assume it is month
3311 isMonth = TRUE;
3312 }
3313 else // not the month
3314 {
3315 wxDateTime_t maxDays = haveMon
3316 ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
3317 : 31;
3318
3319 // can it be day?
3897b707 3320 if ( (val == 0) || (val > (unsigned long)maxDays) ) // cast to shut up compiler warning in BCC
cd0b1709 3321 {
3ca6a5f0 3322 isYear = TRUE;
cd0b1709 3323 }
3ca6a5f0 3324 else
cd0b1709
VZ
3325 {
3326 isDay = TRUE;
cd0b1709
VZ
3327 }
3328 }
3329
cd0b1709
VZ
3330 if ( isYear )
3331 {
3332 if ( haveYear )
cd0b1709 3333 break;
cd0b1709
VZ
3334
3335 haveYear = TRUE;
3336
479cd5de 3337 year = (wxDateTime_t)val;
cd0b1709 3338 }
3ca6a5f0 3339 else if ( isDay )
cd0b1709 3340 {
3ca6a5f0 3341 if ( haveDay )
cd0b1709 3342 break;
cd0b1709 3343
3ca6a5f0 3344 haveDay = TRUE;
cd0b1709 3345
3ca6a5f0 3346 day = (wxDateTime_t)val;
cd0b1709 3347 }
3ca6a5f0 3348 else if ( isMonth )
cd0b1709 3349 {
3ca6a5f0 3350 haveMon = TRUE;
cd0b1709 3351
3ca6a5f0 3352 mon = (Month)(val - 1);
cd0b1709
VZ
3353 }
3354 }
3355 else // not a number
3356 {
f6bcfd97
BP
3357 // be careful not to overwrite the current mon value
3358 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3359 if ( mon2 != Inv_Month )
cd0b1709
VZ
3360 {
3361 // it's a month
3362 if ( haveMon )
3363 {
6411a32c
VZ
3364 // but we already have a month - maybe we guessed wrong?
3365 if ( !haveDay )
3366 {
f5cd9787 3367 // no need to check in month range as always < 12, but
6411a32c
VZ
3368 // the days are counted from 1 unlike the months
3369 day = (wxDateTime_t)mon + 1;
3370 haveDay = TRUE;
3371 }
3372 else
3373 {
3374 // could possible be the year (doesn't the year come
3375 // before the month in the japanese format?) (FIXME)
3376 break;
f5cd9787 3377 }
cd0b1709
VZ
3378 }
3379
f6bcfd97
BP
3380 mon = mon2;
3381
cd0b1709
VZ
3382 haveMon = TRUE;
3383 }
6411a32c 3384 else // not a valid month name
cd0b1709 3385 {
f0f951fa 3386 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3387 if ( wday != Inv_WeekDay )
3388 {
3389 // a week day
3390 if ( haveWDay )
3391 {
3392 break;
3393 }
3394
3395 haveWDay = TRUE;
3396 }
6411a32c 3397 else // not a valid weekday name
cd0b1709
VZ
3398 {
3399 // try the ordinals
3400 static const wxChar *ordinals[] =
3401 {
3402 wxTRANSLATE("first"),
3403 wxTRANSLATE("second"),
3404 wxTRANSLATE("third"),
3405 wxTRANSLATE("fourth"),
3406 wxTRANSLATE("fifth"),
3407 wxTRANSLATE("sixth"),
3408 wxTRANSLATE("seventh"),
3409 wxTRANSLATE("eighth"),
3410 wxTRANSLATE("ninth"),
3411 wxTRANSLATE("tenth"),
3412 wxTRANSLATE("eleventh"),
3413 wxTRANSLATE("twelfth"),
3414 wxTRANSLATE("thirteenth"),
3415 wxTRANSLATE("fourteenth"),
3416 wxTRANSLATE("fifteenth"),
3417 wxTRANSLATE("sixteenth"),
3418 wxTRANSLATE("seventeenth"),
3419 wxTRANSLATE("eighteenth"),
3420 wxTRANSLATE("nineteenth"),
3421 wxTRANSLATE("twentieth"),
3422 // that's enough - otherwise we'd have problems with
6411a32c 3423 // composite (or not) ordinals
cd0b1709
VZ
3424 };
3425
3426 size_t n;
3427 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3428 {
3429 if ( token.CmpNoCase(ordinals[n]) == 0 )
3430 {
3431 break;
3432 }
3433 }
3434
3435 if ( n == WXSIZEOF(ordinals) )
3436 {
3437 // stop here - something unknown
3438 break;
3439 }
3440
3441 // it's a day
3442 if ( haveDay )
3443 {
3444 // don't try anything here (as in case of numeric day
3445 // above) - the symbolic day spec should always
3446 // precede the month/year
3447 break;
3448 }
3449
3450 haveDay = TRUE;
3451
33ac7e6f 3452 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3453 }
3454 }
3455 }
f6bcfd97
BP
3456
3457 nPosCur = tok.GetPosition();
cd0b1709
VZ
3458 }
3459
3460 // either no more tokens or the scan was stopped by something we couldn't
3461 // parse - in any case, see if we can construct a date from what we have
3462 if ( !haveDay && !haveWDay )
3463 {
f6bcfd97 3464 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709
VZ
3465
3466 return (wxChar *)NULL;
3467 }
3468
3469 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3470 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3471 {
3472 // without adjectives (which we don't support here) the week day only
3473 // makes sense completely separately or with the full date
3474 // specification (what would "Wed 1999" mean?)
3475 return (wxChar *)NULL;
3476 }
3477
f6bcfd97
BP
3478 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3479 {
3480 // may be we have month and day instead of day and year?
3481 if ( haveDay && !haveMon )
3482 {
3483 if ( day <= 12 )
3484 {
3485 // exchange day and month
3486 mon = (wxDateTime::Month)(day - 1);
3487
3488 // we're in the current year then
196be0f1 3489 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97
BP
3490 {
3491 day = year;
3492
3493 haveMon = TRUE;
3494 haveYear = FALSE;
3495 }
3496 //else: no, can't exchange, leave haveMon == FALSE
3497 }
3498 }
3499
3500 if ( !haveMon )
3501 {
3502 // if we give the year, month and day must be given too
3503 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3504
3505 return (wxChar *)NULL;
3506 }
3507 }
3508
cd0b1709
VZ
3509 if ( !haveMon )
3510 {
3511 mon = GetCurrentMonth();
3512 }
3513
3514 if ( !haveYear )
3515 {
3516 year = GetCurrentYear();
3517 }
3518
3519 if ( haveDay )
3520 {
3521 Set(day, mon, year);
3522
3523 if ( haveWDay )
3524 {
3525 // check that it is really the same
3526 if ( GetWeekDay() != wday )
3527 {
3528 // inconsistency detected
f6bcfd97
BP
3529 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3530
cd0b1709
VZ
3531 return (wxChar *)NULL;
3532 }
3533 }
3534 }
3535 else // haveWDay
3536 {
3537 *this = Today();
3538
3539 SetToWeekDayInSameWeek(wday);
3540 }
3541
f6bcfd97
BP
3542 // return the pointer to the first unparsed char
3543 p += nPosCur;
3544 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
3545 {
3546 // if we couldn't parse the token after the delimiter, put back the
3547 // delimiter as well
3548 p--;
3549 }
3550
3551 return p;
cd0b1709
VZ
3552}
3553
3554const wxChar *wxDateTime::ParseTime(const wxChar *time)
3555{
3556 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3557
f0f951fa
VZ
3558 // first try some extra things
3559 static const struct
3560 {
3561 const wxChar *name;
3562 wxDateTime_t hour;
3563 } stdTimes[] =
3564 {
3565 { wxTRANSLATE("noon"), 12 },
3566 { wxTRANSLATE("midnight"), 00 },
3567 // anything else?
3568 };
cd0b1709 3569
f0f951fa
VZ
3570 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3571 {
3572 wxString timeString = wxGetTranslation(stdTimes[n].name);
3573 size_t len = timeString.length();
3574 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3575 {
4ded51f2
CE
3576 // casts required by DigitalMars
3577 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
3578
3579 return time + len;
3580 }
3581 }
3582
c7f9a482
VZ
3583 // try all time formats we may think about in the order from longest to
3584 // shortest
3585
3586 // 12hour with AM/PM?
3587 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
3588
f0f951fa
VZ
3589 if ( !result )
3590 {
3591 // normally, it's the same, but why not try it?
3592 result = ParseFormat(time, _T("%H:%M:%S"));
3593 }
3594
3595 if ( !result )
3596 {
c7f9a482
VZ
3597 // 12hour with AM/PM but without seconds?
3598 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
3599 }
3600
3601 if ( !result )
3602 {
3603 // without seconds?
3604 result = ParseFormat(time, _T("%H:%M"));
3605 }
3606
3607 if ( !result )
3608 {
c7f9a482
VZ
3609 // just the hour and AM/PM?
3610 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
3611 }
3612
3613 if ( !result )
3614 {
3615 // just the hour?
3616 result = ParseFormat(time, _T("%H"));
3617 }
3618
3619 if ( !result )
3620 {
c7f9a482
VZ
3621 // parse the standard format: normally it is one of the formats above
3622 // but it may be set to something completely different by the user
3623 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
3624 }
3625
3626 // TODO: parse timezones
3627
3628 return result;
cd0b1709
VZ
3629}
3630
4f6aed9c
VZ
3631// ----------------------------------------------------------------------------
3632// Workdays and holidays support
3633// ----------------------------------------------------------------------------
3634
3635bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3636{
3637 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3638}
3639
fcc3d7cb
VZ
3640// ============================================================================
3641// wxTimeSpan
3642// ============================================================================
3643
033400eb
VZ
3644// this enum is only used in wxTimeSpan::Format() below but we can't declare
3645// it locally to the method as it provokes an internal compiler error in egcs
3646// 2.91.60 when building with -O2
3647enum TimeSpanPart
3648{
3649 Part_Week,
3650 Part_Day,
3651 Part_Hour,
3652 Part_Min,
3653 Part_Sec,
3654 Part_MSec
3655};
3656
e6ec579c
VZ
3657// not all strftime(3) format specifiers make sense here because, for example,
3658// a time span doesn't have a year nor a timezone
3659//
3660// Here are the ones which are supported (all of them are supported by strftime
3661// as well):
3662// %H hour in 24 hour format
3663// %M minute (00 - 59)
3664// %S second (00 - 59)
3665// %% percent sign
3666//
3667// Also, for MFC CTimeSpan compatibility, we support
3668// %D number of days
3669//
3670// And, to be better than MFC :-), we also have
3671// %E number of wEeks
3672// %l milliseconds (000 - 999)
fcc3d7cb
VZ
3673wxString wxTimeSpan::Format(const wxChar *format) const
3674{
e6ec579c 3675 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
3676
3677 wxString str;
5b735202 3678 str.Alloc(wxStrlen(format));
e6ec579c 3679
df05cdc5
VZ
3680 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3681 //
3682 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3683 // question is what should ts.Format("%S") do? The code here returns "3273"
3684 // in this case (i.e. the total number of seconds, not just seconds % 60)
3685 // because, for me, this call means "give me entire time interval in
3686 // seconds" and not "give me the seconds part of the time interval"
3687 //
3688 // If we agree that it should behave like this, it is clear that the
3689 // interpretation of each format specifier depends on the presence of the
3690 // other format specs in the string: if there was "%H" before "%M", we
3691 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3692
3693 // we remember the most important unit found so far
033400eb 3694 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 3695
f6bcfd97 3696 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
3697 {
3698 wxChar ch = *pch;
3699
f6bcfd97 3700 if ( ch == _T('%') )
e6ec579c 3701 {
df05cdc5
VZ
3702 // the start of the format specification of the printf() below
3703 wxString fmtPrefix = _T('%');
3704
3705 // the number
3706 long n;
e6ec579c 3707
f6bcfd97 3708 ch = *++pch; // get the format spec char
e6ec579c
VZ
3709 switch ( ch )
3710 {
3711 default:
3712 wxFAIL_MSG( _T("invalid format character") );
3713 // fall through
3714
f6bcfd97 3715 case _T('%'):
df05cdc5
VZ
3716 str += ch;
3717
3718 // skip the part below switch
3719 continue;
e6ec579c 3720
f6bcfd97 3721 case _T('D'):
df05cdc5
VZ
3722 n = GetDays();
3723 if ( partBiggest < Part_Day )
3724 {
3725 n %= DAYS_PER_WEEK;
3726 }
3727 else
3728 {
3729 partBiggest = Part_Day;
3730 }
e6ec579c
VZ
3731 break;
3732
f6bcfd97 3733 case _T('E'):
df05cdc5
VZ
3734 partBiggest = Part_Week;
3735 n = GetWeeks();
e6ec579c
VZ
3736 break;
3737
f6bcfd97 3738 case _T('H'):
df05cdc5
VZ
3739 n = GetHours();
3740 if ( partBiggest < Part_Hour )
3741 {
3742 n %= HOURS_PER_DAY;
3743 }
3744 else
3745 {
3746 partBiggest = Part_Hour;
3747 }
3748
3749 fmtPrefix += _T("02");
e6ec579c
VZ
3750 break;
3751
f6bcfd97 3752 case _T('l'):
df05cdc5
VZ
3753 n = GetMilliseconds().ToLong();
3754 if ( partBiggest < Part_MSec )
3755 {
3756 n %= 1000;
3757 }
3758 //else: no need to reset partBiggest to Part_MSec, it is
3759 // the least significant one anyhow
3760
3761 fmtPrefix += _T("03");
e6ec579c
VZ
3762 break;
3763
f6bcfd97 3764 case _T('M'):
df05cdc5
VZ
3765 n = GetMinutes();
3766 if ( partBiggest < Part_Min )
3767 {
3768 n %= MIN_PER_HOUR;
3769 }
3770 else
3771 {
3772 partBiggest = Part_Min;
3773 }
3774
3775 fmtPrefix += _T("02");
e6ec579c
VZ
3776 break;
3777
f6bcfd97 3778 case _T('S'):
df05cdc5
VZ
3779 n = GetSeconds().ToLong();
3780 if ( partBiggest < Part_Sec )
3781 {
3782 n %= SEC_PER_MIN;
3783 }
3784 else
3785 {
3786 partBiggest = Part_Sec;
3787 }
3788
3789 fmtPrefix += _T("02");
e6ec579c
VZ
3790 break;
3791 }
3792
df05cdc5
VZ
3793 str += wxString::Format(fmtPrefix + _T("ld"), n);
3794 }
3795 else
3796 {
3797 // normal character, just copy
3798 str += ch;
e6ec579c 3799 }
e6ec579c 3800 }
fcc3d7cb
VZ
3801
3802 return str;
3803}
4f6aed9c
VZ
3804
3805// ============================================================================
3806// wxDateTimeHolidayAuthority and related classes
3807// ============================================================================
3808
3809#include "wx/arrimpl.cpp"
3810
f6bcfd97 3811WX_DEFINE_OBJARRAY(wxDateTimeArray);
4f6aed9c
VZ
3812
3813static int wxCMPFUNC_CONV
3814wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
3815{
3816 wxDateTime dt1 = **first,
3817 dt2 = **second;
3818
3819 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
3820}
3821
3822// ----------------------------------------------------------------------------
3823// wxDateTimeHolidayAuthority
3824// ----------------------------------------------------------------------------
3825
3826wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
3827
3828/* static */
3829bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
3830{
df5168c4 3831 size_t count = ms_authorities.size();
4f6aed9c
VZ
3832 for ( size_t n = 0; n < count; n++ )
3833 {
3834 if ( ms_authorities[n]->DoIsHoliday(dt) )
3835 {
3836 return TRUE;
3837 }
3838 }
3839
3840 return FALSE;
3841}
3842
3843/* static */
3844size_t
3845wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
3846 const wxDateTime& dtEnd,
3847 wxDateTimeArray& holidays)
3848{
3849 wxDateTimeArray hol;
3850
df5168c4 3851 holidays.Clear();
4f6aed9c 3852
df5168c4 3853 size_t count = ms_authorities.size();
6dc6fda6 3854 for ( size_t nAuth = 0; nAuth < count; nAuth++ )
4f6aed9c 3855 {
6dc6fda6 3856 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
3857
3858 WX_APPEND_ARRAY(holidays, hol);
3859 }
3860
3861 holidays.Sort(wxDateTimeCompareFunc);
3862
df5168c4 3863 return holidays.size();
4f6aed9c
VZ
3864}
3865
3866/* static */
3867void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3868{
3869 WX_CLEAR_ARRAY(ms_authorities);
3870}
3871
3872/* static */
3873void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
3874{
df5168c4 3875 ms_authorities.push_back(auth);
4f6aed9c
VZ
3876}
3877
30e671a5
VZ
3878wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
3879{
3880 // nothing to do here
3881}
3882
4f6aed9c
VZ
3883// ----------------------------------------------------------------------------
3884// wxDateTimeWorkDays
3885// ----------------------------------------------------------------------------
3886
3887bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
3888{
3889 wxDateTime::WeekDay wd = dt.GetWeekDay();
3890
3891 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
3892}
3893
3894size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
3895 const wxDateTime& dtEnd,
3896 wxDateTimeArray& holidays) const
3897{
3898 if ( dtStart > dtEnd )
3899 {
3900 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3901
3902 return 0u;
3903 }
3904
3905 holidays.Empty();
3906
3907 // instead of checking all days, start with the first Sat after dtStart and
3908 // end with the last Sun before dtEnd
3909 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
3910 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
3911 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
3912 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
3913 dt;
3914
3915 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
3916 {
3917 holidays.Add(dt);
3918 }
3919
3920 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
3921 {
3922 holidays.Add(dt);
3923 }
3924
3925 return holidays.GetCount();
3926}
3e0b743f 3927
1e6feb95 3928#endif // wxUSE_DATETIME