Windows CE doesn't seem to be supported by Microsoft any longer. Last CE release was in early 2013 and the PocketPC and Smartphone targets supported by wxWidgets are long gone. The build files where already removed in an earlier cleanup this commit removes all files, every #ifdef and all documentation regarding the Windows CE support. Closes https://github.com/wxWidgets/wxWidgets/pull/81
2398 lines
75 KiB
C++
2398 lines
75 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/common/datetimefmt.cpp
|
|
// Purpose: wxDateTime formatting & parsing code
|
|
// Author: Vadim Zeitlin
|
|
// Modified by:
|
|
// Created: 11.05.99
|
|
// Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
|
|
// parts of code taken from sndcal library by Scott E. Lee:
|
|
//
|
|
// Copyright 1993-1995, Scott E. Lee, all rights reserved.
|
|
// Permission granted to use, copy, modify, distribute and sell
|
|
// so long as the above copyright and this permission statement
|
|
// are retained in all copies.
|
|
//
|
|
// Licence: wxWindows licence
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
|
|
|
|
#ifndef WX_PRECOMP
|
|
#ifdef __WINDOWS__
|
|
#include "wx/msw/wrapwin.h"
|
|
#endif
|
|
#include "wx/string.h"
|
|
#include "wx/log.h"
|
|
#include "wx/intl.h"
|
|
#include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
|
|
#include "wx/module.h"
|
|
#include "wx/crt.h"
|
|
#endif // WX_PRECOMP
|
|
|
|
#include "wx/thread.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#ifdef __WINDOWS__
|
|
#include <winnls.h>
|
|
#include <locale.h>
|
|
#endif
|
|
|
|
#include "wx/datetime.h"
|
|
#include "wx/time.h"
|
|
|
|
// ============================================================================
|
|
// implementation of wxDateTime
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// helpers shared between datetime.cpp and datetimefmt.cpp
|
|
// ----------------------------------------------------------------------------
|
|
|
|
extern void InitTm(struct tm& tm);
|
|
|
|
extern wxString CallStrftime(const wxString& format, const tm* tm);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// constants (see also datetime.cpp)
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static const int DAYS_PER_WEEK = 7;
|
|
|
|
static const int HOURS_PER_DAY = 24;
|
|
|
|
static const int SEC_PER_MIN = 60;
|
|
|
|
static const int MIN_PER_HOUR = 60;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// parsing helpers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
namespace
|
|
{
|
|
|
|
// all the functions below taking non-const wxString::const_iterator p advance
|
|
// it until the end of the match
|
|
|
|
// 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)
|
|
{
|
|
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++;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
enum
|
|
{
|
|
DateLang_English = 1,
|
|
DateLang_Local = 2
|
|
};
|
|
|
|
// return the month if the string is a month name or Inv_Month otherwise
|
|
//
|
|
// 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) )
|
|
{
|
|
// case-insensitive comparison either one of or with both abbreviated
|
|
// and not versions
|
|
if ( flags & wxDateTime::Name_Full )
|
|
{
|
|
if ( lang & DateLang_English )
|
|
{
|
|
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 ( lang & DateLang_English )
|
|
{
|
|
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
|
|
//
|
|
// 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) )
|
|
{
|
|
if ( flags & wxDateTime::Name_Full )
|
|
{
|
|
if ( lang & DateLang_English )
|
|
{
|
|
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 ( lang & DateLang_English )
|
|
{
|
|
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;
|
|
}
|
|
|
|
// 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,
|
|
const wxString& fmtAlt = wxString())
|
|
{
|
|
const wxString str(p, end);
|
|
wxString::const_iterator endParse;
|
|
wxDateTime dt;
|
|
|
|
// 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);
|
|
|
|
if ( dt.ParseFormat(str, fmt, dtDef, &endParse) ||
|
|
(!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, dtDef, &endParse)) )
|
|
{
|
|
p += endParse - str.begin();
|
|
}
|
|
//else: all formats failed
|
|
|
|
return dt;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxDateTime to/from text representations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
|
|
{
|
|
wxCHECK_MSG( !formatp.empty(), wxEmptyString,
|
|
wxT("NULL format in wxDateTime::Format") );
|
|
|
|
wxString format = formatp;
|
|
#ifdef __WXOSX__
|
|
if ( format.Contains("%c") )
|
|
format.Replace("%c", wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT));
|
|
if ( format.Contains("%x") )
|
|
format.Replace("%x", wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT));
|
|
if ( format.Contains("%X") )
|
|
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()
|
|
#ifdef wxHAS_STRFTIME
|
|
time_t time = GetTicks();
|
|
|
|
bool canUseStrftime = time != (time_t)-1;
|
|
|
|
// We also can't use strftime() if we use non standard specifier: either
|
|
// our own extension "%l" or one of "%g", "%G", "%V", "%z" which are POSIX
|
|
// but not supported under Windows.
|
|
for ( wxString::const_iterator p = format.begin();
|
|
canUseStrftime && p != format.end();
|
|
++p )
|
|
{
|
|
if ( *p != '%' )
|
|
continue;
|
|
|
|
// set the default format
|
|
switch ( (*++p).GetValue() )
|
|
{
|
|
case 'l':
|
|
#ifdef __WINDOWS__
|
|
case 'g':
|
|
case 'G':
|
|
case 'V':
|
|
case 'z':
|
|
#endif // __WINDOWS__
|
|
canUseStrftime = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( canUseStrftime )
|
|
{
|
|
// use strftime()
|
|
struct tm tmstruct;
|
|
struct tm *tm;
|
|
if ( tz.GetOffset() == -wxGetTimeZone() )
|
|
{
|
|
// we are working with local time
|
|
tm = wxLocaltime_r(&time, &tmstruct);
|
|
|
|
// should never happen
|
|
wxCHECK_MSG( tm, wxEmptyString, wxT("wxLocaltime_r() failed") );
|
|
}
|
|
else
|
|
{
|
|
time += (int)tz.GetOffset();
|
|
|
|
#if defined(__VMS__) // time is unsigned so avoid warning
|
|
int time2 = (int) time;
|
|
if ( time2 >= 0 )
|
|
#else
|
|
if ( time >= 0 )
|
|
#endif
|
|
{
|
|
tm = wxGmtime_r(&time, &tmstruct);
|
|
|
|
// should never happen
|
|
wxCHECK_MSG( tm, wxEmptyString, wxT("wxGmtime_r() failed") );
|
|
}
|
|
else
|
|
{
|
|
tm = (struct tm *)NULL;
|
|
}
|
|
}
|
|
|
|
if ( tm )
|
|
{
|
|
return CallStrftime(format, tm);
|
|
}
|
|
}
|
|
//else: use generic code below
|
|
#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
|
|
// specifier allowing to get the number of milliseconds
|
|
Tm tm = GetTm(tz);
|
|
|
|
// 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_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
|
|
|
|
wxString tmp, res, fmt;
|
|
for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p )
|
|
{
|
|
if ( *p != wxT('%') )
|
|
{
|
|
// copy as is
|
|
res += *p;
|
|
|
|
continue;
|
|
}
|
|
|
|
// set the default format
|
|
switch ( (*++p).GetValue() )
|
|
{
|
|
case wxT('Y'): // year has 4 digits
|
|
case wxT('G'): // (and ISO week year too)
|
|
case wxT('z'): // time zone as well
|
|
fmt = wxT("%04d");
|
|
break;
|
|
|
|
case wxT('j'): // day of year has 3 digits
|
|
case wxT('l'): // milliseconds have 3 digits
|
|
fmt = wxT("%03d");
|
|
break;
|
|
|
|
case wxT('w'): // week day as number has only one
|
|
fmt = wxT("%d");
|
|
break;
|
|
|
|
default:
|
|
// it's either another valid format specifier in which case
|
|
// 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 = wxT("%02d");
|
|
}
|
|
|
|
bool restart = true;
|
|
while ( restart )
|
|
{
|
|
restart = false;
|
|
|
|
// start of the format specification
|
|
switch ( (*p).GetValue() )
|
|
{
|
|
case wxT('a'): // a weekday name
|
|
case wxT('A'):
|
|
// second parameter should be true for abbreviated names
|
|
res += GetWeekDayName(tm.GetWeekDay(),
|
|
*p == wxT('a') ? Name_Abbr : Name_Full);
|
|
break;
|
|
|
|
case wxT('b'): // a month name
|
|
case wxT('B'):
|
|
res += GetMonthName(tm.mon,
|
|
*p == wxT('b') ? Name_Abbr : Name_Full);
|
|
break;
|
|
|
|
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.
|
|
//
|
|
// 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 occurrences 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 occurrences of YEAR"
|
|
// means all occurrences 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 of 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// finally move the year below 2000 so that the 2-digit
|
|
// year number can never match the month or day of the
|
|
// month when we do the replacements below
|
|
if ( year >= 2000 )
|
|
year -= 28;
|
|
|
|
wxASSERT_MSG( year >= 1970 && year < 2000,
|
|
wxT("logic error in wxDateTime::Format") );
|
|
|
|
|
|
// 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 == wxT('c') ? wxT("%c")
|
|
: wxT("%x"),
|
|
&tmAdjusted);
|
|
|
|
// now replace the replacement year with the real year:
|
|
// notice that we have to replace the 4 digit year with
|
|
// a unique string not appearing in strftime() output
|
|
// first to prevent the 2 digit year from matching any
|
|
// substring of the 4 digit year (but any day, month,
|
|
// hours or minutes components should be safe because
|
|
// they are never in 70-99 range)
|
|
wxString replacement("|");
|
|
while ( str.find(replacement) != wxString::npos )
|
|
replacement += '|';
|
|
|
|
str.Replace(wxString::Format("%d", year),
|
|
replacement);
|
|
str.Replace(wxString::Format("%d", year % 100),
|
|
wxString::Format("%d", yearReal % 100));
|
|
str.Replace(replacement,
|
|
wxString::Format("%d", yearReal));
|
|
|
|
res += str;
|
|
}
|
|
#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 // wxHAS_STRFTIME/!wxHAS_STRFTIME
|
|
break;
|
|
|
|
case wxT('d'): // day of a month (01-31)
|
|
res += wxString::Format(fmt, tm.mday);
|
|
break;
|
|
|
|
case wxT('g'): // 2-digit week-based year
|
|
res += wxString::Format(fmt, GetWeekBasedYear() % 100);
|
|
break;
|
|
|
|
case wxT('G'): // week-based year with century
|
|
res += wxString::Format(fmt, GetWeekBasedYear());
|
|
break;
|
|
|
|
case wxT('H'): // hour in 24h format (00-23)
|
|
res += wxString::Format(fmt, tm.hour);
|
|
break;
|
|
|
|
case wxT('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 wxT('j'): // day of the year
|
|
res += wxString::Format(fmt, GetDayOfYear(tz));
|
|
break;
|
|
|
|
case wxT('l'): // milliseconds (NOT STANDARD)
|
|
res += wxString::Format(fmt, GetMillisecond(tz));
|
|
break;
|
|
|
|
case wxT('m'): // month as a number (01-12)
|
|
res += wxString::Format(fmt, tm.mon + 1);
|
|
break;
|
|
|
|
case wxT('M'): // minute as a decimal number (00-59)
|
|
res += wxString::Format(fmt, tm.min);
|
|
break;
|
|
|
|
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 // wxHAS_STRFTIME/!wxHAS_STRFTIME
|
|
break;
|
|
|
|
case wxT('S'): // second as a decimal number (00-61)
|
|
res += wxString::Format(fmt, tm.sec);
|
|
break;
|
|
|
|
case wxT('U'): // week number in the year (Sunday 1st week day)
|
|
res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
|
|
break;
|
|
|
|
case wxT('V'): // ISO week number
|
|
case wxT('W'): // week number in the year (Monday 1st week day)
|
|
res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
|
|
break;
|
|
|
|
case wxT('w'): // weekday as a number (0-6), Sunday = 0
|
|
res += wxString::Format(fmt, tm.GetWeekDay());
|
|
break;
|
|
|
|
// case wxT('x'): -- handled with "%c"
|
|
|
|
case wxT('X'): // locale default time representation
|
|
// just use strftime() to format the time for us
|
|
#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 // wxHAS_STRFTIME/!wxHAS_STRFTIME
|
|
break;
|
|
|
|
case wxT('y'): // year without century (00-99)
|
|
res += wxString::Format(fmt, tm.year % 100);
|
|
break;
|
|
|
|
case wxT('Y'): // year with century
|
|
res += wxString::Format(fmt, tm.year);
|
|
break;
|
|
|
|
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?
|
|
for ( fmt.clear();
|
|
*p == wxT('-') || *p == wxT('+') ||
|
|
*p == wxT(' ') || wxIsdigit(*p);
|
|
++p )
|
|
{
|
|
fmt += *p;
|
|
}
|
|
|
|
if ( !fmt.empty() )
|
|
{
|
|
// we've only got the flags and width so far in fmt
|
|
fmt.Prepend(wxT('%'));
|
|
fmt.Append(wxT('d'));
|
|
|
|
restart = true;
|
|
|
|
break;
|
|
}
|
|
|
|
// no, it wasn't the width
|
|
wxFAIL_MSG(wxT("unknown format specifier"));
|
|
|
|
wxFALLTHROUGH;
|
|
|
|
case wxT('%'): // a percent sign
|
|
res += *p;
|
|
break;
|
|
|
|
case 0: // 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 += wxT('%');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// this function parses a string in (strict) RFC 822 format: see the section 5
|
|
// of the RFC for the detailed description, but briefly it's something of the
|
|
// form "Sat, 18 Dec 1999 00:48:30 +0100"
|
|
//
|
|
// this function is "strict" by design - it must reject anything except true
|
|
// RFC822 time specs.
|
|
bool
|
|
wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end)
|
|
{
|
|
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) )
|
|
return false;
|
|
|
|
wxDateTime_t day = (wxDateTime_t)(*p++ - '0');
|
|
if ( wxIsdigit(*p) )
|
|
{
|
|
day *= 10;
|
|
day = (wxDateTime_t)(day + (*p++ - '0'));
|
|
}
|
|
|
|
if ( *p++ != ' ' )
|
|
return false;
|
|
|
|
// 4. month name
|
|
const Month mon = GetMonthFromName(p, pEnd, Name_Abbr, DateLang_English);
|
|
if ( mon == Inv_Month )
|
|
return false;
|
|
|
|
if ( *p++ != ' ' )
|
|
return false;
|
|
|
|
// 5. year
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
int year = *p++ - '0';
|
|
if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year
|
|
return false;
|
|
|
|
year *= 10;
|
|
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++ - '0';
|
|
|
|
if ( !wxIsdigit(*p) )
|
|
{
|
|
// no 3 digit years please
|
|
return false;
|
|
}
|
|
|
|
year *= 10;
|
|
year += *p++ - '0';
|
|
}
|
|
|
|
if ( *p++ != ' ' )
|
|
return false;
|
|
|
|
// 6. time in hh:mm:ss format with seconds being optional
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
wxDateTime_t hour = (wxDateTime_t)(*p++ - '0');
|
|
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
hour *= 10;
|
|
hour = (wxDateTime_t)(hour + (*p++ - '0'));
|
|
|
|
if ( *p++ != ':' )
|
|
return false;
|
|
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
wxDateTime_t min = (wxDateTime_t)(*p++ - '0');
|
|
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
min *= 10;
|
|
min += (wxDateTime_t)(*p++ - '0');
|
|
|
|
wxDateTime_t sec = 0;
|
|
if ( *p == ':' )
|
|
{
|
|
p++;
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
sec = (wxDateTime_t)(*p++ - '0');
|
|
|
|
if ( !wxIsdigit(*p) )
|
|
return false;
|
|
|
|
sec *= 10;
|
|
sec += (wxDateTime_t)(*p++ - '0');
|
|
}
|
|
|
|
if ( *p++ != ' ' )
|
|
return false;
|
|
|
|
// 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++ == '+';
|
|
|
|
if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
|
|
return false;
|
|
|
|
|
|
// hours
|
|
offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0'));
|
|
|
|
p += 2;
|
|
|
|
if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
|
|
return false;
|
|
|
|
// minutes
|
|
offset += 10*(*p - '0') + (*(p + 1) - '0');
|
|
|
|
if ( !plus )
|
|
offset = -offset;
|
|
|
|
p += 2;
|
|
}
|
|
else // not numeric
|
|
{
|
|
// the symbolic timezone given: may be either military timezone or one
|
|
// of standard abbreviations
|
|
if ( !*(p + 1) )
|
|
{
|
|
// military: Z = UTC, J unused, A = -1, ..., Y = +12
|
|
static const int offsets[26] =
|
|
{
|
|
//A B C D E F G H I J K L M
|
|
-1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
|
|
//N O P R Q S T U V W Z Y Z
|
|
+1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
|
|
};
|
|
|
|
if ( *p < wxT('A') || *p > wxT('Z') || *p == wxT('J') )
|
|
return false;
|
|
|
|
offset = offsets[*p++ - 'A'];
|
|
}
|
|
else
|
|
{
|
|
// abbreviation
|
|
const wxString tz(p, date.end());
|
|
if ( tz == wxT("UT") || tz == wxT("UTC") || tz == wxT("GMT") )
|
|
offset = 0;
|
|
else if ( tz == wxT("AST") )
|
|
offset = AST - GMT0;
|
|
else if ( tz == wxT("ADT") )
|
|
offset = ADT - GMT0;
|
|
else if ( tz == wxT("EST") )
|
|
offset = EST - GMT0;
|
|
else if ( tz == wxT("EDT") )
|
|
offset = EDT - GMT0;
|
|
else if ( tz == wxT("CST") )
|
|
offset = CST - GMT0;
|
|
else if ( tz == wxT("CDT") )
|
|
offset = CDT - GMT0;
|
|
else if ( tz == wxT("MST") )
|
|
offset = MST - GMT0;
|
|
else if ( tz == wxT("MDT") )
|
|
offset = MDT - GMT0;
|
|
else if ( tz == wxT("PST") )
|
|
offset = PST - GMT0;
|
|
else if ( tz == wxT("PDT") )
|
|
offset = PDT - GMT0;
|
|
else
|
|
return false;
|
|
|
|
p += tz.length();
|
|
}
|
|
|
|
// make it minutes
|
|
offset *= MIN_PER_HOUR;
|
|
}
|
|
|
|
|
|
// the spec was correct, construct the date from the values we found
|
|
Set(day, mon, year, hour, min, sec);
|
|
|
|
// As always, dealing with the time zone is the most interesting part: we
|
|
// can't just use MakeFromTimeZone() here because it wouldn't handle the
|
|
// DST correctly because the TZ specified in the string is DST-invariant
|
|
// and so we have to manually shift to the UTC first and then convert to
|
|
// the local TZ.
|
|
*this -= wxTimeSpan::Minutes(offset);
|
|
MakeFromUTC();
|
|
|
|
if ( end )
|
|
*end = p;
|
|
|
|
return true;
|
|
}
|
|
|
|
const char* wxDateTime::ParseRfc822Date(const char* date)
|
|
{
|
|
wxString::const_iterator end;
|
|
wxString dateStr(date);
|
|
if ( !ParseRfc822Date(dateStr, &end) )
|
|
return NULL;
|
|
|
|
return date + dateStr.IterOffsetInMBStr(end);
|
|
}
|
|
|
|
const wchar_t* wxDateTime::ParseRfc822Date(const wchar_t* date)
|
|
{
|
|
wxString::const_iterator end;
|
|
wxString dateStr(date);
|
|
if ( !ParseRfc822Date(dateStr, &end) )
|
|
return NULL;
|
|
|
|
return date + (end - dateStr.begin());
|
|
}
|
|
|
|
bool
|
|
wxDateTime::ParseFormat(const wxString& date,
|
|
const wxString& format,
|
|
const wxDateTime& dateDef,
|
|
wxString::const_iterator *endParse)
|
|
{
|
|
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;
|
|
|
|
// what fields have we found?
|
|
bool haveWDay = false,
|
|
haveYDay = false,
|
|
haveDay = false,
|
|
haveMon = false,
|
|
haveYear = false,
|
|
haveHour = false,
|
|
haveMin = false,
|
|
haveSec = false,
|
|
haveMsec = false;
|
|
|
|
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,
|
|
min = 0,
|
|
hour = 0;
|
|
WeekDay wday = Inv_WeekDay;
|
|
wxDateTime_t yday = 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();
|
|
for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt )
|
|
{
|
|
if ( *fmt != wxT('%') )
|
|
{
|
|
if ( wxIsspace(*fmt) )
|
|
{
|
|
// a white space in the format string matches 0 or more white
|
|
// spaces in the input
|
|
while ( input != end && wxIsspace(*input) )
|
|
{
|
|
input++;
|
|
}
|
|
}
|
|
else // !space
|
|
{
|
|
// any other character (not whitespace, not '%') must be
|
|
// matched by itself in the input
|
|
if ( input == end || *input++ != *fmt )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// done with this format char
|
|
continue;
|
|
}
|
|
|
|
// start of a format specification
|
|
|
|
// parse the optional width
|
|
size_t width = 0;
|
|
while ( wxIsdigit(*++fmt) )
|
|
{
|
|
width *= 10;
|
|
width += *fmt - '0';
|
|
}
|
|
|
|
// the default widths for the various fields
|
|
if ( !width )
|
|
{
|
|
switch ( (*fmt).GetValue() )
|
|
{
|
|
case wxT('Y'): // year has 4 digits
|
|
width = 4;
|
|
break;
|
|
|
|
case wxT('j'): // day of year has 3 digits
|
|
case wxT('l'): // milliseconds have 3 digits
|
|
width = 3;
|
|
break;
|
|
|
|
case wxT('w'): // week day as number has only one
|
|
width = 1;
|
|
break;
|
|
|
|
default:
|
|
// default for all other fields
|
|
width = 2;
|
|
}
|
|
}
|
|
|
|
// then the format itself
|
|
switch ( (*fmt).GetValue() )
|
|
{
|
|
case wxT('a'): // a weekday name
|
|
case wxT('A'):
|
|
{
|
|
wday = GetWeekDayFromName
|
|
(
|
|
input, end,
|
|
*fmt == 'a' ? Name_Abbr : Name_Full,
|
|
DateLang_Local
|
|
);
|
|
if ( wday == Inv_WeekDay )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
}
|
|
haveWDay = true;
|
|
break;
|
|
|
|
case wxT('b'): // a month name
|
|
case wxT('B'):
|
|
{
|
|
mon = GetMonthFromName
|
|
(
|
|
input, end,
|
|
*fmt == 'b' ? Name_Abbr : Name_Full,
|
|
DateLang_Local
|
|
);
|
|
if ( mon == Inv_Month )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
}
|
|
haveMon = true;
|
|
break;
|
|
|
|
case wxT('c'): // locale default date and time representation
|
|
{
|
|
wxDateTime dt;
|
|
|
|
#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() )
|
|
{
|
|
// 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"));
|
|
}
|
|
|
|
if ( !dt.IsValid() )
|
|
{
|
|
// and finally also the two generic date/time formats
|
|
dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x"));
|
|
}
|
|
|
|
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 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 false;
|
|
}
|
|
|
|
// we can't check whether the day range is correct yet, will
|
|
// do it later - assume ok for now
|
|
haveDay = true;
|
|
mday = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('H'): // hour in 24h format (00-23)
|
|
if ( !GetNumericToken(width, input, end, &num) || (num > 23) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveHour = true;
|
|
hour = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('I'): // hour in 12h format (01-12)
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
!num || (num > 12) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveHour = true;
|
|
hourIsIn12hFormat = true;
|
|
hour = (wxDateTime_t)(num % 12); // 12 should be 0
|
|
break;
|
|
|
|
case wxT('j'): // day of the year
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
!num || (num > 366) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveYDay = true;
|
|
yday = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('l'): // milliseconds (0-999)
|
|
if ( !GetNumericToken(width, input, end, &num) )
|
|
return false;
|
|
|
|
haveMsec = true;
|
|
msec = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('m'): // month as a number (01-12)
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
!num || (num > 12) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveMon = true;
|
|
mon = (Month)(num - 1);
|
|
break;
|
|
|
|
case wxT('M'): // minute as a decimal number (00-59)
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
(num > 59) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveMin = true;
|
|
min = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('p'): // AM or PM string
|
|
{
|
|
wxString am, pm;
|
|
GetAmPmStrings(&am, &pm);
|
|
|
|
// 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 ( date.compare(pos, am.length(), am) == 0 )
|
|
{
|
|
input += am.length();
|
|
}
|
|
else // no match
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case wxT('r'): // time as %I:%M:%S %p
|
|
{
|
|
wxDateTime dt;
|
|
if ( !dt.ParseFormat(wxString(input, end),
|
|
wxS("%I:%M:%S %p"), &input) )
|
|
return false;
|
|
|
|
haveHour = haveMin = haveSec = true;
|
|
|
|
const Tm tm = dt.GetTm();
|
|
hour = tm.hour;
|
|
min = tm.min;
|
|
sec = tm.sec;
|
|
}
|
|
break;
|
|
|
|
case wxT('R'): // time as %H:%M
|
|
{
|
|
const wxDateTime
|
|
dt = ParseFormatAt(input, end, wxS("%H:%M"));
|
|
if ( !dt.IsValid() )
|
|
return false;
|
|
|
|
haveHour =
|
|
haveMin = true;
|
|
|
|
const Tm tm = dt.GetTm();
|
|
hour = tm.hour;
|
|
min = tm.min;
|
|
}
|
|
break;
|
|
|
|
case wxT('S'): // second as a decimal number (00-61)
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
(num > 61) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveSec = true;
|
|
sec = (wxDateTime_t)num;
|
|
break;
|
|
|
|
case wxT('T'): // time as %H:%M:%S
|
|
{
|
|
const wxDateTime
|
|
dt = ParseFormatAt(input, end, wxS("%H:%M:%S"));
|
|
if ( !dt.IsValid() )
|
|
return false;
|
|
|
|
haveHour =
|
|
haveMin =
|
|
haveSec = true;
|
|
|
|
const Tm tm = dt.GetTm();
|
|
hour = tm.hour;
|
|
min = tm.min;
|
|
sec = tm.sec;
|
|
}
|
|
break;
|
|
|
|
case wxT('w'): // weekday as a number (0-6), Sunday = 0
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
(wday > 6) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveWDay = true;
|
|
wday = (WeekDay)num;
|
|
break;
|
|
|
|
case wxT('x'): // locale default date representation
|
|
{
|
|
#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() )
|
|
{
|
|
if ( IsWestEuropeanCountry(GetCountry()) ||
|
|
GetCountry() == Russia )
|
|
{
|
|
fmtDate = wxS("%d/%m/%Y");
|
|
fmtDateAlt = wxS("%m/%d/%Y");
|
|
}
|
|
else // assume USA
|
|
{
|
|
fmtDate = wxS("%m/%d/%Y");
|
|
fmtDateAlt = wxS("%d/%m/%Y");
|
|
}
|
|
}
|
|
|
|
wxDateTime
|
|
dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
|
|
|
|
if ( !dt.IsValid() )
|
|
{
|
|
// try with short years too
|
|
fmtDate.Replace("%Y","%y");
|
|
fmtDateAlt.Replace("%Y","%y");
|
|
dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
|
|
|
|
if ( !dt.IsValid() )
|
|
return false;
|
|
}
|
|
|
|
const Tm tm = dt.GetTm();
|
|
|
|
haveDay =
|
|
haveMon =
|
|
haveYear = true;
|
|
|
|
year = tm.year;
|
|
mon = tm.mon;
|
|
mday = tm.mday;
|
|
}
|
|
|
|
break;
|
|
|
|
case wxT('X'): // locale default time representation
|
|
{
|
|
#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() )
|
|
{
|
|
// 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";
|
|
}
|
|
|
|
const wxDateTime
|
|
dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt);
|
|
if ( !dt.IsValid() )
|
|
return false;
|
|
|
|
haveHour =
|
|
haveMin =
|
|
haveSec = true;
|
|
|
|
const Tm tm = dt.GetTm();
|
|
hour = tm.hour;
|
|
min = tm.min;
|
|
sec = tm.sec;
|
|
}
|
|
break;
|
|
|
|
case wxT('y'): // year without century (00-99)
|
|
if ( !GetNumericToken(width, input, end, &num) ||
|
|
(num > 99) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveYear = true;
|
|
|
|
// 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 wxT('Y'): // year with century
|
|
if ( !GetNumericToken(width, input, end, &num) )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
haveYear = true;
|
|
year = (wxDateTime_t)num;
|
|
break;
|
|
|
|
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 wxT('%'): // a percent sign
|
|
if ( input == end || *input++ != wxT('%') )
|
|
{
|
|
// no match
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 0: // the end of string
|
|
wxFAIL_MSG(wxT("unexpected format end"));
|
|
|
|
wxFALLTHROUGH;
|
|
|
|
default: // not a known format spec
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// format matched, try to construct a date from what we have now
|
|
Tm tmDef;
|
|
if ( dateDef.IsValid() )
|
|
{
|
|
// take this date as default
|
|
tmDef = dateDef.GetTm();
|
|
}
|
|
else if ( IsValid() )
|
|
{
|
|
// if this date is valid, don't change it
|
|
tmDef = GetTm();
|
|
}
|
|
else
|
|
{
|
|
// no default and this date is invalid - fall back to Today()
|
|
tmDef = Today().GetTm();
|
|
}
|
|
|
|
Tm tm = tmDef;
|
|
|
|
// set the date
|
|
if ( haveMon )
|
|
{
|
|
tm.mon = mon;
|
|
}
|
|
|
|
if ( haveYear )
|
|
{
|
|
tm.year = year;
|
|
}
|
|
|
|
// TODO we don't check here that the values are consistent, if both year
|
|
// day and month/day were found, we just ignore the year day and we
|
|
// also always ignore the week day
|
|
if ( haveDay )
|
|
{
|
|
if ( mday > GetNumberOfDays(tm.mon, tm.year) )
|
|
return false;
|
|
|
|
tm.mday = mday;
|
|
}
|
|
else if ( haveYDay )
|
|
{
|
|
if ( yday > GetNumberOfDays(tm.year) )
|
|
return false;
|
|
|
|
Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
|
|
|
|
tm.mon = tm2.mon;
|
|
tm.mday = tm2.mday;
|
|
}
|
|
|
|
// deal with AM/PM
|
|
if ( haveHour && hourIsIn12hFormat && isPM )
|
|
{
|
|
// translate to 24hour format
|
|
hour += 12;
|
|
}
|
|
//else: either already in 24h format or no translation needed
|
|
|
|
// set the time
|
|
if ( haveHour )
|
|
{
|
|
tm.hour = hour;
|
|
}
|
|
|
|
if ( haveMin )
|
|
{
|
|
tm.min = min;
|
|
}
|
|
|
|
if ( haveSec )
|
|
{
|
|
tm.sec = sec;
|
|
}
|
|
|
|
if ( haveMsec )
|
|
tm.msec = msec;
|
|
|
|
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;
|
|
|
|
*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;
|
|
|
|
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 + (end - dateStr.begin());
|
|
}
|
|
|
|
bool
|
|
wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end)
|
|
{
|
|
wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
|
|
|
|
wxDateTime
|
|
dtDate,
|
|
dtTime;
|
|
|
|
wxString::const_iterator
|
|
endTime,
|
|
endDate,
|
|
endBoth;
|
|
|
|
// If we got a date in the beginning, see if there is a time specified
|
|
// after the date
|
|
if ( dtDate.ParseDate(date, &endDate) )
|
|
{
|
|
// Skip spaces, as the ParseTime() function fails on spaces
|
|
while ( endDate != date.end() && wxIsspace(*endDate) )
|
|
++endDate;
|
|
|
|
const wxString timestr(endDate, date.end());
|
|
if ( !dtTime.ParseTime(timestr, &endTime) )
|
|
return false;
|
|
|
|
endBoth = endDate + (endTime - timestr.begin());
|
|
}
|
|
else // no date in the beginning
|
|
{
|
|
// check if we have a time followed by a date
|
|
if ( !dtTime.ParseTime(date, &endTime) )
|
|
return false;
|
|
|
|
while ( endTime != date.end() && wxIsspace(*endTime) )
|
|
++endTime;
|
|
|
|
const wxString datestr(endTime, date.end());
|
|
if ( !dtDate.ParseDate(datestr, &endDate) )
|
|
return false;
|
|
|
|
endBoth = endTime + (endDate - datestr.begin());
|
|
}
|
|
|
|
Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
|
|
dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
|
|
dtTime.GetMillisecond());
|
|
|
|
*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 + (end - dateStr.begin());
|
|
}
|
|
|
|
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 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
|
|
static struct
|
|
{
|
|
const char *str;
|
|
int dayDiffFromToday;
|
|
} literalDates[] =
|
|
{
|
|
{ wxTRANSLATE("today"), 0 },
|
|
{ wxTRANSLATE("yesterday"), -1 },
|
|
{ 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 ( len > lenRest )
|
|
continue;
|
|
|
|
const wxString::const_iterator pEndStr = p + len;
|
|
if ( wxString(p, pEndStr).CmpNoCase(dateStr) == 0 )
|
|
{
|
|
// nothing can follow this, so stop here
|
|
|
|
p = pEndStr;
|
|
|
|
int dayDiffFromToday = literalDates[n].dayDiffFromToday;
|
|
*this = Today();
|
|
if ( dayDiffFromToday )
|
|
{
|
|
*this += wxDateSpan::Days(dayDiffFromToday);
|
|
}
|
|
|
|
*end = pEndStr;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 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?
|
|
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;
|
|
wxDateTime::Month mon = Inv_Month;
|
|
int year = 0;
|
|
|
|
// tokenize the string
|
|
while ( p != pEnd )
|
|
{
|
|
// skip white space and date delimiters
|
|
if ( wxStrchr(".,/-\t\r\n ", *p) )
|
|
{
|
|
++p;
|
|
continue;
|
|
}
|
|
|
|
// 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 ( GetNumericToken(10 /* max length */, pCopy, pEnd, &val) )
|
|
{
|
|
// guess what this number is
|
|
|
|
bool isDay = false,
|
|
isMonth = false,
|
|
isYear = false;
|
|
|
|
if ( !haveMon && val > 0 && val <= 12 )
|
|
{
|
|
// assume it is month
|
|
isMonth = true;
|
|
}
|
|
else // not the month
|
|
{
|
|
if ( haveDay )
|
|
{
|
|
// this can only be the year
|
|
isYear = true;
|
|
}
|
|
else // may be either day or year
|
|
{
|
|
// use a leap year if we don't have the year yet to allow
|
|
// dates like 2/29/1976 which would be rejected otherwise
|
|
wxDateTime_t max_days = (wxDateTime_t)(
|
|
haveMon
|
|
? GetNumberOfDays(mon, haveYear ? year : 1976)
|
|
: 31
|
|
);
|
|
|
|
// can it be day?
|
|
if ( (val == 0) || (val > (unsigned long)max_days) )
|
|
{
|
|
// no
|
|
isYear = true;
|
|
}
|
|
else // yes, suppose it's the day
|
|
{
|
|
isDay = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( isYear )
|
|
{
|
|
if ( haveYear )
|
|
break;
|
|
|
|
haveYear = true;
|
|
|
|
year = (wxDateTime_t)val;
|
|
}
|
|
else if ( isDay )
|
|
{
|
|
if ( haveDay )
|
|
break;
|
|
|
|
haveDay = true;
|
|
|
|
day = (wxDateTime_t)val;
|
|
}
|
|
else if ( isMonth )
|
|
{
|
|
haveMon = true;
|
|
monWasNumeric = true;
|
|
|
|
mon = (Month)(val - 1);
|
|
}
|
|
}
|
|
else // not a number
|
|
{
|
|
// be careful not to overwrite the current mon value
|
|
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
|
|
// 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;
|
|
|
|
haveMon = true;
|
|
}
|
|
else // not a valid month name
|
|
{
|
|
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;
|
|
|
|
haveWDay = true;
|
|
}
|
|
else // not a valid weekday name
|
|
{
|
|
// try the ordinals
|
|
static const char *const ordinals[] =
|
|
{
|
|
wxTRANSLATE("first"),
|
|
wxTRANSLATE("second"),
|
|
wxTRANSLATE("third"),
|
|
wxTRANSLATE("fourth"),
|
|
wxTRANSLATE("fifth"),
|
|
wxTRANSLATE("sixth"),
|
|
wxTRANSLATE("seventh"),
|
|
wxTRANSLATE("eighth"),
|
|
wxTRANSLATE("ninth"),
|
|
wxTRANSLATE("tenth"),
|
|
wxTRANSLATE("eleventh"),
|
|
wxTRANSLATE("twelfth"),
|
|
wxTRANSLATE("thirteenth"),
|
|
wxTRANSLATE("fourteenth"),
|
|
wxTRANSLATE("fifteenth"),
|
|
wxTRANSLATE("sixteenth"),
|
|
wxTRANSLATE("seventeenth"),
|
|
wxTRANSLATE("eighteenth"),
|
|
wxTRANSLATE("nineteenth"),
|
|
wxTRANSLATE("twentieth"),
|
|
// that's enough - otherwise we'd have problems with
|
|
// composite (or not) ordinals
|
|
};
|
|
|
|
size_t n;
|
|
for ( n = 0; n < WXSIZEOF(ordinals); n++ )
|
|
{
|
|
const wxString ord = wxGetTranslation(ordinals[n]);
|
|
const size_t len = ord.length();
|
|
if ( date.compare(p - pBegin, len, ord) == 0 )
|
|
{
|
|
p += len;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( n == WXSIZEOF(ordinals) )
|
|
{
|
|
// stop here - something unknown
|
|
break;
|
|
}
|
|
|
|
// it's a day
|
|
if ( haveDay )
|
|
{
|
|
// don't try anything here (as in case of numeric day
|
|
// above) - the symbolic day spec should always
|
|
// precede the month/year
|
|
break;
|
|
}
|
|
|
|
haveDay = true;
|
|
|
|
day = (wxDateTime_t)(n + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 )
|
|
return false;
|
|
|
|
if ( haveWDay && (haveMon || haveYear || haveDay) &&
|
|
!(haveDay && haveMon && haveYear) )
|
|
{
|
|
// 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 false;
|
|
}
|
|
|
|
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 > 0) && (year <= (int)GetNumberOfDays(mon, Inv_Year)) )
|
|
{
|
|
day = (wxDateTime_t)year;
|
|
|
|
haveMon = true;
|
|
haveYear = false;
|
|
}
|
|
//else: no, can't exchange, leave haveMon == false
|
|
}
|
|
}
|
|
|
|
if ( !haveMon )
|
|
return false;
|
|
}
|
|
|
|
if ( !haveMon )
|
|
{
|
|
mon = GetCurrentMonth();
|
|
}
|
|
|
|
if ( !haveYear )
|
|
{
|
|
year = GetCurrentYear();
|
|
}
|
|
|
|
if ( haveDay )
|
|
{
|
|
// 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 false;
|
|
|
|
Set(day, mon, year);
|
|
|
|
if ( haveWDay )
|
|
{
|
|
// check that it is really the same
|
|
if ( GetWeekDay() != wday )
|
|
return false;
|
|
}
|
|
}
|
|
else // haveWDay
|
|
{
|
|
*this = Today();
|
|
|
|
SetToWeekDayInSameWeek(wday);
|
|
}
|
|
|
|
*end = p;
|
|
|
|
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 + (end - dateStr.begin());
|
|
}
|
|
|
|
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;
|
|
} stdTimes[] =
|
|
{
|
|
{ wxTRANSLATE("noon"), 12 },
|
|
{ wxTRANSLATE("midnight"), 00 },
|
|
// anything else?
|
|
};
|
|
|
|
for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
|
|
{
|
|
const wxString timeString = wxGetTranslation(stdTimes[n].name);
|
|
if ( timeString.CmpNoCase(wxString(time, timeString.length())) == 0 )
|
|
{
|
|
Set(stdTimes[n].hour, 0, 0);
|
|
|
|
if ( end )
|
|
*end = time.begin() + timeString.length();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// try all time formats we may think about in the order from longest to
|
|
// shortest
|
|
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", // 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
|
|
|
|
// TODO: parse timezones
|
|
};
|
|
|
|
for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ )
|
|
{
|
|
if ( ParseFormat(time, timeFormats[nFmt], end) )
|
|
return true;
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Workdays and holidays support
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
|
|
{
|
|
return !wxDateTimeHolidayAuthority::IsHoliday(*this);
|
|
}
|
|
|
|
// ============================================================================
|
|
// wxDateSpan
|
|
// ============================================================================
|
|
|
|
wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
|
|
{
|
|
wxDateSpan ds1(ds);
|
|
return ds1.Multiply(n);
|
|
}
|
|
|
|
// ============================================================================
|
|
// wxTimeSpan
|
|
// ============================================================================
|
|
|
|
wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
|
|
{
|
|
return wxTimeSpan(ts).Multiply(n);
|
|
}
|
|
|
|
// this enum is only used in wxTimeSpan::Format() below but we can't declare
|
|
// it locally to the method as it provokes an internal compiler error in egcs
|
|
// 2.91.60 when building with -O2
|
|
enum TimeSpanPart
|
|
{
|
|
Part_Week,
|
|
Part_Day,
|
|
Part_Hour,
|
|
Part_Min,
|
|
Part_Sec,
|
|
Part_MSec
|
|
};
|
|
|
|
// not all strftime(3) format specifiers make sense here because, for example,
|
|
// a time span doesn't have a year nor a timezone
|
|
//
|
|
// Here are the ones which are supported (all of them are supported by strftime
|
|
// as well):
|
|
// %H hour in 24 hour format
|
|
// %M minute (00 - 59)
|
|
// %S second (00 - 59)
|
|
// %% percent sign
|
|
//
|
|
// Also, for MFC CTimeSpan compatibility, we support
|
|
// %D number of days
|
|
//
|
|
// And, to be better than MFC :-), we also have
|
|
// %E number of wEeks
|
|
// %l milliseconds (000 - 999)
|
|
wxString wxTimeSpan::Format(const wxString& format) const
|
|
{
|
|
// we deal with only positive time spans here and just add the sign in
|
|
// front for the negative ones
|
|
if ( IsNegative() )
|
|
{
|
|
wxString str(Negate().Format(format));
|
|
return "-" + str;
|
|
}
|
|
|
|
wxCHECK_MSG( !format.empty(), wxEmptyString,
|
|
wxT("NULL format in wxTimeSpan::Format") );
|
|
|
|
wxString str;
|
|
str.Alloc(format.length());
|
|
|
|
// 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
|
|
TimeSpanPart partBiggest = Part_MSec;
|
|
|
|
for ( wxString::const_iterator pch = format.begin(); pch != format.end(); ++pch )
|
|
{
|
|
wxChar ch = *pch;
|
|
|
|
if ( ch == wxT('%') )
|
|
{
|
|
// the start of the format specification of the printf() below
|
|
wxString fmtPrefix(wxT('%'));
|
|
|
|
// the number
|
|
long n;
|
|
|
|
// the number of digits for the format string, 0 if unused
|
|
unsigned digits = 0;
|
|
|
|
ch = *++pch; // get the format spec char
|
|
switch ( ch )
|
|
{
|
|
default:
|
|
wxFAIL_MSG( wxT("invalid format character") );
|
|
wxFALLTHROUGH;
|
|
|
|
case wxT('%'):
|
|
str += ch;
|
|
|
|
// skip the part below switch
|
|
continue;
|
|
|
|
case wxT('D'):
|
|
n = GetDays();
|
|
if ( partBiggest < Part_Day )
|
|
{
|
|
n %= DAYS_PER_WEEK;
|
|
}
|
|
else
|
|
{
|
|
partBiggest = Part_Day;
|
|
}
|
|
break;
|
|
|
|
case wxT('E'):
|
|
partBiggest = Part_Week;
|
|
n = GetWeeks();
|
|
break;
|
|
|
|
case wxT('H'):
|
|
n = GetHours();
|
|
if ( partBiggest < Part_Hour )
|
|
{
|
|
n %= HOURS_PER_DAY;
|
|
}
|
|
else
|
|
{
|
|
partBiggest = Part_Hour;
|
|
}
|
|
|
|
digits = 2;
|
|
break;
|
|
|
|
case wxT('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
|
|
|
|
digits = 3;
|
|
break;
|
|
|
|
case wxT('M'):
|
|
n = GetMinutes();
|
|
if ( partBiggest < Part_Min )
|
|
{
|
|
n %= MIN_PER_HOUR;
|
|
}
|
|
else
|
|
{
|
|
partBiggest = Part_Min;
|
|
}
|
|
|
|
digits = 2;
|
|
break;
|
|
|
|
case wxT('S'):
|
|
n = GetSeconds().ToLong();
|
|
if ( partBiggest < Part_Sec )
|
|
{
|
|
n %= SEC_PER_MIN;
|
|
}
|
|
else
|
|
{
|
|
partBiggest = Part_Sec;
|
|
}
|
|
|
|
digits = 2;
|
|
break;
|
|
}
|
|
|
|
if ( digits )
|
|
{
|
|
fmtPrefix << wxT("0") << digits;
|
|
}
|
|
|
|
str += wxString::Format(fmtPrefix + wxT("ld"), n);
|
|
}
|
|
else
|
|
{
|
|
// normal character, just copy
|
|
str += ch;
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
#endif // wxUSE_DATETIME
|