pikotools/src/date/date.h

1265 lines
27 KiB
C++

/*
* This file is a part of PikoTools
* and is distributed under the 2-Clause BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* Copyright (c) 2012-2023, Tomasz Sowa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef headerfile_pikotools_src_date_date
#define headerfile_pikotools_src_date_date
#include <ctime>
#include <string>
#include "convert/inttostr.h"
namespace pt
{
/*
this class represents a Date (year, month, day, hour, min, sec)
it has O(1) algorithm when converting from/to time_t (seconds from the Unix Epoch)
algorithm description:
http://alcor.concordia.ca/~gpkatch/gdate-algorithm.html
http://alcor.concordia.ca/~gpkatch/gdate-method.html
current limitation:
we do not support leap seconds
*/
class Date
{
public:
static const time_t ONE_MIN = 60; /* one minute in seconds */
static const time_t ONE_HOUR = ONE_MIN * 60; /* one hour in seconds */
static const time_t ONE_DAY = ONE_HOUR * 24; /* one day in seconds */
static const time_t ONE_WEEK = ONE_DAY * 7; /* one week in seconds */
/*
the date
*/
int year; // 1970 - 10000
int month; // 1 - 12
int day; // 1 - 31
int hour; // 0 - 23
int min; // 0 - 59
int sec; // 0 - 59
/*
default c-ctor sets the Unix Epoch (Clear method): 1970.01.01 00:00:00
*/
Date();
/*
converting from Date, time_t (seconds from the Unix Epoch), tm structure, and strings
*/
Date(const Date & d);
Date(time_t t);
Date(const tm & t);
Date(const char * str);
Date(const wchar_t * str);
Date(const std::string & str);
Date(const std::wstring & str);
Date & operator=(const Date & d);
Date & operator=(time_t t);
Date & operator=(const tm & t);
Date & operator=(const char * str);
Date & operator=(const wchar_t * str);
Date & operator=(const std::string & str);
Date & operator=(const std::wstring & str);
/*
adding/subtracting time_t (seconds from the Unix Epoch)
*/
Date operator+(time_t t) const;
Date operator-(time_t t) const;
Date & operator+=(time_t t);
Date & operator-=(time_t t);
/*
swapping the contents of *this with date
*/
void Swap(Date & date);
/*
converts time_t in seconds (from the Unix Epoch) to this object
*/
void FromTime(time_t t);
/*
converts tm structure to this object
*/
void FromTm(const tm & t);
/*
returns time_t (in seconds from the Unix Epoch)
*/
time_t ToTime() const;
/*
return tm structure
only tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec fields are set
the rest is equal to zero
*/
tm ToTm() const;
/*
getting/setting the number of days from 0000:03:01 (year 0, month 3 - March, day 1)
(ToDays() and FromDays() can work even with a year less than 1970)
*/
long long ToDays() const;
void FromDays(long long g);
/*
returns a difference in second between two dates
(always a value greater than zero)
*/
time_t operator-(const Date & d) const;
/*
'Compare' methods returns zero if this and d are equal
return value less than zero if this is lower than d
and a value greater than zero if this is greater than d
*/
int CompareDate(const Date & d, bool ignore_year = false) const;
int CompareTime(const Date & d) const;
int Compare(const Date & d, bool ignore_year = false) const;
/*
returns true if year, month and day are the same
*/
bool IsTheSameDay(const Date & d) const;
/*
returns true if hour, min and sec are the same
*/
bool IsTheSameHour(const Date & d) const;
/*
operators for comparing
*/
bool operator==(const Date & d) const;
bool operator!=(const Date & d) const;
bool operator>(const Date & d) const;
bool operator>=(const Date & d) const;
bool operator<(const Date & d) const;
bool operator<=(const Date & d) const;
/*
set the date to: 1970-01-01
the time is not changed
*/
void ClearDate();
/*
set the time to 00:00:00
the date is not changed
*/
void ClearTime();
/*
set the Unix Epoch: 1970-01-01 00:00:00
*/
void Clear();
/*
assert a correct date (values will be from the correct range)
year: 1970 - 10000
month: 1 - 12
day: 1 - MonthLen
hour: 0 - 23
min: 0 - 59
sec: 0 - 59
*/
void AssertCorrectDate();
/*
return true if values are from the correct range
year: 1970 - 10000
month: 1 - 12
day: 1 - MonthLen
hour: 0 - 23
min: 0 - 59
sec: 0 - 59
*/
bool IsCorrectDate();
/*
returns how many days there is in a month
y - year 1970 - ...
m - month 1-12
*/
static int MonthLen(int y, int m);
/*
returns true if 'y' is a leap year
leap year has one additional day (in february) - so the year lasts 366 days
*/
static bool IsYearLeap(int y);
/*
returns true if the currecn year is a leap year
*/
bool IsYearLeap() const;
/*
returns a day index from a week
sunday - 0
monday - 1
...
saturday - 6
*/
int WeekDay() const;
/*
this method outputs to the given stream the number of a month in Roman numerals
e.g: if month is equal to 3 then III will be put to 'out'
month should be in the range [1,12]
*/
template<class Stream>
static void SerializeMonthAsRoman(Stream & out, int month);
/*
this method outputs to the given stream: YYYY-MM-DD, eg. 1990-02-12
ISO 8601 format
*/
template<class Stream>
void SerializeYearMonthDay(Stream & out, bool roman_month = false) const;
/*
this method outputs to the given stream: HH:MM:SS, eg: 13:05:39
ISO 8601 format
*/
template<class Stream>
void SerializeHourMinSec(Stream & out) const;
/*
this method outputs to the given stream: MM-DD, eg. 02-12 (02 month, 12 day)
*/
template<class Stream>
void SerializeMonthDay(Stream & out, bool roman_month = false) const;
/*
this method outputs to the given stream: HH:MM, eg: 13:05
*/
template<class Stream>
void SerializeHourMin(Stream & out) const;
/*
this method outputs to the given stream: YYYY-MM-DD HH:MM:SS, eg: 1990-02-12 13:05:39
ISO 8601 format
*/
template<class Stream>
void Serialize(Stream & out, bool roman_month = false, bool with_seconds = true) const;
/*
this method outputs to the given stream: YYYY-MM-DDTHH:MM:SSZ, eg: 1990-02-12T13:05:39Z
ISO 8601 format
*/
template<class Stream>
void SerializeISO(Stream & out) const;
/*
parsing day month and year
the input string can be as follows:
"12-10-2008"
white characters are ommited and the method stops after reading the year
so the input string can be:
" 12 - 10 - 2008some text "
a white character means a space or a tab
as a separator can be '-' '/' or '.'
so below strings have the same meaning:
" 12.10.2008 "
" 12/10 / 2008 "
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseDayMonthYear(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseDayMonthYear(const StringType & str);
/*
parsing year month and day
the input string can be as follows:
"2008-10-12"
white characters are ommited and the method stops after reading the day
so the input string can be:
" 2008 - 10 - 12some text "
a white character means a space or a tab
as a separator can be '-' '/' or '.'
so below strings have the same meaning:
" 2008.10.12 "
" 2008/10 / 12 "
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseYearMonthDay(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseYearMonthDay(const StringType & str);
/*
parsing month and day
the input string can be as follows:
"10-12" (month: 10, day: 12)
white characters are ommited and the method stops after reading the day
so the input string can be:
" 10 - 12some text "
a white character means a space or a tab
as a separator can be '-' '/' or '.'
so below strings have the same meaning:
" 10.12 "
" 10 / 12 "
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseMonthDay(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseMonthDay(const StringType & str);
/*
parsing hour minutes and seconds
the input string can be as follows:
"14:10:35"
white characters are ommited and the method stops after reading seconds
so the input string can be:
" 14 : 10 : 35some text "
a white character means a space or a tab
a separator is only the ':' character
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseHourMinSec(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseHourMinSec(const StringType & str);
/*
parsing hour and minutes
the input string can be as follows:
"14:10"
white characters are ommited and the method stops after reading minutes
so the input string can be:
" 14 : 10some text "
a white character means a space or a tab
a separator is only the ':' character
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseHourMin(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseHourMin(const StringType & str);
template<class CStringType>
bool ParseZoneOffset(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseZoneOffset(const StringType & str);
/*
parsing a year and a month (if exists) and a day (if exists)
the input string can be as follows: YYYY[sep]MM[sep]DD
the separator is optional, it can be: '.', '-', '/' or just white characters
(white characters before and after a separator are skipped)
sample valid dates:
"2022"
" 2022"
"202212"
" 2022 12"
"2022-12"
" 2022 - 12"
"20221222"
" 20221222 "
" 2022 12 22 "
" 2022 12 22 "
"2022-12-22"
" 2022-12-22"
" 2022 - 12 - 22 "
if the month is not provided then it is set to 01,
if the day is not provided then it is set to 01 too
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseDate(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseDate(const StringType & str);
/*
parsing an hour and minutes (if exists) and seconds (if exists)
the input string can be as follows: HH[sep]MM[sep]SS
separator is optional, it can be ':' or just white characters
(white characters before and after a separator are skipped)
"14" -- only an hour given (min and sec will be zero)
"14:10" -- hour with minutes (sec will be zero)
"1410" -- the same as above
"14:10:35" -- hour, minutes and seconds
"141035" -- the same as above
"14 10 35" -- the same as above
white characters are ommited so these are valid strings too:
" 14 : 10 : 35 "
" 14 : 10 : 35some text "
a white character means a space or a tab
a decimal fraction may be added to the lowest order time element present, e.g:
"14.500" = 14:30:00
"14:10.500" = 14:10:30
"14:10:35.500" = 14:10:35 (the seconds' fraction is skipped)
instead of dot you can use a comma too,
the fraction part can be from 1 to 9 digits
this method doesn't test if the values are correct
use IsCorrectDate() to check
*/
template<class CStringType>
bool ParseTime(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseTime(const StringType & str);
/*
parsing month, day, hour and minutes (if exists) and seconds (if exists)
the input string can be as follows:
"10-23 14" -- only month, day and hour given (min and sec will be zero)
"10-23 14:10" -- month, day and hour with minutes (sec will be zero)
"10-23 14:10:35" -- month, day, hour, minutes and seconds
white characters are ommited so these are valid strings too:
" 10 - 23 14 : 10 : 35 "
" 10 - 23 14 : 10 : 35some text "
a white character means a space or a tab
this method doesn't test if the values are correct
use IsCorrectDate() to check
the time is parsed with ParseTime() method, look there for an additional description about available time strings
*/
template<class CStringType>
bool ParseMonthDayTime(const CStringType * str, const CStringType ** str_after = nullptr);
template<class StringType>
bool ParseMonthDayTime(const StringType & str);
/*
parsing year/month/day hour:min:sec
the input strings can be as follows:
"20081012 141035"
"20081012T141035"
"20081012141035"
"2008-10-12 14:10:35"
"2008/10/12 14:10:35"
"2008.10.12 14:10:35"
"2008-10/12 14:10:35"
"2008-10-12 14:10:35.500"
"2008-10-12 14:10.500"
"2008-10-12 14.500"
"2008-10-12 14"
"2008-10 14"
"2008 14"
months and days can be omitted - in such a case 01 is set,
similar min and sec can be omitted (they are assumed to be 00 in such a case)
white characters are ommited
so the input string can be:
" 2008 - 10 / 12 14 : 10 : 35 "
a white character means a space or a tab
as an optional separator for the date can be '-' '/' or '.'
as an optional separator for the time is the ':' character
at the and of the string there can be defined a time zone in the form of "+01:00", e.g:
"2008-10-12 14:10:35+01:00"
"2008-10-12 14:10:35-02:00"
or without a separator:
"2008-10-12 14:10:35+0100"
"2008-10-12 14:10:35-0200"
or just a 'Z' character indicating 00:00 zone
"2008-10-12 14:10:35Z"
at the end the method checks if the values are correct
(by using IsCorrectDate())
the format is similar to ISO 8601
https://en.wikipedia.org/wiki/ISO_8601
at the moment there is no support for week dates e.g. "2008-W01-3" and ordinal dates e.g. "2008-010" formats
see ParseDate() and ParseTime for more examples
*/
template<class CStringType>
bool Parse(const CStringType * str, const CStringType ** str_after, bool check_time_zone = true);
template<class CStringType>
bool Parse(const CStringType * str, bool check_time_zone = true);
template<class StringType>
bool Parse(const StringType & str, bool check_time_zone = true);
private:
void AssertRange(int & val, int val_min, int val_max);
template<class Stream>
void SerializeInt(Stream & out, int val, size_t min_width) const;
template<class Stream>
void SerializeInt(Stream & out, int val) const;
template<class CStringType>
void SetAfter(const CStringType * str, const CStringType ** str_after);
template<class CStringType>
void SkipWhite(const CStringType * & str);
template<class CStringType>
bool IsDigit(const CStringType * str);
template<class CStringType>
bool ReadInt(const CStringType * & str, int & result, size_t max_digits = 0, size_t * digits_read = nullptr, int * digits_base = nullptr);
template<class CStringType>
bool ReadCommaInt(const CStringType * & str, bool & was_comma, int & result, size_t max_digits = 0, size_t * digits_read = nullptr, int * base = nullptr);
template<class CStringType>
bool ParseTimeValue(const CStringType * & str, int & value, bool & has_fraction, int & fraction, int * fraction_base);
template<class CStringType>
bool SkipSeparator(const CStringType * & str, int separator, int separator2 = -1, int separator3 = -1);
};
template<class Stream>
void Date::SerializeInt(Stream & out, int val, size_t min_width) const
{
char buf[64];
size_t len;
if( Toa(val, buf, sizeof(buf) / sizeof(char), 10, &len) )
{
for(size_t i = len ; i < min_width ; ++i)
{
out << '0';
}
out << buf;
}
}
template<class Stream>
void Date::SerializeInt(Stream & out, int val) const
{
SerializeInt(out, val, 2);
}
template<class Stream>
void Date::SerializeMonthAsRoman(Stream & out, int month)
{
const char * month_roman[] = { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII" };
if( month >= 1 && month <= 12 )
{
const char * month_str = month_roman[month - 1];
for( ; *month_str ; ++month_str)
out << *month_str;
}
}
template<class Stream>
void Date::SerializeYearMonthDay(Stream & out, bool roman_month) const
{
SerializeInt(out, year, 4);
out << '-';
if( roman_month )
SerializeMonthAsRoman(out, month);
else
SerializeInt(out, month);
out << '-';
SerializeInt(out, day);
}
template<class Stream>
void Date::SerializeHourMinSec(Stream & out) const
{
SerializeInt(out, hour);
out << ':';
SerializeInt(out, min);
out << ':';
SerializeInt(out, sec);
}
template<class Stream>
void Date::SerializeMonthDay(Stream & out, bool roman_month) const
{
if( roman_month )
SerializeMonthAsRoman(out, month);
else
SerializeInt(out, month);
out << '-';
SerializeInt(out, day);
}
template<class Stream>
void Date::SerializeHourMin(Stream & out) const
{
SerializeInt(out, hour);
out << ':';
SerializeInt(out, min);
}
template<class Stream>
void Date::Serialize(Stream & out, bool roman_month, bool with_seconds) const
{
SerializeYearMonthDay(out, roman_month);
out << ' ';
if( with_seconds )
SerializeHourMinSec(out);
else
SerializeHourMin(out);
}
template<class Stream>
void Date::SerializeISO(Stream & out) const
{
SerializeYearMonthDay(out, false);
out << 'T';
SerializeHourMinSec(out);
out << 'Z';
}
template<class CStringType>
bool Date::ParseDayMonthYear(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
if( ReadInt(str, day) && SkipSeparator(str, '.', '-', '/') )
if( ReadInt(str, month) && SkipSeparator(str, '.', '-', '/') )
if( ReadInt(str, year) )
result = true;
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseDayMonthYear(const StringType & str)
{
return ParseDayMonthYear(str.c_str());
}
template<class CStringType>
bool Date::ParseYearMonthDay(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
if( ReadInt(str, year) && SkipSeparator(str, '.', '-', '/') )
if( ReadInt(str, month) && SkipSeparator(str, '.', '-', '/') )
if( ReadInt(str, day) )
result = true;
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseYearMonthDay(const StringType & str)
{
return ParseYearMonthDay(str.c_str());
}
template<class CStringType>
bool Date::ParseMonthDay(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
if( ReadInt(str, month) && SkipSeparator(str, '.', '-', '/') )
if( ReadInt(str, day) )
result = true;
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseMonthDay(const StringType & str)
{
return ParseMonthDay(str.c_str());
}
template<class CStringType>
bool Date::ParseHourMinSec(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
if( ReadInt(str, hour) && SkipSeparator(str, ':') )
if( ReadInt(str, min) && SkipSeparator(str, ':') )
if( ReadInt(str, sec) )
result = true;
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseHourMinSec(const StringType & str)
{
return ParseHourMinSec(str.c_str());
}
template<class CStringType>
bool Date::ParseHourMin(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
if( ReadInt(str, hour) && SkipSeparator(str, ':') )
if( ReadInt(str, min) )
result = true;
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseHourMin(const StringType & str)
{
return ParseHourMin(str.c_str());
}
template<class CStringType>
bool Date::ParseZoneOffset(const CStringType * str, const CStringType ** str_after)
{
bool result = false;
bool is_sign = false;
int offset_hour = 0;
int offset_min = 0;
SkipWhite(str);
if( *str == '-' || *str == '+' )
{
if( *str == '-' )
is_sign = true;
str += 1;
result = true; // if there are no digits we return true
if( IsDigit(str) )
{
result = ReadInt(str, offset_hour, 2) && offset_hour >= -12 && offset_hour <= 14;
if( result )
{
SkipSeparator(str, ':');
if( IsDigit(str) )
{
// offset_min is optional
result = ReadInt(str, offset_min, 2) && offset_min > -60 && offset_min < 60;
}
if( result )
{
time_t offset = (time_t)offset_hour * 60 * 60 + (time_t)offset_min * 60;
if( is_sign )
offset = -offset;
FromTime(ToTime() - offset);
}
}
}
}
SetAfter(str, str_after);
return result;
}
template<class StringType>
bool Date::ParseZoneOffset(const StringType & str)
{
return ParseZoneOffset(str.c_str());
}
template<class CStringType>
bool Date::ParseDate(const CStringType * str, const CStringType ** str_after)
{
bool status = false;
ClearDate();
if( ReadInt(str, year, 4) )
{
status = true;
SkipSeparator(str, '.', '-', '/');
if( IsDigit(str) )
{
status = ReadInt(str, month, 2);
SkipSeparator(str, '.', '-', '/');
if( status && IsDigit(str) )
{
status = ReadInt(str, day, 2);
}
}
}
SetAfter(str, str_after);
return status;
}
template<class StringType>
bool Date::ParseDate(const StringType & str)
{
return ParseDate(str.c_str());
}
template<class CStringType>
bool Date::ParseTimeValue(const CStringType * & str, int & value, bool & has_fraction, int & fraction, int * fraction_base)
{
bool status = false;
size_t digits_read = 0;
bool was_comma = false;
has_fraction = false;
fraction = 0;
*fraction_base = 1;
if( ReadInt(str, value, 2) )
{
status = ReadCommaInt(str, was_comma, fraction, 0, &digits_read, fraction_base);
if( status )
{
if( was_comma )
has_fraction = true;
SkipSeparator(str, ':');
}
}
return status;
}
template<class CStringType>
bool Date::ParseTime(const CStringType * str, const CStringType ** str_after)
{
bool status = false;
bool has_fraction = false;
int fraction = 0;
int fraction_base = 0;
ClearTime();
if( ParseTimeValue(str, hour, has_fraction, fraction, &fraction_base) )
{
status = true;
if( has_fraction )
{
min = (60L * fraction) / (long)(fraction_base);
int min_rem = (60L * fraction) % (long)(fraction_base);
sec = (min_rem * 60L) / (long)fraction_base;
}
else
if( IsDigit(str) )
{
status = ParseTimeValue(str, min, has_fraction, fraction, &fraction_base);
if( status )
{
if( has_fraction )
{
sec = (60L * fraction) / (long)(fraction_base);
}
else
if( IsDigit(str) )
{
status = ParseTimeValue(str, sec, has_fraction, fraction, &fraction_base);
// ignore the seconds fraction if exists
}
}
}
}
SetAfter(str, str_after);
return status;
}
template<class StringType>
bool Date::ParseTime(const StringType & str)
{
return ParseTime(str.c_str());
}
template<class CStringType>
bool Date::ParseMonthDayTime(const CStringType * str, const CStringType ** str_after)
{
const CStringType * after;
bool result = false;
if( ParseMonthDay(str, &after) )
if( ParseTime(after, &after) )
result = true;
SetAfter(after, str_after);
return result;
}
template<class StringType>
bool Date::ParseMonthDayTime(const StringType & str)
{
return ParseMonthDayTime(str.c_str());
}
template<class CStringType>
bool Date::Parse(const CStringType * str, const CStringType ** str_after, bool check_time_zone)
{
const CStringType * after;
bool result = false;
Clear();
if( ParseDate(str, &after) )
{
result = true;
SkipWhite(after);
if( *after == 'T' )
{
after += 1;
SkipWhite(after);
}
if( IsDigit(after) )
{
result = ParseTime(after, &after);
if( result && check_time_zone )
{
SkipWhite(after);
if( *after == 'Z' )
{
after += 1;
}
else
if( *after == '-' || *after == '+' )
{
result = ParseZoneOffset(after, &after);
}
}
}
}
SetAfter(after, str_after);
if( result )
result = IsCorrectDate();
return result;
}
template<class CStringType>
bool Date::Parse(const CStringType * str, bool check_time_zone)
{
const CStringType * str_after = nullptr;
return Parse(str, &str_after, check_time_zone);
}
template<class StringType>
bool Date::Parse(const StringType & str, bool check_time_zone)
{
const typename StringType::value_type * after_string = nullptr;
return Parse(str.c_str(), &after_string, check_time_zone);
}
template<class CStringType>
void Date::SetAfter(const CStringType * str, const CStringType ** str_after)
{
if( str_after )
*str_after = str;
}
template<class CStringType>
void Date::SkipWhite(const CStringType * & str)
{
while( *str==' ' || *str=='\t' )
str += 1;
}
template<class CStringType>
bool Date::IsDigit(const CStringType * str)
{
return (*str >= '0' && *str <= '9');
}
template<class CStringType>
bool Date::ReadInt(const CStringType * & str, int & result, size_t max_digits, size_t * digits_read, int * digits_base)
{
SkipWhite(str);
result = 0;
size_t read_chars = 0;
int base = 1;
bool skip_last_digits = false;
if( max_digits == 0 )
skip_last_digits = true;
if( max_digits == 0 || max_digits > 9 )
max_digits = 9;
while( IsDigit(str) && read_chars < max_digits )
{
result = result * 10 + (*str - '0');
base = base * 10;
str += 1;
read_chars += 1;
}
if( skip_last_digits )
{
while( IsDigit(str) )
{
str += 1;
read_chars += 1;
}
}
if( digits_read )
*digits_read = read_chars;
if( digits_base )
*digits_base = base;
return read_chars > 0 && read_chars <= max_digits;
}
template<class CStringType>
bool Date::ReadCommaInt(const CStringType * & str, bool & was_comma, int & result, size_t max_digits, size_t * digits_read, int * base)
{
bool status = true; // the comma is optional so we return true if it not exists
result = 0;
was_comma = false;
if( digits_read )
*digits_read = 0;
if( base )
*base = 1;
if( *str == '.' || *str == ',' )
{
str += 1;
was_comma = true;
if( IsDigit(str) )
status = ReadInt(str, result, max_digits, digits_read, base);
}
return status;
}
template<class CStringType>
bool Date::SkipSeparator(const CStringType * & str, int separator, int separator2, int separator3)
{
SkipWhite(str);
if( *str == separator )
{
str += 1;
SkipWhite(str);
return true;
}
if( separator2 != -1 && *str == separator2 )
{
str += 1;
SkipWhite(str);
return true;
}
if( separator3 != -1 && *str == separator3 )
{
str += 1;
SkipWhite(str);
return true;
}
return false;
}
} // namespace
#endif