#include <ctype.h>
#include "wx/datetime.h"
+#include "wx/timer.h" // for wxGetLocalTimeMillis()
// ----------------------------------------------------------------------------
// conditional compilation
#if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
#define WX_TIMEZONE _timezone
#elif defined(__MWERKS__)
- long wxmw_timezone = 28800;
- #define WX_TIMEZONE wxmw_timezone ;
+ long wxmw_timezone = 28800;
+ #define WX_TIMEZONE wxmw_timezone;
#else // unknown platform - try timezone
#define WX_TIMEZONE timezone
#endif
#endif // !WX_TIMEZONE
+// ----------------------------------------------------------------------------
+// macros
+// ----------------------------------------------------------------------------
+
+// debugging helper: just a convenient replacement of wxCHECK()
+#define wxDATETIME_CHECK(expr, msg) \
+ if ( !(expr) ) \
+ { \
+ wxFAIL_MSG(msg); \
+ *this = wxInvalidDateTime; \
+ return *this; \
+ }
+
// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------
// some trivial ones
static const int MONTHS_IN_YEAR = 12;
-static const int SECONDS_IN_MINUTE = 60;
+static const int SEC_PER_MIN = 60;
+
+static const int MIN_PER_HOUR = 60;
+
+static const int HOURS_PER_DAY = 24;
static const long SECONDS_PER_DAY = 86400l;
+static const int DAYS_PER_WEEK = 7;
+
static const long MILLISECONDS_PER_DAY = 86400000l;
// this is the integral part of JDN of the midnight of Jan 1, 1970
// global data
// ----------------------------------------------------------------------------
-static wxDateTime gs_dtDefault;
+// in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
+// indicate an invalid wxDateTime object
+static const wxDateTime gs_dtDefault = wxLongLong((long)ULONG_MAX, ULONG_MAX);
-wxDateTime& wxDefaultDateTime = gs_dtDefault;
+const wxDateTime& wxDefaultDateTime = gs_dtDefault;
wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
{
// just call localtime() instead of figuring out whether this system
// supports tzset(), _tzset() or something else
- time_t t;
- (void)localtime(&t);
+ time_t t = 0;
+ (void)localtime(&t);
s_timezoneSet = TRUE;
}
return wd;
}
-// scans all digits and returns the resulting number
-static bool GetNumericToken(const wxChar*& p, unsigned long *number)
+// scans all digits (but no more than len) and returns the resulting number
+static bool GetNumericToken(size_t len, const wxChar*& p, unsigned long *number)
{
+ size_t n = 1;
wxString s;
while ( wxIsdigit(*p) )
{
s += *p++;
+
+ if ( len && ++n > len )
+ break;
}
return !!s && s.ToULong(number);
// constructors and assignment operators
// ----------------------------------------------------------------------------
+// return the current time with ms precision
+/* static */ wxDateTime wxDateTime::UNow()
+{
+ return wxDateTime(wxGetLocalTimeMillis());
+}
+
// the values in the tm structure contain the local time
wxDateTime& wxDateTime::Set(const struct tm& tm)
{
wxFAIL_MSG( _T("mktime() failed") );
- return wxInvalidDateTime;
+ *this = wxInvalidDateTime;
+
+ return *this;
}
else
{
// we allow seconds to be 61 to account for the leap seconds, even if we
// don't use them really
- wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
- wxInvalidDateTime,
- _T("Invalid time in wxDateTime::Set()") );
+ wxDATETIME_CHECK( hour < 24 &&
+ second < 62 &&
+ minute < 60 &&
+ millisec < 1000,
+ _T("Invalid time in wxDateTime::Set()") );
// get the current date from system
struct tm *tm = GetTmNow();
- wxCHECK_MSG( tm, wxInvalidDateTime, _T("localtime() failed") );
+ wxDATETIME_CHECK( tm, _T("localtime() failed") );
// adjust the time
tm->tm_hour = hour;
{
wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
- wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
- wxInvalidDateTime,
- _T("Invalid time in wxDateTime::Set()") );
+ wxDATETIME_CHECK( hour < 24 &&
+ second < 62 &&
+ minute < 60 &&
+ millisec < 1000,
+ _T("Invalid time in wxDateTime::Set()") );
ReplaceDefaultYearMonthWithCurrent(&year, &month);
- wxCHECK_MSG( (0 < day) && (day <= GetNumberOfDays(month, year)),
- wxInvalidDateTime,
- _T("Invalid date in wxDateTime::Set()") );
+ wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
+ _T("Invalid date in wxDateTime::Set()") );
// the range of time_t type (inclusive)
static const int yearMinInRange = 1970;
tm = localtime(&time);
// should never happen
- wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
+ wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
}
else
{
if ( tm )
{
- return Tm(*tm, tz);
+ // adjust the milliseconds
+ Tm tm2(*tm, tz);
+ long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
+ tm2.msec = (wxDateTime_t)(timeOnly % 1000);
+ return tm2;
}
//else: use generic code below
}
wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday)
{
- wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") );
+ wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
WeekDay wdayThis = GetWeekDay();
if ( weekday == wdayThis )
}
else if ( weekday < wdayThis )
{
- return Substract(wxDateSpan::Days(wdayThis - weekday));
+ return Subtract(wxDateSpan::Days(wdayThis - weekday));
}
else // weekday > wdayThis
{
wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
{
- wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") );
+ wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
int diff;
WeekDay wdayThis = GetWeekDay();
wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
{
- wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") );
+ wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
int diff;
WeekDay wdayThis = GetWeekDay();
diff = wdayThis - weekday;
}
- return Substract(wxDateSpan::Days(diff));
+ return Subtract(wxDateSpan::Days(diff));
}
bool wxDateTime::SetToWeekDay(WeekDay weekday,
wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
{
int year = GetYear();
- wxCHECK_MSG( (0 < yday) && (yday <= GetNumberOfDays(year)),
- wxInvalidDateTime, _T("invalid year day") );
+ wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
+ _T("invalid year day") );
bool isLeap = IsLeapYear(year);
for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
secDiff -= 3600;
}
- return Substract(wxTimeSpan::Seconds(secDiff));
+ return Subtract(wxTimeSpan::Seconds(secDiff));
}
// ----------------------------------------------------------------------------
{
wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
+ // we have to use our own implementation if the date is out of range of
+ // strftime() or if we use non standard specificators
time_t time = GetTicks();
- if ( time != (time_t)-1 )
+ if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
{
// use strftime()
tm *tm;
}
// we only parse ANSI C format specifications here, no POSIX 2
- // complications, no GNU extensions
+ // complications, no GNU extensions but we do add support for a "%l" format
+ // specifier allowing to get the number of milliseconds
Tm tm = GetTm(tz);
// used for calls to strftime() when we only deal with time
break;
case _T('j'): // day of year has 3 digits
+ case _T('l'): // milliseconds have 3 digits
fmt = _T("%03d");
break;
fmt = _T("%02d");
}
-restart:
- // start of the format specification
- switch ( *p )
+ bool restart = TRUE;
+ while ( restart )
{
- case _T('a'): // a weekday name
- case _T('A'):
- // second parameter should be TRUE for abbreviated names
- res += GetWeekDayName(tm.GetWeekDay(),
- *p == _T('a') ? Name_Abbr : Name_Full);
- break;
+ restart = FALSE;
- case _T('b'): // a month name
- case _T('B'):
- res += GetMonthName(tm.mon,
- *p == _T('b') ? Name_Abbr : Name_Full);
- break;
+ // start of the format specification
+ switch ( *p )
+ {
+ case _T('a'): // a weekday name
+ case _T('A'):
+ // second parameter should be TRUE for abbreviated names
+ res += GetWeekDayName(tm.GetWeekDay(),
+ *p == _T('a') ? Name_Abbr : Name_Full);
+ break;
- case _T('c'): // locale default date and time representation
- case _T('x'): // locale default date representation
- //
- // the problem: there is no way to know what do these format
- // specifications correspond to for the current locale.
- //
- // the solution: use a hack and still use strftime(): first
- // find the YEAR which is a year in the strftime() range (1970
- // - 2038) whose Jan 1 falls on the same week day as the Jan 1
- // of the real year. Then make a copy of the format and
- // replace all occurences of YEAR in it with some unique
- // string not appearing anywhere else in it, then use
- // strftime() to format the date in year YEAR and then replace
- // YEAR back by the real year and the unique replacement
- // string back with YEAR. Notice that "all occurences of YEAR"
- // means all occurences of 4 digit as well as 2 digit form!
- //
- // the bugs: we assume that neither of %c nor %x contains any
- // fields which may change between the YEAR and real year. For
- // example, the week number (%U, %W) and the day number (%j)
- // will change if one of these years is leap and the other one
- // is not!
- {
- // find the YEAR: normally, for any year X, Jan 1 or the
- // year X + 28 is the same weekday as Jan 1 of X (because
- // the weekday advances by 1 for each normal X and by 2
- // for each leap X, hence by 5 every 4 years or by 35
- // which is 0 mod 7 every 28 years) but this rule breaks
- // down if there are years between X and Y which are
- // divisible by 4 but not leap (i.e. divisible by 100 but
- // not 400), hence the correction.
-
- int yearReal = GetYear(tz);
- int mod28 = yearReal % 28;
-
- // be careful to not go too far - we risk to leave the
- // supported range
- int year;
- if ( mod28 < 10 )
- {
- year = 1988 + mod28; // 1988 == 0 (mod 28)
- }
- else
+ case _T('b'): // a month name
+ case _T('B'):
+ res += GetMonthName(tm.mon,
+ *p == _T('b') ? Name_Abbr : Name_Full);
+ break;
+
+ case _T('c'): // locale default date and time representation
+ case _T('x'): // locale default date representation
+ //
+ // the problem: there is no way to know what do these format
+ // specifications correspond to for the current locale.
+ //
+ // the solution: use a hack and still use strftime(): first
+ // find the YEAR which is a year in the strftime() range (1970
+ // - 2038) whose Jan 1 falls on the same week day as the Jan 1
+ // of the real year. Then make a copy of the format and
+ // replace all occurences of YEAR in it with some unique
+ // string not appearing anywhere else in it, then use
+ // strftime() to format the date in year YEAR and then replace
+ // YEAR back by the real year and the unique replacement
+ // string back with YEAR. Notice that "all occurences of YEAR"
+ // means all occurences of 4 digit as well as 2 digit form!
+ //
+ // the bugs: we assume that neither of %c nor %x contains any
+ // fields which may change between the YEAR and real year. For
+ // example, the week number (%U, %W) and the day number (%j)
+ // will change if one of these years is leap and the other one
+ // is not!
{
- year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
- }
+ // find the YEAR: normally, for any year X, Jan 1 or the
+ // year X + 28 is the same weekday as Jan 1 of X (because
+ // the weekday advances by 1 for each normal X and by 2
+ // for each leap X, hence by 5 every 4 years or by 35
+ // which is 0 mod 7 every 28 years) but this rule breaks
+ // down if there are years between X and Y which are
+ // divisible by 4 but not leap (i.e. divisible by 100 but
+ // not 400), hence the correction.
+
+ int yearReal = GetYear(tz);
+ int mod28 = yearReal % 28;
+
+ // be careful to not go too far - we risk to leave the
+ // supported range
+ int year;
+ if ( mod28 < 10 )
+ {
+ year = 1988 + mod28; // 1988 == 0 (mod 28)
+ }
+ else
+ {
+ year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
+ }
- int nCentury = year / 100,
- nCenturyReal = yearReal / 100;
+ int nCentury = year / 100,
+ nCenturyReal = yearReal / 100;
- // need to adjust for the years divisble by 400 which are
- // not leap but are counted like leap ones if we just take
- // the number of centuries in between for nLostWeekDays
- int nLostWeekDays = (nCentury - nCenturyReal) -
- (nCentury / 4 - nCenturyReal / 4);
+ // need to adjust for the years divisble by 400 which are
+ // not leap but are counted like leap ones if we just take
+ // the number of centuries in between for nLostWeekDays
+ int nLostWeekDays = (nCentury - nCenturyReal) -
+ (nCentury / 4 - nCenturyReal / 4);
- // we have to gain back the "lost" weekdays: note that the
- // effect of this loop is to not do anything to
- // nLostWeekDays (which we won't use any more), but to
- // (indirectly) set the year correctly
- while ( (nLostWeekDays % 7) != 0 )
- {
- nLostWeekDays += year++ % 4 ? 1 : 2;
- }
+ // we have to gain back the "lost" weekdays: note that the
+ // effect of this loop is to not do anything to
+ // nLostWeekDays (which we won't use any more), but to
+ // (indirectly) set the year correctly
+ while ( (nLostWeekDays % 7) != 0 )
+ {
+ nLostWeekDays += year++ % 4 ? 1 : 2;
+ }
- // at any rate, we couldn't go further than 1988 + 9 + 28!
- wxASSERT_MSG( year < 2030,
- _T("logic error in wxDateTime::Format") );
+ // at any rate, we couldn't go further than 1988 + 9 + 28!
+ wxASSERT_MSG( year < 2030,
+ _T("logic error in wxDateTime::Format") );
- wxString strYear, strYear2;
- strYear.Printf(_T("%d"), year);
- strYear2.Printf(_T("%d"), year % 100);
+ wxString strYear, strYear2;
+ strYear.Printf(_T("%d"), year);
+ strYear2.Printf(_T("%d"), year % 100);
- // find two strings not occuring in format (this is surely
- // not optimal way of doing it... improvements welcome!)
- wxString fmt = format;
- wxString replacement = (wxChar)-1;
- while ( fmt.Find(replacement) != wxNOT_FOUND )
- {
- replacement << (wxChar)-1;
- }
+ // find two strings not occuring in format (this is surely
+ // not optimal way of doing it... improvements welcome!)
+ wxString fmt = format;
+ wxString replacement = (wxChar)-1;
+ while ( fmt.Find(replacement) != wxNOT_FOUND )
+ {
+ replacement << (wxChar)-1;
+ }
- wxString replacement2 = (wxChar)-2;
- while ( fmt.Find(replacement) != wxNOT_FOUND )
- {
- replacement << (wxChar)-2;
- }
+ wxString replacement2 = (wxChar)-2;
+ while ( fmt.Find(replacement) != wxNOT_FOUND )
+ {
+ replacement << (wxChar)-2;
+ }
- // replace all occurences of year with it
- bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
- if ( !wasReplaced )
- wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
+ // replace all occurences of year with it
+ bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
+ if ( !wasReplaced )
+ wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
+
+ // use strftime() to format the same date but in supported
+ // year
+ //
+ // NB: we assume that strftime() doesn't check for the
+ // date validity and will happily format the date
+ // corresponding to Feb 29 of a non leap year (which
+ // may happen if yearReal was leap and year is not)
+ struct tm tmAdjusted;
+ InitTm(tmAdjusted);
+ tmAdjusted.tm_hour = tm.hour;
+ tmAdjusted.tm_min = tm.min;
+ tmAdjusted.tm_sec = tm.sec;
+ tmAdjusted.tm_wday = tm.GetWeekDay();
+ tmAdjusted.tm_yday = GetDayOfYear();
+ tmAdjusted.tm_mday = tm.mday;
+ 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"),
+ &tmAdjusted);
+
+ // now replace the occurence of 1999 with the real year
+ wxString strYearReal, strYearReal2;
+ strYearReal.Printf(_T("%04d"), yearReal);
+ strYearReal2.Printf(_T("%02d"), yearReal % 100);
+ str.Replace(strYear, strYearReal);
+ str.Replace(strYear2, strYearReal2);
+
+ // and replace back all occurences of replacement string
+ if ( wasReplaced )
+ {
+ str.Replace(replacement2, strYear2);
+ str.Replace(replacement, strYear);
+ }
- // use strftime() to format the same date but in supported
- // year
- //
- // NB: we assume that strftime() doesn't check for the
- // date validity and will happily format the date
- // corresponding to Feb 29 of a non leap year (which
- // may happen if yearReal was leap and year is not)
- struct tm tmAdjusted;
- InitTm(tmAdjusted);
- tmAdjusted.tm_hour = tm.hour;
- tmAdjusted.tm_min = tm.min;
- tmAdjusted.tm_sec = tm.sec;
- tmAdjusted.tm_wday = tm.GetWeekDay();
- tmAdjusted.tm_yday = GetDayOfYear();
- tmAdjusted.tm_mday = tm.mday;
- 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"),
- &tmAdjusted);
-
- // now replace the occurence of 1999 with the real year
- wxString strYearReal, strYearReal2;
- strYearReal.Printf(_T("%04d"), yearReal);
- strYearReal2.Printf(_T("%02d"), yearReal % 100);
- str.Replace(strYear, strYearReal);
- str.Replace(strYear2, strYearReal2);
-
- // and replace back all occurences of replacement string
- if ( wasReplaced )
- {
- str.Replace(replacement2, strYear2);
- str.Replace(replacement, strYear);
+ res += str;
}
+ break;
- res += str;
- }
- break;
+ case _T('d'): // day of a month (01-31)
+ res += wxString::Format(fmt, tm.mday);
+ break;
- case _T('d'): // day of a month (01-31)
- res += wxString::Format(fmt, tm.mday);
- break;
+ case _T('H'): // hour in 24h format (00-23)
+ res += wxString::Format(fmt, tm.hour);
+ break;
- case _T('H'): // hour in 24h format (00-23)
- res += wxString::Format(fmt, tm.hour);
- break;
+ case _T('I'): // hour in 12h format (01-12)
+ {
+ // 24h -> 12h, 0h -> 12h too
+ int hour12 = tm.hour > 12 ? tm.hour - 12
+ : tm.hour ? tm.hour : 12;
+ res += wxString::Format(fmt, hour12);
+ }
+ break;
- case _T('I'): // hour in 12h format (01-12)
- {
- // 24h -> 12h, 0h -> 12h too
- int hour12 = tm.hour > 12 ? tm.hour - 12
- : tm.hour ? tm.hour : 12;
- res += wxString::Format(fmt, hour12);
- }
- break;
+ case _T('j'): // day of the year
+ res += wxString::Format(fmt, GetDayOfYear(tz));
+ break;
- case _T('j'): // day of the year
- res += wxString::Format(fmt, GetDayOfYear(tz));
- break;
+ case _T('l'): // milliseconds (NOT STANDARD)
+ res += wxString::Format(fmt, GetMillisecond(tz));
+ break;
- case _T('m'): // month as a number (01-12)
- res += wxString::Format(fmt, tm.mon + 1);
- break;
+ case _T('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)
- res += wxString::Format(fmt, tm.min);
- break;
+ case _T('M'): // minute as a decimal number (00-59)
+ res += wxString::Format(fmt, tm.min);
+ break;
- case _T('p'): // AM or PM string
- res += CallStrftime(_T("%p"), &tmTimeOnly);
- break;
+ case _T('p'): // AM or PM string
+ res += CallStrftime(_T("%p"), &tmTimeOnly);
+ break;
- case _T('S'): // second as a decimal number (00-61)
- res += wxString::Format(fmt, tm.sec);
- break;
+ case _T('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)
- res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
- break;
+ case _T('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)
- res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
- break;
+ case _T('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
- res += wxString::Format(fmt, tm.GetWeekDay());
- break;
+ case _T('w'): // weekday as a number (0-6), Sunday = 0
+ res += wxString::Format(fmt, tm.GetWeekDay());
+ break;
- // case _T('x'): -- handled with "%c"
+ // case _T('x'): -- handled with "%c"
- case _T('X'): // locale default time representation
- // just use strftime() to format the time for us
- res += CallStrftime(_T("%X"), &tmTimeOnly);
- break;
+ case _T('X'): // locale default time representation
+ // just use strftime() to format the time for us
+ res += CallStrftime(_T("%X"), &tmTimeOnly);
+ break;
- case _T('y'): // year without century (00-99)
- res += wxString::Format(fmt, tm.year % 100);
- break;
+ case _T('y'): // year without century (00-99)
+ res += wxString::Format(fmt, tm.year % 100);
+ break;
- case _T('Y'): // year with century
- res += wxString::Format(fmt, tm.year);
- break;
+ case _T('Y'): // year with century
+ res += wxString::Format(fmt, tm.year);
+ break;
- case _T('Z'): // timezone name
- res += CallStrftime(_T("%Z"), &tmTimeOnly);
- break;
+ case _T('Z'): // timezone name
+ res += CallStrftime(_T("%Z"), &tmTimeOnly);
+ break;
- default:
- // is it the format width?
- fmt.Empty();
- while ( *p == _T('-') || *p == _T('+') ||
- *p == _T(' ') || wxIsdigit(*p) )
- {
- fmt += *p;
- }
+ default:
+ // is it the format width?
+ fmt.Empty();
+ while ( *p == _T('-') || *p == _T('+') ||
+ *p == _T(' ') || wxIsdigit(*p) )
+ {
+ fmt += *p;
+ }
- if ( !fmt.IsEmpty() )
- {
- // we've only got the flags and width so far in fmt
- fmt.Prepend(_T('%'));
- fmt.Append(_T('d'));
+ if ( !fmt.IsEmpty() )
+ {
+ // we've only got the flags and width so far in fmt
+ fmt.Prepend(_T('%'));
+ fmt.Append(_T('d'));
- goto restart;
- }
+ restart = TRUE;
- // no, it wasn't the width
- wxFAIL_MSG(_T("unknown format specificator"));
+ break;
+ }
- // fall through and just copy it nevertheless
+ // no, it wasn't the width
+ wxFAIL_MSG(_T("unknown format specificator"));
- case _T('%'): // a percent sign
- res += *p;
- break;
+ // fall through and just copy it nevertheless
- case 0: // the end of string
- wxFAIL_MSG(_T("missing format at the end of string"));
+ case _T('%'): // a percent sign
+ res += *p;
+ break;
- // just put the '%' which was the last char in format
- res += _T('%');
- break;
+ case 0: // the end of string
+ wxFAIL_MSG(_T("missing format at the end of string"));
+
+ // just put the '%' which was the last char in format
+ res += _T('%');
+ break;
+ }
}
}
}
// start of a format specification
- switch ( *++fmt )
+
+ // parse the optional width
+ size_t width = 0;
+ while ( isdigit(*++fmt) )
+ {
+ width *= 10;
+ width += *fmt - _T('0');
+ }
+
+ // then the format itself
+ switch ( *fmt )
{
case _T('a'): // a weekday name
case _T('A'):
break;
case _T('d'): // day of a month (01-31)
- if ( !GetNumericToken(input, &num) || (num > 31) )
+ if ( !GetNumericToken(width, input, &num) ||
+ (num > 31) || (num < 1) )
{
// no match
return (wxChar *)NULL;
break;
case _T('H'): // hour in 24h format (00-23)
- if ( !GetNumericToken(input, &num) || (num > 23) )
+ if ( !GetNumericToken(width, input, &num) || (num > 23) )
{
// no match
return (wxChar *)NULL;
break;
case _T('I'): // hour in 12h format (01-12)
- if ( !GetNumericToken(input, &num) || !num || (num > 12) )
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
{
// no match
return (wxChar *)NULL;
break;
case _T('j'): // day of the year
- if ( !GetNumericToken(input, &num) || !num || (num > 366) )
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
{
// no match
return (wxChar *)NULL;
break;
case _T('m'): // month as a number (01-12)
- if ( !GetNumericToken(input, &num) || !num || (num > 12) )
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
{
// no match
return (wxChar *)NULL;
break;
case _T('M'): // minute as a decimal number (00-59)
- if ( !GetNumericToken(input, &num) || (num > 59) )
+ if ( !GetNumericToken(width, input, &num) || (num > 59) )
{
// no match
return (wxChar *)NULL;
}
case _T('S'): // second as a decimal number (00-61)
- if ( !GetNumericToken(input, &num) || (num > 61) )
+ if ( !GetNumericToken(width, input, &num) || (num > 61) )
{
// no match
return (wxChar *)NULL;
break;
case _T('w'): // weekday as a number (0-6), Sunday = 0
- if ( !GetNumericToken(input, &num) || (wday > 6) )
+ if ( !GetNumericToken(width, input, &num) || (wday > 6) )
{
// no match
return (wxChar *)NULL;
break;
case _T('y'): // year without century (00-99)
- if ( !GetNumericToken(input, &num) || (num > 99) )
+ if ( !GetNumericToken(width, input, &num) || (num > 99) )
{
// no match
return (wxChar *)NULL;
}
haveYear = TRUE;
- year = 1900 + (wxDateTime_t)num;
+
+ // TODO should have an option for roll over date instead of
+ // hard coding it here
+ year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
break;
case _T('Y'): // year with century
- if ( !GetNumericToken(input, &num) )
+ if ( !GetNumericToken(width, input, &num) )
{
// no match
return (wxChar *)NULL;
// take this date as default
tmDef = dateDef.GetTm();
}
+#ifdef __WIN16__
else if ( m_time != 0 )
+#else
+ else if ( m_time != wxLongLong(0) )
+#endif
{
// if this date is valid, don't change it
tmDef = GetTm();
{
wxString date = wxGetTranslation(literalDates[n].str);
size_t len = date.length();
- if ( wxString(p, len).CmpNoCase(date) == 0 )
+ if ( wxStrlen(p) >= len && (wxString(p, len).CmpNoCase(date) == 0) )
{
// nothing can follow this, so stop here
p += len;
}
}
+ // We try to guess what we have here: for each new (numeric) token, we
+ // determine if it can be a month, day or a year. Of course, there is an
+ // ambiguity as some numbers may be days as well as months, so we also
+ // have the ability to back track.
+
// what do we have?
bool haveDay = FALSE, // the months day?
haveWDay = FALSE, // the day of week?
int year = 0;
// tokenize the string
- wxStringTokenizer tok(p, _T(",/-\t\n "));
+ size_t nPosCur = 0;
+ static const wxChar *dateDelimiters = _T(".,/-\t\n ");
+ wxStringTokenizer tok(p, dateDelimiters);
while ( tok.HasMoreTokens() )
{
wxString token = tok.GetNextToken();
+ if ( !token )
+ continue;
// is it a number?
unsigned long val;
bool isDay = FALSE,
isMonth = FALSE,
- // only years are counted from 0
- isYear = (val == 0) || (val > 31);
- if ( !isYear )
+ isYear = FALSE;
+
+ if ( !haveMon && val > 0 && val <= 12 )
{
- // may be the month or month day or the year, assume the
- // month day by default and fallback to month if the range
- // allow it or to the year if our assumption doesn't work
- if ( haveDay )
+ // assume it is month
+ isMonth = TRUE;
+ }
+ else // not the month
+ {
+ wxDateTime_t maxDays = haveMon
+ ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
+ : 31;
+
+ // can it be day?
+ if ( (val == 0) || (val > maxDays) )
{
- // we already have the day, so may only be a month or year
- if ( val > 12 )
- {
- isYear = TRUE;
- }
- else
- {
- isMonth = TRUE;
- }
+ isYear = TRUE;
}
- else // it may be day
+ else
{
isDay = TRUE;
-
- // check the range
- if ( haveMon )
- {
- if ( val > GetNumOfDaysInMonth(haveYear ? year
- : Inv_Year,
- mon) )
- {
- // oops, it can't be a day finally
- isDay = FALSE;
-
- if ( val > 12 )
- {
- isYear = TRUE;
- }
- else
- {
- isMonth = TRUE;
- }
- }
- }
}
}
- // remember that we have this and stop the scan if it's the second
- // time we find this, except for the day logic (see there)
if ( isYear )
{
if ( haveYear )
- {
break;
- }
haveYear = TRUE;
- // no roll over - 99 means 99, not 1999 for us
year = (wxDateTime_t)val;
}
- else if ( isMonth )
+ else if ( isDay )
{
- if ( haveMon )
- {
+ if ( haveDay )
break;
- }
- haveMon = TRUE;
+ haveDay = TRUE;
- mon = (wxDateTime::Month)val;
+ day = (wxDateTime_t)val;
}
- else
+ else if ( isMonth )
{
- wxASSERT_MSG( isDay, _T("logic error") );
-
- if ( haveDay )
- {
- // may be were mistaken when we found it for the first
- // time? may be it was a month or year instead?
- //
- // this ability to "backtrack" allows us to correctly parse
- // both things like 01/13 and 13/01 - but, of course, we
- // still can't resolve the ambiguity in 01/02 (it will be
- // Feb 1 for us, not Jan 2 as americans might expect!)
- if ( (day <= 12) && !haveMon )
- {
- // exchange day and month
- mon = (wxDateTime::Month)day;
-
- haveMon = TRUE;
- }
- else if ( !haveYear )
- {
- // exchange day and year
- year = day;
-
- haveYear = TRUE;
- }
- }
-
- haveDay = TRUE;
+ haveMon = TRUE;
- day = (wxDateTime_t)val;
+ mon = (Month)(val - 1);
}
}
else // not a number
{
- mon = GetMonthFromName(token, Name_Full | Name_Abbr);
- if ( mon != Inv_Month )
+ // be careful not to overwrite the current mon value
+ Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
+ if ( mon2 != Inv_Month )
{
// it's a month
if ( haveMon )
{
- break;
+ // 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)
+ break;
+ }
}
+ mon = mon2;
+
haveMon = TRUE;
}
- else
+ else // not a valid month name
{
wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
if ( wday != Inv_WeekDay )
haveWDay = TRUE;
}
- else
+ else // not a valid weekday name
{
// try the ordinals
static const wxChar *ordinals[] =
wxTRANSLATE("nineteenth"),
wxTRANSLATE("twentieth"),
// that's enough - otherwise we'd have problems with
- // composite (or not) ordinals otherwise
+ // composite (or not) ordinals
};
size_t n;
}
}
}
+
+ nPosCur = tok.GetPosition();
}
// 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("no day, no weekday hence no date."));
+ wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
return (wxChar *)NULL;
}
if ( haveWDay && (haveMon || haveYear || haveDay) &&
- !(haveMon && haveMon && haveYear) )
+ !(haveDay && haveMon && haveYear) )
{
// without adjectives (which we don't support here) the week day only
// makes sense completely separately or with the full date
return (wxChar *)NULL;
}
+ if ( !haveWDay && haveYear && !(haveDay && haveMon) )
+ {
+ // may be we have month and day instead of day and year?
+ if ( haveDay && !haveMon )
+ {
+ if ( day <= 12 )
+ {
+ // exchange day and month
+ mon = (wxDateTime::Month)(day - 1);
+
+ // we're in the current year then
+ if ( year <= GetNumOfDaysInMonth(Inv_Year, mon) )
+ {
+ day = year;
+
+ haveMon = TRUE;
+ haveYear = FALSE;
+ }
+ //else: no, can't exchange, leave haveMon == FALSE
+ }
+ }
+
+ 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 (wxChar *)NULL;
+ }
+ }
+
if ( !haveMon )
{
mon = GetCurrentMonth();
if ( GetWeekDay() != wday )
{
// inconsistency detected
+ wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
+
return (wxChar *)NULL;
}
}
SetToWeekDayInSameWeek(wday);
}
- // return the pointer to the next char
- return p + wxStrlen(p) - wxStrlen(tok.GetString());
+ // 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--;
+ }
+
+ return p;
}
const wxChar *wxDateTime::ParseTime(const wxChar *time)
wxString str;
str.Alloc(wxStrlen(format));
- for ( const wxChar *pch = format; pch; pch++ )
+ // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
+ //
+ // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
+ // question is what should ts.Format("%S") do? The code here returns "3273"
+ // in this case (i.e. the total number of seconds, not just seconds % 60)
+ // because, for me, this call means "give me entire time interval in
+ // seconds" and not "give me the seconds part of the time interval"
+ //
+ // If we agree that it should behave like this, it is clear that the
+ // interpretation of each format specifier depends on the presence of the
+ // other format specs in the string: if there was "%H" before "%M", we
+ // should use GetMinutes() % 60, otherwise just GetMinutes() &c
+
+ // we remember the most important unit found so far
+ enum TimeSpanPart
+ {
+ Part_Week,
+ Part_Day,
+ Part_Hour,
+ Part_Min,
+ Part_Sec,
+ Part_MSec
+ } partBiggest = Part_MSec;
+
+ for ( const wxChar *pch = format; *pch; pch++ )
{
wxChar ch = *pch;
- if ( ch == '%' )
+ if ( ch == _T('%') )
{
- wxString tmp;
+ // the start of the format specification of the printf() below
+ wxString fmtPrefix = _T('%');
+
+ // the number
+ long n;
- ch = *pch++;
+ ch = *++pch; // get the format spec char
switch ( ch )
{
default:
wxFAIL_MSG( _T("invalid format character") );
// fall through
- case '%':
- // will get to str << ch below
- break;
+ case _T('%'):
+ str += ch;
- case 'D':
- tmp.Printf(_T("%d"), GetDays());
- break;
+ // skip the part below switch
+ continue;
- case 'E':
- tmp.Printf(_T("%d"), GetWeeks());
+ case _T('D'):
+ n = GetDays();
+ if ( partBiggest < Part_Day )
+ {
+ n %= DAYS_PER_WEEK;
+ }
+ else
+ {
+ partBiggest = Part_Day;
+ }
break;
- case 'H':
- tmp.Printf(_T("%02d"), GetHours());
+ case _T('E'):
+ partBiggest = Part_Week;
+ n = GetWeeks();
break;
- case 'l':
- tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong());
+ case _T('H'):
+ n = GetHours();
+ if ( partBiggest < Part_Hour )
+ {
+ n %= HOURS_PER_DAY;
+ }
+ else
+ {
+ partBiggest = Part_Hour;
+ }
+
+ fmtPrefix += _T("02");
break;
- case 'M':
- tmp.Printf(_T("%02d"), GetMinutes());
+ case _T('l'):
+ n = GetMilliseconds().ToLong();
+ if ( partBiggest < Part_MSec )
+ {
+ n %= 1000;
+ }
+ //else: no need to reset partBiggest to Part_MSec, it is
+ // the least significant one anyhow
+
+ fmtPrefix += _T("03");
break;
- case 'S':
- tmp.Printf(_T("%02ld"), GetSeconds().ToLong());
+ case _T('M'):
+ n = GetMinutes();
+ if ( partBiggest < Part_Min )
+ {
+ n %= MIN_PER_HOUR;
+ }
+ else
+ {
+ partBiggest = Part_Min;
+ }
+
+ fmtPrefix += _T("02");
break;
- }
- if ( !!tmp )
- {
- str += tmp;
+ case _T('S'):
+ n = GetSeconds().ToLong();
+ if ( partBiggest < Part_Sec )
+ {
+ n %= SEC_PER_MIN;
+ }
+ else
+ {
+ partBiggest = Part_Sec;
+ }
- // skip str += ch below
- continue;
+ fmtPrefix += _T("02");
+ break;
}
- }
- str += ch;
+ str += wxString::Format(fmtPrefix + _T("ld"), n);
+ }
+ else
+ {
+ // normal character, just copy
+ str += ch;
+ }
}
return str;
#include "wx/arrimpl.cpp"
-WX_DEFINE_OBJARRAY(wxDateTimeArray)
+WX_DEFINE_OBJARRAY(wxDateTimeArray);
static int wxCMPFUNC_CONV
wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)