]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetimefmt.cpp
added length to wx(Scoped)CharBuffer to improve handling of embedded NULs
[wxWidgets.git] / src / common / datetimefmt.cpp
CommitLineData
98919134
FM
1///////////////////////////////////////////////////////////////////////////////
2// Name: src/common/datetimefmt.cpp
3// Purpose: wxDateTime formatting & parsing code
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 11.05.99
7// RCS-ID: $Id$
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//
16// Licence: wxWindows licence
17///////////////////////////////////////////////////////////////////////////////
18
19// ============================================================================
20// declarations
21// ============================================================================
22
23// ----------------------------------------------------------------------------
24// headers
25// ----------------------------------------------------------------------------
26
27// For compilers that support precompilation, includes "wx.h".
28#include "wx/wxprec.h"
29
30#ifdef __BORLANDC__
31 #pragma hdrstop
32#endif
33
34#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
35
36#ifndef WX_PRECOMP
37 #ifdef __WXMSW__
38 #include "wx/msw/wrapwin.h"
39 #endif
40 #include "wx/string.h"
41 #include "wx/log.h"
42 #include "wx/intl.h"
43 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
44 #include "wx/module.h"
45 #include "wx/crt.h"
46#endif // WX_PRECOMP
47
48#include "wx/thread.h"
49#include "wx/tokenzr.h"
50
51#include <ctype.h>
52
53#ifdef __WINDOWS__
54 #include <winnls.h>
55 #ifndef __WXWINCE__
56 #include <locale.h>
57 #endif
58#endif
59
60#include "wx/datetime.h"
61
62// ============================================================================
63// implementation of wxDateTime
64// ============================================================================
65
1ee2f9d9
FM
66// ----------------------------------------------------------------------------
67// helpers shared between datetime.cpp and datetimefmt.cpp
68// ----------------------------------------------------------------------------
69
70extern void InitTm(struct tm& tm);
71
72extern int GetTimeZone();
73
74extern wxString CallStrftime(const wxString& format, const tm* tm);
75
54e660f8
FM
76// ----------------------------------------------------------------------------
77// constants (see also datetime.cpp)
78// ----------------------------------------------------------------------------
79
80static const int DAYS_PER_WEEK = 7;
81
82static const int HOURS_PER_DAY = 24;
83
84static const int SEC_PER_MIN = 60;
85
86static const int MIN_PER_HOUR = 60;
87
88// ----------------------------------------------------------------------------
89// parsing helpers
90// ----------------------------------------------------------------------------
91
66f22f4a
VZ
92namespace
93{
94
254696bb
VZ
95enum
96{
97 DateLang_English = 1,
98 DateLang_Local = 2
99};
100
54e660f8 101// return the month if the string is a month name or Inv_Month otherwise
254696bb
VZ
102//
103// flags can contain wxDateTime::Name_Abbr/Name_Full or both of them and lang
104// can be either DateLang_Local (default) to interpret string as a localized
105// month name or DateLang_English to parse it as a standard English name or
106// their combination to interpret it in any way
107wxDateTime::Month
108GetMonthFromName(const wxString& name, int flags, int lang)
54e660f8
FM
109{
110 wxDateTime::Month mon;
111 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
112 {
113 // case-insensitive comparison either one of or with both abbreviated
114 // and not versions
115 if ( flags & wxDateTime::Name_Full )
116 {
254696bb 117 if ( lang & DateLang_English )
54e660f8 118 {
254696bb
VZ
119 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
120 wxDateTime::Name_Full)) == 0 )
121 break;
122 }
123
124 if ( lang & DateLang_Local )
125 {
126 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
127 wxDateTime::Name_Full)) == 0 )
128 break;
54e660f8
FM
129 }
130 }
131
132 if ( flags & wxDateTime::Name_Abbr )
133 {
254696bb 134 if ( lang & DateLang_English )
54e660f8 135 {
254696bb
VZ
136 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
137 wxDateTime::Name_Abbr)) == 0 )
138 break;
139 }
140
141 if ( lang & DateLang_Local )
142 {
143 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
144 wxDateTime::Name_Abbr)) == 0 )
145 break;
54e660f8
FM
146 }
147 }
148 }
149
150 return mon;
151}
152
153// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
254696bb
VZ
154//
155// flags and lang parameters have the same meaning as for GetMonthFromName()
156// above
157wxDateTime::WeekDay
158GetWeekDayFromName(const wxString& name, int flags, int lang)
54e660f8
FM
159{
160 wxDateTime::WeekDay wd;
161 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
162 {
54e660f8
FM
163 if ( flags & wxDateTime::Name_Full )
164 {
254696bb 165 if ( lang & DateLang_English )
54e660f8 166 {
254696bb
VZ
167 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
168 wxDateTime::Name_Full)) == 0 )
169 break;
170 }
171
172 if ( lang & DateLang_Local )
173 {
174 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
175 wxDateTime::Name_Full)) == 0 )
176 break;
54e660f8
FM
177 }
178 }
179
180 if ( flags & wxDateTime::Name_Abbr )
181 {
254696bb 182 if ( lang & DateLang_English )
54e660f8 183 {
254696bb
VZ
184 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
185 wxDateTime::Name_Abbr)) == 0 )
186 break;
187 }
188
189 if ( lang & DateLang_Local )
190 {
191 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
192 wxDateTime::Name_Abbr)) == 0 )
193 break;
54e660f8
FM
194 }
195 }
196 }
197
198 return wd;
199}
200
54e660f8 201// scans all digits (but no more than len) and returns the resulting number
66f22f4a
VZ
202bool GetNumericToken(size_t len,
203 wxString::const_iterator& p,
ed973feb 204 const wxString::const_iterator& end,
66f22f4a 205 unsigned long *number)
54e660f8
FM
206{
207 size_t n = 1;
208 wxString s;
ed973feb 209 while ( p != end && wxIsdigit(*p) )
54e660f8
FM
210 {
211 s += *p++;
212
213 if ( len && ++n > len )
214 break;
215 }
216
217 return !s.empty() && s.ToULong(number);
218}
219
220// scans all alphabetic characters and returns the resulting string
ed973feb
VZ
221wxString
222GetAlphaToken(wxString::const_iterator& p,
223 const wxString::const_iterator& end)
54e660f8
FM
224{
225 wxString s;
ed973feb 226 while ( p != end && wxIsalpha(*p) )
54e660f8
FM
227 {
228 s += *p++;
229 }
230
231 return s;
232}
233
66f22f4a
VZ
234// parses string starting at given iterator using the specified format and,
235// optionally, a fall back format (and optionally another one... but it stops
236// there, really)
237//
238// if unsuccessful, returns invalid wxDateTime without changing p; otherwise
239// advance p to the end of the match and returns wxDateTime containing the
240// results of the parsing
241wxDateTime
242ParseFormatAt(wxString::const_iterator& p,
243 const wxString::const_iterator& end,
244 const wxString& fmt,
115eded7
VZ
245 // FIXME-VC6: using wxString() instead of wxEmptyString in the
246 // line below results in error C2062: type 'class
247 // wxString (__cdecl *)(void)' unexpected
89a7e1ff 248 const wxString& fmtAlt = wxEmptyString)
66f22f4a
VZ
249{
250 const wxString str(p, end);
251 wxString::const_iterator endParse;
252 wxDateTime dt;
253 if ( dt.ParseFormat(str, fmt, &endParse) ||
89a7e1ff 254 (!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, &endParse)) )
66f22f4a
VZ
255 {
256 p += endParse - str.begin();
257 }
258 //else: all formats failed
259
260 return dt;
261}
262
263} // anonymous namespace
264
98919134
FM
265// ----------------------------------------------------------------------------
266// wxDateTime to/from text representations
267// ----------------------------------------------------------------------------
268
269wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const
270{
271 wxCHECK_MSG( !format.empty(), wxEmptyString,
272 _T("NULL format in wxDateTime::Format") );
273
274 // we have to use our own implementation if the date is out of range of
275 // strftime() or if we use non standard specificators
276#ifdef HAVE_STRFTIME
277 time_t time = GetTicks();
278
279 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
280 {
281 // use strftime()
282 struct tm tmstruct;
283 struct tm *tm;
284 if ( tz.GetOffset() == -GetTimeZone() )
285 {
286 // we are working with local time
287 tm = wxLocaltime_r(&time, &tmstruct);
288
289 // should never happen
290 wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") );
291 }
292 else
293 {
294 time += (int)tz.GetOffset();
295
296#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
297 int time2 = (int) time;
298 if ( time2 >= 0 )
299#else
300 if ( time >= 0 )
301#endif
302 {
303 tm = wxGmtime_r(&time, &tmstruct);
304
305 // should never happen
306 wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") );
307 }
308 else
309 {
310 tm = (struct tm *)NULL;
311 }
312 }
313
314 if ( tm )
315 {
316 return CallStrftime(format, tm);
317 }
318 }
319 //else: use generic code below
320#endif // HAVE_STRFTIME
321
322 // we only parse ANSI C format specifications here, no POSIX 2
323 // complications, no GNU extensions but we do add support for a "%l" format
324 // specifier allowing to get the number of milliseconds
325 Tm tm = GetTm(tz);
326
327 // used for calls to strftime() when we only deal with time
328 struct tm tmTimeOnly;
329 tmTimeOnly.tm_hour = tm.hour;
330 tmTimeOnly.tm_min = tm.min;
331 tmTimeOnly.tm_sec = tm.sec;
332 tmTimeOnly.tm_wday = 0;
333 tmTimeOnly.tm_yday = 0;
334 tmTimeOnly.tm_mday = 1; // any date will do
335 tmTimeOnly.tm_mon = 0;
336 tmTimeOnly.tm_year = 76;
337 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
338
339 wxString tmp, res, fmt;
340 for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p )
341 {
342 if ( *p != _T('%') )
343 {
344 // copy as is
345 res += *p;
346
347 continue;
348 }
349
350 // set the default format
351 switch ( (*++p).GetValue() )
352 {
353 case _T('Y'): // year has 4 digits
354 fmt = _T("%04d");
355 break;
356
357 case _T('j'): // day of year has 3 digits
358 case _T('l'): // milliseconds have 3 digits
359 fmt = _T("%03d");
360 break;
361
362 case _T('w'): // week day as number has only one
363 fmt = _T("%d");
364 break;
365
366 default:
367 // it's either another valid format specifier in which case
368 // the format is "%02d" (for all the rest) or we have the
369 // field width preceding the format in which case it will
370 // override the default format anyhow
371 fmt = _T("%02d");
372 }
373
374 bool restart = true;
375 while ( restart )
376 {
377 restart = false;
378
379 // start of the format specification
380 switch ( (*p).GetValue() )
381 {
382 case _T('a'): // a weekday name
383 case _T('A'):
384 // second parameter should be true for abbreviated names
385 res += GetWeekDayName(tm.GetWeekDay(),
386 *p == _T('a') ? Name_Abbr : Name_Full);
387 break;
388
389 case _T('b'): // a month name
390 case _T('B'):
391 res += GetMonthName(tm.mon,
392 *p == _T('b') ? Name_Abbr : Name_Full);
393 break;
394
395 case _T('c'): // locale default date and time representation
396 case _T('x'): // locale default date representation
397#ifdef HAVE_STRFTIME
398 //
399 // the problem: there is no way to know what do these format
400 // specifications correspond to for the current locale.
401 //
402 // the solution: use a hack and still use strftime(): first
403 // find the YEAR which is a year in the strftime() range (1970
404 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
405 // of the real year. Then make a copy of the format and
406 // replace all occurrences of YEAR in it with some unique
407 // string not appearing anywhere else in it, then use
408 // strftime() to format the date in year YEAR and then replace
409 // YEAR back by the real year and the unique replacement
410 // string back with YEAR. Notice that "all occurrences of YEAR"
411 // means all occurrences of 4 digit as well as 2 digit form!
412 //
413 // the bugs: we assume that neither of %c nor %x contains any
414 // fields which may change between the YEAR and real year. For
415 // example, the week number (%U, %W) and the day number (%j)
416 // will change if one of these years is leap and the other one
417 // is not!
418 {
419 // find the YEAR: normally, for any year X, Jan 1 of the
420 // year X + 28 is the same weekday as Jan 1 of X (because
421 // the weekday advances by 1 for each normal X and by 2
422 // for each leap X, hence by 5 every 4 years or by 35
423 // which is 0 mod 7 every 28 years) but this rule breaks
424 // down if there are years between X and Y which are
425 // divisible by 4 but not leap (i.e. divisible by 100 but
426 // not 400), hence the correction.
427
428 int yearReal = GetYear(tz);
429 int mod28 = yearReal % 28;
430
431 // be careful to not go too far - we risk to leave the
432 // supported range
433 int year;
434 if ( mod28 < 10 )
435 {
436 year = 1988 + mod28; // 1988 == 0 (mod 28)
437 }
438 else
439 {
440 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
441 }
442
443 int nCentury = year / 100,
444 nCenturyReal = yearReal / 100;
445
446 // need to adjust for the years divisble by 400 which are
447 // not leap but are counted like leap ones if we just take
448 // the number of centuries in between for nLostWeekDays
449 int nLostWeekDays = (nCentury - nCenturyReal) -
450 (nCentury / 4 - nCenturyReal / 4);
451
452 // we have to gain back the "lost" weekdays: note that the
453 // effect of this loop is to not do anything to
454 // nLostWeekDays (which we won't use any more), but to
455 // (indirectly) set the year correctly
456 while ( (nLostWeekDays % 7) != 0 )
457 {
458 nLostWeekDays += year++ % 4 ? 1 : 2;
459 }
460
461 // finally move the year below 2000 so that the 2-digit
462 // year number can never match the month or day of the
463 // month when we do the replacements below
464 if ( year >= 2000 )
465 year -= 28;
466
467 wxASSERT_MSG( year >= 1970 && year < 2000,
468 _T("logic error in wxDateTime::Format") );
469
470
471 // use strftime() to format the same date but in supported
472 // year
473 //
474 // NB: we assume that strftime() doesn't check for the
475 // date validity and will happily format the date
476 // corresponding to Feb 29 of a non leap year (which
477 // may happen if yearReal was leap and year is not)
478 struct tm tmAdjusted;
479 InitTm(tmAdjusted);
480 tmAdjusted.tm_hour = tm.hour;
481 tmAdjusted.tm_min = tm.min;
482 tmAdjusted.tm_sec = tm.sec;
483 tmAdjusted.tm_wday = tm.GetWeekDay();
484 tmAdjusted.tm_yday = GetDayOfYear();
485 tmAdjusted.tm_mday = tm.mday;
486 tmAdjusted.tm_mon = tm.mon;
487 tmAdjusted.tm_year = year - 1900;
488 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
489 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
490 : _T("%x"),
491 &tmAdjusted);
492
493 // now replace the replacement year with the real year:
494 // notice that we have to replace the 4 digit year with
495 // a unique string not appearing in strftime() output
496 // first to prevent the 2 digit year from matching any
497 // substring of the 4 digit year (but any day, month,
498 // hours or minutes components should be safe because
499 // they are never in 70-99 range)
500 wxString replacement("|");
501 while ( str.find(replacement) != wxString::npos )
502 replacement += '|';
503
504 str.Replace(wxString::Format("%d", year),
505 replacement);
506 str.Replace(wxString::Format("%d", year % 100),
507 wxString::Format("%d", yearReal % 100));
508 str.Replace(replacement,
509 wxString::Format("%d", yearReal));
510
511 res += str;
512 }
513#else // !HAVE_STRFTIME
514 // Use "%m/%d/%y %H:%M:%S" format instead
515 res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
516 tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
517#endif // HAVE_STRFTIME/!HAVE_STRFTIME
518 break;
519
520 case _T('d'): // day of a month (01-31)
521 res += wxString::Format(fmt, tm.mday);
522 break;
523
524 case _T('H'): // hour in 24h format (00-23)
525 res += wxString::Format(fmt, tm.hour);
526 break;
527
528 case _T('I'): // hour in 12h format (01-12)
529 {
530 // 24h -> 12h, 0h -> 12h too
531 int hour12 = tm.hour > 12 ? tm.hour - 12
532 : tm.hour ? tm.hour : 12;
533 res += wxString::Format(fmt, hour12);
534 }
535 break;
536
537 case _T('j'): // day of the year
538 res += wxString::Format(fmt, GetDayOfYear(tz));
539 break;
540
541 case _T('l'): // milliseconds (NOT STANDARD)
542 res += wxString::Format(fmt, GetMillisecond(tz));
543 break;
544
545 case _T('m'): // month as a number (01-12)
546 res += wxString::Format(fmt, tm.mon + 1);
547 break;
548
549 case _T('M'): // minute as a decimal number (00-59)
550 res += wxString::Format(fmt, tm.min);
551 break;
552
553 case _T('p'): // AM or PM string
554#ifdef HAVE_STRFTIME
555 res += CallStrftime(_T("%p"), &tmTimeOnly);
556#else // !HAVE_STRFTIME
557 res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
558#endif // HAVE_STRFTIME/!HAVE_STRFTIME
559 break;
560
561 case _T('S'): // second as a decimal number (00-61)
562 res += wxString::Format(fmt, tm.sec);
563 break;
564
565 case _T('U'): // week number in the year (Sunday 1st week day)
566 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
567 break;
568
569 case _T('W'): // week number in the year (Monday 1st week day)
570 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
571 break;
572
573 case _T('w'): // weekday as a number (0-6), Sunday = 0
574 res += wxString::Format(fmt, tm.GetWeekDay());
575 break;
576
577 // case _T('x'): -- handled with "%c"
578
579 case _T('X'): // locale default time representation
580 // just use strftime() to format the time for us
581#ifdef HAVE_STRFTIME
582 res += CallStrftime(_T("%X"), &tmTimeOnly);
583#else // !HAVE_STRFTIME
584 res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
585#endif // HAVE_STRFTIME/!HAVE_STRFTIME
586 break;
587
588 case _T('y'): // year without century (00-99)
589 res += wxString::Format(fmt, tm.year % 100);
590 break;
591
592 case _T('Y'): // year with century
593 res += wxString::Format(fmt, tm.year);
594 break;
595
596 case _T('Z'): // timezone name
597#ifdef HAVE_STRFTIME
598 res += CallStrftime(_T("%Z"), &tmTimeOnly);
599#endif
600 break;
601
602 default:
603 // is it the format width?
604 fmt.Empty();
605 while ( *p == _T('-') || *p == _T('+') ||
606 *p == _T(' ') || wxIsdigit(*p) )
607 {
608 fmt += *p;
609 }
610
611 if ( !fmt.empty() )
612 {
613 // we've only got the flags and width so far in fmt
614 fmt.Prepend(_T('%'));
615 fmt.Append(_T('d'));
616
617 restart = true;
618
619 break;
620 }
621
622 // no, it wasn't the width
623 wxFAIL_MSG(_T("unknown format specificator"));
624
625 // fall through and just copy it nevertheless
626
627 case _T('%'): // a percent sign
628 res += *p;
629 break;
630
631 case 0: // the end of string
632 wxFAIL_MSG(_T("missing format at the end of string"));
633
634 // just put the '%' which was the last char in format
635 res += _T('%');
636 break;
637 }
638 }
639 }
640
641 return res;
642}
643
644// this function parses a string in (strict) RFC 822 format: see the section 5
645// of the RFC for the detailed description, but briefly it's something of the
646// form "Sat, 18 Dec 1999 00:48:30 +0100"
647//
648// this function is "strict" by design - it must reject anything except true
649// RFC822 time specs.
c398434d 650bool
98919134
FM
651wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end)
652{
254696bb 653 wxString::const_iterator p = date.begin();
98919134 654
254696bb
VZ
655 // 1. week day
656 static const int WDAY_LEN = 3;
657 const wxString::const_iterator endWday = p + WDAY_LEN;
658 const wxString wday(p, endWday);
659 if ( GetWeekDayFromName(wday, Name_Abbr, DateLang_English) == Inv_WeekDay )
c398434d 660 return false;
254696bb
VZ
661 //else: ignore week day for now, we could also check that it really
662 // corresponds to the specified date
98919134 663
254696bb 664 p = endWday;
98919134 665
254696bb
VZ
666 // 2. separating comma
667 if ( *p++ != ',' || *p++ != ' ' )
c398434d 668 return false;
98919134 669
254696bb 670 // 3. day number
98919134 671 if ( !wxIsdigit(*p) )
c398434d 672 return false;
98919134 673
254696bb 674 wxDateTime_t day = (wxDateTime_t)(*p++ - '0');
98919134
FM
675 if ( wxIsdigit(*p) )
676 {
677 day *= 10;
254696bb 678 day = (wxDateTime_t)(day + (*p++ - '0'));
98919134
FM
679 }
680
254696bb 681 if ( *p++ != ' ' )
c398434d 682 return false;
98919134 683
254696bb
VZ
684 // 4. month name
685 static const int MONTH_LEN = 3;
686 const wxString::const_iterator endMonth = p + MONTH_LEN;
687 const wxString monName(p, endMonth);
688 Month mon = GetMonthFromName(monName, Name_Abbr, DateLang_English);
689 if ( mon == Inv_Month )
c398434d 690 return false;
98919134 691
254696bb 692 p = endMonth;
98919134 693
254696bb 694 if ( *p++ != ' ' )
c398434d 695 return false;
98919134 696
254696bb 697 // 5. year
98919134 698 if ( !wxIsdigit(*p) )
c398434d 699 return false;
98919134 700
254696bb
VZ
701 int year = *p++ - '0';
702 if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year
c398434d 703 return false;
98919134
FM
704
705 year *= 10;
254696bb 706 year += *p++ - '0';
98919134
FM
707
708 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
709 if ( wxIsdigit(*p) )
710 {
711 year *= 10;
254696bb 712 year += *p++ - '0';
98919134
FM
713
714 if ( !wxIsdigit(*p) )
715 {
716 // no 3 digit years please
c398434d 717 return false;
98919134
FM
718 }
719
720 year *= 10;
254696bb 721 year += *p++ - '0';
98919134
FM
722 }
723
254696bb 724 if ( *p++ != ' ' )
c398434d 725 return false;
98919134 726
254696bb 727 // 6. time in hh:mm:ss format with seconds being optional
98919134 728 if ( !wxIsdigit(*p) )
c398434d 729 return false;
98919134 730
254696bb 731 wxDateTime_t hour = (wxDateTime_t)(*p++ - '0');
98919134
FM
732
733 if ( !wxIsdigit(*p) )
c398434d 734 return false;
98919134
FM
735
736 hour *= 10;
254696bb 737 hour = (wxDateTime_t)(hour + (*p++ - '0'));
98919134 738
254696bb 739 if ( *p++ != ':' )
c398434d 740 return false;
98919134
FM
741
742 if ( !wxIsdigit(*p) )
c398434d 743 return false;
98919134 744
254696bb 745 wxDateTime_t min = (wxDateTime_t)(*p++ - '0');
98919134
FM
746
747 if ( !wxIsdigit(*p) )
c398434d 748 return false;
98919134
FM
749
750 min *= 10;
254696bb 751 min += (wxDateTime_t)(*p++ - '0');
98919134
FM
752
753 wxDateTime_t sec = 0;
254696bb 754 if ( *p == ':' )
98919134
FM
755 {
756 p++;
757 if ( !wxIsdigit(*p) )
c398434d 758 return false;
98919134 759
254696bb 760 sec = (wxDateTime_t)(*p++ - '0');
98919134
FM
761
762 if ( !wxIsdigit(*p) )
c398434d 763 return false;
98919134
FM
764
765 sec *= 10;
254696bb 766 sec += (wxDateTime_t)(*p++ - '0');
98919134
FM
767 }
768
254696bb 769 if ( *p++ != ' ' )
c398434d 770 return false;
98919134 771
254696bb 772 // 7. now the interesting part: the timezone
98919134 773 int offset wxDUMMY_INITIALIZE(0);
254696bb 774 if ( *p == '-' || *p == '+' )
98919134
FM
775 {
776 // the explicit offset given: it has the form of hhmm
254696bb 777 bool plus = *p++ == '+';
98919134
FM
778
779 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
c398434d 780 return false;
254696bb 781
98919134
FM
782
783 // hours
254696bb 784 offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0'));
98919134
FM
785
786 p += 2;
787
788 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
c398434d 789 return false;
98919134
FM
790
791 // minutes
254696bb 792 offset += 10*(*p - '0') + (*(p + 1) - '0');
98919134
FM
793
794 if ( !plus )
98919134 795 offset = -offset;
98919134
FM
796
797 p += 2;
798 }
254696bb 799 else // not numeric
98919134
FM
800 {
801 // the symbolic timezone given: may be either military timezone or one
802 // of standard abbreviations
803 if ( !*(p + 1) )
804 {
805 // military: Z = UTC, J unused, A = -1, ..., Y = +12
806 static const int offsets[26] =
807 {
808 //A B C D E F G H I J K L M
809 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
810 //N O P R Q S T U V W Z Y Z
811 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
812 };
813
814 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
c398434d 815 return false;
98919134 816
254696bb 817 offset = offsets[*p++ - 'A'];
98919134
FM
818 }
819 else
820 {
821 // abbreviation
254696bb 822 const wxString tz(p, date.end());
98919134
FM
823 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
824 offset = 0;
825 else if ( tz == _T("AST") )
826 offset = AST - GMT0;
827 else if ( tz == _T("ADT") )
828 offset = ADT - GMT0;
829 else if ( tz == _T("EST") )
830 offset = EST - GMT0;
831 else if ( tz == _T("EDT") )
832 offset = EDT - GMT0;
833 else if ( tz == _T("CST") )
834 offset = CST - GMT0;
835 else if ( tz == _T("CDT") )
836 offset = CDT - GMT0;
837 else if ( tz == _T("MST") )
838 offset = MST - GMT0;
839 else if ( tz == _T("MDT") )
840 offset = MDT - GMT0;
841 else if ( tz == _T("PST") )
842 offset = PST - GMT0;
843 else if ( tz == _T("PDT") )
844 offset = PDT - GMT0;
845 else
c398434d 846 return false;
98919134
FM
847
848 p += tz.length();
849 }
850
851 // make it minutes
852 offset *= MIN_PER_HOUR;
853 }
854
254696bb 855
98919134
FM
856 // the spec was correct, construct the date from the values we found
857 Set(day, mon, year, hour, min, sec);
858 MakeFromTimezone(TimeZone::Make(offset*SEC_PER_MIN));
859
98919134 860 if ( end )
254696bb 861 *end = p;
98919134 862
c398434d 863 return true;
98919134
FM
864}
865
c398434d 866bool
98919134
FM
867wxDateTime::ParseFormat(const wxString& date,
868 const wxString& format,
869 const wxDateTime& dateDef,
ed973feb 870 wxString::const_iterator *endParse)
98919134 871{
c398434d 872 wxCHECK_MSG( !format.empty(), false, "format can't be empty" );
ed973feb 873 wxCHECK_MSG( endParse, false, "end iterator pointer must be specified" );
98919134
FM
874
875 wxString str;
876 unsigned long num;
877
878 // what fields have we found?
879 bool haveWDay = false,
880 haveYDay = false,
881 haveDay = false,
882 haveMon = false,
883 haveYear = false,
884 haveHour = false,
885 haveMin = false,
886 haveSec = false,
887 haveMsec = false;
888
889 bool hourIsIn12hFormat = false, // or in 24h one?
890 isPM = false; // AM by default
891
892 // and the value of the items we have (init them to get rid of warnings)
893 wxDateTime_t msec = 0,
894 sec = 0,
895 min = 0,
896 hour = 0;
897 WeekDay wday = Inv_WeekDay;
898 wxDateTime_t yday = 0,
899 mday = 0;
900 wxDateTime::Month mon = Inv_Month;
901 int year = 0;
902
66f22f4a 903 wxString::const_iterator input = date.begin();
ed973feb 904 const wxString::const_iterator end = date.end();
98919134
FM
905 for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt )
906 {
907 if ( *fmt != _T('%') )
908 {
909 if ( wxIsspace(*fmt) )
910 {
911 // a white space in the format string matches 0 or more white
912 // spaces in the input
913 while ( wxIsspace(*input) )
914 {
915 input++;
916 }
917 }
918 else // !space
919 {
920 // any other character (not whitespace, not '%') must be
921 // matched by itself in the input
922 if ( *input++ != *fmt )
923 {
924 // no match
c398434d 925 return false;
98919134
FM
926 }
927 }
928
929 // done with this format char
930 continue;
931 }
932
933 // start of a format specification
934
935 // parse the optional width
936 size_t width = 0;
937 while ( wxIsdigit(*++fmt) )
938 {
939 width *= 10;
254696bb 940 width += *fmt - '0';
98919134
FM
941 }
942
943 // the default widths for the various fields
944 if ( !width )
945 {
946 switch ( (*fmt).GetValue() )
947 {
948 case _T('Y'): // year has 4 digits
949 width = 4;
950 break;
951
952 case _T('j'): // day of year has 3 digits
953 case _T('l'): // milliseconds have 3 digits
954 width = 3;
955 break;
956
957 case _T('w'): // week day as number has only one
958 width = 1;
959 break;
960
961 default:
962 // default for all other fields
963 width = 2;
964 }
965 }
966
967 // then the format itself
968 switch ( (*fmt).GetValue() )
969 {
970 case _T('a'): // a weekday name
971 case _T('A'):
972 {
254696bb
VZ
973 wday = GetWeekDayFromName
974 (
ed973feb 975 GetAlphaToken(input, end),
254696bb
VZ
976 *fmt == 'a' ? Name_Abbr : Name_Full,
977 DateLang_Local
978 );
98919134
FM
979 if ( wday == Inv_WeekDay )
980 {
981 // no match
c398434d 982 return false;
98919134
FM
983 }
984 }
985 haveWDay = true;
986 break;
987
988 case _T('b'): // a month name
989 case _T('B'):
990 {
254696bb
VZ
991 mon = GetMonthFromName
992 (
ed973feb 993 GetAlphaToken(input, end),
254696bb
VZ
994 *fmt == 'b' ? Name_Abbr : Name_Full,
995 DateLang_Local
996 );
98919134
FM
997 if ( mon == Inv_Month )
998 {
999 // no match
c398434d 1000 return false;
98919134
FM
1001 }
1002 }
1003 haveMon = true;
1004 break;
1005
1006 case _T('c'): // locale default date and time representation
1007 {
89a7e1ff 1008 wxDateTime dt;
48fd6e9d 1009
89a7e1ff
VZ
1010 const wxString
1011 fmtDateTime = wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT);
1012 if ( !fmtDateTime.empty() )
1013 dt = ParseFormatAt(input, end, fmtDateTime);
cac1cfce 1014
89a7e1ff
VZ
1015 if ( !dt.IsValid() )
1016 {
1017 // also try the format which corresponds to ctime()
1018 // output (i.e. the "C" locale default)
1019 dt = ParseFormatAt(input, end, wxS("%a %b %d %H:%M:%S %Y"));
cac1cfce 1020 }
89a7e1ff
VZ
1021
1022 if ( !dt.IsValid() )
66f22f4a 1023 {
89a7e1ff
VZ
1024 // and finally also the two generic date/time formats
1025 dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x"));
1026 }
55fffc34 1027
89a7e1ff
VZ
1028 if ( !dt.IsValid() )
1029 return false;
98919134 1030
89a7e1ff 1031 const Tm tm = dt.GetTm();
cac1cfce 1032
89a7e1ff
VZ
1033 hour = tm.hour;
1034 min = tm.min;
1035 sec = tm.sec;
cac1cfce 1036
89a7e1ff
VZ
1037 year = tm.year;
1038 mon = tm.mon;
1039 mday = tm.mday;
66f22f4a
VZ
1040
1041 haveDay = haveMon = haveYear =
1042 haveHour = haveMin = haveSec = true;
98919134
FM
1043 }
1044 break;
1045
1046 case _T('d'): // day of a month (01-31)
ed973feb 1047 if ( !GetNumericToken(width, input, end, &num) ||
98919134
FM
1048 (num > 31) || (num < 1) )
1049 {
1050 // no match
c398434d 1051 return false;
98919134
FM
1052 }
1053
1054 // we can't check whether the day range is correct yet, will
1055 // do it later - assume ok for now
1056 haveDay = true;
1057 mday = (wxDateTime_t)num;
1058 break;
1059
1060 case _T('H'): // hour in 24h format (00-23)
ed973feb 1061 if ( !GetNumericToken(width, input, end, &num) || (num > 23) )
98919134
FM
1062 {
1063 // no match
c398434d 1064 return false;
98919134
FM
1065 }
1066
1067 haveHour = true;
1068 hour = (wxDateTime_t)num;
1069 break;
1070
1071 case _T('I'): // hour in 12h format (01-12)
ed973feb
VZ
1072 if ( !GetNumericToken(width, input, end, &num) ||
1073 !num || (num > 12) )
98919134
FM
1074 {
1075 // no match
c398434d 1076 return false;
98919134
FM
1077 }
1078
1079 haveHour = true;
1080 hourIsIn12hFormat = true;
1081 hour = (wxDateTime_t)(num % 12); // 12 should be 0
1082 break;
1083
1084 case _T('j'): // day of the year
ed973feb
VZ
1085 if ( !GetNumericToken(width, input, end, &num) ||
1086 !num || (num > 366) )
98919134
FM
1087 {
1088 // no match
c398434d 1089 return false;
98919134
FM
1090 }
1091
1092 haveYDay = true;
1093 yday = (wxDateTime_t)num;
1094 break;
1095
1096 case _T('l'): // milliseconds (0-999)
ed973feb 1097 if ( !GetNumericToken(width, input, end, &num) )
c398434d 1098 return false;
98919134
FM
1099
1100 haveMsec = true;
1101 msec = (wxDateTime_t)num;
1102 break;
1103
1104 case _T('m'): // month as a number (01-12)
ed973feb
VZ
1105 if ( !GetNumericToken(width, input, end, &num) ||
1106 !num || (num > 12) )
98919134
FM
1107 {
1108 // no match
c398434d 1109 return false;
98919134
FM
1110 }
1111
1112 haveMon = true;
1113 mon = (Month)(num - 1);
1114 break;
1115
1116 case _T('M'): // minute as a decimal number (00-59)
ed973feb
VZ
1117 if ( !GetNumericToken(width, input, end, &num) ||
1118 (num > 59) )
98919134
FM
1119 {
1120 // no match
c398434d 1121 return false;
98919134
FM
1122 }
1123
1124 haveMin = true;
1125 min = (wxDateTime_t)num;
1126 break;
1127
1128 case _T('p'): // AM or PM string
1129 {
ed973feb 1130 wxString am, pm, token = GetAlphaToken(input, end);
66f22f4a 1131
8aa25f40 1132 // some locales have empty AM/PM tokens and thus when formatting
9decc52d 1133 // dates with the %p specifier nothing is generated; when trying to
8aa25f40
FM
1134 // parse them back, we get an empty token here... but that's not
1135 // an error.
1136 if (token.empty())
1137 break;
98919134
FM
1138
1139 GetAmPmStrings(&am, &pm);
1140 if (am.empty() && pm.empty())
c398434d 1141 return false; // no am/pm strings defined
98919134
FM
1142 if ( token.CmpNoCase(pm) == 0 )
1143 {
1144 isPM = true;
1145 }
1146 else if ( token.CmpNoCase(am) != 0 )
1147 {
1148 // no match
c398434d 1149 return false;
98919134
FM
1150 }
1151 }
1152 break;
1153
1154 case _T('r'): // time as %I:%M:%S %p
1155 {
1156 wxDateTime dt;
ed973feb 1157 if ( !dt.ParseFormat(wxString(input, end),
66f22f4a 1158 wxS("%I:%M:%S %p"), &input) )
c398434d 1159 return false;
98919134
FM
1160
1161 haveHour = haveMin = haveSec = true;
1162
89a7e1ff 1163 const Tm tm = dt.GetTm();
98919134
FM
1164 hour = tm.hour;
1165 min = tm.min;
1166 sec = tm.sec;
1167 }
1168 break;
1169
1170 case _T('R'): // time as %H:%M
1171 {
66f22f4a 1172 const wxDateTime
ed973feb 1173 dt = ParseFormatAt(input, end, wxS("%H:%M"));
66f22f4a 1174 if ( !dt.IsValid() )
c398434d 1175 return false;
98919134 1176
66f22f4a
VZ
1177 haveHour =
1178 haveMin = true;
98919134 1179
89a7e1ff 1180 const Tm tm = dt.GetTm();
98919134
FM
1181 hour = tm.hour;
1182 min = tm.min;
1183 }
1184 break;
1185
1186 case _T('S'): // second as a decimal number (00-61)
ed973feb
VZ
1187 if ( !GetNumericToken(width, input, end, &num) ||
1188 (num > 61) )
98919134
FM
1189 {
1190 // no match
c398434d 1191 return false;
98919134
FM
1192 }
1193
1194 haveSec = true;
1195 sec = (wxDateTime_t)num;
1196 break;
1197
1198 case _T('T'): // time as %H:%M:%S
1199 {
66f22f4a 1200 const wxDateTime
ed973feb 1201 dt = ParseFormatAt(input, end, wxS("%H:%M:%S"));
66f22f4a 1202 if ( !dt.IsValid() )
c398434d 1203 return false;
98919134 1204
66f22f4a
VZ
1205 haveHour =
1206 haveMin =
1207 haveSec = true;
98919134 1208
89a7e1ff 1209 const Tm tm = dt.GetTm();
98919134
FM
1210 hour = tm.hour;
1211 min = tm.min;
1212 sec = tm.sec;
1213 }
1214 break;
1215
1216 case _T('w'): // weekday as a number (0-6), Sunday = 0
ed973feb
VZ
1217 if ( !GetNumericToken(width, input, end, &num) ||
1218 (wday > 6) )
98919134
FM
1219 {
1220 // no match
c398434d 1221 return false;
98919134
FM
1222 }
1223
1224 haveWDay = true;
1225 wday = (WeekDay)num;
1226 break;
1227
1228 case _T('x'): // locale default date representation
98919134 1229 {
89a7e1ff
VZ
1230 wxString
1231 fmtDate = wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT),
1232 fmtDateAlt = wxLocale::GetInfo(wxLOCALE_LONG_DATE_FMT);
98919134 1233
98919134 1234 if ( fmtDate.empty() )
98919134
FM
1235 {
1236 if ( IsWestEuropeanCountry(GetCountry()) ||
1237 GetCountry() == Russia )
1238 {
89a7e1ff
VZ
1239 fmtDate = wxS("%d/%m/%Y");
1240 fmtDateAlt = wxS("%m/%d/%Y");
be8dbffa 1241 }
98919134
FM
1242 else // assume USA
1243 {
89a7e1ff
VZ
1244 fmtDate = wxS("%m/%d/%Y");
1245 fmtDateAlt = wxS("%d/%m/%Y");
98919134
FM
1246 }
1247 }
1248
89a7e1ff
VZ
1249 wxDateTime
1250 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
55fffc34 1251
66f22f4a 1252 if ( !dt.IsValid() )
be8dbffa 1253 {
89a7e1ff
VZ
1254 // try with short years too
1255 fmtDate.Replace("%Y","%y");
1256 fmtDateAlt.Replace("%Y","%y");
1257 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
98919134 1258
89a7e1ff 1259 if ( !dt.IsValid() )
be8dbffa
SC
1260 return false;
1261 }
89a7e1ff
VZ
1262
1263 const Tm tm = dt.GetTm();
98919134
FM
1264
1265 haveDay =
1266 haveMon =
1267 haveYear = true;
1268
1269 year = tm.year;
1270 mon = tm.mon;
1271 mday = tm.mday;
98919134
FM
1272 }
1273
1274 break;
1275
1276 case _T('X'): // locale default time representation
98919134 1277 {
89a7e1ff
VZ
1278 wxString fmtTime = wxLocale::GetInfo(wxLOCALE_TIME_FMT),
1279 fmtTimeAlt;
98919134 1280
89a7e1ff
VZ
1281 if ( fmtTime.empty() )
1282 {
1283 // try to parse what follows as "%H:%M:%S" and, if this
1284 // fails, as "%I:%M:%S %p" - this should catch the most
1285 // common cases
1286 fmtTime = "%T";
1287 fmtTimeAlt = "%r";
1288 }
98919134 1289
66f22f4a 1290 const wxDateTime
89a7e1ff 1291 dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt);
66f22f4a 1292 if ( !dt.IsValid() )
c398434d 1293 return false;
98919134
FM
1294
1295 haveHour =
1296 haveMin =
1297 haveSec = true;
1298
89a7e1ff 1299 const Tm tm = dt.GetTm();
98919134
FM
1300 hour = tm.hour;
1301 min = tm.min;
1302 sec = tm.sec;
98919134 1303 }
98919134
FM
1304 break;
1305
1306 case _T('y'): // year without century (00-99)
ed973feb
VZ
1307 if ( !GetNumericToken(width, input, end, &num) ||
1308 (num > 99) )
98919134
FM
1309 {
1310 // no match
c398434d 1311 return false;
98919134
FM
1312 }
1313
1314 haveYear = true;
1315
1316 // TODO should have an option for roll over date instead of
1317 // hard coding it here
1318 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
1319 break;
1320
1321 case _T('Y'): // year with century
ed973feb 1322 if ( !GetNumericToken(width, input, end, &num) )
98919134
FM
1323 {
1324 // no match
c398434d 1325 return false;
98919134
FM
1326 }
1327
1328 haveYear = true;
1329 year = (wxDateTime_t)num;
1330 break;
1331
1332 case _T('Z'): // timezone name
89a7e1ff
VZ
1333 // FIXME: currently we just ignore everything that looks like a
1334 // time zone here
1335 GetAlphaToken(input, end);
98919134
FM
1336 break;
1337
1338 case _T('%'): // a percent sign
1339 if ( *input++ != _T('%') )
1340 {
1341 // no match
c398434d 1342 return false;
98919134
FM
1343 }
1344 break;
1345
1346 case 0: // the end of string
1347 wxFAIL_MSG(_T("unexpected format end"));
1348
1349 // fall through
1350
1351 default: // not a known format spec
c398434d 1352 return false;
98919134
FM
1353 }
1354 }
1355
1356 // format matched, try to construct a date from what we have now
1357 Tm tmDef;
1358 if ( dateDef.IsValid() )
1359 {
1360 // take this date as default
1361 tmDef = dateDef.GetTm();
1362 }
1363 else if ( IsValid() )
1364 {
1365 // if this date is valid, don't change it
1366 tmDef = GetTm();
1367 }
1368 else
1369 {
1370 // no default and this date is invalid - fall back to Today()
1371 tmDef = Today().GetTm();
1372 }
1373
1374 Tm tm = tmDef;
1375
1376 // set the date
1377 if ( haveMon )
1378 {
1379 tm.mon = mon;
1380 }
1381
1382 if ( haveYear )
1383 {
1384 tm.year = year;
1385 }
1386
1387 // TODO we don't check here that the values are consistent, if both year
1388 // day and month/day were found, we just ignore the year day and we
1389 // also always ignore the week day
1390 if ( haveDay )
1391 {
c4e08560 1392 if ( mday > GetNumberOfDays(tm.mon, tm.year) )
c398434d 1393 return false;
98919134
FM
1394
1395 tm.mday = mday;
1396 }
1397 else if ( haveYDay )
1398 {
1399 if ( yday > GetNumberOfDays(tm.year) )
c398434d 1400 return false;
98919134
FM
1401
1402 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
1403
1404 tm.mon = tm2.mon;
1405 tm.mday = tm2.mday;
1406 }
1407
1408 // deal with AM/PM
1409 if ( haveHour && hourIsIn12hFormat && isPM )
1410 {
1411 // translate to 24hour format
1412 hour += 12;
1413 }
1414 //else: either already in 24h format or no translation needed
1415
1416 // set the time
1417 if ( haveHour )
1418 {
1419 tm.hour = hour;
1420 }
1421
1422 if ( haveMin )
1423 {
1424 tm.min = min;
1425 }
1426
1427 if ( haveSec )
1428 {
1429 tm.sec = sec;
1430 }
1431
1432 if ( haveMsec )
1433 tm.msec = msec;
1434
1435 Set(tm);
1436
1437 // finally check that the week day is consistent -- if we had it
1438 if ( haveWDay && GetWeekDay() != wday )
c398434d 1439 return false;
98919134 1440
ed973feb 1441 *endParse = input;
98919134 1442
c398434d 1443 return true;
98919134
FM
1444}
1445
c398434d 1446bool
98919134
FM
1447wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end)
1448{
c398434d
VZ
1449 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1450
98919134
FM
1451 // Set to current day and hour, so strings like '14:00' becomes today at
1452 // 14, not some other random date
1453 wxDateTime dtDate = wxDateTime::Today();
1454 wxDateTime dtTime = wxDateTime::Today();
1455
1456 wxString::const_iterator
1457 endTime,
1458 endDate,
1459 endBoth;
1460
1461 // If we got a date in the beginning, see if there is a time specified
1462 // after the date
1463 if ( dtDate.ParseDate(date, &endDate) )
1464 {
1465 // Skip spaces, as the ParseTime() function fails on spaces
1466 while ( endDate != date.end() && wxIsspace(*endDate) )
1467 ++endDate;
1468
1469 const wxString timestr(endDate, date.end());
1470 if ( !dtTime.ParseTime(timestr, &endTime) )
c398434d 1471 return false;
98919134
FM
1472
1473 endBoth = endDate + (endTime - timestr.begin());
1474 }
1475 else // no date in the beginning
1476 {
1477 // check if we have a time followed by a date
1478 if ( !dtTime.ParseTime(date, &endTime) )
c398434d 1479 return false;
98919134
FM
1480
1481 while ( endTime != date.end() && wxIsspace(*endTime) )
1482 ++endTime;
1483
1484 const wxString datestr(endTime, date.end());
1485 if ( !dtDate.ParseDate(datestr, &endDate) )
c398434d 1486 return false;
98919134
FM
1487
1488 endBoth = endTime + (endDate - datestr.begin());
1489 }
1490
1491 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
1492 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
1493 dtTime.GetMillisecond());
1494
c398434d 1495 *end = endBoth;
98919134 1496
c398434d 1497 return true;
98919134
FM
1498}
1499
c398434d 1500bool
98919134
FM
1501wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end)
1502{
c398434d
VZ
1503 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1504
98919134
FM
1505 // this is a simplified version of ParseDateTime() which understands only
1506 // "today" (for wxDate compatibility) and digits only otherwise (and not
1507 // all esoteric constructions ParseDateTime() knows about)
1508
66f22f4a
VZ
1509 const wxString::const_iterator pBegin = date.begin();
1510
1511 wxString::const_iterator p = pBegin;
98919134
FM
1512 while ( wxIsspace(*p) )
1513 p++;
1514
1515 // some special cases
1516 static struct
1517 {
1518 const char *str;
1519 int dayDiffFromToday;
1520 } literalDates[] =
1521 {
1522 { wxTRANSLATE("today"), 0 },
1523 { wxTRANSLATE("yesterday"), -1 },
1524 { wxTRANSLATE("tomorrow"), 1 },
1525 };
1526
ed973feb 1527 const size_t lenRest = date.end() - p;
98919134
FM
1528 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
1529 {
1530 const wxString dateStr = wxGetTranslation(literalDates[n].str);
1531 size_t len = dateStr.length();
98919134 1532
ed973feb
VZ
1533 if ( len > lenRest )
1534 continue;
1535
66f22f4a
VZ
1536 const wxString::const_iterator pEnd = p + len;
1537 if ( wxString(p, pEnd).CmpNoCase(dateStr) == 0 )
1538 {
1539 // nothing can follow this, so stop here
98919134 1540
66f22f4a 1541 p = pEnd;
98919134 1542
66f22f4a
VZ
1543 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
1544 *this = Today();
1545 if ( dayDiffFromToday )
1546 {
1547 *this += wxDateSpan::Days(dayDiffFromToday);
98919134 1548 }
66f22f4a 1549
c398434d 1550 *end = pEnd;
66f22f4a 1551
c398434d 1552 return true;
98919134
FM
1553 }
1554 }
1555
1556 // We try to guess what we have here: for each new (numeric) token, we
1557 // determine if it can be a month, day or a year. Of course, there is an
1558 // ambiguity as some numbers may be days as well as months, so we also
1559 // have the ability to back track.
1560
1561 // what do we have?
1562 bool haveDay = false, // the months day?
1563 haveWDay = false, // the day of week?
1564 haveMon = false, // the month?
1565 haveYear = false; // the year?
1566
1567 // and the value of the items we have (init them to get rid of warnings)
1568 WeekDay wday = Inv_WeekDay;
1569 wxDateTime_t day = 0;
1570 wxDateTime::Month mon = Inv_Month;
1571 int year = 0;
1572
1573 // tokenize the string
1574 size_t nPosCur = 0;
1575 static const wxStringCharType *dateDelimiters = wxS(".,/-\t\r\n ");
66f22f4a 1576 wxStringTokenizer tok(wxString(p, date.end()), dateDelimiters);
98919134
FM
1577 while ( tok.HasMoreTokens() )
1578 {
1579 wxString token = tok.GetNextToken();
1580 if ( !token )
1581 continue;
1582
1583 // is it a number?
1584 unsigned long val;
1585 if ( token.ToULong(&val) )
1586 {
1587 // guess what this number is
1588
1589 bool isDay = false,
1590 isMonth = false,
1591 isYear = false;
1592
1593 if ( !haveMon && val > 0 && val <= 12 )
1594 {
1595 // assume it is month
1596 isMonth = true;
1597 }
1598 else // not the month
1599 {
1600 if ( haveDay )
1601 {
1602 // this can only be the year
1603 isYear = true;
1604 }
1605 else // may be either day or year
1606 {
1607 // use a leap year if we don't have the year yet to allow
1608 // dates like 2/29/1976 which would be rejected otherwise
1609 wxDateTime_t max_days = (wxDateTime_t)(
1610 haveMon
c4e08560 1611 ? GetNumberOfDays(mon, haveYear ? year : 1976)
98919134
FM
1612 : 31
1613 );
1614
1615 // can it be day?
1616 if ( (val == 0) || (val > (unsigned long)max_days) )
1617 {
1618 // no
1619 isYear = true;
1620 }
1621 else // yes, suppose it's the day
1622 {
1623 isDay = true;
1624 }
1625 }
1626 }
1627
1628 if ( isYear )
1629 {
1630 if ( haveYear )
1631 break;
1632
1633 haveYear = true;
1634
1635 year = (wxDateTime_t)val;
1636 }
1637 else if ( isDay )
1638 {
1639 if ( haveDay )
1640 break;
1641
1642 haveDay = true;
1643
1644 day = (wxDateTime_t)val;
1645 }
1646 else if ( isMonth )
1647 {
1648 haveMon = true;
1649
1650 mon = (Month)(val - 1);
1651 }
1652 }
1653 else // not a number
1654 {
1655 // be careful not to overwrite the current mon value
254696bb
VZ
1656 Month mon2 = GetMonthFromName
1657 (
1658 token,
1659 Name_Full | Name_Abbr,
1660 DateLang_Local | DateLang_English
1661 );
98919134
FM
1662 if ( mon2 != Inv_Month )
1663 {
1664 // it's a month
1665 if ( haveMon )
1666 {
1667 // but we already have a month - maybe we guessed wrong?
1668 if ( !haveDay )
1669 {
1670 // no need to check in month range as always < 12, but
1671 // the days are counted from 1 unlike the months
1672 day = (wxDateTime_t)(mon + 1);
1673 haveDay = true;
1674 }
1675 else
1676 {
1677 // could possible be the year (doesn't the year come
1678 // before the month in the japanese format?) (FIXME)
1679 break;
1680 }
1681 }
1682
1683 mon = mon2;
1684
1685 haveMon = true;
1686 }
1687 else // not a valid month name
1688 {
254696bb
VZ
1689 WeekDay wday2 = GetWeekDayFromName
1690 (
1691 token,
1692 Name_Full | Name_Abbr,
1693 DateLang_Local | DateLang_English
1694 );
98919134
FM
1695 if ( wday2 != Inv_WeekDay )
1696 {
1697 // a week day
1698 if ( haveWDay )
1699 {
1700 break;
1701 }
1702
1703 wday = wday2;
1704
1705 haveWDay = true;
1706 }
1707 else // not a valid weekday name
1708 {
1709 // try the ordinals
1710 static const char *ordinals[] =
1711 {
1712 wxTRANSLATE("first"),
1713 wxTRANSLATE("second"),
1714 wxTRANSLATE("third"),
1715 wxTRANSLATE("fourth"),
1716 wxTRANSLATE("fifth"),
1717 wxTRANSLATE("sixth"),
1718 wxTRANSLATE("seventh"),
1719 wxTRANSLATE("eighth"),
1720 wxTRANSLATE("ninth"),
1721 wxTRANSLATE("tenth"),
1722 wxTRANSLATE("eleventh"),
1723 wxTRANSLATE("twelfth"),
1724 wxTRANSLATE("thirteenth"),
1725 wxTRANSLATE("fourteenth"),
1726 wxTRANSLATE("fifteenth"),
1727 wxTRANSLATE("sixteenth"),
1728 wxTRANSLATE("seventeenth"),
1729 wxTRANSLATE("eighteenth"),
1730 wxTRANSLATE("nineteenth"),
1731 wxTRANSLATE("twentieth"),
1732 // that's enough - otherwise we'd have problems with
1733 // composite (or not) ordinals
1734 };
1735
1736 size_t n;
1737 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
1738 {
1739 if ( token.CmpNoCase(ordinals[n]) == 0 )
1740 {
1741 break;
1742 }
1743 }
1744
1745 if ( n == WXSIZEOF(ordinals) )
1746 {
1747 // stop here - something unknown
1748 break;
1749 }
1750
1751 // it's a day
1752 if ( haveDay )
1753 {
1754 // don't try anything here (as in case of numeric day
1755 // above) - the symbolic day spec should always
1756 // precede the month/year
1757 break;
1758 }
1759
1760 haveDay = true;
1761
1762 day = (wxDateTime_t)(n + 1);
1763 }
1764 }
1765 }
1766
1767 nPosCur = tok.GetPosition();
1768 }
1769
1770 // either no more tokens or the scan was stopped by something we couldn't
1771 // parse - in any case, see if we can construct a date from what we have
1772 if ( !haveDay && !haveWDay )
c398434d 1773 return false;
98919134
FM
1774
1775 if ( haveWDay && (haveMon || haveYear || haveDay) &&
1776 !(haveDay && haveMon && haveYear) )
1777 {
1778 // without adjectives (which we don't support here) the week day only
1779 // makes sense completely separately or with the full date
1780 // specification (what would "Wed 1999" mean?)
c398434d 1781 return false;
98919134
FM
1782 }
1783
1784 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
1785 {
1786 // may be we have month and day instead of day and year?
1787 if ( haveDay && !haveMon )
1788 {
1789 if ( day <= 12 )
1790 {
1791 // exchange day and month
1792 mon = (wxDateTime::Month)(day - 1);
1793
1794 // we're in the current year then
c4e08560 1795 if ( (year > 0) && (year <= (int)GetNumberOfDays(mon, Inv_Year)) )
98919134
FM
1796 {
1797 day = (wxDateTime_t)year;
1798
1799 haveMon = true;
1800 haveYear = false;
1801 }
1802 //else: no, can't exchange, leave haveMon == false
1803 }
1804 }
1805
1806 if ( !haveMon )
c398434d 1807 return false;
98919134
FM
1808 }
1809
1810 if ( !haveMon )
1811 {
1812 mon = GetCurrentMonth();
1813 }
1814
1815 if ( !haveYear )
1816 {
1817 year = GetCurrentYear();
1818 }
1819
1820 if ( haveDay )
1821 {
1822 // normally we check the day above but the check is optimistic in case
1823 // we find the day before its month/year so we have to redo it now
c4e08560 1824 if ( day > GetNumberOfDays(mon, year) )
c398434d 1825 return false;
98919134
FM
1826
1827 Set(day, mon, year);
1828
1829 if ( haveWDay )
1830 {
1831 // check that it is really the same
1832 if ( GetWeekDay() != wday )
c398434d 1833 return false;
98919134
FM
1834 }
1835 }
1836 else // haveWDay
1837 {
1838 *this = Today();
1839
1840 SetToWeekDayInSameWeek(wday);
1841 }
1842
1843 // return the pointer to the first unparsed char
1844 p += nPosCur;
1845 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
1846 {
1847 // if we couldn't parse the token after the delimiter, put back the
1848 // delimiter as well
1849 p--;
1850 }
1851
c398434d 1852 *end = p;
98919134 1853
c398434d 1854 return true;
98919134
FM
1855}
1856
c398434d 1857bool
98919134
FM
1858wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end)
1859{
c398434d
VZ
1860 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1861
98919134
FM
1862 // first try some extra things
1863 static const struct
1864 {
1865 const char *name;
254696bb 1866 wxDateTime_t hour;
98919134
FM
1867 } stdTimes[] =
1868 {
1869 { wxTRANSLATE("noon"), 12 },
1870 { wxTRANSLATE("midnight"), 00 },
1871 // anything else?
1872 };
1873
1874 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
1875 {
254696bb
VZ
1876 const wxString timeString = wxGetTranslation(stdTimes[n].name);
1877 const wxString::const_iterator p = time.begin() + timeString.length();
1878 if ( timeString.CmpNoCase(wxString(time.begin(), p)) == 0 )
98919134
FM
1879 {
1880 // casts required by DigitalMars
1881 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
1882
1883 if ( end )
254696bb 1884 *end = p;
98919134 1885
c398434d 1886 return true;
98919134
FM
1887 }
1888 }
1889
1890 // try all time formats we may think about in the order from longest to
1891 // shortest
1892 static const char *timeFormats[] =
1893 {
1894 "%I:%M:%S %p", // 12hour with AM/PM
1895 "%H:%M:%S", // could be the same or 24 hour one so try it too
1896 "%I:%M %p", // 12hour with AM/PM but without seconds
1897 "%H:%M:%S", // and a possibly 24 hour version without seconds
1898 "%X", // possibly something from above or maybe something
1899 // completely different -- try it last
1900
1901 // TODO: parse timezones
1902 };
1903
1904 for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ )
1905 {
c398434d
VZ
1906 if ( ParseFormat(time, timeFormats[nFmt], end) )
1907 return true;
98919134
FM
1908 }
1909
c398434d 1910 return false;
98919134
FM
1911}
1912
1913// ----------------------------------------------------------------------------
1914// Workdays and holidays support
1915// ----------------------------------------------------------------------------
1916
1917bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
1918{
1919 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
1920}
1921
1922// ============================================================================
1923// wxDateSpan
1924// ============================================================================
1925
1926wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
1927{
1928 wxDateSpan ds1(ds);
1929 return ds1.Multiply(n);
1930}
1931
1932// ============================================================================
1933// wxTimeSpan
1934// ============================================================================
1935
1936wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
1937{
1938 return wxTimeSpan(ts).Multiply(n);
1939}
1940
1941// this enum is only used in wxTimeSpan::Format() below but we can't declare
1942// it locally to the method as it provokes an internal compiler error in egcs
1943// 2.91.60 when building with -O2
1944enum TimeSpanPart
1945{
1946 Part_Week,
1947 Part_Day,
1948 Part_Hour,
1949 Part_Min,
1950 Part_Sec,
1951 Part_MSec
1952};
1953
1954// not all strftime(3) format specifiers make sense here because, for example,
1955// a time span doesn't have a year nor a timezone
1956//
1957// Here are the ones which are supported (all of them are supported by strftime
1958// as well):
1959// %H hour in 24 hour format
1960// %M minute (00 - 59)
1961// %S second (00 - 59)
1962// %% percent sign
1963//
1964// Also, for MFC CTimeSpan compatibility, we support
1965// %D number of days
1966//
1967// And, to be better than MFC :-), we also have
1968// %E number of wEeks
1969// %l milliseconds (000 - 999)
1970wxString wxTimeSpan::Format(const wxString& format) const
1971{
1972 // we deal with only positive time spans here and just add the sign in
1973 // front for the negative ones
1974 if ( IsNegative() )
1975 {
1976 wxString str(Negate().Format(format));
1977 return "-" + str;
1978 }
1979
1980 wxCHECK_MSG( !format.empty(), wxEmptyString,
1981 _T("NULL format in wxTimeSpan::Format") );
1982
1983 wxString str;
1984 str.Alloc(format.length());
1985
1986 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
1987 //
1988 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
1989 // question is what should ts.Format("%S") do? The code here returns "3273"
1990 // in this case (i.e. the total number of seconds, not just seconds % 60)
1991 // because, for me, this call means "give me entire time interval in
1992 // seconds" and not "give me the seconds part of the time interval"
1993 //
1994 // If we agree that it should behave like this, it is clear that the
1995 // interpretation of each format specifier depends on the presence of the
1996 // other format specs in the string: if there was "%H" before "%M", we
1997 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
1998
1999 // we remember the most important unit found so far
2000 TimeSpanPart partBiggest = Part_MSec;
2001
2002 for ( wxString::const_iterator pch = format.begin(); pch != format.end(); ++pch )
2003 {
2004 wxChar ch = *pch;
2005
2006 if ( ch == _T('%') )
2007 {
2008 // the start of the format specification of the printf() below
2009 wxString fmtPrefix(_T('%'));
2010
2011 // the number
2012 long n;
2013
2014 // the number of digits for the format string, 0 if unused
2015 unsigned digits = 0;
2016
2017 ch = *++pch; // get the format spec char
2018 switch ( ch )
2019 {
2020 default:
2021 wxFAIL_MSG( _T("invalid format character") );
2022 // fall through
2023
2024 case _T('%'):
2025 str += ch;
2026
2027 // skip the part below switch
2028 continue;
2029
2030 case _T('D'):
2031 n = GetDays();
2032 if ( partBiggest < Part_Day )
2033 {
2034 n %= DAYS_PER_WEEK;
2035 }
2036 else
2037 {
2038 partBiggest = Part_Day;
2039 }
2040 break;
2041
2042 case _T('E'):
2043 partBiggest = Part_Week;
2044 n = GetWeeks();
2045 break;
2046
2047 case _T('H'):
2048 n = GetHours();
2049 if ( partBiggest < Part_Hour )
2050 {
2051 n %= HOURS_PER_DAY;
2052 }
2053 else
2054 {
2055 partBiggest = Part_Hour;
2056 }
2057
2058 digits = 2;
2059 break;
2060
2061 case _T('l'):
2062 n = GetMilliseconds().ToLong();
2063 if ( partBiggest < Part_MSec )
2064 {
2065 n %= 1000;
2066 }
2067 //else: no need to reset partBiggest to Part_MSec, it is
2068 // the least significant one anyhow
2069
2070 digits = 3;
2071 break;
2072
2073 case _T('M'):
2074 n = GetMinutes();
2075 if ( partBiggest < Part_Min )
2076 {
2077 n %= MIN_PER_HOUR;
2078 }
2079 else
2080 {
2081 partBiggest = Part_Min;
2082 }
2083
2084 digits = 2;
2085 break;
2086
2087 case _T('S'):
2088 n = GetSeconds().ToLong();
2089 if ( partBiggest < Part_Sec )
2090 {
2091 n %= SEC_PER_MIN;
2092 }
2093 else
2094 {
2095 partBiggest = Part_Sec;
2096 }
2097
2098 digits = 2;
2099 break;
2100 }
2101
2102 if ( digits )
2103 {
2104 fmtPrefix << _T("0") << digits;
2105 }
2106
2107 str += wxString::Format(fmtPrefix + _T("ld"), n);
2108 }
2109 else
2110 {
2111 // normal character, just copy
2112 str += ch;
2113 }
2114 }
2115
2116 return str;
2117}
2118
2119#endif // wxUSE_DATETIME