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:
@@ -451,6 +451,7 @@ Major new features in this release
|
||||
All:
|
||||
|
||||
- Added wxLogFormatter to allow customizing wxLog output (Sébastien Gallou).
|
||||
- Added "%z" support to wxDateTime::Format() and Parse() (Armel Asselin).
|
||||
|
||||
All (GUI):
|
||||
|
||||
|
@@ -319,11 +319,17 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
|
||||
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 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
|
||||
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()
|
||||
struct tm tmstruct;
|
||||
@@ -397,6 +403,7 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
|
||||
switch ( (*++p).GetValue() )
|
||||
{
|
||||
case wxT('Y'): // year has 4 digits
|
||||
case wxT('z'): // time zone as well
|
||||
fmt = wxT("%04d");
|
||||
break;
|
||||
|
||||
@@ -639,6 +646,25 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
|
||||
res += wxString::Format(fmt, tm.year);
|
||||
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
|
||||
#ifdef wxHAS_STRFTIME
|
||||
res += CallStrftime(wxT("%Z"), &tmTimeOnly);
|
||||
@@ -929,6 +955,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,
|
||||
@@ -939,6 +967,7 @@ 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
|
||||
|
||||
wxString::const_iterator input = date.begin();
|
||||
const wxString::const_iterator end = date.end();
|
||||
@@ -1377,6 +1406,36 @@ wxDateTime::ParseFormat(const wxString& date,
|
||||
year = (wxDateTime_t)num;
|
||||
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
|
||||
// FIXME: currently we just ignore everything that looks like a
|
||||
// time zone here
|
||||
@@ -1482,6 +1541,14 @@ 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 )
|
||||
return false;
|
||||
|
@@ -666,6 +666,18 @@ void DateTimeTestCase::TestTimeFormat()
|
||||
{ CompareTime, "Time is %H:%M:%S or %I:%M:%S %p" },
|
||||
{ CompareNone, "The day of year: %j, the week of year: %W" },
|
||||
{ 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[] =
|
||||
@@ -686,78 +698,110 @@ void DateTimeTestCase::TestTimeFormat()
|
||||
#endif
|
||||
};
|
||||
|
||||
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
|
||||
for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz )
|
||||
{
|
||||
wxDateTime dt = formatTestDates[d].DT();
|
||||
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
|
||||
wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
|
||||
const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone();
|
||||
|
||||
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
|
||||
{
|
||||
const char *fmt = formatTestFormats[n].format;
|
||||
|
||||
// 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 )
|
||||
wxDateTime dt = formatTestDates[d].DT();
|
||||
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
|
||||
{
|
||||
// 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++;
|
||||
const char *fmt = formatTestFormats[n].format;
|
||||
|
||||
WX_ASSERT_MESSAGE(
|
||||
("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
|
||||
n, result, s),
|
||||
!*result
|
||||
);
|
||||
// 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;
|
||||
|
||||
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:
|
||||
if ( dt2.GetCentury() != dt.GetCentury() )
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100,
|
||||
dt2.GetYear() % 100);
|
||||
// DST computation doesn't work correctly for dates above
|
||||
// 2038 currently on the systems with 32 bit time_t.
|
||||
if ( dt.GetYear() >= 2038 )
|
||||
continue;
|
||||
|
||||
dt2.SetYear(dt.GetYear());
|
||||
}
|
||||
// fall through and compare everything
|
||||
// We can't compare just dates nor just times when doing TZ
|
||||
// conversion as both are affected by the DST: for the
|
||||
// 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:
|
||||
CPPUNIT_ASSERT_EQUAL( dt, dt2 );
|
||||
break;
|
||||
// do convert date to string
|
||||
wxString s = dt.Format(fmt, tz);
|
||||
|
||||
case CompareDate:
|
||||
CPPUNIT_ASSERT( dt.IsSameDate(dt2) );
|
||||
break;
|
||||
// convert back
|
||||
wxDateTime dt2;
|
||||
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:
|
||||
CPPUNIT_ASSERT( dt.IsSameTime(dt2) );
|
||||
break;
|
||||
WX_ASSERT_MESSAGE(
|
||||
("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
|
||||
n, result, s),
|
||||
!*result
|
||||
);
|
||||
|
||||
case CompareNone:
|
||||
wxFAIL_MSG( wxT("unexpected") );
|
||||
break;
|
||||
// Without "%z" we can't recover the time zone used in the
|
||||
// call to Format() so we need to call MakeFromTimezone()
|
||||
// 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},
|
||||
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
|
||||
|
Reference in New Issue
Block a user