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;
switch ( (*++p).GetValue() )
{
case wxT('Y'): // year has 4 digits
+ case wxT('z'): // time zone as well
fmt = wxT("%04d");
break;
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);
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,
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();
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
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;
{ 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[] =
#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++ )
- {
- 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;
+ wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
+ const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone();
- wxString s = dt.Format(fmt);
+ for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
+ {
+ wxDateTime dt = formatTestDates[d].DT();
+ for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
+ {
+ 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;
+
+ // 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 )
+ {
+ // DST computation doesn't work correctly for dates above
+ // 2038 currently on the systems with 32 bit time_t.
+ if ( dt.GetYear() >= 2038 )
+ continue;
+
+ // 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;
+ }
- // what can we recover?
- CompareKind kind = formatTestFormats[n].compareKind;
+ // do convert date to string
+ wxString s = dt.Format(fmt, tz);
- // 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++;
-
- WX_ASSERT_MESSAGE(
- ("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
- n, result, s),
- !*result
- );
-
- switch ( kind )
+ // convert back
+ wxDateTime dt2;
+ const char *result = dt2.ParseFormat(s, fmt);
+ if ( !result )
{
- 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;
+ // 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++;
+
+ WX_ASSERT_MESSAGE(
+ ("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
+ n, result, s),
+ !*result
+ );
+
+ // 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;
+ }
}
}
}
{ 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