1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/datetimefmt.cpp
3 // Purpose: wxDateTime formatting & parsing code
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // parts of code taken from sndcal library by Scott E. Lee:
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.
16 // Licence: wxWindows licence
17 ///////////////////////////////////////////////////////////////////////////////
19 // ============================================================================
21 // ============================================================================
23 // ----------------------------------------------------------------------------
25 // ----------------------------------------------------------------------------
27 // For compilers that support precompilation, includes "wx.h".
28 #include "wx/wxprec.h"
34 #if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
38 #include "wx/msw/wrapwin.h"
40 #include "wx/string.h"
43 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
44 #include "wx/module.h"
48 #include "wx/thread.h"
49 #include "wx/tokenzr.h"
60 #include "wx/datetime.h"
62 // ============================================================================
63 // implementation of wxDateTime
64 // ============================================================================
66 // ----------------------------------------------------------------------------
67 // helpers shared between datetime.cpp and datetimefmt.cpp
68 // ----------------------------------------------------------------------------
70 extern void InitTm(struct tm
& tm
);
72 extern int GetTimeZone();
74 extern wxString
CallStrftime(const wxString
& format
, const tm
* tm
);
76 // ----------------------------------------------------------------------------
77 // constants (see also datetime.cpp)
78 // ----------------------------------------------------------------------------
80 static const int DAYS_PER_WEEK
= 7;
82 static const int HOURS_PER_DAY
= 24;
84 static const int SEC_PER_MIN
= 60;
86 static const int MIN_PER_HOUR
= 60;
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
101 // return the month if the string is a month name or Inv_Month otherwise
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
108 GetMonthFromName(const wxString
& name
, int flags
, int lang
)
110 wxDateTime::Month mon
;
111 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
113 // case-insensitive comparison either one of or with both abbreviated
115 if ( flags
& wxDateTime::Name_Full
)
117 if ( lang
& DateLang_English
)
119 if ( name
.CmpNoCase(wxDateTime::GetEnglishMonthName(mon
,
120 wxDateTime::Name_Full
)) == 0 )
124 if ( lang
& DateLang_Local
)
126 if ( name
.CmpNoCase(wxDateTime::GetMonthName(mon
,
127 wxDateTime::Name_Full
)) == 0 )
132 if ( flags
& wxDateTime::Name_Abbr
)
134 if ( lang
& DateLang_English
)
136 if ( name
.CmpNoCase(wxDateTime::GetEnglishMonthName(mon
,
137 wxDateTime::Name_Abbr
)) == 0 )
141 if ( lang
& DateLang_Local
)
143 if ( name
.CmpNoCase(wxDateTime::GetMonthName(mon
,
144 wxDateTime::Name_Abbr
)) == 0 )
153 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
155 // flags and lang parameters have the same meaning as for GetMonthFromName()
158 GetWeekDayFromName(const wxString
& name
, int flags
, int lang
)
160 wxDateTime::WeekDay wd
;
161 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
163 if ( flags
& wxDateTime::Name_Full
)
165 if ( lang
& DateLang_English
)
167 if ( name
.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd
,
168 wxDateTime::Name_Full
)) == 0 )
172 if ( lang
& DateLang_Local
)
174 if ( name
.CmpNoCase(wxDateTime::GetWeekDayName(wd
,
175 wxDateTime::Name_Full
)) == 0 )
180 if ( flags
& wxDateTime::Name_Abbr
)
182 if ( lang
& DateLang_English
)
184 if ( name
.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd
,
185 wxDateTime::Name_Abbr
)) == 0 )
189 if ( lang
& DateLang_Local
)
191 if ( name
.CmpNoCase(wxDateTime::GetWeekDayName(wd
,
192 wxDateTime::Name_Abbr
)) == 0 )
201 // scans all digits (but no more than len) and returns the resulting number
202 bool GetNumericToken(size_t len
,
203 wxString::const_iterator
& p
,
204 const wxString::const_iterator
& end
,
205 unsigned long *number
)
209 while ( p
!= end
&& wxIsdigit(*p
) )
213 if ( len
&& ++n
> len
)
217 return !s
.empty() && s
.ToULong(number
);
220 // scans all alphabetic characters and returns the resulting string
222 GetAlphaToken(wxString::const_iterator
& p
,
223 const wxString::const_iterator
& end
)
226 while ( p
!= end
&& wxIsalpha(*p
) )
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
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
242 ParseFormatAt(wxString::const_iterator
& p
,
243 const wxString::const_iterator
& end
,
245 // FIXME-VC6: using wxString() instead of wxEmptyString in the
246 // line below results in error C2062: type 'class
247 // wxString (__cdecl *)(void)' unexpected
248 const wxString
& fmtAlt
= wxEmptyString
)
250 const wxString
str(p
, end
);
251 wxString::const_iterator endParse
;
253 if ( dt
.ParseFormat(str
, fmt
, &endParse
) ||
254 (!fmtAlt
.empty() && dt
.ParseFormat(str
, fmtAlt
, &endParse
)) )
256 p
+= endParse
- str
.begin();
258 //else: all formats failed
263 } // anonymous namespace
265 // ----------------------------------------------------------------------------
266 // wxDateTime to/from text representations
267 // ----------------------------------------------------------------------------
269 wxString
wxDateTime::Format(const wxString
& formatp
, const TimeZone
& tz
) const
271 wxCHECK_MSG( !formatp
.empty(), wxEmptyString
,
272 _T("NULL format in wxDateTime::Format") );
274 wxString format
= formatp
;
276 format
.Replace("%c",wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT
));
277 format
.Replace("%x",wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT
));
278 format
.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT
));
280 // we have to use our own implementation if the date is out of range of
281 // strftime() or if we use non standard specificators
283 time_t time
= GetTicks();
285 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
290 if ( tz
.GetOffset() == -GetTimeZone() )
292 // we are working with local time
293 tm
= wxLocaltime_r(&time
, &tmstruct
);
295 // should never happen
296 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxLocaltime_r() failed") );
300 time
+= (int)tz
.GetOffset();
302 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
303 int time2
= (int) time
;
309 tm
= wxGmtime_r(&time
, &tmstruct
);
311 // should never happen
312 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxGmtime_r() failed") );
316 tm
= (struct tm
*)NULL
;
322 return CallStrftime(format
, tm
);
325 //else: use generic code below
326 #endif // HAVE_STRFTIME
328 // we only parse ANSI C format specifications here, no POSIX 2
329 // complications, no GNU extensions but we do add support for a "%l" format
330 // specifier allowing to get the number of milliseconds
333 // used for calls to strftime() when we only deal with time
334 struct tm tmTimeOnly
;
335 tmTimeOnly
.tm_hour
= tm
.hour
;
336 tmTimeOnly
.tm_min
= tm
.min
;
337 tmTimeOnly
.tm_sec
= tm
.sec
;
338 tmTimeOnly
.tm_wday
= 0;
339 tmTimeOnly
.tm_yday
= 0;
340 tmTimeOnly
.tm_mday
= 1; // any date will do
341 tmTimeOnly
.tm_mon
= 0;
342 tmTimeOnly
.tm_year
= 76;
343 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
345 wxString tmp
, res
, fmt
;
346 for ( wxString::const_iterator p
= format
.begin(); p
!= format
.end(); ++p
)
356 // set the default format
357 switch ( (*++p
).GetValue() )
359 case _T('Y'): // year has 4 digits
363 case _T('j'): // day of year has 3 digits
364 case _T('l'): // milliseconds have 3 digits
368 case _T('w'): // week day as number has only one
373 // it's either another valid format specifier in which case
374 // the format is "%02d" (for all the rest) or we have the
375 // field width preceding the format in which case it will
376 // override the default format anyhow
385 // start of the format specification
386 switch ( (*p
).GetValue() )
388 case _T('a'): // a weekday name
390 // second parameter should be true for abbreviated names
391 res
+= GetWeekDayName(tm
.GetWeekDay(),
392 *p
== _T('a') ? Name_Abbr
: Name_Full
);
395 case _T('b'): // a month name
397 res
+= GetMonthName(tm
.mon
,
398 *p
== _T('b') ? Name_Abbr
: Name_Full
);
401 case _T('c'): // locale default date and time representation
402 case _T('x'): // locale default date representation
405 // the problem: there is no way to know what do these format
406 // specifications correspond to for the current locale.
408 // the solution: use a hack and still use strftime(): first
409 // find the YEAR which is a year in the strftime() range (1970
410 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
411 // of the real year. Then make a copy of the format and
412 // replace all occurrences of YEAR in it with some unique
413 // string not appearing anywhere else in it, then use
414 // strftime() to format the date in year YEAR and then replace
415 // YEAR back by the real year and the unique replacement
416 // string back with YEAR. Notice that "all occurrences of YEAR"
417 // means all occurrences of 4 digit as well as 2 digit form!
419 // the bugs: we assume that neither of %c nor %x contains any
420 // fields which may change between the YEAR and real year. For
421 // example, the week number (%U, %W) and the day number (%j)
422 // will change if one of these years is leap and the other one
425 // find the YEAR: normally, for any year X, Jan 1 of the
426 // year X + 28 is the same weekday as Jan 1 of X (because
427 // the weekday advances by 1 for each normal X and by 2
428 // for each leap X, hence by 5 every 4 years or by 35
429 // which is 0 mod 7 every 28 years) but this rule breaks
430 // down if there are years between X and Y which are
431 // divisible by 4 but not leap (i.e. divisible by 100 but
432 // not 400), hence the correction.
434 int yearReal
= GetYear(tz
);
435 int mod28
= yearReal
% 28;
437 // be careful to not go too far - we risk to leave the
442 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
446 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
449 int nCentury
= year
/ 100,
450 nCenturyReal
= yearReal
/ 100;
452 // need to adjust for the years divisble by 400 which are
453 // not leap but are counted like leap ones if we just take
454 // the number of centuries in between for nLostWeekDays
455 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
456 (nCentury
/ 4 - nCenturyReal
/ 4);
458 // we have to gain back the "lost" weekdays: note that the
459 // effect of this loop is to not do anything to
460 // nLostWeekDays (which we won't use any more), but to
461 // (indirectly) set the year correctly
462 while ( (nLostWeekDays
% 7) != 0 )
464 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
467 // finally move the year below 2000 so that the 2-digit
468 // year number can never match the month or day of the
469 // month when we do the replacements below
473 wxASSERT_MSG( year
>= 1970 && year
< 2000,
474 _T("logic error in wxDateTime::Format") );
477 // use strftime() to format the same date but in supported
480 // NB: we assume that strftime() doesn't check for the
481 // date validity and will happily format the date
482 // corresponding to Feb 29 of a non leap year (which
483 // may happen if yearReal was leap and year is not)
484 struct tm tmAdjusted
;
486 tmAdjusted
.tm_hour
= tm
.hour
;
487 tmAdjusted
.tm_min
= tm
.min
;
488 tmAdjusted
.tm_sec
= tm
.sec
;
489 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
490 tmAdjusted
.tm_yday
= GetDayOfYear();
491 tmAdjusted
.tm_mday
= tm
.mday
;
492 tmAdjusted
.tm_mon
= tm
.mon
;
493 tmAdjusted
.tm_year
= year
- 1900;
494 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
495 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
499 // now replace the replacement year with the real year:
500 // notice that we have to replace the 4 digit year with
501 // a unique string not appearing in strftime() output
502 // first to prevent the 2 digit year from matching any
503 // substring of the 4 digit year (but any day, month,
504 // hours or minutes components should be safe because
505 // they are never in 70-99 range)
506 wxString
replacement("|");
507 while ( str
.find(replacement
) != wxString::npos
)
510 str
.Replace(wxString::Format("%d", year
),
512 str
.Replace(wxString::Format("%d", year
% 100),
513 wxString::Format("%d", yearReal
% 100));
514 str
.Replace(replacement
,
515 wxString::Format("%d", yearReal
));
519 #else // !HAVE_STRFTIME
520 // Use "%m/%d/%y %H:%M:%S" format instead
521 res
+= wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
522 tm
.mon
+1,tm
.mday
, tm
.year
, tm
.hour
, tm
.min
, tm
.sec
);
523 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
526 case _T('d'): // day of a month (01-31)
527 res
+= wxString::Format(fmt
, tm
.mday
);
530 case _T('H'): // hour in 24h format (00-23)
531 res
+= wxString::Format(fmt
, tm
.hour
);
534 case _T('I'): // hour in 12h format (01-12)
536 // 24h -> 12h, 0h -> 12h too
537 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
538 : tm
.hour
? tm
.hour
: 12;
539 res
+= wxString::Format(fmt
, hour12
);
543 case _T('j'): // day of the year
544 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
547 case _T('l'): // milliseconds (NOT STANDARD)
548 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
551 case _T('m'): // month as a number (01-12)
552 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
555 case _T('M'): // minute as a decimal number (00-59)
556 res
+= wxString::Format(fmt
, tm
.min
);
559 case _T('p'): // AM or PM string
561 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
562 #else // !HAVE_STRFTIME
563 res
+= (tmTimeOnly
.tm_hour
> 12) ? wxT("pm") : wxT("am");
564 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
567 case _T('S'): // second as a decimal number (00-61)
568 res
+= wxString::Format(fmt
, tm
.sec
);
571 case _T('U'): // week number in the year (Sunday 1st week day)
572 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
575 case _T('W'): // week number in the year (Monday 1st week day)
576 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
579 case _T('w'): // weekday as a number (0-6), Sunday = 0
580 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
583 // case _T('x'): -- handled with "%c"
585 case _T('X'): // locale default time representation
586 // just use strftime() to format the time for us
588 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
589 #else // !HAVE_STRFTIME
590 res
+= wxString::Format(wxT("%02d:%02d:%02d"),tm
.hour
, tm
.min
, tm
.sec
);
591 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
594 case _T('y'): // year without century (00-99)
595 res
+= wxString::Format(fmt
, tm
.year
% 100);
598 case _T('Y'): // year with century
599 res
+= wxString::Format(fmt
, tm
.year
);
602 case _T('Z'): // timezone name
604 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
609 // is it the format width?
611 while ( *p
== _T('-') || *p
== _T('+') ||
612 *p
== _T(' ') || wxIsdigit(*p
) )
619 // we've only got the flags and width so far in fmt
620 fmt
.Prepend(_T('%'));
628 // no, it wasn't the width
629 wxFAIL_MSG(_T("unknown format specificator"));
631 // fall through and just copy it nevertheless
633 case _T('%'): // a percent sign
637 case 0: // the end of string
638 wxFAIL_MSG(_T("missing format at the end of string"));
640 // just put the '%' which was the last char in format
650 // this function parses a string in (strict) RFC 822 format: see the section 5
651 // of the RFC for the detailed description, but briefly it's something of the
652 // form "Sat, 18 Dec 1999 00:48:30 +0100"
654 // this function is "strict" by design - it must reject anything except true
655 // RFC822 time specs.
657 wxDateTime::ParseRfc822Date(const wxString
& date
, wxString::const_iterator
*end
)
659 wxString::const_iterator p
= date
.begin();
662 static const int WDAY_LEN
= 3;
663 const wxString::const_iterator endWday
= p
+ WDAY_LEN
;
664 const wxString
wday(p
, endWday
);
665 if ( GetWeekDayFromName(wday
, Name_Abbr
, DateLang_English
) == Inv_WeekDay
)
667 //else: ignore week day for now, we could also check that it really
668 // corresponds to the specified date
672 // 2. separating comma
673 if ( *p
++ != ',' || *p
++ != ' ' )
677 if ( !wxIsdigit(*p
) )
680 wxDateTime_t day
= (wxDateTime_t
)(*p
++ - '0');
684 day
= (wxDateTime_t
)(day
+ (*p
++ - '0'));
691 static const int MONTH_LEN
= 3;
692 const wxString::const_iterator endMonth
= p
+ MONTH_LEN
;
693 const wxString
monName(p
, endMonth
);
694 Month mon
= GetMonthFromName(monName
, Name_Abbr
, DateLang_English
);
695 if ( mon
== Inv_Month
)
704 if ( !wxIsdigit(*p
) )
707 int year
= *p
++ - '0';
708 if ( !wxIsdigit(*p
) ) // should have at least 2 digits in the year
714 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
720 if ( !wxIsdigit(*p
) )
722 // no 3 digit years please
733 // 6. time in hh:mm:ss format with seconds being optional
734 if ( !wxIsdigit(*p
) )
737 wxDateTime_t hour
= (wxDateTime_t
)(*p
++ - '0');
739 if ( !wxIsdigit(*p
) )
743 hour
= (wxDateTime_t
)(hour
+ (*p
++ - '0'));
748 if ( !wxIsdigit(*p
) )
751 wxDateTime_t min
= (wxDateTime_t
)(*p
++ - '0');
753 if ( !wxIsdigit(*p
) )
757 min
+= (wxDateTime_t
)(*p
++ - '0');
759 wxDateTime_t sec
= 0;
763 if ( !wxIsdigit(*p
) )
766 sec
= (wxDateTime_t
)(*p
++ - '0');
768 if ( !wxIsdigit(*p
) )
772 sec
+= (wxDateTime_t
)(*p
++ - '0');
778 // 7. now the interesting part: the timezone
779 int offset
wxDUMMY_INITIALIZE(0);
780 if ( *p
== '-' || *p
== '+' )
782 // the explicit offset given: it has the form of hhmm
783 bool plus
= *p
++ == '+';
785 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
790 offset
= MIN_PER_HOUR
*(10*(*p
- '0') + (*(p
+ 1) - '0'));
794 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
798 offset
+= 10*(*p
- '0') + (*(p
+ 1) - '0');
807 // the symbolic timezone given: may be either military timezone or one
808 // of standard abbreviations
811 // military: Z = UTC, J unused, A = -1, ..., Y = +12
812 static const int offsets
[26] =
814 //A B C D E F G H I J K L M
815 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
816 //N O P R Q S T U V W Z Y Z
817 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
820 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
823 offset
= offsets
[*p
++ - 'A'];
828 const wxString
tz(p
, date
.end());
829 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
831 else if ( tz
== _T("AST") )
833 else if ( tz
== _T("ADT") )
835 else if ( tz
== _T("EST") )
837 else if ( tz
== _T("EDT") )
839 else if ( tz
== _T("CST") )
841 else if ( tz
== _T("CDT") )
843 else if ( tz
== _T("MST") )
845 else if ( tz
== _T("MDT") )
847 else if ( tz
== _T("PST") )
849 else if ( tz
== _T("PDT") )
858 offset
*= MIN_PER_HOUR
;
862 // the spec was correct, construct the date from the values we found
863 Set(day
, mon
, year
, hour
, min
, sec
);
864 MakeFromTimezone(TimeZone::Make(offset
*SEC_PER_MIN
));
873 wxDateTime::ParseFormat(const wxString
& date
,
874 const wxString
& format
,
875 const wxDateTime
& dateDef
,
876 wxString::const_iterator
*endParse
)
878 wxCHECK_MSG( !format
.empty(), false, "format can't be empty" );
879 wxCHECK_MSG( endParse
, false, "end iterator pointer must be specified" );
884 // what fields have we found?
885 bool haveWDay
= false,
895 bool hourIsIn12hFormat
= false, // or in 24h one?
896 isPM
= false; // AM by default
898 // and the value of the items we have (init them to get rid of warnings)
899 wxDateTime_t msec
= 0,
903 WeekDay wday
= Inv_WeekDay
;
904 wxDateTime_t yday
= 0,
906 wxDateTime::Month mon
= Inv_Month
;
909 wxString::const_iterator input
= date
.begin();
910 const wxString::const_iterator end
= date
.end();
911 for ( wxString::const_iterator fmt
= format
.begin(); fmt
!= format
.end(); ++fmt
)
913 if ( *fmt
!= _T('%') )
915 if ( wxIsspace(*fmt
) )
917 // a white space in the format string matches 0 or more white
918 // spaces in the input
919 while ( input
!= end
&& wxIsspace(*input
) )
926 // any other character (not whitespace, not '%') must be
927 // matched by itself in the input
928 if ( input
== end
|| *input
++ != *fmt
)
935 // done with this format char
939 // start of a format specification
941 // parse the optional width
943 while ( wxIsdigit(*++fmt
) )
949 // the default widths for the various fields
952 switch ( (*fmt
).GetValue() )
954 case _T('Y'): // year has 4 digits
958 case _T('j'): // day of year has 3 digits
959 case _T('l'): // milliseconds have 3 digits
963 case _T('w'): // week day as number has only one
968 // default for all other fields
973 // then the format itself
974 switch ( (*fmt
).GetValue() )
976 case _T('a'): // a weekday name
979 wday
= GetWeekDayFromName
981 GetAlphaToken(input
, end
),
982 *fmt
== 'a' ? Name_Abbr
: Name_Full
,
985 if ( wday
== Inv_WeekDay
)
994 case _T('b'): // a month name
997 mon
= GetMonthFromName
999 GetAlphaToken(input
, end
),
1000 *fmt
== 'b' ? Name_Abbr
: Name_Full
,
1003 if ( mon
== Inv_Month
)
1012 case _T('c'): // locale default date and time representation
1017 fmtDateTime
= wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT
);
1018 if ( !fmtDateTime
.empty() )
1019 dt
= ParseFormatAt(input
, end
, fmtDateTime
);
1021 if ( !dt
.IsValid() )
1023 // also try the format which corresponds to ctime()
1024 // output (i.e. the "C" locale default)
1025 dt
= ParseFormatAt(input
, end
, wxS("%a %b %d %H:%M:%S %Y"));
1028 if ( !dt
.IsValid() )
1030 // and finally also the two generic date/time formats
1031 dt
= ParseFormatAt(input
, end
, wxS("%x %X"), wxS("%X %x"));
1034 if ( !dt
.IsValid() )
1037 const Tm tm
= dt
.GetTm();
1047 haveDay
= haveMon
= haveYear
=
1048 haveHour
= haveMin
= haveSec
= true;
1052 case _T('d'): // day of a month (01-31)
1053 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1054 (num
> 31) || (num
< 1) )
1060 // we can't check whether the day range is correct yet, will
1061 // do it later - assume ok for now
1063 mday
= (wxDateTime_t
)num
;
1066 case _T('H'): // hour in 24h format (00-23)
1067 if ( !GetNumericToken(width
, input
, end
, &num
) || (num
> 23) )
1074 hour
= (wxDateTime_t
)num
;
1077 case _T('I'): // hour in 12h format (01-12)
1078 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1079 !num
|| (num
> 12) )
1086 hourIsIn12hFormat
= true;
1087 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
1090 case _T('j'): // day of the year
1091 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1092 !num
|| (num
> 366) )
1099 yday
= (wxDateTime_t
)num
;
1102 case _T('l'): // milliseconds (0-999)
1103 if ( !GetNumericToken(width
, input
, end
, &num
) )
1107 msec
= (wxDateTime_t
)num
;
1110 case _T('m'): // month as a number (01-12)
1111 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1112 !num
|| (num
> 12) )
1119 mon
= (Month
)(num
- 1);
1122 case _T('M'): // minute as a decimal number (00-59)
1123 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1131 min
= (wxDateTime_t
)num
;
1134 case _T('p'): // AM or PM string
1136 wxString am
, pm
, token
= GetAlphaToken(input
, end
);
1138 // some locales have empty AM/PM tokens and thus when formatting
1139 // dates with the %p specifier nothing is generated; when trying to
1140 // parse them back, we get an empty token here... but that's not
1145 GetAmPmStrings(&am
, &pm
);
1146 if (am
.empty() && pm
.empty())
1147 return false; // no am/pm strings defined
1148 if ( token
.CmpNoCase(pm
) == 0 )
1152 else if ( token
.CmpNoCase(am
) != 0 )
1160 case _T('r'): // time as %I:%M:%S %p
1163 if ( !dt
.ParseFormat(wxString(input
, end
),
1164 wxS("%I:%M:%S %p"), &input
) )
1167 haveHour
= haveMin
= haveSec
= true;
1169 const Tm tm
= dt
.GetTm();
1176 case _T('R'): // time as %H:%M
1179 dt
= ParseFormatAt(input
, end
, wxS("%H:%M"));
1180 if ( !dt
.IsValid() )
1186 const Tm tm
= dt
.GetTm();
1192 case _T('S'): // second as a decimal number (00-61)
1193 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1201 sec
= (wxDateTime_t
)num
;
1204 case _T('T'): // time as %H:%M:%S
1207 dt
= ParseFormatAt(input
, end
, wxS("%H:%M:%S"));
1208 if ( !dt
.IsValid() )
1215 const Tm tm
= dt
.GetTm();
1222 case _T('w'): // weekday as a number (0-6), Sunday = 0
1223 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1231 wday
= (WeekDay
)num
;
1234 case _T('x'): // locale default date representation
1237 fmtDate
= wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT
),
1238 fmtDateAlt
= wxLocale::GetInfo(wxLOCALE_LONG_DATE_FMT
);
1240 if ( fmtDate
.empty() )
1242 if ( IsWestEuropeanCountry(GetCountry()) ||
1243 GetCountry() == Russia
)
1245 fmtDate
= wxS("%d/%m/%Y");
1246 fmtDateAlt
= wxS("%m/%d/%Y");
1250 fmtDate
= wxS("%m/%d/%Y");
1251 fmtDateAlt
= wxS("%d/%m/%Y");
1256 dt
= ParseFormatAt(input
, end
, fmtDate
, fmtDateAlt
);
1258 if ( !dt
.IsValid() )
1260 // try with short years too
1261 fmtDate
.Replace("%Y","%y");
1262 fmtDateAlt
.Replace("%Y","%y");
1263 dt
= ParseFormatAt(input
, end
, fmtDate
, fmtDateAlt
);
1265 if ( !dt
.IsValid() )
1269 const Tm tm
= dt
.GetTm();
1282 case _T('X'): // locale default time representation
1284 wxString fmtTime
= wxLocale::GetInfo(wxLOCALE_TIME_FMT
),
1287 if ( fmtTime
.empty() )
1289 // try to parse what follows as "%H:%M:%S" and, if this
1290 // fails, as "%I:%M:%S %p" - this should catch the most
1297 dt
= ParseFormatAt(input
, end
, fmtTime
, fmtTimeAlt
);
1298 if ( !dt
.IsValid() )
1305 const Tm tm
= dt
.GetTm();
1312 case _T('y'): // year without century (00-99)
1313 if ( !GetNumericToken(width
, input
, end
, &num
) ||
1322 // TODO should have an option for roll over date instead of
1323 // hard coding it here
1324 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
1327 case _T('Y'): // year with century
1328 if ( !GetNumericToken(width
, input
, end
, &num
) )
1335 year
= (wxDateTime_t
)num
;
1338 case _T('Z'): // timezone name
1339 // FIXME: currently we just ignore everything that looks like a
1341 GetAlphaToken(input
, end
);
1344 case _T('%'): // a percent sign
1345 if ( *input
++ != _T('%') )
1352 case 0: // the end of string
1353 wxFAIL_MSG(_T("unexpected format end"));
1357 default: // not a known format spec
1362 // format matched, try to construct a date from what we have now
1364 if ( dateDef
.IsValid() )
1366 // take this date as default
1367 tmDef
= dateDef
.GetTm();
1369 else if ( IsValid() )
1371 // if this date is valid, don't change it
1376 // no default and this date is invalid - fall back to Today()
1377 tmDef
= Today().GetTm();
1393 // TODO we don't check here that the values are consistent, if both year
1394 // day and month/day were found, we just ignore the year day and we
1395 // also always ignore the week day
1398 if ( mday
> GetNumberOfDays(tm
.mon
, tm
.year
) )
1403 else if ( haveYDay
)
1405 if ( yday
> GetNumberOfDays(tm
.year
) )
1408 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
1415 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
1417 // translate to 24hour format
1420 //else: either already in 24h format or no translation needed
1443 // finally check that the week day is consistent -- if we had it
1444 if ( haveWDay
&& GetWeekDay() != wday
)
1453 wxDateTime::ParseDateTime(const wxString
& date
, wxString::const_iterator
*end
)
1455 wxCHECK_MSG( end
, false, "end iterator pointer must be specified" );
1457 // Set to current day and hour, so strings like '14:00' becomes today at
1458 // 14, not some other random date
1459 wxDateTime dtDate
= wxDateTime::Today();
1460 wxDateTime dtTime
= wxDateTime::Today();
1462 wxString::const_iterator
1467 // If we got a date in the beginning, see if there is a time specified
1469 if ( dtDate
.ParseDate(date
, &endDate
) )
1471 // Skip spaces, as the ParseTime() function fails on spaces
1472 while ( endDate
!= date
.end() && wxIsspace(*endDate
) )
1475 const wxString
timestr(endDate
, date
.end());
1476 if ( !dtTime
.ParseTime(timestr
, &endTime
) )
1479 endBoth
= endDate
+ (endTime
- timestr
.begin());
1481 else // no date in the beginning
1483 // check if we have a time followed by a date
1484 if ( !dtTime
.ParseTime(date
, &endTime
) )
1487 while ( endTime
!= date
.end() && wxIsspace(*endTime
) )
1490 const wxString
datestr(endTime
, date
.end());
1491 if ( !dtDate
.ParseDate(datestr
, &endDate
) )
1494 endBoth
= endTime
+ (endDate
- datestr
.begin());
1497 Set(dtDate
.GetDay(), dtDate
.GetMonth(), dtDate
.GetYear(),
1498 dtTime
.GetHour(), dtTime
.GetMinute(), dtTime
.GetSecond(),
1499 dtTime
.GetMillisecond());
1507 wxDateTime::ParseDate(const wxString
& date
, wxString::const_iterator
*end
)
1509 wxCHECK_MSG( end
, false, "end iterator pointer must be specified" );
1511 // this is a simplified version of ParseDateTime() which understands only
1512 // "today" (for wxDate compatibility) and digits only otherwise (and not
1513 // all esoteric constructions ParseDateTime() knows about)
1515 const wxString::const_iterator pBegin
= date
.begin();
1517 wxString::const_iterator p
= pBegin
;
1518 while ( wxIsspace(*p
) )
1521 // some special cases
1525 int dayDiffFromToday
;
1528 { wxTRANSLATE("today"), 0 },
1529 { wxTRANSLATE("yesterday"), -1 },
1530 { wxTRANSLATE("tomorrow"), 1 },
1533 const size_t lenRest
= date
.end() - p
;
1534 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
1536 const wxString dateStr
= wxGetTranslation(literalDates
[n
].str
);
1537 size_t len
= dateStr
.length();
1539 if ( len
> lenRest
)
1542 const wxString::const_iterator pEnd
= p
+ len
;
1543 if ( wxString(p
, pEnd
).CmpNoCase(dateStr
) == 0 )
1545 // nothing can follow this, so stop here
1549 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
1551 if ( dayDiffFromToday
)
1553 *this += wxDateSpan::Days(dayDiffFromToday
);
1562 // We try to guess what we have here: for each new (numeric) token, we
1563 // determine if it can be a month, day or a year. Of course, there is an
1564 // ambiguity as some numbers may be days as well as months, so we also
1565 // have the ability to back track.
1568 bool haveDay
= false, // the months day?
1569 haveWDay
= false, // the day of week?
1570 haveMon
= false, // the month?
1571 haveYear
= false; // the year?
1573 // and the value of the items we have (init them to get rid of warnings)
1574 WeekDay wday
= Inv_WeekDay
;
1575 wxDateTime_t day
= 0;
1576 wxDateTime::Month mon
= Inv_Month
;
1579 // tokenize the string
1581 static const wxStringCharType
*dateDelimiters
= wxS(".,/-\t\r\n ");
1582 wxStringTokenizer
tok(wxString(p
, date
.end()), dateDelimiters
);
1583 while ( tok
.HasMoreTokens() )
1585 wxString token
= tok
.GetNextToken();
1591 if ( token
.ToULong(&val
) )
1593 // guess what this number is
1599 if ( !haveMon
&& val
> 0 && val
<= 12 )
1601 // assume it is month
1604 else // not the month
1608 // this can only be the year
1611 else // may be either day or year
1613 // use a leap year if we don't have the year yet to allow
1614 // dates like 2/29/1976 which would be rejected otherwise
1615 wxDateTime_t max_days
= (wxDateTime_t
)(
1617 ? GetNumberOfDays(mon
, haveYear
? year
: 1976)
1622 if ( (val
== 0) || (val
> (unsigned long)max_days
) )
1627 else // yes, suppose it's the day
1641 year
= (wxDateTime_t
)val
;
1650 day
= (wxDateTime_t
)val
;
1656 mon
= (Month
)(val
- 1);
1659 else // not a number
1661 // be careful not to overwrite the current mon value
1662 Month mon2
= GetMonthFromName
1665 Name_Full
| Name_Abbr
,
1666 DateLang_Local
| DateLang_English
1668 if ( mon2
!= Inv_Month
)
1673 // but we already have a month - maybe we guessed wrong?
1676 // no need to check in month range as always < 12, but
1677 // the days are counted from 1 unlike the months
1678 day
= (wxDateTime_t
)(mon
+ 1);
1683 // could possible be the year (doesn't the year come
1684 // before the month in the japanese format?) (FIXME)
1693 else // not a valid month name
1695 WeekDay wday2
= GetWeekDayFromName
1698 Name_Full
| Name_Abbr
,
1699 DateLang_Local
| DateLang_English
1701 if ( wday2
!= Inv_WeekDay
)
1713 else // not a valid weekday name
1716 static const char *ordinals
[] =
1718 wxTRANSLATE("first"),
1719 wxTRANSLATE("second"),
1720 wxTRANSLATE("third"),
1721 wxTRANSLATE("fourth"),
1722 wxTRANSLATE("fifth"),
1723 wxTRANSLATE("sixth"),
1724 wxTRANSLATE("seventh"),
1725 wxTRANSLATE("eighth"),
1726 wxTRANSLATE("ninth"),
1727 wxTRANSLATE("tenth"),
1728 wxTRANSLATE("eleventh"),
1729 wxTRANSLATE("twelfth"),
1730 wxTRANSLATE("thirteenth"),
1731 wxTRANSLATE("fourteenth"),
1732 wxTRANSLATE("fifteenth"),
1733 wxTRANSLATE("sixteenth"),
1734 wxTRANSLATE("seventeenth"),
1735 wxTRANSLATE("eighteenth"),
1736 wxTRANSLATE("nineteenth"),
1737 wxTRANSLATE("twentieth"),
1738 // that's enough - otherwise we'd have problems with
1739 // composite (or not) ordinals
1743 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
1745 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
1751 if ( n
== WXSIZEOF(ordinals
) )
1753 // stop here - something unknown
1760 // don't try anything here (as in case of numeric day
1761 // above) - the symbolic day spec should always
1762 // precede the month/year
1768 day
= (wxDateTime_t
)(n
+ 1);
1773 nPosCur
= tok
.GetPosition();
1776 // either no more tokens or the scan was stopped by something we couldn't
1777 // parse - in any case, see if we can construct a date from what we have
1778 if ( !haveDay
&& !haveWDay
)
1781 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
1782 !(haveDay
&& haveMon
&& haveYear
) )
1784 // without adjectives (which we don't support here) the week day only
1785 // makes sense completely separately or with the full date
1786 // specification (what would "Wed 1999" mean?)
1790 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
1792 // may be we have month and day instead of day and year?
1793 if ( haveDay
&& !haveMon
)
1797 // exchange day and month
1798 mon
= (wxDateTime::Month
)(day
- 1);
1800 // we're in the current year then
1801 if ( (year
> 0) && (year
<= (int)GetNumberOfDays(mon
, Inv_Year
)) )
1803 day
= (wxDateTime_t
)year
;
1808 //else: no, can't exchange, leave haveMon == false
1818 mon
= GetCurrentMonth();
1823 year
= GetCurrentYear();
1828 // normally we check the day above but the check is optimistic in case
1829 // we find the day before its month/year so we have to redo it now
1830 if ( day
> GetNumberOfDays(mon
, year
) )
1833 Set(day
, mon
, year
);
1837 // check that it is really the same
1838 if ( GetWeekDay() != wday
)
1846 SetToWeekDayInSameWeek(wday
);
1849 // return the pointer to the first unparsed char
1851 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
1853 // if we couldn't parse the token after the delimiter, put back the
1854 // delimiter as well
1864 wxDateTime::ParseTime(const wxString
& time
, wxString::const_iterator
*end
)
1866 wxCHECK_MSG( end
, false, "end iterator pointer must be specified" );
1868 // first try some extra things
1875 { wxTRANSLATE("noon"), 12 },
1876 { wxTRANSLATE("midnight"), 00 },
1880 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
1882 const wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
1883 const wxString::const_iterator p
= time
.begin() + timeString
.length();
1884 if ( timeString
.CmpNoCase(wxString(time
.begin(), p
)) == 0 )
1886 // casts required by DigitalMars
1887 Set(stdTimes
[n
].hour
, wxDateTime_t(0), wxDateTime_t(0));
1896 // try all time formats we may think about in the order from longest to
1898 static const char *timeFormats
[] =
1900 "%I:%M:%S %p", // 12hour with AM/PM
1901 "%H:%M:%S", // could be the same or 24 hour one so try it too
1902 "%I:%M %p", // 12hour with AM/PM but without seconds
1903 "%H:%M:%S", // and a possibly 24 hour version without seconds
1904 "%X", // possibly something from above or maybe something
1905 // completely different -- try it last
1907 // TODO: parse timezones
1910 for ( size_t nFmt
= 0; nFmt
< WXSIZEOF(timeFormats
); nFmt
++ )
1912 if ( ParseFormat(time
, timeFormats
[nFmt
], end
) )
1919 // ----------------------------------------------------------------------------
1920 // Workdays and holidays support
1921 // ----------------------------------------------------------------------------
1923 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
1925 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
1928 // ============================================================================
1930 // ============================================================================
1932 wxDateSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxDateSpan
& ds
)
1935 return ds1
.Multiply(n
);
1938 // ============================================================================
1940 // ============================================================================
1942 wxTimeSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxTimeSpan
& ts
)
1944 return wxTimeSpan(ts
).Multiply(n
);
1947 // this enum is only used in wxTimeSpan::Format() below but we can't declare
1948 // it locally to the method as it provokes an internal compiler error in egcs
1949 // 2.91.60 when building with -O2
1960 // not all strftime(3) format specifiers make sense here because, for example,
1961 // a time span doesn't have a year nor a timezone
1963 // Here are the ones which are supported (all of them are supported by strftime
1965 // %H hour in 24 hour format
1966 // %M minute (00 - 59)
1967 // %S second (00 - 59)
1970 // Also, for MFC CTimeSpan compatibility, we support
1971 // %D number of days
1973 // And, to be better than MFC :-), we also have
1974 // %E number of wEeks
1975 // %l milliseconds (000 - 999)
1976 wxString
wxTimeSpan::Format(const wxString
& format
) const
1978 // we deal with only positive time spans here and just add the sign in
1979 // front for the negative ones
1982 wxString
str(Negate().Format(format
));
1986 wxCHECK_MSG( !format
.empty(), wxEmptyString
,
1987 _T("NULL format in wxTimeSpan::Format") );
1990 str
.Alloc(format
.length());
1992 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
1994 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
1995 // question is what should ts.Format("%S") do? The code here returns "3273"
1996 // in this case (i.e. the total number of seconds, not just seconds % 60)
1997 // because, for me, this call means "give me entire time interval in
1998 // seconds" and not "give me the seconds part of the time interval"
2000 // If we agree that it should behave like this, it is clear that the
2001 // interpretation of each format specifier depends on the presence of the
2002 // other format specs in the string: if there was "%H" before "%M", we
2003 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
2005 // we remember the most important unit found so far
2006 TimeSpanPart partBiggest
= Part_MSec
;
2008 for ( wxString::const_iterator pch
= format
.begin(); pch
!= format
.end(); ++pch
)
2012 if ( ch
== _T('%') )
2014 // the start of the format specification of the printf() below
2015 wxString
fmtPrefix(_T('%'));
2020 // the number of digits for the format string, 0 if unused
2021 unsigned digits
= 0;
2023 ch
= *++pch
; // get the format spec char
2027 wxFAIL_MSG( _T("invalid format character") );
2033 // skip the part below switch
2038 if ( partBiggest
< Part_Day
)
2044 partBiggest
= Part_Day
;
2049 partBiggest
= Part_Week
;
2055 if ( partBiggest
< Part_Hour
)
2061 partBiggest
= Part_Hour
;
2068 n
= GetMilliseconds().ToLong();
2069 if ( partBiggest
< Part_MSec
)
2073 //else: no need to reset partBiggest to Part_MSec, it is
2074 // the least significant one anyhow
2081 if ( partBiggest
< Part_Min
)
2087 partBiggest
= Part_Min
;
2094 n
= GetSeconds().ToLong();
2095 if ( partBiggest
< Part_Sec
)
2101 partBiggest
= Part_Sec
;
2110 fmtPrefix
<< _T("0") << digits
;
2113 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
2117 // normal character, just copy
2125 #endif // wxUSE_DATETIME