Implement support for "%z" in wxDateTime::Format() and Parse().

"%z" specifier can now be used when printing the dates out to specify the time
zone and is also recognized when parsing dates.

Closes #1215.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@70268 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2012-01-05 00:47:10 +00:00
parent cd72035387
commit 444bc2b24d
3 changed files with 181 additions and 63 deletions

View File

@@ -451,6 +451,7 @@ Major new features in this release
All: All:
- Added wxLogFormatter to allow customizing wxLog output (Sébastien Gallou). - Added wxLogFormatter to allow customizing wxLog output (Sébastien Gallou).
- Added "%z" support to wxDateTime::Format() and Parse() (Armel Asselin).
All (GUI): All (GUI):

View File

@@ -319,11 +319,17 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT)); format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT));
#endif #endif
// we have to use our own implementation if the date is out of range of // we have to use our own implementation if the date is out of range of
// strftime() or if we use non standard specifiers // 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 #ifdef wxHAS_STRFTIME
time_t time = GetTicks(); time_t time = GetTicks();
if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l")) ) if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l"))
#ifdef __WXMSW__
&& !wxStrstr(format, wxT("%z"))
#endif
)
{ {
// use strftime() // use strftime()
struct tm tmstruct; struct tm tmstruct;
@@ -397,6 +403,7 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
switch ( (*++p).GetValue() ) switch ( (*++p).GetValue() )
{ {
case wxT('Y'): // year has 4 digits case wxT('Y'): // year has 4 digits
case wxT('z'): // time zone as well
fmt = wxT("%04d"); fmt = wxT("%04d");
break; break;
@@ -639,6 +646,25 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
res += wxString::Format(fmt, tm.year); res += wxString::Format(fmt, tm.year);
break; break;
case wxT('z'): // time zone as [-+]HHMM
{
int ofs = tz.GetOffset();
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 case wxT('Z'): // timezone name
#ifdef wxHAS_STRFTIME #ifdef wxHAS_STRFTIME
res += CallStrftime(wxT("%Z"), &tmTimeOnly); res += CallStrftime(wxT("%Z"), &tmTimeOnly);
@@ -929,6 +955,8 @@ wxDateTime::ParseFormat(const wxString& date,
bool hourIsIn12hFormat = false, // or in 24h one? bool hourIsIn12hFormat = false, // or in 24h one?
isPM = false; // AM by default isPM = false; // AM by default
bool haveTimeZone = false;
// and the value of the items we have (init them to get rid of warnings) // and the value of the items we have (init them to get rid of warnings)
wxDateTime_t msec = 0, wxDateTime_t msec = 0,
sec = 0, sec = 0,
@@ -939,6 +967,7 @@ wxDateTime::ParseFormat(const wxString& date,
mday = 0; mday = 0;
wxDateTime::Month mon = Inv_Month; wxDateTime::Month mon = Inv_Month;
int year = 0; int year = 0;
long timeZone = 0; // time zone in seconds as expected in Tm structure
wxString::const_iterator input = date.begin(); wxString::const_iterator input = date.begin();
const wxString::const_iterator end = date.end(); const wxString::const_iterator end = date.end();
@@ -1377,6 +1406,36 @@ wxDateTime::ParseFormat(const wxString& date,
year = (wxDateTime_t)num; year = (wxDateTime_t)num;
break; break;
case wxT('z'):
{
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 case wxT('Z'): // timezone name
// FIXME: currently we just ignore everything that looks like a // FIXME: currently we just ignore everything that looks like a
// time zone here // time zone here
@@ -1482,6 +1541,14 @@ wxDateTime::ParseFormat(const wxString& date,
Set(tm); 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 // finally check that the week day is consistent -- if we had it
if ( haveWDay && GetWeekDay() != wday ) if ( haveWDay && GetWeekDay() != wday )
return false; return false;

View File

@@ -666,6 +666,18 @@ void DateTimeTestCase::TestTimeFormat()
{ CompareTime, "Time is %H:%M:%S or %I:%M:%S %p" }, { CompareTime, "Time is %H:%M:%S or %I:%M:%S %p" },
{ CompareNone, "The day of year: %j, the week of year: %W" }, { CompareNone, "The day of year: %j, the week of year: %W" },
{ CompareDate, "ISO date without separators: %Y%m%d" }, { CompareDate, "ISO date without separators: %Y%m%d" },
{ CompareBoth, "RFC 2822 string: %Y-%m-%d %H:%M:%S.%l %z" },
};
const long timeZonesOffsets[] =
{
wxDateTime::TimeZone(wxDateTime::Local).GetOffset(),
// Fictitious TimeZone offsets to ensure time zone formating and
// interpretation works
-(3600 + 2*60),
3*3600 + 30*60
}; };
static const Date formatTestDates[] = static const Date formatTestDates[] =
@@ -686,78 +698,110 @@ void DateTimeTestCase::TestTimeFormat()
#endif #endif
}; };
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ ) for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz )
{ {
wxDateTime dt = formatTestDates[d].DT(); wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ ) const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone();
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
{ {
const char *fmt = formatTestFormats[n].format; wxDateTime dt = formatTestDates[d].DT();
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
// skip the check with %p for those locales which have empty AM/PM strings:
// for those locales it's impossible to pass the test with %p...
wxString am, pm;
wxDateTime::GetAmPmStrings(&am, &pm);
if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL)
continue;
wxString s = dt.Format(fmt);
// what can we recover?
CompareKind kind = formatTestFormats[n].compareKind;
// convert back
wxDateTime dt2;
const char *result = dt2.ParseFormat(s, fmt);
if ( !result )
{ {
// conversion failed - should it have? const char *fmt = formatTestFormats[n].format;
WX_ASSERT_MESSAGE(
("Test #%u failed: failed to parse \"%s\"", n, s),
kind == CompareNone
);
}
else // conversion succeeded
{
// currently ParseFormat() doesn't support "%Z" and so is
// incapable of parsing time zone part used at the end of date
// representations in many (but not "C") locales, compensate
// for it ourselves by simply consuming and ignoring it
while ( *result && (*result >= 'A' && *result <= 'Z') )
result++;
WX_ASSERT_MESSAGE( // skip the check with %p for those locales which have empty AM/PM strings:
("Test #%u failed: \"%s\" was left unparsed in \"%s\"", // for those locales it's impossible to pass the test with %p...
n, result, s), wxString am, pm;
!*result wxDateTime::GetAmPmStrings(&am, &pm);
); if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL)
continue;
switch ( kind ) // what can we recover?
CompareKind kind = formatTestFormats[n].compareKind;
// When using a different time zone we must perform a time zone
// conversion below which doesn't always work correctly, check
// for the cases when it doesn't.
if ( !isLocalTz )
{ {
case CompareYear: // DST computation doesn't work correctly for dates above
if ( dt2.GetCentury() != dt.GetCentury() ) // 2038 currently on the systems with 32 bit time_t.
{ if ( dt.GetYear() >= 2038 )
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100, continue;
dt2.GetYear() % 100);
dt2.SetYear(dt.GetYear()); // We can't compare just dates nor just times when doing TZ
} // conversion as both are affected by the DST: for the
// fall through and compare everything // dates, the DST can switch midnight to 23:00 of the
// previous day while for the times DST can be different
// for the original date and today.
if ( kind == CompareDate || kind == CompareTime )
continue;
}
case CompareBoth: // do convert date to string
CPPUNIT_ASSERT_EQUAL( dt, dt2 ); wxString s = dt.Format(fmt, tz);
break;
case CompareDate: // convert back
CPPUNIT_ASSERT( dt.IsSameDate(dt2) ); wxDateTime dt2;
break; const char *result = dt2.ParseFormat(s, fmt);
if ( !result )
{
// conversion failed - should it have?
WX_ASSERT_MESSAGE(
("Test #%u failed: failed to parse \"%s\"", n, s),
kind == CompareNone
);
}
else // conversion succeeded
{
// currently ParseFormat() doesn't support "%Z" and so is
// incapable of parsing time zone part used at the end of date
// representations in many (but not "C") locales, compensate
// for it ourselves by simply consuming and ignoring it
while ( *result && (*result >= 'A' && *result <= 'Z') )
result++;
case CompareTime: WX_ASSERT_MESSAGE(
CPPUNIT_ASSERT( dt.IsSameTime(dt2) ); ("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
break; n, result, s),
!*result
);
case CompareNone: // Without "%z" we can't recover the time zone used in the
wxFAIL_MSG( wxT("unexpected") ); // call to Format() so we need to call MakeFromTimezone()
break; // explicitly.
if ( !strstr(fmt, "%z") && !isLocalTz )
dt2.MakeFromTimezone(tz);
switch ( kind )
{
case CompareYear:
if ( dt2.GetCentury() != dt.GetCentury() )
{
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100,
dt2.GetYear() % 100);
dt2.SetYear(dt.GetYear());
}
// fall through and compare everything
case CompareBoth:
CPPUNIT_ASSERT_EQUAL( dt, dt2 );
break;
case CompareDate:
CPPUNIT_ASSERT( dt.IsSameDate(dt2) );
break;
case CompareTime:
CPPUNIT_ASSERT( dt.IsSameTime(dt2) );
break;
case CompareNone:
wxFAIL_MSG( wxT("unexpected") );
break;
}
} }
} }
} }
@@ -1096,6 +1140,12 @@ void DateTimeTestCase::TestDateTimeParse()
{ 1, wxDateTime::Jan, 9999, 0, 0, 0}, { 1, wxDateTime::Jan, 9999, 0, 0, 0},
false false
}, },
{
"2012-01-01 10:12:05 +0100",
{ 1, wxDateTime::Jan, 2012, 10, 12, 5, -1 },
false // ParseDateTime does know yet +0100
},
}; };
// the test strings here use "PM" which is not available in all locales so // the test strings here use "PM" which is not available in all locales so