diff --git a/ChangeLog b/ChangeLog index 2b0b14c8f..3c8f7c8b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,7 @@ Features + Stop consider new tasks after quitting a bulk change. + Removed deprecated 'fg:' and 'bg:' attributes. + The 'diagnostics' command now reports libuuid details. + + New characters for parsing and formating dates ('n', 's' and 'v'). Bugs + Fixed bug #1043, where aliases were not recognized by bash autocompletion. diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index 218f230cd..d9c00fbf7 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -493,7 +493,7 @@ will be applied to the date. Entered dates as well as all other displayed dates in reports are formatted according to dateformat. -The default value is: m/d/Y. The string should contain the characters: +The default value is: m/d/Y. The string can contain the characters: .RS .RS @@ -501,13 +501,13 @@ m minimal-digit month, for example 1 or 12 .br d minimal-digit day, for example 1 or 30 .br -y two-digit year, for example 09 +y two-digit year, for example 09 or 12 .br D two-digit day, for example 01 or 30 .br M two-digit month, for example 01 or 12 .br -Y four-digit year, for example 2009 +Y four-digit year, for example 2009 or 2012 .br a short name of weekday, for example Mon or Wed .br @@ -517,9 +517,17 @@ b short name of month, for example Jan or Aug .br B long name of month, for example January or August .br -V weeknumber, for example 03 or 37 +v minimal-digit week, for example 3 or 37 .br -H two-digit hour, for example 03 or 11 +V two-digit week, for example 03 or 37 +.br +h minimal-digit hour, for example 3 or 21 +.br +n minimal-digit minutes, for example 5 or 42 +.br +s minimal-digit seconds, for example 7 or 47 +.br +H two-digit hour, for example 03 or 21 .br N two-digit minutes, for example 05 or 42 .br @@ -527,6 +535,11 @@ S two-digit seconds, for example 07 or 47 .RE .RE +.RS +The characters 'v', 'V', 'a' and 'A' can only be used for formatting printed +dates (not to parse them). +.RE + .RS The string may also contain other characters to act as spacers, or formatting. Examples for other values of dateformat: @@ -550,15 +563,15 @@ Examples for other values of dateformat.report: .RS .RS .br -a D b Y (V) would do an output as "Fri 24 Jul 2009 (30)" +a D b Y (V) would do an output as "Fri 24 Jul 2009 (30)" .br -A, B D, Y would do an output as "Friday, July 24, 2009" +A, B D, Y would do an output as "Friday, July 24, 2009" .br -vV a Y-M-D would do an output as "v30 Fri 2009-07-24" +wV a Y-M-D would do an output as "w30 Fri 2009-07-24" .br -yMD.HN would do an output as "110124.2342" +yMD.HN would do an output as "110124.2342" .br -m/d/Y H:N would do an output as "1/24/2011 10:42" +m/d/Y H:N would do an output as "1/24/2011 10:42" .br a D b Y H:N:S would do an output as "Mon 24 Jan 2011 11:19:42" .RE diff --git a/src/Date.cpp b/src/Date.cpp index be3889e7c..13256e417 100644 --- a/src/Date.cpp +++ b/src/Date.cpp @@ -238,10 +238,13 @@ const std::string Date::toString ( case 'A': sprintf (buffer, "%s", Date::dayName (dayOfWeek ()).c_str ()); break; case 'b': sprintf (buffer, "%.3s", Date::monthName (month ()).c_str ()); break; case 'B': sprintf (buffer, "%.9s", Date::monthName (month ()).c_str ()); break; + case 'v': sprintf (buffer, "%d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break; case 'V': sprintf (buffer, "%02d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break; case 'h': sprintf (buffer, "%d", this->hour ()); break; case 'H': sprintf (buffer, "%02d", this->hour ()); break; + case 'n': sprintf (buffer, "%d", this->minute ()); break; case 'N': sprintf (buffer, "%02d", this->minute ()); break; + case 's': sprintf (buffer, "%d", this->second ()); break; case 'S': sprintf (buffer, "%02d", this->second ()); break; case 'j': sprintf (buffer, "%d", this->dayOfYear ()); break; case 'J': sprintf (buffer, "%03d", this->dayOfYear ()); break; diff --git a/src/Nibbler.cpp b/src/Nibbler.cpp index 09a88d73d..dea1bc7b6 100644 --- a/src/Nibbler.cpp +++ b/src/Nibbler.cpp @@ -717,6 +717,47 @@ bool Nibbler::getDateISO (time_t& t) //////////////////////////////////////////////////////////////////////////////// #ifdef NIBBLER_FEATURE_DATE +// Parse the longest integer using the next 'limit' characters of 'result' +// following position 'i' (when strict is true, the number of digits must be +// equal to limit). +bool Nibbler::parseDigits(std::string::size_type& i, + int& result, + unsigned int limit, + bool strict /* = true */) +{ + // If the result has already been set + if (result != -1) + return false; + for (unsigned int f = limit; f > 0; --f) + { + // Check that the nibbler has enough unparsed characters + if (i + f <= _length) + { + // Check that 'f' of them are digits + unsigned int g; + for (g = 0; g < f; g++) + if (! isdigit (_input[i + g])) + break; + // Parse the integer when it is the case + if (g == f) + { + if (f == 1) + result = _input[i] - '0'; + else + result = atoi (_input.substr (i, f).c_str ()); + // Update the global cursor before returning + i += f; + return true; + } + } + // Do not try smaller limits if the option is strict on the size + if (strict) + break; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// bool Nibbler::getDate (const std::string& format, time_t& t) { std::string::size_type i = _cursor; @@ -728,246 +769,85 @@ bool Nibbler::getDate (const std::string& format, time_t& t) int minute = -1; int second = -1; + // For parsing, unused. + int wday = -1; + int week = -1; + for (unsigned int f = 0; f < format.length (); ++f) { switch (format[f]) { case 'm': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - month = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else if (i + 1 <= _length && - isdigit (_input[i + 0])) - { - month = _input[i] - '0'; - i += 1; - } - else + case 'M': + if (! parseDigits(i, month, 2, format[f] == 'M')) return false; break; case 'd': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - day = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else if (i + 1 <= _length && - isdigit (_input[i + 0])) - { - day = _input[i] - '0'; - i += 1; - } - else + case 'D': + if (! parseDigits(i, day, 2, format[f] == 'D')) return false; break; case 'y': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - year = 2000 + atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else + case 'Y': + if (! parseDigits(i, year, format[f] == 'y' ? 2 : 4)) + return false; + if (format[f] == 'y') + year += 2000; + break; + + case 'h': + case 'H': + if (! parseDigits(i, hour, 2, format[f] == 'H')) return false; break; - case 'M': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - month = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else + case 'n': + case 'N': + if (! parseDigits(i, minute, 2, format[f] == 'N')) return false; break; - case 'D': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - day = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else + case 's': + case 'S': + if (! parseDigits(i, second, 2, format[f] == 'S')) return false; break; // Merely parse, not extract. + case 'v': case 'V': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - day = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else - return false; - break; - - case 'Y': - if (i + 4 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1]) && - isdigit (_input[i + 2]) && - isdigit (_input[i + 3])) - { - year = atoi (_input.substr (i, 4).c_str ()); - i += 4; - } - else + if (! parseDigits(i, week, 2, format[f] == 'V')) return false; break; // Merely parse, not extract. case 'a': - if (i + 3 <= _length && - ! isdigit (_input[i + 0]) && - ! isdigit (_input[i + 1]) && - ! isdigit (_input[i + 2])) - i += 3; - else - return false; - break; - - // Merely parse, not extract. - case 'b': - if (i + 3 <= _length && - ! isdigit (_input[i + 0]) && - ! isdigit (_input[i + 1]) && - ! isdigit (_input[i + 2])) - { - month = Date::monthOfYear (_input.substr (i, 3).c_str()); - i += 3; - } - else - return false; - break; - - // Merely parse, not extract. case 'A': if (i + 3 <= _length && ! isdigit (_input[i + 0]) && ! isdigit (_input[i + 1]) && ! isdigit (_input[i + 2])) - i += Date::dayName (Date::dayOfWeek (_input.substr (i, 3).c_str ())).size (); + { + wday = Date::dayOfWeek (_input.substr (i, 3).c_str ()); + i += (format[f] == 'a') ? 3 : Date::dayName (wday).size (); + } else return false; break; + case 'b': case 'B': if (i + 3 <= _length && ! isdigit (_input[i + 0]) && ! isdigit (_input[i + 1]) && ! isdigit (_input[i + 2])) { - month = Date::monthOfYear (_input.substr (i, 3).c_str ()); - i += Date::monthName (month).size (); - } - else - return false; - break; - - case 'h': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - hour = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else if (i + 1 <= _length && - isdigit (_input[i + 0])) - { - hour = atoi (_input.substr (i, 1).c_str ()); - i += 1; - } - else - return false; - break; - - case 'H': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - hour = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else - return false; - break; - - case 'N': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - minute = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else - return false; - break; - - case 'S': - if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - second = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else - return false; - break; - - case 'j': - if (i + 3 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1]) && - isdigit (_input[i + 2])) - { - day = atoi (_input.substr (i, 3).c_str ()); - i += 3; - } - else if (i + 2 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1])) - { - day = atoi (_input.substr (i, 2).c_str ()); - i += 2; - } - else if (i + 1 <= _length && - isdigit (_input[i + 0])) - { - day = atoi (_input.substr (i, 1).c_str ()); - i += 1; - } - else - return false; - break; - - case 'J': - if (i + 3 <= _length && - isdigit (_input[i + 0]) && - isdigit (_input[i + 1]) && - isdigit (_input[i + 2])) - { - day = atoi (_input.substr (i, 3).c_str ()); - i += 3; + if (month != -1) + return false; + month = Date::monthOfYear (_input.substr (i, 3).c_str()); + i += (format[f] == 'b') ? 3 : Date::monthName (month).size (); } else return false; diff --git a/src/Nibbler.h b/src/Nibbler.h index 4b095b825..5f89a577f 100644 --- a/src/Nibbler.h +++ b/src/Nibbler.h @@ -78,6 +78,7 @@ public: bool getPartialUUID (std::string&); bool getDateISO (time_t&); #ifdef NIBBLER_FEATURE_DATE + bool parseDigits(std::string::size_type&, int&, unsigned int, bool strict = true); bool getDate (const std::string&, time_t&); #endif bool getOneOf (const std::vector &, std::string&); diff --git a/test/date.t.cpp b/test/date.t.cpp index 792fbbc18..3cc604859 100644 --- a/test/date.t.cpp +++ b/test/date.t.cpp @@ -207,17 +207,17 @@ int main (int argc, char** argv) t.is (fromString7.day (), 1, "ctor (std::string) -> d"); t.is (fromString7.year (), 2008, "ctor (std::string) -> y"); - Date fromString8 ("Tue 01 Jan 2008 (01)", "a D b Y (V)"); - t.is (fromString8.month (), 1, "ctor (std::string) -> m"); - t.is (fromString8.day (), 1, "ctor (std::string) -> d"); + Date fromString8 ("Tue 05 Feb 2008 (06)", "a D b Y (V)"); + t.is (fromString8.month (), 2, "ctor (std::string) -> m"); + t.is (fromString8.day (), 5, "ctor (std::string) -> d"); t.is (fromString8.year (), 2008, "ctor (std::string) -> y"); - Date fromString9 ("Tuesday, January 1, 2008", "A, B d, Y"); - t.is (fromString9.month (), 1, "ctor (std::string) -> m"); - t.is (fromString9.day (), 1, "ctor (std::string) -> d"); + Date fromString9 ("Tuesday, February 5, 2008", "A, B d, Y"); + t.is (fromString9.month (), 2, "ctor (std::string) -> m"); + t.is (fromString9.day (), 5, "ctor (std::string) -> d"); t.is (fromString9.year (), 2008, "ctor (std::string) -> y"); - Date fromString10 ("v01 Tue 2008-01-01", "vV a Y-M-D"); + Date fromString10 ("w01 Tue 2008-01-01", "wV a Y-M-D"); t.is (fromString10.month (), 1, "ctor (std::string) -> m"); t.is (fromString10.day (), 1, "ctor (std::string) -> d"); t.is (fromString10.year (), 2008, "ctor (std::string) -> y"); diff --git a/test/dateformat.t b/test/dateformat.t index 7273d25d9..79908ef64 100755 --- a/test/dateformat.t +++ b/test/dateformat.t @@ -57,8 +57,8 @@ if (open my $fh, '>', 'date3.rc') "dateformat=m/d/y\n", "dateformat=m/d/y\n", "weekstart=Monday\n", - "dateformat.info=A D B Y (vV)\n", - "dateformat.report=A D B Y (vV)\n"; + "dateformat.info=A D B Y (wV)\n", + "dateformat.report=A D B Y (wV)\n"; close $fh; ok (-r 'date3.rc', 'Created date3.rc'); } @@ -79,7 +79,7 @@ ok (!-r 'pending.data', 'Removed pending.data'); qx{../src/task rc:date3.rc add foo due:4/8/10 2>&1}; $output = qx{../src/task rc:date3.rc list 2>&1}; -like ($output, qr/Thursday 08 April 2010 \(v14\)/, 'date format A D B Y (vV) parsed'); +like ($output, qr/Thursday 08 April 2010 \(w14\)/, 'date format A D B Y (wV) parsed'); $output = qx{../src/task rc:date3.rc rc.dateformat.report:"D b Y - a" list 2>&1}; like ($output, qr/08 Apr 2010 - Thu/, 'date format D b Y - a parsed'); diff --git a/test/nibbler.t.cpp b/test/nibbler.t.cpp index 725844995..0263bc016 100644 --- a/test/nibbler.t.cpp +++ b/test/nibbler.t.cpp @@ -545,8 +545,8 @@ int main (int argc, char** argv) t.is (dt.day (), 1, "ctor (std::string) -> d"); t.is (dt.year (), 2008, "ctor (std::string) -> y"); - n = Nibbler ("v01 Tue 2008-01-01"); - t.ok (n.getDate ("vV a Y-M-D", ti), "vV a Y-M-D ok"); + n = Nibbler ("w01 Tue 2008-01-01"); + t.ok (n.getDate ("wV a Y-M-D", ti), "wV a Y-M-D ok"); dt = Date (ti); t.is (dt.month (), 1, "ctor (std::string) -> m"); t.is (dt.day (), 1, "ctor (std::string) -> d");