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