/* * This file is a part of PikoTools * and is distributed under the (new) BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2012-2018, 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: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * 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. * * * Neither the name Tomasz Sowa nor the names of contributors to this * project may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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 OWNER 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_picotools_mainparser_mainparser #define headerfile_picotools_mainparser_mainparser #include #include #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: /* the date */ int year; // 1970 - ... 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' 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 Compare(const Date & d) 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 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: YYYY-MM-DD, eg. 1990-02-12 ISO 8601 format */ template void SerializeYearMonthDay(Stream & out) const; /* this method outputs to the given stream: HH:MM:SS, eg: 13:05:39 ISO 8601 format */ template void SerializeHourMinSec(Stream & out) const; /* this method outputs to the given stream: MM-DD, eg. 02-12 (02 month, 12 day) */ template void SerializeMonthDay(Stream & out) const; /* this method outputs to the given stream: HH:MM, eg: 13:05 */ template 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 void Serialize(Stream & out) const; /* this method outputs to the given stream: YYYY-MM-DDTHH:MM:SSZ, eg: 1990-02-12T13:05:39Z ISO 8601 format */ template 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 bool ParseDayMonthYear(const CStringType * str, const CStringType ** str_after = 0); template 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 bool ParseYearMonthDay(const CStringType * str, const CStringType ** str_after = 0); template 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 bool ParseMonthDay(const CStringType * str, const CStringType ** str_after = 0); template 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 bool ParseHourMinSec(const CStringType * str, const CStringType ** str_after = 0); template 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 bool ParseHourMin(const CStringType * str, const CStringType ** str_after = 0); template bool ParseHourMin(const StringType & str); template bool ParseZoneOffset(const CStringType * str, const CStringType ** str_after = 0); template bool ParseZoneOffset(const StringType & str); /* parsing hour and minutes (if exists) and seconds (if exists) the input string can be as follows: "14" -- only an hour given (min and sec will be zero) "14:10" -- hour with minutes (sec will be zero) "14:10:35" -- hour, minutes and seconds 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 this method doesn't test if the values are correct use IsCorrectDate() to check */ template bool ParseTime(const CStringType * str, const CStringType ** str_after = 0); template 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 */ template bool ParseMonthDayTime(const CStringType * str, const CStringType ** str_after = 0); template bool ParseMonthDayTime(const StringType & str); /* parsing year/month/day hour:min:sec the input strings can be as follows: "2008-10-12 14:10:35" "2008/10/12 14:10:35" "2008.10.12 14:10:35" "2008-10/12 14:10:35" 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 a separator for year/month/day can be '-' '/' or '.' see ParseYearMonthDay() for details as a separator for hour:min:sec is the ':' character see ParseHourMinSec() for details at the end the method checks if the values are correct (by using IsCorrectDate()) */ template bool Parse(const CStringType * str, const CStringType ** str_after = 0); template bool Parse(const StringType & str); private: void AssertRange(int & val, int val_min, int val_max); template void SerializeInt(Stream & out, int val, size_t min_width) const; template void SerializeInt(Stream & out, int val) const; template void SetAfter(const CStringType * str, const CStringType ** str_after); template void SkipWhite(const CStringType * & str); template bool ReadInt(const CStringType * & str, int & result, size_t max_digits = 0); template bool SkipSeparator(const CStringType * & str, int separator, int separator2 = -1, int separator3 = -1); }; template 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 void Date::SerializeInt(Stream & out, int val) const { SerializeInt(out, val, 2); } template void Date::SerializeYearMonthDay(Stream & out) const { SerializeInt(out, year, 4); out << '-'; SerializeInt(out, month); out << '-'; SerializeInt(out, day); } template void Date::SerializeHourMinSec(Stream & out) const { SerializeInt(out, hour); out << ':'; SerializeInt(out, min); out << ':'; SerializeInt(out, sec); } template void Date::SerializeMonthDay(Stream & out) const { SerializeInt(out, month); out << '-'; SerializeInt(out, day); } template void Date::SerializeHourMin(Stream & out) const { SerializeInt(out, hour); out << ':'; SerializeInt(out, min); } template void Date::Serialize(Stream & out) const { SerializeYearMonthDay(out); out << ' '; SerializeHourMinSec(out); } template void Date::SerializeISO(Stream & out) const { SerializeYearMonthDay(out); out << 'T'; SerializeHourMinSec(out); out << 'Z'; } template 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 bool Date::ParseDayMonthYear(const StringType & str) { return ParseDayMonthYear(str.c_str()); } template 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 bool Date::ParseYearMonthDay(const StringType & str) { return ParseYearMonthDay(str.c_str()); } template 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 bool Date::ParseMonthDay(const StringType & str) { return ParseMonthDay(str.c_str()); } template 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 bool Date::ParseHourMinSec(const StringType & str) { return ParseHourMinSec(str.c_str()); } template 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 bool Date::ParseHourMin(const StringType & str) { return ParseHourMin(str.c_str()); } template 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; if( ReadInt(str, offset_hour, 2) && offset_hour >= -12 && offset_hour <= 14 ) { SkipWhite(str); SetAfter(str, str_after); if( *str == ':' ) { str += 1; SkipWhite(str); SetAfter(str, str_after); } if( ReadInt(str, offset_min, 2) && offset_min > -60 && offset_min < 60 ) { SetAfter(str, str_after); } else { offset_min = 0; } time_t offset = (time_t)offset_hour * 60 * 60 + (time_t)offset_min * 60; result = true; if( is_sign ) offset = -offset; FromTime(ToTime() - offset); } } return result; } template bool Date::ParseZoneOffset(const StringType & str) { return ParseZoneOffset(str.c_str()); } template bool Date::ParseTime(const CStringType * str, const CStringType ** str_after) { if( !ReadInt(str, hour) ) { SetAfter(str, str_after); return false; } min = 0; sec = 0; if( !SkipSeparator(str, ':') ) { SetAfter(str, str_after); return true; // only an hour given } if( !ReadInt(str, min) ) { SetAfter(str, str_after); return false; } if( !SkipSeparator(str, ':') ) { SetAfter(str, str_after); return true; // only an hour and minutes given } if( !ReadInt(str, sec) ) { SetAfter(str, str_after); return false; } SetAfter(str, str_after); return true; } template bool Date::ParseTime(const StringType & str) { return ParseTime(str.c_str()); } template 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 bool Date::ParseMonthDayTime(const StringType & str) { return ParseMonthDayTime(str.c_str()); } template bool Date::Parse(const CStringType * str, const CStringType ** str_after) { const CStringType * after; bool result = false; if( ParseYearMonthDay(str, &after) ) { SkipWhite(after); if( *after == 'T' ) { // ISO 8601 format // https://en.wikipedia.org/wiki/ISO_8601 // at the moment skip the 'T' character only after += 1; } if( ParseHourMinSec(after, &after) ) { SkipWhite(after); result = true; if( *after == 'Z' ) { after += 1; } else { // we dont have to check errors here ParseZoneOffset(after, &after); } } } SetAfter(after, str_after); if( result ) result = IsCorrectDate(); return result; } template bool Date::Parse(const StringType & str) { return Parse(str.c_str()); } template void Date::SetAfter(const CStringType * str, const CStringType ** str_after) { if( str_after ) *str_after = str; } template void Date::SkipWhite(const CStringType * & str) { while( *str==' ' || *str=='\t' ) str += 1; } template bool Date::ReadInt(const CStringType * & str, int & result, size_t max_digits) { bool something_read = false; SkipWhite(str); result = 0; size_t len = 0; while( *str >= '0' && *str <= '9' && (max_digits == 0 || len < max_digits)) { result = result * 10 + (*str - '0'); str += 1; len += 1; something_read = true; if( result > 10000 ) { // we assumed the max year to be 10000 return false; } } return something_read; } template bool Date::SkipSeparator(const CStringType * & str, int separator, int separator2, int separator3) { SkipWhite(str); if( *str == separator ) { str += 1; return true; } if( separator2 != -1 && *str == separator2 ) { str += 1; return true; } if( separator3 != -1 && *str == separator3 ) { str += 1; return true; } return false; } } // namespace #endif