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