mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
657 lines
17 KiB
C++
657 lines
17 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
// of this software and associated documentation files (the "Software"), to deal
|
||
// in the Software without restriction, including without limitation the rights
|
||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the Software is
|
||
// furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included
|
||
// in all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
// SOFTWARE.
|
||
//
|
||
// http://www.opensource.org/licenses/mit-license.php
|
||
//
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
#include <cmake.h>
|
||
#include <Lexer.h>
|
||
#include <ISO8601.h>
|
||
#include <Date.h>
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601d::ISO8601d ()
|
||
{
|
||
clear ();
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601d::~ISO8601d ()
|
||
{
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601d::operator time_t () const
|
||
{
|
||
return _value;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Supported:
|
||
//
|
||
// result ::= date 'T' time 'Z' # UTC
|
||
// | date 'T' time # Local
|
||
// | date-ext 'T' time-ext 'Z' # UTC
|
||
// | date-ext 'T' time-ext offset-ext # Specified TZ
|
||
// | date-ext 'T' time-ext # Local
|
||
// | date-ext # Local
|
||
// | time-ext 'Z'
|
||
// | time-ext offset-ext Not needed
|
||
// | time-ext
|
||
// ;
|
||
//
|
||
// date-ext ::= ±YYYYY-MM-DD Νot needed
|
||
// | ±YYYYY-Www-D Νot needed
|
||
// | ±YYYYY-Www Νot needed
|
||
// | ±YYYYY-DDD Νot needed
|
||
// | YYYY-MM-DD
|
||
// | YYYY-DDD
|
||
// | YYYY-Www-D
|
||
// | YYYY-Www
|
||
// ;
|
||
//
|
||
// time-ext ::= hh:mm:ss[,ss]
|
||
// | hh:mm[,mm]
|
||
// | hh[,hh] Ambiguous (number)
|
||
// ;
|
||
//
|
||
// time-utc-ext ::= hh:mm[:ss] 'Z' ;
|
||
//
|
||
// offset-ext ::= ±hh[:mm] ;
|
||
//
|
||
// Not yet supported:
|
||
//
|
||
// recurrence ::=
|
||
// | 'R' [n] '/' designated '/' datetime-ext # duration end
|
||
// | 'R' [n] '/' designated '/' datetime # duration end
|
||
// | 'R' [n] '/' designated # duration
|
||
// | 'R' [n] '/' datetime-ext '/' designated # start duration
|
||
// | 'R' [n] '/' datetime-ext '/' datetime-ext # start end
|
||
// | 'R' [n] '/' datetime '/' designated # start duration
|
||
// | 'R' [n] '/' datetime '/' datetime # start end
|
||
// ;
|
||
//
|
||
bool ISO8601d::parse (const std::string& input, std::string::size_type& start)
|
||
{
|
||
auto i = start;
|
||
Nibbler n (input.substr (i));
|
||
|
||
if (parse_date_time (n) || // Strictest first.
|
||
parse_date_time_ext (n) ||
|
||
parse_date_ext (n) ||
|
||
parse_time_utc_ext (n) ||
|
||
parse_time_off_ext (n) ||
|
||
parse_time_ext (n)) // Time last, as it is the most permissive.
|
||
{
|
||
// Check the values and determine time_t.
|
||
if (validate ())
|
||
{
|
||
// Record cursor position.
|
||
start = n.cursor ();
|
||
|
||
resolve ();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
void ISO8601d::clear ()
|
||
{
|
||
_year = 0;
|
||
_month = 0;
|
||
_week = 0;
|
||
_weekday = 0;
|
||
_julian = 0;
|
||
_day = 0;
|
||
_seconds = 0;
|
||
_offset = 0;
|
||
_utc = false;
|
||
_value = 0;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
bool ISO8601d::parse_date_time (Nibbler& n)
|
||
{
|
||
n.save ();
|
||
int year, month, day, hour, minute, second;
|
||
if (n.getDigit4 (year) &&
|
||
n.getDigit2 (month) && month &&
|
||
n.getDigit2 (day) && day &&
|
||
n.skip ('T') &&
|
||
n.getDigit2 (hour) &&
|
||
n.getDigit2 (minute) && minute < 60 &&
|
||
n.getDigit2 (second) && second < 60)
|
||
{
|
||
if (n.skip ('Z'))
|
||
_utc = true;
|
||
|
||
_year = year;
|
||
_month = month;
|
||
_day = day;
|
||
_seconds = (((hour * 60) + minute) * 60) + second;
|
||
|
||
return true;
|
||
}
|
||
|
||
_year = 0;
|
||
_month = 0;
|
||
_day = 0;
|
||
_seconds = 0;
|
||
|
||
n.restore ();
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// date-ext 'T' time-ext 'Z'
|
||
// date-ext 'T' time-ext offset-ext
|
||
// date-ext 'T' time-ext
|
||
bool ISO8601d::parse_date_time_ext (Nibbler& n)
|
||
{
|
||
n.save ();
|
||
if (parse_date_ext (n))
|
||
{
|
||
if (n.skip ('T') &&
|
||
parse_time_ext (n))
|
||
{
|
||
if (n.skip ('Z'))
|
||
_utc = true;
|
||
else if (parse_off_ext (n))
|
||
;
|
||
|
||
if (! Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
|
||
// Restore date_ext
|
||
_year = 0;
|
||
_month = 0;
|
||
_week = 0;
|
||
_weekday = 0;
|
||
_julian = 0;
|
||
_day = 0;
|
||
}
|
||
|
||
n.restore ();
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// YYYY-MM-DD
|
||
// YYYY-DDD
|
||
// YYYY-Www-D
|
||
// YYYY-Www
|
||
bool ISO8601d::parse_date_ext (Nibbler& n)
|
||
{
|
||
Nibbler backup (n);
|
||
int year;
|
||
if (n.getDigit4 (year) &&
|
||
n.skip ('-'))
|
||
{
|
||
int month;
|
||
int day;
|
||
if (n.skip ('W') &&
|
||
n.getDigit2 (_week) && _week)
|
||
{
|
||
if (n.skip ('-') &&
|
||
n.getDigit (_weekday))
|
||
{
|
||
}
|
||
|
||
_year = year;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
else if (n.getDigit3 (_julian) && _julian)
|
||
{
|
||
_year = year;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
else if (n.getDigit2 (month) && month &&
|
||
n.skip ('-') &&
|
||
n.getDigit2 (day) && day)
|
||
{
|
||
_year = year;
|
||
_month = month;
|
||
_day = day;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// ±hh[:mm]
|
||
bool ISO8601d::parse_off_ext (Nibbler& n)
|
||
{
|
||
Nibbler backup (n);
|
||
std::string sign;
|
||
if (n.getN (1, sign) && (sign == "+" || sign == "-"))
|
||
{
|
||
int offset;
|
||
int hh;
|
||
int mm;
|
||
if (n.getDigit2 (hh) && hh <= 12 &&
|
||
!n.getDigit (mm))
|
||
{
|
||
offset = hh * 3600;
|
||
|
||
if (n.skip (':'))
|
||
{
|
||
if (n.getDigit2 (mm) && mm < 60)
|
||
{
|
||
offset += mm * 60;
|
||
}
|
||
else
|
||
{
|
||
n = backup;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_offset = (sign == "-") ? -offset : offset;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// hh:mm[:ss]
|
||
bool ISO8601d::parse_time_ext (Nibbler& n)
|
||
{
|
||
Nibbler backup (n);
|
||
int seconds = 0;
|
||
int hh;
|
||
int mm;
|
||
int ss;
|
||
if (n.getDigit2 (hh) && hh <= 24 &&
|
||
n.skip (':') &&
|
||
n.getDigit2 (mm) && mm < 60)
|
||
{
|
||
seconds = (hh * 3600) + (mm * 60);
|
||
|
||
if (n.skip (':'))
|
||
{
|
||
if (n.getDigit2 (ss) && ss < 60)
|
||
{
|
||
seconds += ss;
|
||
_seconds = seconds;
|
||
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
_seconds = seconds;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// time-ext 'Z'
|
||
bool ISO8601d::parse_time_utc_ext (Nibbler& n)
|
||
{
|
||
n.save ();
|
||
if (parse_time_ext (n) &&
|
||
n.skip ('Z'))
|
||
{
|
||
_utc = true;
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
|
||
n.restore ();
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// time-ext offset-ext
|
||
bool ISO8601d::parse_time_off_ext (Nibbler& n)
|
||
{
|
||
Nibbler backup (n);
|
||
if (parse_time_ext (n) &&
|
||
parse_off_ext (n))
|
||
{
|
||
if (!Lexer::isDigit (n.next ()))
|
||
return true;
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Using Zeller's Congruence.
|
||
int ISO8601d::dayOfWeek (int year, int month, int day)
|
||
{
|
||
int adj = (14 - month) / 12;
|
||
int m = month + 12 * adj - 2;
|
||
int y = year - adj;
|
||
return (day + (13 * m - 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Validation via simple range checking.
|
||
bool ISO8601d::validate ()
|
||
{
|
||
// _year;
|
||
if ((_year && (_year < 1900 || _year > 2100)) ||
|
||
(_month && (_month < 1 || _month > 12)) ||
|
||
(_week && (_week < 1 || _week > 53)) ||
|
||
(_weekday && (_weekday < 0 || _weekday > 6)) ||
|
||
(_julian && (_julian < 1 || _julian > Date::daysInYear (_year))) ||
|
||
(_day && (_day < 1 || _day > Date::daysInMonth (_month, _year))) ||
|
||
(_seconds && (_seconds < 1 || _seconds > 86400)) ||
|
||
(_offset && (_offset < -86400 || _offset > 86400)))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// int tm_sec; seconds (0 - 60)
|
||
// int tm_min; minutes (0 - 59)
|
||
// int tm_hour; hours (0 - 23)
|
||
// int tm_mday; day of month (1 - 31)
|
||
// int tm_mon; month of year (0 - 11)
|
||
// int tm_year; year - 1900
|
||
// int tm_wday; day of week (Sunday = 0)
|
||
// int tm_yday; day of year (0 - 365)
|
||
// int tm_isdst; is summer time in effect?
|
||
// char *tm_zone; abbreviation of timezone name
|
||
// long tm_gmtoff; offset from UTC in seconds
|
||
void ISO8601d::resolve ()
|
||
{
|
||
// Don't touch the original values.
|
||
int year = _year;
|
||
int month = _month;
|
||
int week = _week;
|
||
int weekday = _weekday;
|
||
int julian = _julian;
|
||
int day = _day;
|
||
int seconds = _seconds;
|
||
int offset = _offset;
|
||
bool utc = _utc;
|
||
|
||
// Get current time.
|
||
time_t now = time (NULL);
|
||
|
||
// A UTC offset needs to be accommodated. Once the offset is subtracted,
|
||
// only local and UTC times remain.
|
||
if (offset)
|
||
{
|
||
seconds -= offset;
|
||
now -= offset;
|
||
utc = true;
|
||
}
|
||
|
||
// Get 'now' in the relevant location.
|
||
struct tm* t_now = utc ? gmtime (&now) : localtime (&now);
|
||
|
||
int seconds_now = (t_now->tm_hour * 3600) +
|
||
(t_now->tm_min * 60) +
|
||
t_now->tm_sec;
|
||
|
||
// Project forward one day if the specified seconds are earlier in the day
|
||
// than the current seconds.
|
||
if (year == 0 &&
|
||
month == 0 &&
|
||
day == 0 &&
|
||
week == 0 &&
|
||
weekday == 0 &&
|
||
seconds < seconds_now)
|
||
{
|
||
seconds += 86400;
|
||
}
|
||
|
||
// Convert week + weekday --> julian.
|
||
if (week)
|
||
{
|
||
julian = (week * 7) + weekday - dayOfWeek (year, 1, 4) - 3;
|
||
}
|
||
|
||
// Provide default values for year, month, day.
|
||
else
|
||
{
|
||
// Default values for year, month, day:
|
||
//
|
||
// y m d --> y m d
|
||
// y m - --> y m 1
|
||
// y - - --> y 1 1
|
||
// - - - --> now now now
|
||
//
|
||
if (year == 0)
|
||
{
|
||
year = t_now->tm_year + 1900;
|
||
month = t_now->tm_mon + 1;
|
||
day = t_now->tm_mday;
|
||
}
|
||
else
|
||
{
|
||
if (month == 0)
|
||
{
|
||
month = 1;
|
||
day = 1;
|
||
}
|
||
else if (day == 0)
|
||
day = 1;
|
||
}
|
||
}
|
||
|
||
if (julian)
|
||
{
|
||
month = 1;
|
||
day = julian;
|
||
}
|
||
|
||
struct tm t = {0};
|
||
t.tm_isdst = -1; // Requests that mktime/gmtime determine summer time effect.
|
||
t.tm_year = year - 1900;
|
||
t.tm_mon = month - 1;
|
||
t.tm_mday = day;
|
||
|
||
if (seconds > 86400)
|
||
{
|
||
int days = seconds / 86400;
|
||
t.tm_mday += days;
|
||
seconds %= 86400;
|
||
}
|
||
|
||
t.tm_hour = seconds / 3600;
|
||
t.tm_min = (seconds % 3600) / 60;
|
||
t.tm_sec = seconds % 60;
|
||
|
||
_value = utc ? timegm (&t) : mktime (&t);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601p::ISO8601p ()
|
||
{
|
||
clear ();
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601p::~ISO8601p ()
|
||
{
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
ISO8601p::operator time_t () const
|
||
{
|
||
return _value;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Supported:
|
||
//
|
||
// duration ::= designated # duration
|
||
//
|
||
// designated ::= 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']]
|
||
//
|
||
// Not supported:
|
||
//
|
||
// duration ::= designated '/' datetime-ext # duration end
|
||
// | degignated '/' datetime # duration end
|
||
// | designated # duration
|
||
// | 'P' datetime-ext '/' datetime-ext # start end
|
||
// | 'P' datetime '/' datetime # start end
|
||
// | 'P' datetime-ext # start
|
||
// | 'P' datetime # start
|
||
// | datetime-ext '/' designated # start duration
|
||
// | datetime-ext '/' 'P' datetime-ext # start end
|
||
// | datetime-ext '/' datetime-ext # start end
|
||
// | datetime '/' designated # start duration
|
||
// | datetime '/' 'P' datetime # start end
|
||
// | datetime '/' datetime # start end
|
||
// ;
|
||
//
|
||
bool ISO8601p::parse (const std::string& input, std::string::size_type& start)
|
||
{
|
||
auto i = start;
|
||
Nibbler n (input.substr (i));
|
||
|
||
if (parse_designated (n))
|
||
{
|
||
// Check the values and determine time_t.
|
||
if (validate ())
|
||
{
|
||
// Record cursor position.
|
||
start = n.cursor ();
|
||
|
||
resolve ();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
void ISO8601p::clear ()
|
||
{
|
||
_year = 0;
|
||
_month = 0;
|
||
_day = 0;
|
||
_hours = 0;
|
||
_minutes = 0;
|
||
_seconds = 0;
|
||
_value = 0;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']]
|
||
bool ISO8601p::parse_designated (Nibbler& n)
|
||
{
|
||
Nibbler backup (n);
|
||
|
||
if (n.skip ('P'))
|
||
{
|
||
int value;
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('Y'))
|
||
_year = value;
|
||
else
|
||
n.restore ();
|
||
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('M'))
|
||
_month = value;
|
||
else
|
||
n.restore ();
|
||
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('D'))
|
||
_day = value;
|
||
else
|
||
n.restore ();
|
||
|
||
if (n.skip ('T'))
|
||
{
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('H'))
|
||
_hours = value;
|
||
else
|
||
n.restore ();
|
||
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('M'))
|
||
_minutes = value;
|
||
else
|
||
n.restore ();
|
||
|
||
n.save ();
|
||
if (n.getUnsignedInt (value) && n.skip ('S'))
|
||
_seconds = value;
|
||
else
|
||
n.restore ();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
n = backup;
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
bool ISO8601p::validate ()
|
||
{
|
||
return _year ||
|
||
_month ||
|
||
_day ||
|
||
_hours ||
|
||
_minutes ||
|
||
_seconds;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Allow un-normalized values.
|
||
void ISO8601p::resolve ()
|
||
{
|
||
_value = (_year * 365 * 86400) +
|
||
(_month * 30 * 86400) +
|
||
(_day * 86400) +
|
||
(_hours * 3600) +
|
||
(_minutes * 60) +
|
||
_seconds;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|