X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8aa25f40256648a6770b6fa0e980801acea5e614..44c8e75ba92de8f003f193f1e80b374b3402419d:/src/common/datetimefmt.cpp diff --git a/src/common/datetimefmt.cpp b/src/common/datetimefmt.cpp index 57c2312465..69cc996454 100644 --- a/src/common/datetimefmt.cpp +++ b/src/common/datetimefmt.cpp @@ -34,7 +34,7 @@ #if !defined(wxUSE_DATETIME) || wxUSE_DATETIME #ifndef WX_PRECOMP - #ifdef __WXMSW__ + #ifdef __WINDOWS__ #include "wx/msw/wrapwin.h" #endif #include "wx/string.h" @@ -46,7 +46,6 @@ #endif // WX_PRECOMP #include "wx/thread.h" -#include "wx/tokenzr.h" #include @@ -58,6 +57,7 @@ #endif #include "wx/datetime.h" +#include "wx/time.h" // ============================================================================ // implementation of wxDateTime @@ -69,8 +69,6 @@ extern void InitTm(struct tm& tm); -extern int GetTimeZone(); - extern wxString CallStrftime(const wxString& format, const tm* tm); // ---------------------------------------------------------------------------- @@ -89,44 +87,68 @@ static const int MIN_PER_HOUR = 60; // parsing helpers // ---------------------------------------------------------------------------- -#ifdef HAVE_STRPTIME +namespace +{ -#if wxUSE_UNIX && !defined(HAVE_STRPTIME_DECL) - // configure detected that we had strptime() but not its declaration, - // provide it ourselves - extern "C" char *strptime(const char *, const char *, struct tm *); -#endif +// all the functions below taking non-const wxString::const_iterator p advance +// it until the end of the match -// Unicode-friendly strptime() wrapper -static const wxStringCharType * -CallStrptime(const wxStringCharType *input, const char *fmt, tm *tm) +// scans all digits (but no more than len) and returns the resulting number +bool GetNumericToken(size_t len, + wxString::const_iterator& p, + const wxString::const_iterator& end, + unsigned long *number) { - // the problem here is that strptime() returns pointer into the string we - // passed to it while we're really interested in the pointer into the - // original, Unicode, string so we try to transform the pointer back -#if wxUSE_UNICODE_WCHAR - wxCharBuffer inputMB(wxConvertWX2MB(input)); -#else // ASCII - const char * const inputMB = input; -#endif // Unicode/Ascii - - const char *result = strptime(inputMB, fmt, tm); - if ( !result ) - return NULL; + size_t n = 1; + wxString s; + while ( p != end && wxIsdigit(*p) ) + { + s += *p++; + + if ( len && ++n > len ) + break; + } + + return !s.empty() && s.ToULong(number); +} + +// scans all alphabetic characters and returns the resulting string +wxString +GetAlphaToken(wxString::const_iterator& p, + const wxString::const_iterator& end) +{ + wxString s; + while ( p != end && wxIsalpha(*p) ) + { + s += *p++; + } -#if wxUSE_UNICODE_WCHAR - // FIXME: this is wrong in presence of surrogates &c - return input + (result - inputMB.data()); -#else // ASCII - return result; -#endif // Unicode/Ascii + return s; } -#endif // HAVE_STRPTIME +enum +{ + DateLang_English = 1, + DateLang_Local = 2 +}; // return the month if the string is a month name or Inv_Month otherwise -static wxDateTime::Month GetMonthFromName(const wxString& name, int flags) +// +// flags can contain wxDateTime::Name_Abbr/Name_Full or both of them and lang +// can be either DateLang_Local (default) to interpret string as a localized +// month name or DateLang_English to parse it as a standard English name or +// their combination to interpret it in any way +wxDateTime::Month +GetMonthFromName(wxString::const_iterator& p, + const wxString::const_iterator& end, + int flags, + int lang) { + const wxString::const_iterator pOrig = p; + const wxString name = GetAlphaToken(p, end); + if ( name.empty() ) + return wxDateTime::Inv_Month; + wxDateTime::Month mon; for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) ) { @@ -134,112 +156,197 @@ static wxDateTime::Month GetMonthFromName(const wxString& name, int flags) // and not versions if ( flags & wxDateTime::Name_Full ) { - if ( name.CmpNoCase(wxDateTime:: - GetMonthName(mon, wxDateTime::Name_Full)) == 0 ) + if ( lang & DateLang_English ) { - break; + if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon, + wxDateTime::Name_Full)) == 0 ) + break; + } + + if ( lang & DateLang_Local ) + { + if ( name.CmpNoCase(wxDateTime::GetMonthName(mon, + wxDateTime::Name_Full)) == 0 ) + break; } } if ( flags & wxDateTime::Name_Abbr ) { - if ( name.CmpNoCase(wxDateTime:: - GetMonthName(mon, wxDateTime::Name_Abbr)) == 0 ) + if ( lang & DateLang_English ) { - break; + if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon, + wxDateTime::Name_Abbr)) == 0 ) + break; + } + + if ( lang & DateLang_Local ) + { + // some locales (e.g. French one) use periods for the + // abbreviated month names but it's never part of name so + // compare it specially + wxString nameAbbr = wxDateTime::GetMonthName(mon, + wxDateTime::Name_Abbr); + const bool hasPeriod = *nameAbbr.rbegin() == '.'; + if ( hasPeriod ) + nameAbbr.erase(nameAbbr.end() - 1); + + if ( name.CmpNoCase(nameAbbr) == 0 ) + { + if ( hasPeriod ) + { + // skip trailing period if it was part of the match + if ( *p == '.' ) + ++p; + else // no match as no matching period + continue; + } + + break; + } } } } + if ( mon == wxDateTime::Inv_Month ) + p = pOrig; + return mon; } // return the weekday if the string is a weekday name or Inv_WeekDay otherwise -static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name, int flags) +// +// flags and lang parameters have the same meaning as for GetMonthFromName() +// above +wxDateTime::WeekDay +GetWeekDayFromName(wxString::const_iterator& p, + const wxString::const_iterator& end, + int flags, int lang) { + const wxString::const_iterator pOrig = p; + const wxString name = GetAlphaToken(p, end); + if ( name.empty() ) + return wxDateTime::Inv_WeekDay; + wxDateTime::WeekDay wd; for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) ) { - // case-insensitive comparison either one of or with both abbreviated - // and not versions if ( flags & wxDateTime::Name_Full ) { - if ( name.CmpNoCase(wxDateTime:: - GetWeekDayName(wd, wxDateTime::Name_Full)) == 0 ) + if ( lang & DateLang_English ) { - break; + if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd, + wxDateTime::Name_Full)) == 0 ) + break; + } + + if ( lang & DateLang_Local ) + { + if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd, + wxDateTime::Name_Full)) == 0 ) + break; } } if ( flags & wxDateTime::Name_Abbr ) { - if ( name.CmpNoCase(wxDateTime:: - GetWeekDayName(wd, wxDateTime::Name_Abbr)) == 0 ) + if ( lang & DateLang_English ) { - break; + if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd, + wxDateTime::Name_Abbr)) == 0 ) + break; + } + + if ( lang & DateLang_Local ) + { + if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd, + wxDateTime::Name_Abbr)) == 0 ) + break; } } } + if ( wd == wxDateTime::Inv_WeekDay ) + p = pOrig; + return wd; } -// scans all digits (but no more than len) and returns the resulting number -static bool GetNumericToken(size_t len, - const wxStringCharType*& p, - unsigned long *number) +// parses string starting at given iterator using the specified format and, +// optionally, a fall back format (and optionally another one... but it stops +// there, really) +// +// if unsuccessful, returns invalid wxDateTime without changing p; otherwise +// advance p to the end of the match and returns wxDateTime containing the +// results of the parsing +wxDateTime +ParseFormatAt(wxString::const_iterator& p, + const wxString::const_iterator& end, + const wxString& fmt, + // FIXME-VC6: using wxString() instead of wxEmptyString in the + // line below results in error C2062: type 'class + // wxString (__cdecl *)(void)' unexpected + const wxString& fmtAlt = wxEmptyString) { - size_t n = 1; - wxString s; - while ( wxIsdigit(*p) ) - { - s += *p++; - - if ( len && ++n > len ) - break; - } + const wxString str(p, end); + wxString::const_iterator endParse; + wxDateTime dt; - return !s.empty() && s.ToULong(number); -} + // Use a default date outside of the DST period to avoid problems with + // parsing the time differently depending on the today's date (which is used + // as the fall back date if none is explicitly specified). + static const wxDateTime dtDef(1, wxDateTime::Jan, 2012); -// scans all alphabetic characters and returns the resulting string -static wxString GetAlphaToken(const wxStringCharType*& p) -{ - wxString s; - while ( wxIsalpha(*p) ) + if ( dt.ParseFormat(str, fmt, dtDef, &endParse) || + (!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, dtDef, &endParse)) ) { - s += *p++; + p += endParse - str.begin(); } + //else: all formats failed - return s; + return dt; } +} // anonymous namespace + // ---------------------------------------------------------------------------- // wxDateTime to/from text representations // ---------------------------------------------------------------------------- -wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const +wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const { - wxCHECK_MSG( !format.empty(), wxEmptyString, - _T("NULL format in wxDateTime::Format") ); - + wxCHECK_MSG( !formatp.empty(), wxEmptyString, + wxT("NULL format in wxDateTime::Format") ); + + wxString format = formatp; +#ifdef __WXOSX__ + format.Replace("%c",wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT)); + format.Replace("%x",wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT)); + format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT)); +#endif // we have to use our own implementation if the date is out of range of - // strftime() or if we use non standard specificators -#ifdef HAVE_STRFTIME + // strftime() or if we use non standard specifiers (notice that "%z" is + // special because it is de facto standard under Unix but is not supported + // under Windows) +#ifdef wxHAS_STRFTIME time_t time = GetTicks(); - if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) ) + if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l")) +#ifdef __WINDOWS__ + && !wxStrstr(format, wxT("%z")) +#endif + ) { // use strftime() struct tm tmstruct; struct tm *tm; - if ( tz.GetOffset() == -GetTimeZone() ) + if ( tz.GetOffset() == -wxGetTimeZone() ) { // we are working with local time tm = wxLocaltime_r(&time, &tmstruct); // should never happen - wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") ); + wxCHECK_MSG( tm, wxEmptyString, wxT("wxLocaltime_r() failed") ); } else { @@ -255,7 +362,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const tm = wxGmtime_r(&time, &tmstruct); // should never happen - wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") ); + wxCHECK_MSG( tm, wxEmptyString, wxT("wxGmtime_r() failed") ); } else { @@ -269,7 +376,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const } } //else: use generic code below -#endif // HAVE_STRFTIME +#endif // wxHAS_STRFTIME // we only parse ANSI C format specifications here, no POSIX 2 // complications, no GNU extensions but we do add support for a "%l" format @@ -278,12 +385,11 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // used for calls to strftime() when we only deal with time struct tm tmTimeOnly; + memset(&tmTimeOnly, 0, sizeof(tmTimeOnly)); tmTimeOnly.tm_hour = tm.hour; tmTimeOnly.tm_min = tm.min; tmTimeOnly.tm_sec = tm.sec; - tmTimeOnly.tm_wday = 0; - tmTimeOnly.tm_yday = 0; - tmTimeOnly.tm_mday = 1; // any date will do + tmTimeOnly.tm_mday = 1; // any date will do, use 1976-01-01 tmTimeOnly.tm_mon = 0; tmTimeOnly.tm_year = 76; tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves @@ -291,7 +397,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const wxString tmp, res, fmt; for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p ) { - if ( *p != _T('%') ) + if ( *p != wxT('%') ) { // copy as is res += *p; @@ -302,17 +408,18 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // set the default format switch ( (*++p).GetValue() ) { - case _T('Y'): // year has 4 digits - fmt = _T("%04d"); + case wxT('Y'): // year has 4 digits + case wxT('z'): // time zone as well + fmt = wxT("%04d"); break; - case _T('j'): // day of year has 3 digits - case _T('l'): // milliseconds have 3 digits - fmt = _T("%03d"); + case wxT('j'): // day of year has 3 digits + case wxT('l'): // milliseconds have 3 digits + fmt = wxT("%03d"); break; - case _T('w'): // week day as number has only one - fmt = _T("%d"); + case wxT('w'): // week day as number has only one + fmt = wxT("%d"); break; default: @@ -320,7 +427,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // the format is "%02d" (for all the rest) or we have the // field width preceding the format in which case it will // override the default format anyhow - fmt = _T("%02d"); + fmt = wxT("%02d"); } bool restart = true; @@ -331,22 +438,22 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // start of the format specification switch ( (*p).GetValue() ) { - case _T('a'): // a weekday name - case _T('A'): + case wxT('a'): // a weekday name + case wxT('A'): // second parameter should be true for abbreviated names res += GetWeekDayName(tm.GetWeekDay(), - *p == _T('a') ? Name_Abbr : Name_Full); + *p == wxT('a') ? Name_Abbr : Name_Full); break; - case _T('b'): // a month name - case _T('B'): + case wxT('b'): // a month name + case wxT('B'): res += GetMonthName(tm.mon, - *p == _T('b') ? Name_Abbr : Name_Full); + *p == wxT('b') ? Name_Abbr : Name_Full); break; - case _T('c'): // locale default date and time representation - case _T('x'): // locale default date representation -#ifdef HAVE_STRFTIME + case wxT('c'): // locale default date and time representation + case wxT('x'): // locale default date representation +#ifdef wxHAS_STRFTIME // // the problem: there is no way to know what do these format // specifications correspond to for the current locale. @@ -407,7 +514,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // (indirectly) set the year correctly while ( (nLostWeekDays % 7) != 0 ) { - nLostWeekDays += year++ % 4 ? 1 : 2; + nLostWeekDays += (year++ % 4) ? 1 : 2; } // finally move the year below 2000 so that the 2-digit @@ -417,7 +524,7 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const year -= 28; wxASSERT_MSG( year >= 1970 && year < 2000, - _T("logic error in wxDateTime::Format") ); + wxT("logic error in wxDateTime::Format") ); // use strftime() to format the same date but in supported @@ -438,8 +545,8 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const tmAdjusted.tm_mon = tm.mon; tmAdjusted.tm_year = year - 1900; tmAdjusted.tm_isdst = 0; // no DST, already adjusted - wxString str = CallStrftime(*p == _T('c') ? _T("%c") - : _T("%x"), + wxString str = CallStrftime(*p == wxT('c') ? wxT("%c") + : wxT("%x"), &tmAdjusted); // now replace the replacement year with the real year: @@ -462,22 +569,22 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const res += str; } -#else // !HAVE_STRFTIME +#else // !wxHAS_STRFTIME // Use "%m/%d/%y %H:%M:%S" format instead res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"), tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec); -#endif // HAVE_STRFTIME/!HAVE_STRFTIME +#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME break; - case _T('d'): // day of a month (01-31) + case wxT('d'): // day of a month (01-31) res += wxString::Format(fmt, tm.mday); break; - case _T('H'): // hour in 24h format (00-23) + case wxT('H'): // hour in 24h format (00-23) res += wxString::Format(fmt, tm.hour); break; - case _T('I'): // hour in 12h format (01-12) + case wxT('I'): // hour in 12h format (01-12) { // 24h -> 12h, 0h -> 12h too int hour12 = tm.hour > 12 ? tm.hour - 12 @@ -486,76 +593,108 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const } break; - case _T('j'): // day of the year + case wxT('j'): // day of the year res += wxString::Format(fmt, GetDayOfYear(tz)); break; - case _T('l'): // milliseconds (NOT STANDARD) + case wxT('l'): // milliseconds (NOT STANDARD) res += wxString::Format(fmt, GetMillisecond(tz)); break; - case _T('m'): // month as a number (01-12) + case wxT('m'): // month as a number (01-12) res += wxString::Format(fmt, tm.mon + 1); break; - case _T('M'): // minute as a decimal number (00-59) + case wxT('M'): // minute as a decimal number (00-59) res += wxString::Format(fmt, tm.min); break; - case _T('p'): // AM or PM string -#ifdef HAVE_STRFTIME - res += CallStrftime(_T("%p"), &tmTimeOnly); -#else // !HAVE_STRFTIME + case wxT('p'): // AM or PM string +#ifdef wxHAS_STRFTIME + res += CallStrftime(wxT("%p"), &tmTimeOnly); +#else // !wxHAS_STRFTIME res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am"); -#endif // HAVE_STRFTIME/!HAVE_STRFTIME +#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME break; - case _T('S'): // second as a decimal number (00-61) + case wxT('S'): // second as a decimal number (00-61) res += wxString::Format(fmt, tm.sec); break; - case _T('U'): // week number in the year (Sunday 1st week day) + case wxT('U'): // week number in the year (Sunday 1st week day) res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz)); break; - case _T('W'): // week number in the year (Monday 1st week day) + case wxT('W'): // week number in the year (Monday 1st week day) res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz)); break; - case _T('w'): // weekday as a number (0-6), Sunday = 0 + case wxT('w'): // weekday as a number (0-6), Sunday = 0 res += wxString::Format(fmt, tm.GetWeekDay()); break; - // case _T('x'): -- handled with "%c" + // case wxT('x'): -- handled with "%c" - case _T('X'): // locale default time representation + case wxT('X'): // locale default time representation // just use strftime() to format the time for us -#ifdef HAVE_STRFTIME - res += CallStrftime(_T("%X"), &tmTimeOnly); -#else // !HAVE_STRFTIME +#ifdef wxHAS_STRFTIME + res += CallStrftime(wxT("%X"), &tmTimeOnly); +#else // !wxHAS_STRFTIME res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec); -#endif // HAVE_STRFTIME/!HAVE_STRFTIME +#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME break; - case _T('y'): // year without century (00-99) + case wxT('y'): // year without century (00-99) res += wxString::Format(fmt, tm.year % 100); break; - case _T('Y'): // year with century + case wxT('Y'): // year with century res += wxString::Format(fmt, tm.year); break; - case _T('Z'): // timezone name -#ifdef HAVE_STRFTIME - res += CallStrftime(_T("%Z"), &tmTimeOnly); + case wxT('z'): // time zone as [-+]HHMM + { + int ofs = tz.GetOffset(); + + // The time zone offset does not include the DST, but + // we do need to take it into account when showing the + // time in the local time zone to the user. + if ( ofs == -wxGetTimeZone() && IsDST() == 1 ) + { + // FIXME: As elsewhere in wxDateTime, we assume + // that the DST is always 1 hour, but this is not + // true in general. + ofs += 3600; + } + + if ( ofs < 0 ) + { + res += '-'; + ofs = -ofs; + } + else + { + res += '+'; + } + + // Converts seconds to HHMM representation. + res += wxString::Format(fmt, + 100*(ofs/3600) + (ofs/60)%60); + } + break; + + case wxT('Z'): // timezone name +#ifdef wxHAS_STRFTIME + res += CallStrftime(wxT("%Z"), &tmTimeOnly); #endif break; default: // is it the format width? - fmt.Empty(); - while ( *p == _T('-') || *p == _T('+') || - *p == _T(' ') || wxIsdigit(*p) ) + for ( fmt.clear(); + *p == wxT('-') || *p == wxT('+') || + *p == wxT(' ') || wxIsdigit(*p); + ++p ) { fmt += *p; } @@ -563,8 +702,8 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const if ( !fmt.empty() ) { // we've only got the flags and width so far in fmt - fmt.Prepend(_T('%')); - fmt.Append(_T('d')); + fmt.Prepend(wxT('%')); + fmt.Append(wxT('d')); restart = true; @@ -572,19 +711,19 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const } // no, it wasn't the width - wxFAIL_MSG(_T("unknown format specificator")); + wxFAIL_MSG(wxT("unknown format specifier")); // fall through and just copy it nevertheless - case _T('%'): // a percent sign + case wxT('%'): // a percent sign res += *p; break; case 0: // the end of string - wxFAIL_MSG(_T("missing format at the end of string")); + wxFAIL_MSG(wxT("missing format at the end of string")); // just put the '%' which was the last char in format - res += _T('%'); + res += wxT('%'); break; } } @@ -599,226 +738,149 @@ wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const // // this function is "strict" by design - it must reject anything except true // RFC822 time specs. -// -// TODO a great candidate for using reg exps -const char * +bool wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end) { - // TODO: rewrite using iterators instead of wxChar pointers - const wxStringCharType *p = date.wx_str(); - const wxStringCharType *comma = wxStrchr(p, wxS(',')); - if ( comma ) - { - // the part before comma is the weekday - - // skip it for now - we don't use but might check that it really - // corresponds to the specfied date - p = comma + 1; - - if ( *p != _T(' ') ) - { - wxLogDebug(_T("no space after weekday in RFC822 time spec")); - - return NULL; - } - - p++; // skip space - } - - // the following 1 or 2 digits are the day number + const wxString::const_iterator pEnd = date.end(); + wxString::const_iterator p = date.begin(); + + // 1. week day + const wxDateTime::WeekDay + wd = GetWeekDayFromName(p, pEnd, Name_Abbr, DateLang_English); + if ( wd == Inv_WeekDay ) + return false; + //else: ignore week day for now, we could also check that it really + // corresponds to the specified date + + // 2. separating comma + if ( *p++ != ',' || *p++ != ' ' ) + return false; + + // 3. day number if ( !wxIsdigit(*p) ) - { - wxLogDebug(_T("day number expected in RFC822 time spec, none found")); - - return NULL; - } + return false; - wxDateTime_t day = (wxDateTime_t)(*p++ - _T('0')); + wxDateTime_t day = (wxDateTime_t)(*p++ - '0'); if ( wxIsdigit(*p) ) { day *= 10; - day = (wxDateTime_t)(day + (*p++ - _T('0'))); + day = (wxDateTime_t)(day + (*p++ - '0')); } - if ( *p++ != _T(' ') ) - { - return NULL; - } - - // the following 3 letters specify the month - wxString monName(p, 3); - Month mon; - if ( monName == _T("Jan") ) - mon = Jan; - else if ( monName == _T("Feb") ) - mon = Feb; - else if ( monName == _T("Mar") ) - mon = Mar; - else if ( monName == _T("Apr") ) - mon = Apr; - else if ( monName == _T("May") ) - mon = May; - else if ( monName == _T("Jun") ) - mon = Jun; - else if ( monName == _T("Jul") ) - mon = Jul; - else if ( monName == _T("Aug") ) - mon = Aug; - else if ( monName == _T("Sep") ) - mon = Sep; - else if ( monName == _T("Oct") ) - mon = Oct; - else if ( monName == _T("Nov") ) - mon = Nov; - else if ( monName == _T("Dec") ) - mon = Dec; - else - { - wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str()); - - return NULL; - } + if ( *p++ != ' ' ) + return false; - p += 3; + // 4. month name + const Month mon = GetMonthFromName(p, pEnd, Name_Abbr, DateLang_English); + if ( mon == Inv_Month ) + return false; - if ( *p++ != _T(' ') ) - { - return NULL; - } + if ( *p++ != ' ' ) + return false; - // next is the year + // 5. year if ( !wxIsdigit(*p) ) - { - // no year? - return NULL; - } + return false; - int year = *p++ - _T('0'); - - if ( !wxIsdigit(*p) ) - { - // should have at least 2 digits in the year - return NULL; - } + int year = *p++ - '0'; + if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year + return false; year *= 10; - year += *p++ - _T('0'); + year += *p++ - '0'; // is it a 2 digit year (as per original RFC 822) or a 4 digit one? if ( wxIsdigit(*p) ) { year *= 10; - year += *p++ - _T('0'); + year += *p++ - '0'; if ( !wxIsdigit(*p) ) { // no 3 digit years please - return NULL; + return false; } year *= 10; - year += *p++ - _T('0'); + year += *p++ - '0'; } - if ( *p++ != _T(' ') ) - { - return NULL; - } + if ( *p++ != ' ' ) + return false; - // time is in the format hh:mm:ss and seconds are optional + // 6. time in hh:mm:ss format with seconds being optional if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; - wxDateTime_t hour = (wxDateTime_t)(*p++ - _T('0')); + wxDateTime_t hour = (wxDateTime_t)(*p++ - '0'); if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; hour *= 10; - hour = (wxDateTime_t)(hour + (*p++ - _T('0'))); + hour = (wxDateTime_t)(hour + (*p++ - '0')); - if ( *p++ != _T(':') ) - { - return NULL; - } + if ( *p++ != ':' ) + return false; if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; - wxDateTime_t min = (wxDateTime_t)(*p++ - _T('0')); + wxDateTime_t min = (wxDateTime_t)(*p++ - '0'); if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; min *= 10; - min = (wxDateTime_t)(min + *p++ - _T('0')); + min += (wxDateTime_t)(*p++ - '0'); wxDateTime_t sec = 0; - if ( *p == _T(':') ) + if ( *p == ':' ) { p++; if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; - sec = (wxDateTime_t)(*p++ - _T('0')); + sec = (wxDateTime_t)(*p++ - '0'); if ( !wxIsdigit(*p) ) - { - return NULL; - } + return false; sec *= 10; - sec = (wxDateTime_t)(sec + *p++ - _T('0')); + sec += (wxDateTime_t)(*p++ - '0'); } - if ( *p++ != _T(' ') ) - { - return NULL; - } + if ( *p++ != ' ' ) + return false; - // and now the interesting part: the timezone - int offset wxDUMMY_INITIALIZE(0); - if ( *p == _T('-') || *p == _T('+') ) + // 7. now the interesting part: the timezone + int offset = 0; // just to suppress warnings + if ( *p == '-' || *p == '+' ) { // the explicit offset given: it has the form of hhmm - bool plus = *p++ == _T('+'); + bool plus = *p++ == '+'; if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) ) - { - return NULL; - } + return false; + // hours - offset = MIN_PER_HOUR*(10*(*p - _T('0')) + (*(p + 1) - _T('0'))); + offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0')); p += 2; if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) ) - { - return NULL; - } + return false; // minutes - offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0')); + offset += 10*(*p - '0') + (*(p + 1) - '0'); if ( !plus ) - { offset = -offset; - } p += 2; } - else + else // not numeric { // the symbolic timezone given: may be either military timezone or one // of standard abbreviations @@ -833,47 +895,39 @@ wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end) +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0 }; - if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') ) - { - wxLogDebug(_T("Invalid militaty timezone '%c'"), *p); + if ( *p < wxT('A') || *p > wxT('Z') || *p == wxT('J') ) + return false; - return NULL; - } - - offset = offsets[*p++ - _T('A')]; + offset = offsets[*p++ - 'A']; } else { // abbreviation - wxString tz = p; - if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") ) + const wxString tz(p, date.end()); + if ( tz == wxT("UT") || tz == wxT("UTC") || tz == wxT("GMT") ) offset = 0; - else if ( tz == _T("AST") ) + else if ( tz == wxT("AST") ) offset = AST - GMT0; - else if ( tz == _T("ADT") ) + else if ( tz == wxT("ADT") ) offset = ADT - GMT0; - else if ( tz == _T("EST") ) + else if ( tz == wxT("EST") ) offset = EST - GMT0; - else if ( tz == _T("EDT") ) + else if ( tz == wxT("EDT") ) offset = EDT - GMT0; - else if ( tz == _T("CST") ) + else if ( tz == wxT("CST") ) offset = CST - GMT0; - else if ( tz == _T("CDT") ) + else if ( tz == wxT("CDT") ) offset = CDT - GMT0; - else if ( tz == _T("MST") ) + else if ( tz == wxT("MST") ) offset = MST - GMT0; - else if ( tz == _T("MDT") ) + else if ( tz == wxT("MDT") ) offset = MDT - GMT0; - else if ( tz == _T("PST") ) + else if ( tz == wxT("PST") ) offset = PST - GMT0; - else if ( tz == _T("PDT") ) + else if ( tz == wxT("PDT") ) offset = PDT - GMT0; else - { - wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p); - - return NULL; - } + return false; p += tz.length(); } @@ -882,184 +936,45 @@ wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end) offset *= MIN_PER_HOUR; } + // the spec was correct, construct the date from the values we found Set(day, mon, year, hour, min, sec); MakeFromTimezone(TimeZone::Make(offset*SEC_PER_MIN)); - const size_t endpos = p - date.wx_str(); if ( end ) - *end = date.begin() + endpos; + *end = p; - return date.c_str() + endpos; + return true; } -#ifdef __WINDOWS__ - -// returns the string containing strftime() format used for short dates in the -// current locale or an empty string -static wxString GetLocaleDateFormat() +const char* wxDateTime::ParseRfc822Date(const char* date) { - wxString fmtWX; - - // there is no setlocale() under Windows CE, so just always query the - // system there -#ifndef __WXWINCE__ - if ( strcmp(setlocale(LC_ALL, NULL), "C") != 0 ) -#endif - { - // The locale was programatically set to non-C. We assume that this was - // done using wxLocale, in which case thread's current locale is also - // set to correct LCID value and we can use GetLocaleInfo to determine - // the correct formatting string: -#ifdef __WXWINCE__ - LCID lcid = LOCALE_USER_DEFAULT; -#else - LCID lcid = GetThreadLocale(); -#endif - // according to MSDN 80 chars is max allowed for short date format - wxChar fmt[81]; - if ( ::GetLocaleInfo(lcid, LOCALE_SSHORTDATE, fmt, WXSIZEOF(fmt)) ) - { - wxChar chLast = _T('\0'); - size_t lastCount = 0; - for ( const wxChar *p = fmt; /* NUL handled inside */; p++ ) - { - if ( *p == chLast ) - { - lastCount++; - continue; - } - - switch ( *p ) - { - // these characters come in groups, start counting them - case _T('d'): - case _T('M'): - case _T('y'): - case _T('g'): - chLast = *p; - lastCount = 1; - break; - - default: - // first deal with any special characters we have had - if ( lastCount ) - { - switch ( chLast ) - { - case _T('d'): - switch ( lastCount ) - { - case 1: // d - case 2: // dd - // these two are the same as we - // don't distinguish between 1 and - // 2 digits for days - fmtWX += _T("%d"); - break; - - case 3: // ddd - fmtWX += _T("%a"); - break; - - case 4: // dddd - fmtWX += _T("%A"); - break; - - default: - wxFAIL_MSG( _T("too many 'd's") ); - } - break; - - case _T('M'): - switch ( lastCount ) - { - case 1: // M - case 2: // MM - // as for 'd' and 'dd' above - fmtWX += _T("%m"); - break; - - case 3: - fmtWX += _T("%b"); - break; - - case 4: - fmtWX += _T("%B"); - break; - - default: - wxFAIL_MSG( _T("too many 'M's") ); - } - break; - - case _T('y'): - switch ( lastCount ) - { - case 1: // y - case 2: // yy - fmtWX += _T("%y"); - break; - - case 4: // yyyy - fmtWX += _T("%Y"); - break; - - default: - wxFAIL_MSG( _T("wrong number of 'y's") ); - } - break; - - case _T('g'): - // strftime() doesn't have era string, - // ignore this format - wxASSERT_MSG( lastCount <= 2, - _T("too many 'g's") ); - break; - - default: - wxFAIL_MSG( _T("unreachable") ); - } - - chLast = _T('\0'); - lastCount = 0; - } - - // not a special character so must be just a separator, - // treat as is - if ( *p != _T('\0') ) - { - if ( *p == _T('%') ) - { - // this one needs to be escaped - fmtWX += _T('%'); - } + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseRfc822Date(dateStr, &end) ) + return NULL; - fmtWX += *p; - } - } + return date + dateStr.IterOffsetInMBStr(end); +} - if ( *p == _T('\0') ) - break; - } - } - //else: GetLocaleInfo() failed, leave fmtDate value unchanged and - // try our luck with the default formats - } - //else: default C locale, default formats should work +const wchar_t* wxDateTime::ParseRfc822Date(const wchar_t* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseRfc822Date(dateStr, &end) ) + return NULL; - return fmtWX; + return date + (end - dateStr.begin()); } -#endif // __WINDOWS__ - -const char * +bool wxDateTime::ParseFormat(const wxString& date, const wxString& format, const wxDateTime& dateDef, - wxString::const_iterator *end) + wxString::const_iterator *endParse) { - wxCHECK_MSG( !format.empty(), NULL, "format can't be empty" ); + wxCHECK_MSG( !format.empty(), false, "format can't be empty" ); + wxCHECK_MSG( endParse, false, "end iterator pointer must be specified" ); wxString str; unsigned long num; @@ -1078,6 +993,8 @@ wxDateTime::ParseFormat(const wxString& date, bool hourIsIn12hFormat = false, // or in 24h one? isPM = false; // AM by default + bool haveTimeZone = false; + // and the value of the items we have (init them to get rid of warnings) wxDateTime_t msec = 0, sec = 0, @@ -1088,17 +1005,19 @@ wxDateTime::ParseFormat(const wxString& date, mday = 0; wxDateTime::Month mon = Inv_Month; int year = 0; + long timeZone = 0; // time zone in seconds as expected in Tm structure - const wxStringCharType *input = date.wx_str(); + wxString::const_iterator input = date.begin(); + const wxString::const_iterator end = date.end(); for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt ) { - if ( *fmt != _T('%') ) + if ( *fmt != wxT('%') ) { if ( wxIsspace(*fmt) ) { // a white space in the format string matches 0 or more white // spaces in the input - while ( wxIsspace(*input) ) + while ( input != end && wxIsspace(*input) ) { input++; } @@ -1107,10 +1026,10 @@ wxDateTime::ParseFormat(const wxString& date, { // any other character (not whitespace, not '%') must be // matched by itself in the input - if ( *input++ != *fmt ) + if ( input == end || *input++ != *fmt ) { // no match - return NULL; + return false; } } @@ -1125,7 +1044,7 @@ wxDateTime::ParseFormat(const wxString& date, while ( wxIsdigit(*++fmt) ) { width *= 10; - width += *fmt - _T('0'); + width += *fmt - '0'; } // the default widths for the various fields @@ -1133,16 +1052,16 @@ wxDateTime::ParseFormat(const wxString& date, { switch ( (*fmt).GetValue() ) { - case _T('Y'): // year has 4 digits + case wxT('Y'): // year has 4 digits width = 4; break; - case _T('j'): // day of year has 3 digits - case _T('l'): // milliseconds have 3 digits + case wxT('j'): // day of year has 3 digits + case wxT('l'): // milliseconds have 3 digits width = 3; break; - case _T('w'): // week day as number has only one + case wxT('w'): // week day as number has only one width = 1; break; @@ -1155,105 +1074,90 @@ wxDateTime::ParseFormat(const wxString& date, // then the format itself switch ( (*fmt).GetValue() ) { - case _T('a'): // a weekday name - case _T('A'): + case wxT('a'): // a weekday name + case wxT('A'): { - int flag = *fmt == _T('a') ? Name_Abbr : Name_Full; - wday = GetWeekDayFromName(GetAlphaToken(input), flag); + wday = GetWeekDayFromName + ( + input, end, + *fmt == 'a' ? Name_Abbr : Name_Full, + DateLang_Local + ); if ( wday == Inv_WeekDay ) { // no match - return NULL; + return false; } } haveWDay = true; break; - case _T('b'): // a month name - case _T('B'): + case wxT('b'): // a month name + case wxT('B'): { - int flag = *fmt == _T('b') ? Name_Abbr : Name_Full; - mon = GetMonthFromName(GetAlphaToken(input), flag); + mon = GetMonthFromName + ( + input, end, + *fmt == 'b' ? Name_Abbr : Name_Full, + DateLang_Local + ); if ( mon == Inv_Month ) { // no match - return NULL; + return false; } } haveMon = true; break; - case _T('c'): // locale default date and time representation + case wxT('c'): // locale default date and time representation { wxDateTime dt; - const wxString inc(input); - - // NOTE: %c is locale-dependent; try strptime -#ifdef HAVE_STRPTIME - struct tm tm; - - // try using strptime() -- it may fail even if the input is - // correct but the date is out of range, so we will fall back - // to our generic code anyhow - const wxStringCharType * - result = CallStrptime(input, "%c", &tm); - if ( result ) +#if wxUSE_INTL + const wxString + fmtDateTime = wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT); + if ( !fmtDateTime.empty() ) + dt = ParseFormatAt(input, end, fmtDateTime); +#endif // wxUSE_INTL + if ( !dt.IsValid() ) { - haveDay = haveMon = haveYear = - haveHour = haveMin = haveSec = true; - - hour = tm.tm_hour; - min = tm.tm_min; - sec = tm.tm_sec; - - year = 1900 + tm.tm_year; - mon = (Month)tm.tm_mon; - mday = tm.tm_mday; - - input = result; // proceed where strptime() ended + // also try the format which corresponds to ctime() + // output (i.e. the "C" locale default) + dt = ParseFormatAt(input, end, wxS("%a %b %d %H:%M:%S %Y")); } - else - { - // strptime() failed; try generic heuristic code -#endif // HAVE_STRPTIME - - // try the format which corresponds to ctime() output first - wxString::const_iterator endc; - if ( !dt.ParseFormat(inc, wxS("%a %b %d %H:%M:%S %Y"), &endc) && - !dt.ParseFormat(inc, wxS("%x %X"), &endc) && - !dt.ParseFormat(inc, wxS("%X %x"), &endc) ) - { - // we've tried everything and still no match - return NULL; - } - Tm tm = dt.GetTm(); - - haveDay = haveMon = haveYear = - haveHour = haveMin = haveSec = true; - - hour = tm.hour; - min = tm.min; - sec = tm.sec; - - year = tm.year; - mon = tm.mon; - mday = tm.mday; - - input += endc - inc.begin(); -#ifdef HAVE_STRPTIME + if ( !dt.IsValid() ) + { + // and finally also the two generic date/time formats + dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x")); } -#endif // HAVE_STRPTIME + + if ( !dt.IsValid() ) + return false; + + const Tm tm = dt.GetTm(); + + hour = tm.hour; + min = tm.min; + sec = tm.sec; + + year = tm.year; + mon = tm.mon; + mday = tm.mday; + + haveDay = haveMon = haveYear = + haveHour = haveMin = haveSec = true; } break; - case _T('d'): // day of a month (01-31) - if ( !GetNumericToken(width, input, &num) || + case wxT('d'): // day of a month (01-31) + case 'e': // day of a month (1-31) (GNU extension) + if ( !GetNumericToken(width, input, end, &num) || (num > 31) || (num < 1) ) { // no match - return NULL; + return false; } // we can't check whether the day range is correct yet, will @@ -1262,22 +1166,23 @@ wxDateTime::ParseFormat(const wxString& date, mday = (wxDateTime_t)num; break; - case _T('H'): // hour in 24h format (00-23) - if ( !GetNumericToken(width, input, &num) || (num > 23) ) + case wxT('H'): // hour in 24h format (00-23) + if ( !GetNumericToken(width, input, end, &num) || (num > 23) ) { // no match - return NULL; + return false; } haveHour = true; hour = (wxDateTime_t)num; break; - case _T('I'): // hour in 12h format (01-12) - if ( !GetNumericToken(width, input, &num) || !num || (num > 12) ) + case wxT('I'): // hour in 12h format (01-12) + if ( !GetNumericToken(width, input, end, &num) || + !num || (num > 12) ) { // no match - return NULL; + return false; } haveHour = true; @@ -1285,214 +1190,189 @@ wxDateTime::ParseFormat(const wxString& date, hour = (wxDateTime_t)(num % 12); // 12 should be 0 break; - case _T('j'): // day of the year - if ( !GetNumericToken(width, input, &num) || !num || (num > 366) ) + case wxT('j'): // day of the year + if ( !GetNumericToken(width, input, end, &num) || + !num || (num > 366) ) { // no match - return NULL; + return false; } haveYDay = true; yday = (wxDateTime_t)num; break; - case _T('l'): // milliseconds (0-999) - if ( !GetNumericToken(width, input, &num) ) - return NULL; + case wxT('l'): // milliseconds (0-999) + if ( !GetNumericToken(width, input, end, &num) ) + return false; haveMsec = true; msec = (wxDateTime_t)num; break; - case _T('m'): // month as a number (01-12) - if ( !GetNumericToken(width, input, &num) || !num || (num > 12) ) + case wxT('m'): // month as a number (01-12) + if ( !GetNumericToken(width, input, end, &num) || + !num || (num > 12) ) { // no match - return NULL; + return false; } haveMon = true; mon = (Month)(num - 1); break; - case _T('M'): // minute as a decimal number (00-59) - if ( !GetNumericToken(width, input, &num) || (num > 59) ) + case wxT('M'): // minute as a decimal number (00-59) + if ( !GetNumericToken(width, input, end, &num) || + (num > 59) ) { // no match - return NULL; + return false; } haveMin = true; min = (wxDateTime_t)num; break; - case _T('p'): // AM or PM string + case wxT('p'): // AM or PM string { - wxString am, pm, token = GetAlphaToken(input); - - // some locales have empty AM/PM tokens and thus when formatting - // dates with the %p specifier mpthomg os gemerated; when trying to - // parse them back, we get an empty token here... but that's not - // an error. - if (token.empty()) - break; - + wxString am, pm; GetAmPmStrings(&am, &pm); - if (am.empty() && pm.empty()) - return NULL; // no am/pm strings defined - if ( token.CmpNoCase(pm) == 0 ) + + // we can never match %p in locales which don't use AM/PM + if ( am.empty() || pm.empty() ) + return false; + + const size_t pos = input - date.begin(); + if ( date.compare(pos, pm.length(), pm) == 0 ) { isPM = true; + input += pm.length(); } - else if ( token.CmpNoCase(am) != 0 ) + else if ( date.compare(pos, am.length(), am) == 0 ) { - // no match - return NULL; + input += am.length(); + } + else // no match + { + return false; } } break; - case _T('r'): // time as %I:%M:%S %p + case wxT('r'): // time as %I:%M:%S %p { wxDateTime dt; - input = dt.ParseFormat(input, wxS("%I:%M:%S %p")); - if ( !input ) - { - // no match - return NULL; - } + if ( !dt.ParseFormat(wxString(input, end), + wxS("%I:%M:%S %p"), &input) ) + return false; haveHour = haveMin = haveSec = true; - Tm tm = dt.GetTm(); + const Tm tm = dt.GetTm(); hour = tm.hour; min = tm.min; sec = tm.sec; } break; - case _T('R'): // time as %H:%M + case wxT('R'): // time as %H:%M { - wxDateTime dt; - input = dt.ParseFormat(input, wxS("%H:%M")); - if ( !input ) - { - // no match - return NULL; - } + const wxDateTime + dt = ParseFormatAt(input, end, wxS("%H:%M")); + if ( !dt.IsValid() ) + return false; - haveHour = haveMin = true; + haveHour = + haveMin = true; - Tm tm = dt.GetTm(); + const Tm tm = dt.GetTm(); hour = tm.hour; min = tm.min; } break; - case _T('S'): // second as a decimal number (00-61) - if ( !GetNumericToken(width, input, &num) || (num > 61) ) + case wxT('S'): // second as a decimal number (00-61) + if ( !GetNumericToken(width, input, end, &num) || + (num > 61) ) { // no match - return NULL; + return false; } haveSec = true; sec = (wxDateTime_t)num; break; - case _T('T'): // time as %H:%M:%S + case wxT('T'): // time as %H:%M:%S { - wxDateTime dt; - input = dt.ParseFormat(input, _T("%H:%M:%S")); - if ( !input ) - { - // no match - return NULL; - } + const wxDateTime + dt = ParseFormatAt(input, end, wxS("%H:%M:%S")); + if ( !dt.IsValid() ) + return false; - haveHour = haveMin = haveSec = true; + haveHour = + haveMin = + haveSec = true; - Tm tm = dt.GetTm(); + const Tm tm = dt.GetTm(); hour = tm.hour; min = tm.min; sec = tm.sec; } break; - case _T('w'): // weekday as a number (0-6), Sunday = 0 - if ( !GetNumericToken(width, input, &num) || (wday > 6) ) + case wxT('w'): // weekday as a number (0-6), Sunday = 0 + if ( !GetNumericToken(width, input, end, &num) || + (wday > 6) ) { // no match - return NULL; + return false; } haveWDay = true; wday = (WeekDay)num; break; - case _T('x'): // locale default date representation -#ifdef HAVE_STRPTIME - // try using strptime() -- it may fail even if the input is - // correct but the date is out of range, so we will fall back - // to our generic code anyhow - { - struct tm tm; - - const wxStringCharType * - result = CallStrptime(input, "%x", &tm); - if ( result ) - { - input = result; - - haveDay = haveMon = haveYear = true; - - year = 1900 + tm.tm_year; - mon = (Month)tm.tm_mon; - mday = tm.tm_mday; - - break; - } - } -#endif // HAVE_STRPTIME - + case wxT('x'): // locale default date representation { - wxDateTime dt; - wxString fmtDate, - fmtDateAlt; - -#ifdef __WINDOWS__ - // The above doesn't work for all locales, try to query - // Windows for the right way of formatting the date: - fmtDate = GetLocaleDateFormat(); +#if wxUSE_INTL + wxString + fmtDate = wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT), + fmtDateAlt = wxLocale::GetInfo(wxLOCALE_LONG_DATE_FMT); +#else // !wxUSE_INTL + wxString fmtDate, fmtDateAlt; +#endif // wxUSE_INTL/!wxUSE_INTL if ( fmtDate.empty() ) -#endif // __WINDOWS__ { if ( IsWestEuropeanCountry(GetCountry()) || GetCountry() == Russia ) { - fmtDate = _T("%d/%m/%y"); - fmtDateAlt = _T("%m/%d/%y"); - } + fmtDate = wxS("%d/%m/%Y"); + fmtDateAlt = wxS("%m/%d/%Y"); + } else // assume USA { - fmtDate = _T("%m/%d/%y"); - fmtDateAlt = _T("%d/%m/%y"); + fmtDate = wxS("%m/%d/%Y"); + fmtDateAlt = wxS("%d/%m/%Y"); } } - const wxString indate(input); - wxString::const_iterator endDate; - if ( !dt.ParseFormat(indate, fmtDate, &endDate) ) + wxDateTime + dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt); + + if ( !dt.IsValid() ) { - // try another one if we have it - if ( fmtDateAlt.empty() || - !dt.ParseFormat(indate, fmtDateAlt, &endDate) ) - { - return NULL; - } + // try with short years too + fmtDate.Replace("%Y","%y"); + fmtDateAlt.Replace("%Y","%y"); + dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt); + + if ( !dt.IsValid() ) + return false; } - Tm tm = dt.GetTm(); + const Tm tm = dt.GetTm(); haveDay = haveMon = @@ -1501,71 +1381,49 @@ wxDateTime::ParseFormat(const wxString& date, year = tm.year; mon = tm.mon; mday = tm.mday; - - input += endDate - indate.begin(); } break; - case _T('X'): // locale default time representation -#ifdef HAVE_STRPTIME - { - // use strptime() to do it for us (FIXME !Unicode friendly) - struct tm tm; - input = CallStrptime(input, "%X", &tm); - if ( !input ) - { - return NULL; - } - - haveHour = haveMin = haveSec = true; - - hour = tm.tm_hour; - min = tm.tm_min; - sec = tm.tm_sec; - } -#else // !HAVE_STRPTIME - // TODO under Win32 we can query the LOCALE_ITIME system - // setting which says whether the default time format is - // 24 or 12 hour + case wxT('X'): // locale default time representation { - // try to parse what follows as "%H:%M:%S" and, if this - // fails, as "%I:%M:%S %p" - this should catch the most - // common cases - wxDateTime dt; - - const wxStringCharType * - result = dt.ParseFormat(input, wxS("%T")); - if ( !result ) +#if wxUSE_INTL + wxString fmtTime = wxLocale::GetInfo(wxLOCALE_TIME_FMT), + fmtTimeAlt; +#else // !wxUSE_INTL + wxString fmtTime, fmtTimeAlt; +#endif // wxUSE_INTL/!wxUSE_INTL + if ( fmtTime.empty() ) { - result = dt.ParseFormat(input, wxS("%r")); + // try to parse what follows as "%H:%M:%S" and, if this + // fails, as "%I:%M:%S %p" - this should catch the most + // common cases + fmtTime = "%T"; + fmtTimeAlt = "%r"; } - if ( !result ) - { - // no match - return NULL; - } + const wxDateTime + dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt); + if ( !dt.IsValid() ) + return false; haveHour = haveMin = haveSec = true; - Tm tm = dt.GetTm(); + const Tm tm = dt.GetTm(); hour = tm.hour; min = tm.min; sec = tm.sec; - - input = result; } -#endif // HAVE_STRPTIME/!HAVE_STRPTIME break; - case _T('y'): // year without century (00-99) - if ( !GetNumericToken(width, input, &num) || (num > 99) ) + case wxT('y'): // year without century (00-99) + if ( !GetNumericToken(width, input, end, &num) || + (num > 99) ) { // no match - return NULL; + return false; } haveYear = true; @@ -1575,36 +1433,73 @@ wxDateTime::ParseFormat(const wxString& date, year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num; break; - case _T('Y'): // year with century - if ( !GetNumericToken(width, input, &num) ) + case wxT('Y'): // year with century + if ( !GetNumericToken(width, input, end, &num) ) { // no match - return NULL; + return false; } haveYear = true; year = (wxDateTime_t)num; break; - case _T('Z'): // timezone name - wxFAIL_MSG(_T("TODO")); + case wxT('z'): + { + // check that we have something here at all + if ( input == end ) + return false; + + // and then check that it's either plus or minus sign + bool minusFound; + if ( *input == wxT('-') ) + minusFound = true; + else if ( *input == wxT('+') ) + minusFound = false; + else + return false; // no match + + // here should follow 4 digits HHMM + ++input; + unsigned long tzHourMin; + if ( !GetNumericToken(4, input, end, &tzHourMin) ) + return false; // no match + + const unsigned hours = tzHourMin / 100; + const unsigned minutes = tzHourMin % 100; + + if ( hours > 12 || minutes > 59 ) + return false; // bad format + + timeZone = 3600*hours + 60*minutes; + if ( minusFound ) + timeZone = -timeZone; + + haveTimeZone = true; + } + break; + + case wxT('Z'): // timezone name + // FIXME: currently we just ignore everything that looks like a + // time zone here + GetAlphaToken(input, end); break; - case _T('%'): // a percent sign - if ( *input++ != _T('%') ) + case wxT('%'): // a percent sign + if ( input == end || *input++ != wxT('%') ) { // no match - return NULL; + return false; } break; case 0: // the end of string - wxFAIL_MSG(_T("unexpected format end")); + wxFAIL_MSG(wxT("unexpected format end")); // fall through default: // not a known format spec - return NULL; + return false; } } @@ -1645,22 +1540,14 @@ wxDateTime::ParseFormat(const wxString& date, if ( haveDay ) { if ( mday > GetNumberOfDays(tm.mon, tm.year) ) - { - wxLogDebug(_T("bad month day in wxDateTime::ParseFormat")); - - return NULL; - } + return false; tm.mday = mday; } else if ( haveYDay ) { if ( yday > GetNumberOfDays(tm.year) ) - { - wxLogDebug(_T("bad year day in wxDateTime::ParseFormat")); - - return NULL; - } + return false; Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm(); @@ -1697,28 +1584,57 @@ wxDateTime::ParseFormat(const wxString& date, Set(tm); + // If a time zone was specified and it is not the local time zone, we need + // to shift the time accordingly. + // + // Note that avoiding the call to MakeFromTimeZone is necessary to avoid + // DST problems. + if ( haveTimeZone && timeZone != -wxGetTimeZone() ) + MakeFromTimezone(timeZone); + // finally check that the week day is consistent -- if we had it if ( haveWDay && GetWeekDay() != wday ) - { - wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()")); + return false; + *endParse = input; + + return true; +} + +const char* +wxDateTime::ParseFormat(const char* date, + const wxString& format, + const wxDateTime& dateDef) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseFormat(dateStr, format, dateDef, &end) ) return NULL; - } - const size_t endpos = input - date.wx_str(); - if ( end ) - *end = date.begin() + endpos; + return date + dateStr.IterOffsetInMBStr(end); +} + +const wchar_t* +wxDateTime::ParseFormat(const wchar_t* date, + const wxString& format, + const wxDateTime& dateDef) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseFormat(dateStr, format, dateDef, &end) ) + return NULL; - return date.c_str() + endpos; + return date + (end - dateStr.begin()); } -const char * +bool wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end) { - // Set to current day and hour, so strings like '14:00' becomes today at - // 14, not some other random date - wxDateTime dtDate = wxDateTime::Today(); - wxDateTime dtTime = wxDateTime::Today(); + wxCHECK_MSG( end, false, "end iterator pointer must be specified" ); + + wxDateTime + dtDate, + dtTime; wxString::const_iterator endTime, @@ -1735,7 +1651,7 @@ wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end) const wxString timestr(endDate, date.end()); if ( !dtTime.ParseTime(timestr, &endTime) ) - return NULL; + return false; endBoth = endDate + (endTime - timestr.begin()); } @@ -1743,14 +1659,14 @@ wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end) { // check if we have a time followed by a date if ( !dtTime.ParseTime(date, &endTime) ) - return NULL; + return false; while ( endTime != date.end() && wxIsspace(*endTime) ) ++endTime; const wxString datestr(endTime, date.end()); if ( !dtDate.ParseDate(datestr, &endDate) ) - return NULL; + return false; endBoth = endTime + (endDate - datestr.begin()); } @@ -1759,22 +1675,45 @@ wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end) dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(), dtTime.GetMillisecond()); - // Return endpoint of scan - if ( end ) - *end = endBoth; + *end = endBoth; + + return true; +} + +const char* wxDateTime::ParseDateTime(const char* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseDateTime(dateStr, &end) ) + return NULL; + + return date + dateStr.IterOffsetInMBStr(end); +} + +const wchar_t* wxDateTime::ParseDateTime(const wchar_t* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseDateTime(dateStr, &end) ) + return NULL; - return date.c_str() + (endBoth - date.begin()); + return date + (end - dateStr.begin()); } -const char * +bool wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) { + wxCHECK_MSG( end, false, "end iterator pointer must be specified" ); + // this is a simplified version of ParseDateTime() which understands only // "today" (for wxDate compatibility) and digits only otherwise (and not // all esoteric constructions ParseDateTime() knows about) - const wxStringCharType *p = date.wx_str(); - while ( wxIsspace(*p) ) + const wxString::const_iterator pBegin = date.begin(); + const wxString::const_iterator pEnd = date.end(); + + wxString::const_iterator p = pBegin; + while ( p != pEnd && wxIsspace(*p) ) p++; // some special cases @@ -1789,31 +1728,32 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) { wxTRANSLATE("tomorrow"), 1 }, }; + const size_t lenRest = pEnd - p; for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ ) { const wxString dateStr = wxGetTranslation(literalDates[n].str); size_t len = dateStr.length(); - if ( wxStrlen(p) >= len ) - { - wxString str(p, len); - if ( str.CmpNoCase(dateStr) == 0 ) - { - // nothing can follow this, so stop here - p += len; - int dayDiffFromToday = literalDates[n].dayDiffFromToday; - *this = Today(); - if ( dayDiffFromToday ) - { - *this += wxDateSpan::Days(dayDiffFromToday); - } + if ( len > lenRest ) + continue; + + const wxString::const_iterator pEndStr = p + len; + if ( wxString(p, pEndStr).CmpNoCase(dateStr) == 0 ) + { + // nothing can follow this, so stop here - const size_t endpos = p - date.wx_str(); + p = pEndStr; - if ( end ) - *end = date.begin() + endpos; - return date.c_str() + endpos; + int dayDiffFromToday = literalDates[n].dayDiffFromToday; + *this = Today(); + if ( dayDiffFromToday ) + { + *this += wxDateSpan::Days(dayDiffFromToday); } + + *end = pEndStr; + + return true; } } @@ -1828,6 +1768,8 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) haveMon = false, // the month? haveYear = false; // the year? + bool monWasNumeric = false; // was month specified as a number? + // and the value of the items we have (init them to get rid of warnings) WeekDay wday = Inv_WeekDay; wxDateTime_t day = 0; @@ -1835,18 +1777,23 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) int year = 0; // tokenize the string - size_t nPosCur = 0; - static const wxStringCharType *dateDelimiters = wxS(".,/-\t\r\n "); - wxStringTokenizer tok(p, dateDelimiters); - while ( tok.HasMoreTokens() ) + while ( p != pEnd ) { - wxString token = tok.GetNextToken(); - if ( !token ) + // skip white space and date delimiters + if ( wxStrchr(".,/-\t\r\n ", *p) ) + { + ++p; continue; + } - // is it a number? + // modify copy of the iterator as we're not sure if the next token is + // still part of the date at all + wxString::const_iterator pCopy = p; + + // we can have either alphabetic or numeric token, start by testing if + // it's the latter unsigned long val; - if ( token.ToULong(&val) ) + if ( GetNumericToken(10 /* max length */, pCopy, pEnd, &val) ) { // guess what this number is @@ -1910,6 +1857,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) else if ( isMonth ) { haveMon = true; + monWasNumeric = true; mon = (Month)(val - 1); } @@ -1917,26 +1865,29 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) else // not a number { // be careful not to overwrite the current mon value - Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr); + Month mon2 = GetMonthFromName + ( + pCopy, pEnd, + Name_Full | Name_Abbr, + DateLang_Local | DateLang_English + ); if ( mon2 != Inv_Month ) { // it's a month if ( haveMon ) { - // but we already have a month - maybe we guessed wrong? - if ( !haveDay ) - { - // no need to check in month range as always < 12, but - // the days are counted from 1 unlike the months - day = (wxDateTime_t)(mon + 1); - haveDay = true; - } - else - { - // could possible be the year (doesn't the year come - // before the month in the japanese format?) (FIXME) + // but we already have a month - maybe we guessed wrong + // when we had interpreted that numeric value as a month + // and it was the day number instead? + if ( haveDay || !monWasNumeric ) break; - } + + // assume we did and change our mind: reinterpret the month + // value as a day (notice that there is no need to check + // that it is valid as month values are always < 12, but + // the days are counted from 1 unlike the months) + day = (wxDateTime_t)(mon + 1); + haveDay = true; } mon = mon2; @@ -1945,14 +1896,17 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) } else // not a valid month name { - WeekDay wday2 = GetWeekDayFromName(token, Name_Full | Name_Abbr); + WeekDay wday2 = GetWeekDayFromName + ( + pCopy, pEnd, + Name_Full | Name_Abbr, + DateLang_Local | DateLang_English + ); if ( wday2 != Inv_WeekDay ) { // a week day if ( haveWDay ) - { break; - } wday = wday2; @@ -1961,7 +1915,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) else // not a valid weekday name { // try the ordinals - static const char *ordinals[] = + static const char *const ordinals[] = { wxTRANSLATE("first"), wxTRANSLATE("second"), @@ -1990,8 +1944,11 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) size_t n; for ( n = 0; n < WXSIZEOF(ordinals); n++ ) { - if ( token.CmpNoCase(ordinals[n]) == 0 ) + const wxString ord = wxGetTranslation(ordinals[n]); + const size_t len = ord.length(); + if ( date.compare(p - pBegin, len, ord) == 0 ) { + p += len; break; } } @@ -2018,17 +1975,14 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) } } - nPosCur = tok.GetPosition(); + // advance iterator past a successfully parsed token + p = pCopy; } // either no more tokens or the scan was stopped by something we couldn't // parse - in any case, see if we can construct a date from what we have if ( !haveDay && !haveWDay ) - { - wxLogDebug(_T("ParseDate: no day, no weekday hence no date.")); - - return NULL; - } + return false; if ( haveWDay && (haveMon || haveYear || haveDay) && !(haveDay && haveMon && haveYear) ) @@ -2036,7 +1990,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) // without adjectives (which we don't support here) the week day only // makes sense completely separately or with the full date // specification (what would "Wed 1999" mean?) - return NULL; + return false; } if ( !haveWDay && haveYear && !(haveDay && haveMon) ) @@ -2062,12 +2016,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) } if ( !haveMon ) - { - // if we give the year, month and day must be given too - wxLogDebug(_T("ParseDate: day and month should be specified if year is.")); - - return NULL; - } + return false; } if ( !haveMon ) @@ -2085,7 +2034,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) // normally we check the day above but the check is optimistic in case // we find the day before its month/year so we have to redo it now if ( day > GetNumberOfDays(mon, year) ) - return NULL; + return false; Set(day, mon, year); @@ -2093,12 +2042,7 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) { // check that it is really the same if ( GetWeekDay() != wday ) - { - // inconsistency detected - wxLogDebug(_T("ParseDate: inconsistent day/weekday.")); - - return NULL; - } + return false; } } else // haveWDay @@ -2108,30 +2052,41 @@ wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end) SetToWeekDayInSameWeek(wday); } - // return the pointer to the first unparsed char - p += nPosCur; - if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) ) - { - // if we couldn't parse the token after the delimiter, put back the - // delimiter as well - p--; - } + *end = p; - const size_t endpos = p - date.wx_str(); - if ( end ) - *end = date.begin() + endpos; + return true; +} + +const char* wxDateTime::ParseDate(const char* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseDate(dateStr, &end) ) + return NULL; + + return date + dateStr.IterOffsetInMBStr(end); +} + +const wchar_t* wxDateTime::ParseDate(const wchar_t* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseDate(dateStr, &end) ) + return NULL; - return date.c_str() + endpos; + return date + (end - dateStr.begin()); } -const char * +bool wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end) { + wxCHECK_MSG( end, false, "end iterator pointer must be specified" ); + // first try some extra things static const struct { const char *name; - wxDateTime_t hour; + wxDateTime_t hour; } stdTimes[] = { { wxTRANSLATE("noon"), 12 }, @@ -2141,28 +2096,29 @@ wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end) for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ ) { - wxString timeString = wxGetTranslation(stdTimes[n].name); - size_t len = timeString.length(); - if ( timeString.CmpNoCase(wxString(time, len)) == 0 ) + const wxString timeString = wxGetTranslation(stdTimes[n].name); + if ( timeString.CmpNoCase(wxString(time, timeString.length())) == 0 ) { // casts required by DigitalMars Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0)); if ( end ) - *end = time.begin() + len; + *end = time.begin() + timeString.length(); - return time.c_str() + len; + return true; } } // try all time formats we may think about in the order from longest to // shortest - static const char *timeFormats[] = + static const char *const timeFormats[] = { "%I:%M:%S %p", // 12hour with AM/PM "%H:%M:%S", // could be the same or 24 hour one so try it too "%I:%M %p", // 12hour with AM/PM but without seconds - "%H:%M:%S", // and a possibly 24 hour version without seconds + "%H:%M", // and a possibly 24 hour version without seconds + "%I %p", // just hour with AM/AM + "%H", // just hour in 24 hour version "%X", // possibly something from above or maybe something // completely different -- try it last @@ -2171,12 +2127,31 @@ wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end) for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ ) { - const char *result = ParseFormat(time, timeFormats[nFmt], end); - if ( result ) - return result; + if ( ParseFormat(time, timeFormats[nFmt], end) ) + return true; } - return NULL; + return false; +} + +const char* wxDateTime::ParseTime(const char* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseTime(dateStr, &end) ) + return NULL; + + return date + dateStr.IterOffsetInMBStr(end); +} + +const wchar_t* wxDateTime::ParseTime(const wchar_t* date) +{ + wxString::const_iterator end; + wxString dateStr(date); + if ( !ParseTime(dateStr, &end) ) + return NULL; + + return date + (end - dateStr.begin()); } // ---------------------------------------------------------------------------- @@ -2247,7 +2222,7 @@ wxString wxTimeSpan::Format(const wxString& format) const } wxCHECK_MSG( !format.empty(), wxEmptyString, - _T("NULL format in wxTimeSpan::Format") ); + wxT("NULL format in wxTimeSpan::Format") ); wxString str; str.Alloc(format.length()); @@ -2272,10 +2247,10 @@ wxString wxTimeSpan::Format(const wxString& format) const { wxChar ch = *pch; - if ( ch == _T('%') ) + if ( ch == wxT('%') ) { // the start of the format specification of the printf() below - wxString fmtPrefix(_T('%')); + wxString fmtPrefix(wxT('%')); // the number long n; @@ -2287,16 +2262,16 @@ wxString wxTimeSpan::Format(const wxString& format) const switch ( ch ) { default: - wxFAIL_MSG( _T("invalid format character") ); + wxFAIL_MSG( wxT("invalid format character") ); // fall through - case _T('%'): + case wxT('%'): str += ch; // skip the part below switch continue; - case _T('D'): + case wxT('D'): n = GetDays(); if ( partBiggest < Part_Day ) { @@ -2308,12 +2283,12 @@ wxString wxTimeSpan::Format(const wxString& format) const } break; - case _T('E'): + case wxT('E'): partBiggest = Part_Week; n = GetWeeks(); break; - case _T('H'): + case wxT('H'): n = GetHours(); if ( partBiggest < Part_Hour ) { @@ -2327,7 +2302,7 @@ wxString wxTimeSpan::Format(const wxString& format) const digits = 2; break; - case _T('l'): + case wxT('l'): n = GetMilliseconds().ToLong(); if ( partBiggest < Part_MSec ) { @@ -2339,7 +2314,7 @@ wxString wxTimeSpan::Format(const wxString& format) const digits = 3; break; - case _T('M'): + case wxT('M'): n = GetMinutes(); if ( partBiggest < Part_Min ) { @@ -2353,7 +2328,7 @@ wxString wxTimeSpan::Format(const wxString& format) const digits = 2; break; - case _T('S'): + case wxT('S'): n = GetSeconds().ToLong(); if ( partBiggest < Part_Sec ) { @@ -2370,10 +2345,10 @@ wxString wxTimeSpan::Format(const wxString& format) const if ( digits ) { - fmtPrefix << _T("0") << digits; + fmtPrefix << wxT("0") << digits; } - str += wxString::Format(fmtPrefix + _T("ld"), n); + str += wxString::Format(fmtPrefix + wxT("ld"), n); } else {