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 // wxDateTime to/from text representations
68 // ----------------------------------------------------------------------------
70 wxString
wxDateTime::Format(const wxString
& format
, const TimeZone
& tz
) const
72 wxCHECK_MSG( !format
.empty(), wxEmptyString
,
73 _T("NULL format in wxDateTime::Format") );
75 // we have to use our own implementation if the date is out of range of
76 // strftime() or if we use non standard specificators
78 time_t time
= GetTicks();
80 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
85 if ( tz
.GetOffset() == -GetTimeZone() )
87 // we are working with local time
88 tm
= wxLocaltime_r(&time
, &tmstruct
);
90 // should never happen
91 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxLocaltime_r() failed") );
95 time
+= (int)tz
.GetOffset();
97 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
98 int time2
= (int) time
;
104 tm
= wxGmtime_r(&time
, &tmstruct
);
106 // should never happen
107 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxGmtime_r() failed") );
111 tm
= (struct tm
*)NULL
;
117 return CallStrftime(format
, tm
);
120 //else: use generic code below
121 #endif // HAVE_STRFTIME
123 // we only parse ANSI C format specifications here, no POSIX 2
124 // complications, no GNU extensions but we do add support for a "%l" format
125 // specifier allowing to get the number of milliseconds
128 // used for calls to strftime() when we only deal with time
129 struct tm tmTimeOnly
;
130 tmTimeOnly
.tm_hour
= tm
.hour
;
131 tmTimeOnly
.tm_min
= tm
.min
;
132 tmTimeOnly
.tm_sec
= tm
.sec
;
133 tmTimeOnly
.tm_wday
= 0;
134 tmTimeOnly
.tm_yday
= 0;
135 tmTimeOnly
.tm_mday
= 1; // any date will do
136 tmTimeOnly
.tm_mon
= 0;
137 tmTimeOnly
.tm_year
= 76;
138 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
140 wxString tmp
, res
, fmt
;
141 for ( wxString::const_iterator p
= format
.begin(); p
!= format
.end(); ++p
)
151 // set the default format
152 switch ( (*++p
).GetValue() )
154 case _T('Y'): // year has 4 digits
158 case _T('j'): // day of year has 3 digits
159 case _T('l'): // milliseconds have 3 digits
163 case _T('w'): // week day as number has only one
168 // it's either another valid format specifier in which case
169 // the format is "%02d" (for all the rest) or we have the
170 // field width preceding the format in which case it will
171 // override the default format anyhow
180 // start of the format specification
181 switch ( (*p
).GetValue() )
183 case _T('a'): // a weekday name
185 // second parameter should be true for abbreviated names
186 res
+= GetWeekDayName(tm
.GetWeekDay(),
187 *p
== _T('a') ? Name_Abbr
: Name_Full
);
190 case _T('b'): // a month name
192 res
+= GetMonthName(tm
.mon
,
193 *p
== _T('b') ? Name_Abbr
: Name_Full
);
196 case _T('c'): // locale default date and time representation
197 case _T('x'): // locale default date representation
200 // the problem: there is no way to know what do these format
201 // specifications correspond to for the current locale.
203 // the solution: use a hack and still use strftime(): first
204 // find the YEAR which is a year in the strftime() range (1970
205 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
206 // of the real year. Then make a copy of the format and
207 // replace all occurrences of YEAR in it with some unique
208 // string not appearing anywhere else in it, then use
209 // strftime() to format the date in year YEAR and then replace
210 // YEAR back by the real year and the unique replacement
211 // string back with YEAR. Notice that "all occurrences of YEAR"
212 // means all occurrences of 4 digit as well as 2 digit form!
214 // the bugs: we assume that neither of %c nor %x contains any
215 // fields which may change between the YEAR and real year. For
216 // example, the week number (%U, %W) and the day number (%j)
217 // will change if one of these years is leap and the other one
220 // find the YEAR: normally, for any year X, Jan 1 of the
221 // year X + 28 is the same weekday as Jan 1 of X (because
222 // the weekday advances by 1 for each normal X and by 2
223 // for each leap X, hence by 5 every 4 years or by 35
224 // which is 0 mod 7 every 28 years) but this rule breaks
225 // down if there are years between X and Y which are
226 // divisible by 4 but not leap (i.e. divisible by 100 but
227 // not 400), hence the correction.
229 int yearReal
= GetYear(tz
);
230 int mod28
= yearReal
% 28;
232 // be careful to not go too far - we risk to leave the
237 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
241 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
244 int nCentury
= year
/ 100,
245 nCenturyReal
= yearReal
/ 100;
247 // need to adjust for the years divisble by 400 which are
248 // not leap but are counted like leap ones if we just take
249 // the number of centuries in between for nLostWeekDays
250 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
251 (nCentury
/ 4 - nCenturyReal
/ 4);
253 // we have to gain back the "lost" weekdays: note that the
254 // effect of this loop is to not do anything to
255 // nLostWeekDays (which we won't use any more), but to
256 // (indirectly) set the year correctly
257 while ( (nLostWeekDays
% 7) != 0 )
259 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
262 // finally move the year below 2000 so that the 2-digit
263 // year number can never match the month or day of the
264 // month when we do the replacements below
268 wxASSERT_MSG( year
>= 1970 && year
< 2000,
269 _T("logic error in wxDateTime::Format") );
272 // use strftime() to format the same date but in supported
275 // NB: we assume that strftime() doesn't check for the
276 // date validity and will happily format the date
277 // corresponding to Feb 29 of a non leap year (which
278 // may happen if yearReal was leap and year is not)
279 struct tm tmAdjusted
;
281 tmAdjusted
.tm_hour
= tm
.hour
;
282 tmAdjusted
.tm_min
= tm
.min
;
283 tmAdjusted
.tm_sec
= tm
.sec
;
284 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
285 tmAdjusted
.tm_yday
= GetDayOfYear();
286 tmAdjusted
.tm_mday
= tm
.mday
;
287 tmAdjusted
.tm_mon
= tm
.mon
;
288 tmAdjusted
.tm_year
= year
- 1900;
289 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
290 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
294 // now replace the replacement year with the real year:
295 // notice that we have to replace the 4 digit year with
296 // a unique string not appearing in strftime() output
297 // first to prevent the 2 digit year from matching any
298 // substring of the 4 digit year (but any day, month,
299 // hours or minutes components should be safe because
300 // they are never in 70-99 range)
301 wxString
replacement("|");
302 while ( str
.find(replacement
) != wxString::npos
)
305 str
.Replace(wxString::Format("%d", year
),
307 str
.Replace(wxString::Format("%d", year
% 100),
308 wxString::Format("%d", yearReal
% 100));
309 str
.Replace(replacement
,
310 wxString::Format("%d", yearReal
));
314 #else // !HAVE_STRFTIME
315 // Use "%m/%d/%y %H:%M:%S" format instead
316 res
+= wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
317 tm
.mon
+1,tm
.mday
, tm
.year
, tm
.hour
, tm
.min
, tm
.sec
);
318 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
321 case _T('d'): // day of a month (01-31)
322 res
+= wxString::Format(fmt
, tm
.mday
);
325 case _T('H'): // hour in 24h format (00-23)
326 res
+= wxString::Format(fmt
, tm
.hour
);
329 case _T('I'): // hour in 12h format (01-12)
331 // 24h -> 12h, 0h -> 12h too
332 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
333 : tm
.hour
? tm
.hour
: 12;
334 res
+= wxString::Format(fmt
, hour12
);
338 case _T('j'): // day of the year
339 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
342 case _T('l'): // milliseconds (NOT STANDARD)
343 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
346 case _T('m'): // month as a number (01-12)
347 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
350 case _T('M'): // minute as a decimal number (00-59)
351 res
+= wxString::Format(fmt
, tm
.min
);
354 case _T('p'): // AM or PM string
356 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
357 #else // !HAVE_STRFTIME
358 res
+= (tmTimeOnly
.tm_hour
> 12) ? wxT("pm") : wxT("am");
359 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
362 case _T('S'): // second as a decimal number (00-61)
363 res
+= wxString::Format(fmt
, tm
.sec
);
366 case _T('U'): // week number in the year (Sunday 1st week day)
367 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
370 case _T('W'): // week number in the year (Monday 1st week day)
371 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
374 case _T('w'): // weekday as a number (0-6), Sunday = 0
375 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
378 // case _T('x'): -- handled with "%c"
380 case _T('X'): // locale default time representation
381 // just use strftime() to format the time for us
383 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
384 #else // !HAVE_STRFTIME
385 res
+= wxString::Format(wxT("%02d:%02d:%02d"),tm
.hour
, tm
.min
, tm
.sec
);
386 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
389 case _T('y'): // year without century (00-99)
390 res
+= wxString::Format(fmt
, tm
.year
% 100);
393 case _T('Y'): // year with century
394 res
+= wxString::Format(fmt
, tm
.year
);
397 case _T('Z'): // timezone name
399 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
404 // is it the format width?
406 while ( *p
== _T('-') || *p
== _T('+') ||
407 *p
== _T(' ') || wxIsdigit(*p
) )
414 // we've only got the flags and width so far in fmt
415 fmt
.Prepend(_T('%'));
423 // no, it wasn't the width
424 wxFAIL_MSG(_T("unknown format specificator"));
426 // fall through and just copy it nevertheless
428 case _T('%'): // a percent sign
432 case 0: // the end of string
433 wxFAIL_MSG(_T("missing format at the end of string"));
435 // just put the '%' which was the last char in format
445 // this function parses a string in (strict) RFC 822 format: see the section 5
446 // of the RFC for the detailed description, but briefly it's something of the
447 // form "Sat, 18 Dec 1999 00:48:30 +0100"
449 // this function is "strict" by design - it must reject anything except true
450 // RFC822 time specs.
452 // TODO a great candidate for using reg exps
454 wxDateTime::ParseRfc822Date(const wxString
& date
, wxString::const_iterator
*end
)
456 // TODO: rewrite using iterators instead of wxChar pointers
457 const wxStringCharType
*p
= date
.wx_str();
458 const wxStringCharType
*comma
= wxStrchr(p
, wxS(','));
461 // the part before comma is the weekday
463 // skip it for now - we don't use but might check that it really
464 // corresponds to the specfied date
469 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
477 // the following 1 or 2 digits are the day number
478 if ( !wxIsdigit(*p
) )
480 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
485 wxDateTime_t day
= (wxDateTime_t
)(*p
++ - _T('0'));
489 day
= (wxDateTime_t
)(day
+ (*p
++ - _T('0')));
492 if ( *p
++ != _T(' ') )
497 // the following 3 letters specify the month
498 wxString
monName(p
, 3);
500 if ( monName
== _T("Jan") )
502 else if ( monName
== _T("Feb") )
504 else if ( monName
== _T("Mar") )
506 else if ( monName
== _T("Apr") )
508 else if ( monName
== _T("May") )
510 else if ( monName
== _T("Jun") )
512 else if ( monName
== _T("Jul") )
514 else if ( monName
== _T("Aug") )
516 else if ( monName
== _T("Sep") )
518 else if ( monName
== _T("Oct") )
520 else if ( monName
== _T("Nov") )
522 else if ( monName
== _T("Dec") )
526 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
533 if ( *p
++ != _T(' ') )
539 if ( !wxIsdigit(*p
) )
545 int year
= *p
++ - _T('0');
547 if ( !wxIsdigit(*p
) )
549 // should have at least 2 digits in the year
554 year
+= *p
++ - _T('0');
556 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
560 year
+= *p
++ - _T('0');
562 if ( !wxIsdigit(*p
) )
564 // no 3 digit years please
569 year
+= *p
++ - _T('0');
572 if ( *p
++ != _T(' ') )
577 // time is in the format hh:mm:ss and seconds are optional
578 if ( !wxIsdigit(*p
) )
583 wxDateTime_t hour
= (wxDateTime_t
)(*p
++ - _T('0'));
585 if ( !wxIsdigit(*p
) )
591 hour
= (wxDateTime_t
)(hour
+ (*p
++ - _T('0')));
593 if ( *p
++ != _T(':') )
598 if ( !wxIsdigit(*p
) )
603 wxDateTime_t min
= (wxDateTime_t
)(*p
++ - _T('0'));
605 if ( !wxIsdigit(*p
) )
611 min
= (wxDateTime_t
)(min
+ *p
++ - _T('0'));
613 wxDateTime_t sec
= 0;
617 if ( !wxIsdigit(*p
) )
622 sec
= (wxDateTime_t
)(*p
++ - _T('0'));
624 if ( !wxIsdigit(*p
) )
630 sec
= (wxDateTime_t
)(sec
+ *p
++ - _T('0'));
633 if ( *p
++ != _T(' ') )
638 // and now the interesting part: the timezone
639 int offset
wxDUMMY_INITIALIZE(0);
640 if ( *p
== _T('-') || *p
== _T('+') )
642 // the explicit offset given: it has the form of hhmm
643 bool plus
= *p
++ == _T('+');
645 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
651 offset
= MIN_PER_HOUR
*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
655 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
661 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
672 // the symbolic timezone given: may be either military timezone or one
673 // of standard abbreviations
676 // military: Z = UTC, J unused, A = -1, ..., Y = +12
677 static const int offsets
[26] =
679 //A B C D E F G H I J K L M
680 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
681 //N O P R Q S T U V W Z Y Z
682 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
685 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
687 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
692 offset
= offsets
[*p
++ - _T('A')];
698 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
700 else if ( tz
== _T("AST") )
702 else if ( tz
== _T("ADT") )
704 else if ( tz
== _T("EST") )
706 else if ( tz
== _T("EDT") )
708 else if ( tz
== _T("CST") )
710 else if ( tz
== _T("CDT") )
712 else if ( tz
== _T("MST") )
714 else if ( tz
== _T("MDT") )
716 else if ( tz
== _T("PST") )
718 else if ( tz
== _T("PDT") )
722 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
731 offset
*= MIN_PER_HOUR
;
734 // the spec was correct, construct the date from the values we found
735 Set(day
, mon
, year
, hour
, min
, sec
);
736 MakeFromTimezone(TimeZone::Make(offset
*SEC_PER_MIN
));
738 const size_t endpos
= p
- date
.wx_str();
740 *end
= date
.begin() + endpos
;
742 return date
.c_str() + endpos
;
747 // returns the string containing strftime() format used for short dates in the
748 // current locale or an empty string
749 static wxString
GetLocaleDateFormat()
753 // there is no setlocale() under Windows CE, so just always query the
756 if ( strcmp(setlocale(LC_ALL
, NULL
), "C") != 0 )
759 // The locale was programatically set to non-C. We assume that this was
760 // done using wxLocale, in which case thread's current locale is also
761 // set to correct LCID value and we can use GetLocaleInfo to determine
762 // the correct formatting string:
764 LCID lcid
= LOCALE_USER_DEFAULT
;
766 LCID lcid
= GetThreadLocale();
768 // according to MSDN 80 chars is max allowed for short date format
770 if ( ::GetLocaleInfo(lcid
, LOCALE_SSHORTDATE
, fmt
, WXSIZEOF(fmt
)) )
772 wxChar chLast
= _T('\0');
773 size_t lastCount
= 0;
774 for ( const wxChar
*p
= fmt
; /* NUL handled inside */; p
++ )
784 // these characters come in groups, start counting them
794 // first deal with any special characters we have had
804 // these two are the same as we
805 // don't distinguish between 1 and
819 wxFAIL_MSG( _T("too many 'd's") );
828 // as for 'd' and 'dd' above
841 wxFAIL_MSG( _T("too many 'M's") );
858 wxFAIL_MSG( _T("wrong number of 'y's") );
863 // strftime() doesn't have era string,
864 // ignore this format
865 wxASSERT_MSG( lastCount
<= 2,
866 _T("too many 'g's") );
870 wxFAIL_MSG( _T("unreachable") );
877 // not a special character so must be just a separator,
879 if ( *p
!= _T('\0') )
883 // this one needs to be escaped
891 if ( *p
== _T('\0') )
895 //else: GetLocaleInfo() failed, leave fmtDate value unchanged and
896 // try our luck with the default formats
898 //else: default C locale, default formats should work
903 #endif // __WINDOWS__
906 wxDateTime::ParseFormat(const wxString
& date
,
907 const wxString
& format
,
908 const wxDateTime
& dateDef
,
909 wxString::const_iterator
*end
)
911 wxCHECK_MSG( !format
.empty(), NULL
, "format can't be empty" );
916 // what fields have we found?
917 bool haveWDay
= false,
927 bool hourIsIn12hFormat
= false, // or in 24h one?
928 isPM
= false; // AM by default
930 // and the value of the items we have (init them to get rid of warnings)
931 wxDateTime_t msec
= 0,
935 WeekDay wday
= Inv_WeekDay
;
936 wxDateTime_t yday
= 0,
938 wxDateTime::Month mon
= Inv_Month
;
941 const wxStringCharType
*input
= date
.wx_str();
942 for ( wxString::const_iterator fmt
= format
.begin(); fmt
!= format
.end(); ++fmt
)
944 if ( *fmt
!= _T('%') )
946 if ( wxIsspace(*fmt
) )
948 // a white space in the format string matches 0 or more white
949 // spaces in the input
950 while ( wxIsspace(*input
) )
957 // any other character (not whitespace, not '%') must be
958 // matched by itself in the input
959 if ( *input
++ != *fmt
)
966 // done with this format char
970 // start of a format specification
972 // parse the optional width
974 while ( wxIsdigit(*++fmt
) )
977 width
+= *fmt
- _T('0');
980 // the default widths for the various fields
983 switch ( (*fmt
).GetValue() )
985 case _T('Y'): // year has 4 digits
989 case _T('j'): // day of year has 3 digits
990 case _T('l'): // milliseconds have 3 digits
994 case _T('w'): // week day as number has only one
999 // default for all other fields
1004 // then the format itself
1005 switch ( (*fmt
).GetValue() )
1007 case _T('a'): // a weekday name
1010 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
1011 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
1012 if ( wday
== Inv_WeekDay
)
1021 case _T('b'): // a month name
1024 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
1025 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
1026 if ( mon
== Inv_Month
)
1035 case _T('c'): // locale default date and time representation
1039 const wxString
inc(input
);
1041 // try the format which corresponds to ctime() output first
1042 wxString::const_iterator endc
;
1043 if ( !dt
.ParseFormat(inc
, wxS("%a %b %d %H:%M:%S %Y"), &endc
) &&
1044 !dt
.ParseFormat(inc
, wxS("%x %X"), &endc
) &&
1045 !dt
.ParseFormat(inc
, wxS("%X %x"), &endc
) )
1047 // we've tried everything and still no match
1053 haveDay
= haveMon
= haveYear
=
1054 haveHour
= haveMin
= haveSec
= true;
1064 input
+= endc
- inc
.begin();
1068 case _T('d'): // day of a month (01-31)
1069 if ( !GetNumericToken(width
, input
, &num
) ||
1070 (num
> 31) || (num
< 1) )
1076 // we can't check whether the day range is correct yet, will
1077 // do it later - assume ok for now
1079 mday
= (wxDateTime_t
)num
;
1082 case _T('H'): // hour in 24h format (00-23)
1083 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
1090 hour
= (wxDateTime_t
)num
;
1093 case _T('I'): // hour in 12h format (01-12)
1094 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
1101 hourIsIn12hFormat
= true;
1102 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
1105 case _T('j'): // day of the year
1106 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
1113 yday
= (wxDateTime_t
)num
;
1116 case _T('l'): // milliseconds (0-999)
1117 if ( !GetNumericToken(width
, input
, &num
) )
1121 msec
= (wxDateTime_t
)num
;
1124 case _T('m'): // month as a number (01-12)
1125 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
1132 mon
= (Month
)(num
- 1);
1135 case _T('M'): // minute as a decimal number (00-59)
1136 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
1143 min
= (wxDateTime_t
)num
;
1146 case _T('p'): // AM or PM string
1148 wxString am
, pm
, token
= GetAlphaToken(input
);
1150 GetAmPmStrings(&am
, &pm
);
1151 if (am
.empty() && pm
.empty())
1152 return NULL
; // no am/pm strings defined
1153 if ( token
.CmpNoCase(pm
) == 0 )
1157 else if ( token
.CmpNoCase(am
) != 0 )
1165 case _T('r'): // time as %I:%M:%S %p
1168 input
= dt
.ParseFormat(input
, wxS("%I:%M:%S %p"));
1175 haveHour
= haveMin
= haveSec
= true;
1184 case _T('R'): // time as %H:%M
1187 input
= dt
.ParseFormat(input
, wxS("%H:%M"));
1194 haveHour
= haveMin
= true;
1202 case _T('S'): // second as a decimal number (00-61)
1203 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
1210 sec
= (wxDateTime_t
)num
;
1213 case _T('T'): // time as %H:%M:%S
1216 input
= dt
.ParseFormat(input
, _T("%H:%M:%S"));
1223 haveHour
= haveMin
= haveSec
= true;
1232 case _T('w'): // weekday as a number (0-6), Sunday = 0
1233 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
1240 wday
= (WeekDay
)num
;
1243 case _T('x'): // locale default date representation
1244 #ifdef HAVE_STRPTIME
1245 // try using strptime() -- it may fail even if the input is
1246 // correct but the date is out of range, so we will fall back
1247 // to our generic code anyhow
1251 const wxStringCharType
*
1252 result
= CallStrptime(input
, "%x", &tm
);
1257 haveDay
= haveMon
= haveYear
= true;
1259 year
= 1900 + tm
.tm_year
;
1260 mon
= (Month
)tm
.tm_mon
;
1266 #endif // HAVE_STRPTIME
1274 // The above doesn't work for all locales, try to query
1275 // Windows for the right way of formatting the date:
1276 fmtDate
= GetLocaleDateFormat();
1277 if ( fmtDate
.empty() )
1278 #endif // __WINDOWS__
1280 if ( IsWestEuropeanCountry(GetCountry()) ||
1281 GetCountry() == Russia
)
1283 fmtDate
= _T("%d/%m/%y");
1284 fmtDateAlt
= _T("%m/%d/%y");
1288 fmtDate
= _T("%m/%d/%y");
1289 fmtDateAlt
= _T("%d/%m/%y");
1293 const wxString
indate(input
);
1294 wxString::const_iterator endDate
;
1295 if ( !dt
.ParseFormat(indate
, fmtDate
, &endDate
) )
1297 // try another one if we have it
1298 if ( fmtDateAlt
.empty() ||
1299 !dt
.ParseFormat(indate
, fmtDateAlt
, &endDate
) )
1315 input
+= endDate
- indate
.begin();
1320 case _T('X'): // locale default time representation
1321 #ifdef HAVE_STRPTIME
1323 // use strptime() to do it for us (FIXME !Unicode friendly)
1325 input
= CallStrptime(input
, "%X", &tm
);
1331 haveHour
= haveMin
= haveSec
= true;
1337 #else // !HAVE_STRPTIME
1338 // TODO under Win32 we can query the LOCALE_ITIME system
1339 // setting which says whether the default time format is
1342 // try to parse what follows as "%H:%M:%S" and, if this
1343 // fails, as "%I:%M:%S %p" - this should catch the most
1347 const wxStringCharType
*
1348 result
= dt
.ParseFormat(input
, wxS("%T"));
1351 result
= dt
.ParseFormat(input
, wxS("%r"));
1371 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
1374 case _T('y'): // year without century (00-99)
1375 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
1383 // TODO should have an option for roll over date instead of
1384 // hard coding it here
1385 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
1388 case _T('Y'): // year with century
1389 if ( !GetNumericToken(width
, input
, &num
) )
1396 year
= (wxDateTime_t
)num
;
1399 case _T('Z'): // timezone name
1400 wxFAIL_MSG(_T("TODO"));
1403 case _T('%'): // a percent sign
1404 if ( *input
++ != _T('%') )
1411 case 0: // the end of string
1412 wxFAIL_MSG(_T("unexpected format end"));
1416 default: // not a known format spec
1421 // format matched, try to construct a date from what we have now
1423 if ( dateDef
.IsValid() )
1425 // take this date as default
1426 tmDef
= dateDef
.GetTm();
1428 else if ( IsValid() )
1430 // if this date is valid, don't change it
1435 // no default and this date is invalid - fall back to Today()
1436 tmDef
= Today().GetTm();
1452 // TODO we don't check here that the values are consistent, if both year
1453 // day and month/day were found, we just ignore the year day and we
1454 // also always ignore the week day
1457 if ( mday
> GetNumOfDaysInMonth(tm
.year
, tm
.mon
) )
1459 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
1466 else if ( haveYDay
)
1468 if ( yday
> GetNumberOfDays(tm
.year
) )
1470 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
1475 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
1482 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
1484 // translate to 24hour format
1487 //else: either already in 24h format or no translation needed
1510 // finally check that the week day is consistent -- if we had it
1511 if ( haveWDay
&& GetWeekDay() != wday
)
1513 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
1518 const size_t endpos
= input
- date
.wx_str();
1520 *end
= date
.begin() + endpos
;
1522 return date
.c_str() + endpos
;
1526 wxDateTime::ParseDateTime(const wxString
& date
, wxString::const_iterator
*end
)
1528 // Set to current day and hour, so strings like '14:00' becomes today at
1529 // 14, not some other random date
1530 wxDateTime dtDate
= wxDateTime::Today();
1531 wxDateTime dtTime
= wxDateTime::Today();
1533 wxString::const_iterator
1538 // If we got a date in the beginning, see if there is a time specified
1540 if ( dtDate
.ParseDate(date
, &endDate
) )
1542 // Skip spaces, as the ParseTime() function fails on spaces
1543 while ( endDate
!= date
.end() && wxIsspace(*endDate
) )
1546 const wxString
timestr(endDate
, date
.end());
1547 if ( !dtTime
.ParseTime(timestr
, &endTime
) )
1550 endBoth
= endDate
+ (endTime
- timestr
.begin());
1552 else // no date in the beginning
1554 // check if we have a time followed by a date
1555 if ( !dtTime
.ParseTime(date
, &endTime
) )
1558 while ( endTime
!= date
.end() && wxIsspace(*endTime
) )
1561 const wxString
datestr(endTime
, date
.end());
1562 if ( !dtDate
.ParseDate(datestr
, &endDate
) )
1565 endBoth
= endTime
+ (endDate
- datestr
.begin());
1568 Set(dtDate
.GetDay(), dtDate
.GetMonth(), dtDate
.GetYear(),
1569 dtTime
.GetHour(), dtTime
.GetMinute(), dtTime
.GetSecond(),
1570 dtTime
.GetMillisecond());
1572 // Return endpoint of scan
1576 return date
.c_str() + (endBoth
- date
.begin());
1580 wxDateTime::ParseDate(const wxString
& date
, wxString::const_iterator
*end
)
1582 // this is a simplified version of ParseDateTime() which understands only
1583 // "today" (for wxDate compatibility) and digits only otherwise (and not
1584 // all esoteric constructions ParseDateTime() knows about)
1586 const wxStringCharType
*p
= date
.wx_str();
1587 while ( wxIsspace(*p
) )
1590 // some special cases
1594 int dayDiffFromToday
;
1597 { wxTRANSLATE("today"), 0 },
1598 { wxTRANSLATE("yesterday"), -1 },
1599 { wxTRANSLATE("tomorrow"), 1 },
1602 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
1604 const wxString dateStr
= wxGetTranslation(literalDates
[n
].str
);
1605 size_t len
= dateStr
.length();
1606 if ( wxStrlen(p
) >= len
)
1608 wxString
str(p
, len
);
1609 if ( str
.CmpNoCase(dateStr
) == 0 )
1611 // nothing can follow this, so stop here
1614 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
1616 if ( dayDiffFromToday
)
1618 *this += wxDateSpan::Days(dayDiffFromToday
);
1621 const size_t endpos
= p
- date
.wx_str();
1624 *end
= date
.begin() + endpos
;
1625 return date
.c_str() + endpos
;
1630 // We try to guess what we have here: for each new (numeric) token, we
1631 // determine if it can be a month, day or a year. Of course, there is an
1632 // ambiguity as some numbers may be days as well as months, so we also
1633 // have the ability to back track.
1636 bool haveDay
= false, // the months day?
1637 haveWDay
= false, // the day of week?
1638 haveMon
= false, // the month?
1639 haveYear
= false; // the year?
1641 // and the value of the items we have (init them to get rid of warnings)
1642 WeekDay wday
= Inv_WeekDay
;
1643 wxDateTime_t day
= 0;
1644 wxDateTime::Month mon
= Inv_Month
;
1647 // tokenize the string
1649 static const wxStringCharType
*dateDelimiters
= wxS(".,/-\t\r\n ");
1650 wxStringTokenizer
tok(p
, dateDelimiters
);
1651 while ( tok
.HasMoreTokens() )
1653 wxString token
= tok
.GetNextToken();
1659 if ( token
.ToULong(&val
) )
1661 // guess what this number is
1667 if ( !haveMon
&& val
> 0 && val
<= 12 )
1669 // assume it is month
1672 else // not the month
1676 // this can only be the year
1679 else // may be either day or year
1681 // use a leap year if we don't have the year yet to allow
1682 // dates like 2/29/1976 which would be rejected otherwise
1683 wxDateTime_t max_days
= (wxDateTime_t
)(
1685 ? GetNumOfDaysInMonth(haveYear
? year
: 1976, mon
)
1690 if ( (val
== 0) || (val
> (unsigned long)max_days
) )
1695 else // yes, suppose it's the day
1709 year
= (wxDateTime_t
)val
;
1718 day
= (wxDateTime_t
)val
;
1724 mon
= (Month
)(val
- 1);
1727 else // not a number
1729 // be careful not to overwrite the current mon value
1730 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
1731 if ( mon2
!= Inv_Month
)
1736 // but we already have a month - maybe we guessed wrong?
1739 // no need to check in month range as always < 12, but
1740 // the days are counted from 1 unlike the months
1741 day
= (wxDateTime_t
)(mon
+ 1);
1746 // could possible be the year (doesn't the year come
1747 // before the month in the japanese format?) (FIXME)
1756 else // not a valid month name
1758 WeekDay wday2
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
1759 if ( wday2
!= Inv_WeekDay
)
1771 else // not a valid weekday name
1774 static const char *ordinals
[] =
1776 wxTRANSLATE("first"),
1777 wxTRANSLATE("second"),
1778 wxTRANSLATE("third"),
1779 wxTRANSLATE("fourth"),
1780 wxTRANSLATE("fifth"),
1781 wxTRANSLATE("sixth"),
1782 wxTRANSLATE("seventh"),
1783 wxTRANSLATE("eighth"),
1784 wxTRANSLATE("ninth"),
1785 wxTRANSLATE("tenth"),
1786 wxTRANSLATE("eleventh"),
1787 wxTRANSLATE("twelfth"),
1788 wxTRANSLATE("thirteenth"),
1789 wxTRANSLATE("fourteenth"),
1790 wxTRANSLATE("fifteenth"),
1791 wxTRANSLATE("sixteenth"),
1792 wxTRANSLATE("seventeenth"),
1793 wxTRANSLATE("eighteenth"),
1794 wxTRANSLATE("nineteenth"),
1795 wxTRANSLATE("twentieth"),
1796 // that's enough - otherwise we'd have problems with
1797 // composite (or not) ordinals
1801 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
1803 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
1809 if ( n
== WXSIZEOF(ordinals
) )
1811 // stop here - something unknown
1818 // don't try anything here (as in case of numeric day
1819 // above) - the symbolic day spec should always
1820 // precede the month/year
1826 day
= (wxDateTime_t
)(n
+ 1);
1831 nPosCur
= tok
.GetPosition();
1834 // either no more tokens or the scan was stopped by something we couldn't
1835 // parse - in any case, see if we can construct a date from what we have
1836 if ( !haveDay
&& !haveWDay
)
1838 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
1843 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
1844 !(haveDay
&& haveMon
&& haveYear
) )
1846 // without adjectives (which we don't support here) the week day only
1847 // makes sense completely separately or with the full date
1848 // specification (what would "Wed 1999" mean?)
1852 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
1854 // may be we have month and day instead of day and year?
1855 if ( haveDay
&& !haveMon
)
1859 // exchange day and month
1860 mon
= (wxDateTime::Month
)(day
- 1);
1862 // we're in the current year then
1863 if ( (year
> 0) && (year
<= (int)GetNumOfDaysInMonth(Inv_Year
, mon
)) )
1865 day
= (wxDateTime_t
)year
;
1870 //else: no, can't exchange, leave haveMon == false
1876 // if we give the year, month and day must be given too
1877 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
1885 mon
= GetCurrentMonth();
1890 year
= GetCurrentYear();
1895 // normally we check the day above but the check is optimistic in case
1896 // we find the day before its month/year so we have to redo it now
1897 if ( day
> GetNumOfDaysInMonth(year
, mon
) )
1900 Set(day
, mon
, year
);
1904 // check that it is really the same
1905 if ( GetWeekDay() != wday
)
1907 // inconsistency detected
1908 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
1918 SetToWeekDayInSameWeek(wday
);
1921 // return the pointer to the first unparsed char
1923 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
1925 // if we couldn't parse the token after the delimiter, put back the
1926 // delimiter as well
1930 const size_t endpos
= p
- date
.wx_str();
1932 *end
= date
.begin() + endpos
;
1934 return date
.c_str() + endpos
;
1938 wxDateTime::ParseTime(const wxString
& time
, wxString::const_iterator
*end
)
1940 // first try some extra things
1947 { wxTRANSLATE("noon"), 12 },
1948 { wxTRANSLATE("midnight"), 00 },
1952 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
1954 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
1955 size_t len
= timeString
.length();
1956 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
1958 // casts required by DigitalMars
1959 Set(stdTimes
[n
].hour
, wxDateTime_t(0), wxDateTime_t(0));
1962 *end
= time
.begin() + len
;
1964 return time
.c_str() + len
;
1968 // try all time formats we may think about in the order from longest to
1970 static const char *timeFormats
[] =
1972 "%I:%M:%S %p", // 12hour with AM/PM
1973 "%H:%M:%S", // could be the same or 24 hour one so try it too
1974 "%I:%M %p", // 12hour with AM/PM but without seconds
1975 "%H:%M:%S", // and a possibly 24 hour version without seconds
1976 "%X", // possibly something from above or maybe something
1977 // completely different -- try it last
1979 // TODO: parse timezones
1982 for ( size_t nFmt
= 0; nFmt
< WXSIZEOF(timeFormats
); nFmt
++ )
1984 const char *result
= ParseFormat(time
, timeFormats
[nFmt
], end
);
1992 // ----------------------------------------------------------------------------
1993 // Workdays and holidays support
1994 // ----------------------------------------------------------------------------
1996 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
1998 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
2001 // ============================================================================
2003 // ============================================================================
2005 wxDateSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxDateSpan
& ds
)
2008 return ds1
.Multiply(n
);
2011 // ============================================================================
2013 // ============================================================================
2015 wxTimeSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxTimeSpan
& ts
)
2017 return wxTimeSpan(ts
).Multiply(n
);
2020 // this enum is only used in wxTimeSpan::Format() below but we can't declare
2021 // it locally to the method as it provokes an internal compiler error in egcs
2022 // 2.91.60 when building with -O2
2033 // not all strftime(3) format specifiers make sense here because, for example,
2034 // a time span doesn't have a year nor a timezone
2036 // Here are the ones which are supported (all of them are supported by strftime
2038 // %H hour in 24 hour format
2039 // %M minute (00 - 59)
2040 // %S second (00 - 59)
2043 // Also, for MFC CTimeSpan compatibility, we support
2044 // %D number of days
2046 // And, to be better than MFC :-), we also have
2047 // %E number of wEeks
2048 // %l milliseconds (000 - 999)
2049 wxString
wxTimeSpan::Format(const wxString
& format
) const
2051 // we deal with only positive time spans here and just add the sign in
2052 // front for the negative ones
2055 wxString
str(Negate().Format(format
));
2059 wxCHECK_MSG( !format
.empty(), wxEmptyString
,
2060 _T("NULL format in wxTimeSpan::Format") );
2063 str
.Alloc(format
.length());
2065 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
2067 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
2068 // question is what should ts.Format("%S") do? The code here returns "3273"
2069 // in this case (i.e. the total number of seconds, not just seconds % 60)
2070 // because, for me, this call means "give me entire time interval in
2071 // seconds" and not "give me the seconds part of the time interval"
2073 // If we agree that it should behave like this, it is clear that the
2074 // interpretation of each format specifier depends on the presence of the
2075 // other format specs in the string: if there was "%H" before "%M", we
2076 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
2078 // we remember the most important unit found so far
2079 TimeSpanPart partBiggest
= Part_MSec
;
2081 for ( wxString::const_iterator pch
= format
.begin(); pch
!= format
.end(); ++pch
)
2085 if ( ch
== _T('%') )
2087 // the start of the format specification of the printf() below
2088 wxString
fmtPrefix(_T('%'));
2093 // the number of digits for the format string, 0 if unused
2094 unsigned digits
= 0;
2096 ch
= *++pch
; // get the format spec char
2100 wxFAIL_MSG( _T("invalid format character") );
2106 // skip the part below switch
2111 if ( partBiggest
< Part_Day
)
2117 partBiggest
= Part_Day
;
2122 partBiggest
= Part_Week
;
2128 if ( partBiggest
< Part_Hour
)
2134 partBiggest
= Part_Hour
;
2141 n
= GetMilliseconds().ToLong();
2142 if ( partBiggest
< Part_MSec
)
2146 //else: no need to reset partBiggest to Part_MSec, it is
2147 // the least significant one anyhow
2154 if ( partBiggest
< Part_Min
)
2160 partBiggest
= Part_Min
;
2167 n
= GetSeconds().ToLong();
2168 if ( partBiggest
< Part_Sec
)
2174 partBiggest
= Part_Sec
;
2183 fmtPrefix
<< _T("0") << digits
;
2186 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
2190 // normal character, just copy
2198 #endif // wxUSE_DATETIME