Report date format

- added new reportdateformat to extend the formatting of due dates
  in the reports and "task info"
- added new conversion sequences a, A, b, B and Y to be used with
  reportdateformat
This commit is contained in:
Federico Hernandez
2010-01-12 23:12:22 +01:00
parent e92fb9287a
commit 660d0cca3e
14 changed files with 269 additions and 52 deletions

View File

@@ -4,6 +4,9 @@
1.9.0 ()
+ Added feature #292 that permits alternate line coloration in reports
(thanks to Richard Querin).
+ Added feature #254 (#295) which gives task a second date format to be
used in the reports with more conversion sequences like weekday name
or weeknumber. The date format is set with variable "reportdateformat".
+ Added feature #307 that provides vim with syntax highlighting for .taskrc.
+ Added feature #336 which gives task a 'prepend' command for symmetry
with the 'append' command.

1
NEWS
View File

@@ -7,6 +7,7 @@ New Features in task 1.9
- Supports nested .taskrc files with the new "include" statement"
- New columns that include the time as well as date
- New attribute modifiers
- New date format for reports
- Improved .taskrc validation
- Improved calendar report with custom coloring and optional task details output

View File

@@ -148,33 +148,59 @@ tag names you have used, or just the ones used in active tasks.
.TP
.B dateformat=m/d/Y
This is a string of characters that define how task formats dates. The default value is: m/d/Y.
The string should contain the characters
.TP
.B reportdateformat=m/d/Y
This is a string of characters that define how task formats dates. If
.B reportdateformat
is set it will be used for the due date in the output of reports and "task info".
The default value is: m/d/Y. The string should contain the characters
.RS
m minimal-digit month, for example 1 or 12
m minimal-digit month, for example 1 or 12
.br
d minimal-digit day, for example 1 or 30
d minimal-digit day, for example 1 or 30
.br
y two-digit year, for example 09
y two-digit year, for example 09
.br
D two-digit day, for example 01 or 30
D two-digit day, for example 01 or 30
.br
M two-digit month, for example 01 or 12
M two-digit month, for example 01 or 12
.br
Y four-digit year, for example 2009
Y four-digit year, for example 2009
.br
a short name of weekday, for example Mon or Wed
.br
A long name of weekday, for example Monday or Wednesday
.br
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
.RE
The string may also contain other characters to act as spacers, or formatting. Examples for other
variable values:
values of dateformat:
.RS
.br
d/m/Y would output 24/7/2009
d/m/Y would use for input and output 24/7/2009
.br
YMD would output 20090724
yMD would use for input and output 090724
.br
m-d-y would output 07-24-09
M-D-Y would use for input and output 07-24-2009
.RE
Examples for other values of reportdateformat:
.RS
.br
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"
.br
vV a Y-M-D would do an output as "v30 Fri 2009-07.24"
.RE
.TP

View File

@@ -149,6 +149,7 @@ void Config::createDefaultRC (const std::string& rc, const std::string& data)
<< "\n"
<< "# Dates\n"
<< "dateformat=m/d/Y # Preferred input and display date format\n"
<< "#reportdateformat=m/d/Y # Preferred input and display date format\n"
<< "weekstart=Sunday # Sunday or Monday only\n"
<< "displayweeknumber=yes # Show week numbers on calendar\n"
<< "due=7 # Task is considered due in 7 days\n"

View File

@@ -33,6 +33,9 @@
#include "Date.h"
#include "text.h"
#include "util.h"
#include "Context.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
// Defaults to "now".
@@ -85,7 +88,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
if (i >= mdy.length () ||
! isdigit (mdy[i]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (m).";
}
if (i + 1 < mdy.length () &&
@@ -106,7 +109,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
if (i >= mdy.length () ||
! isdigit (mdy[i]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (d).";
}
if (i + 1 < mdy.length () &&
@@ -125,11 +128,11 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
// Double digit.
case 'y':
if (i + 1 >= mdy.length () ||
if (i + 1 >= mdy.length () ||
! isdigit (mdy[i + 0]) ||
! isdigit (mdy[i + 1]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (y).";
}
year = atoi (mdy.substr (i, 2).c_str ()) + 2000;
@@ -141,7 +144,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
! isdigit (mdy[i + 0]) ||
! isdigit (mdy[i + 1]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (M).";
}
month = atoi (mdy.substr (i, 2).c_str ());
@@ -153,13 +156,24 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
! isdigit (mdy[i + 0]) ||
! isdigit (mdy[i + 1]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (D).";
}
day = atoi (mdy.substr (i, 2).c_str ());
i += 2;
break;
case 'V':
if (i + 1 >= mdy.length () ||
! isdigit (mdy[i + 0]) ||
! isdigit (mdy[i + 1]))
{
throw std::string ("\"") + mdy + "\" is not a valid date (V).";
}
i += 2;
break;
// Quadruple digit.
case 'Y':
if (i + 3 >= mdy.length () ||
@@ -168,18 +182,70 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
! isdigit (mdy[i + 2]) ||
! isdigit (mdy[i + 3]))
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (Y).";
}
year = atoi (mdy.substr (i, 4).c_str ());
i += 4;
break;
// Short names with 3 characters
case 'a':
if (i + 2 >= mdy.length () ||
isdigit (mdy[i + 0]) ||
isdigit (mdy[i + 1]) ||
isdigit (mdy[i + 2]))
{
throw std::string ("\"") + mdy + "\" is not a valid date (a).";
}
i += 3;
break;
case 'b':
if (i + 2 >= mdy.length () ||
isdigit (mdy[i + 0]) ||
isdigit (mdy[i + 1]) ||
isdigit (mdy[i + 2]))
{
throw std::string ("\"") + mdy + "\" is not a valid date (b).";
}
month = Date::monthOfYear (mdy.substr (i, 3).c_str());
i += 3;
break;
// Long names
case 'A':
if (i + 2 >= mdy.length () ||
isdigit (mdy[i + 0]) ||
isdigit (mdy[i + 1]) ||
isdigit (mdy[i + 2]))
{
throw std::string ("\"") + mdy + "\" is not a valid date (A).";
}
i += Date::dayName( Date::dayOfWeek (mdy.substr (i, 3).c_str()) ).size();
break;
case 'B':
if (i + 2 >= mdy.length () ||
isdigit (mdy[i + 0]) ||
isdigit (mdy[i + 1]) ||
isdigit (mdy[i + 2]))
{
throw std::string ("\"") + mdy + "\" is not a valid date (B).";
}
month = Date::monthOfYear (mdy.substr (i, 3).c_str());
i += Date::monthName(month).size();
break;
default:
if (i >= mdy.length () ||
mdy[i] != format[f])
{
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (DEFAULT).";
}
++i;
break;
@@ -190,7 +256,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
throw std::string ("\"") + mdy + "\" is not a valid date in " + format + " format.";
if (!valid (month, day, year))
throw std::string ("\"") + mdy + "\" is not a valid date.";
throw std::string ("\"") + mdy + "\" is not a valid date (VALID).";
// Duplicate Date::Date (const int, const int, const int);
struct tm t = {0};
@@ -256,13 +322,18 @@ const std::string Date::toString (const std::string& format /*= "m/d/Y" */) cons
char c = localFormat[i];
switch (c)
{
case 'm': sprintf (buffer, "%d", this->month ()); break;
case 'M': sprintf (buffer, "%02d", this->month ()); break;
case 'd': sprintf (buffer, "%d", this->day ()); break;
case 'D': sprintf (buffer, "%02d", this->day ()); break;
case 'y': sprintf (buffer, "%02d", this->year () % 100); break;
case 'Y': sprintf (buffer, "%d", this->year ()); break;
default: sprintf (buffer, "%c", c); break;
case 'm': sprintf (buffer, "%d", this->month ()); break;
case 'M': sprintf (buffer, "%02d", this->month ()); break;
case 'd': sprintf (buffer, "%d", this->day ()); break;
case 'D': sprintf (buffer, "%02d", this->day ()); break;
case 'y': sprintf (buffer, "%02d", this->year () % 100); break;
case 'Y': sprintf (buffer, "%d", this->year ()); break;
case 'a': sprintf (buffer, "%.3s", Date::dayName(dayOfWeek()).c_str() ); break;
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, "%02d", Date::weekOfYear(Date::dayOfWeek(context.config.get ("weekstart", "Sunday")))); break;
default: sprintf (buffer, "%c", c); break;
}
formatted += buffer;
@@ -437,13 +508,34 @@ int Date::dayOfWeek (const std::string& input)
{
std::string in = lowerCase (input);
if (in == "sunday") return 0;
if (in == "monday") return 1;
if (in == "tuesday") return 2;
if (in == "wednesday") return 3;
if (in == "thursday") return 4;
if (in == "friday") return 5;
if (in == "saturday") return 6;
if (in == "sunday" || in == "sun") return 0;
if (in == "monday" || in == "mon") return 1;
if (in == "tuesday" || in == "tue") return 2;
if (in == "wednesday" || in == "wed") return 3;
if (in == "thursday" || in == "thu") return 4;
if (in == "friday" || in == "fri") return 5;
if (in == "saturday" || in == "sat") return 6;
return -1;
}
////////////////////////////////////////////////////////////////////////////////
int Date::monthOfYear (const std::string& input)
{
std::string in = lowerCase (input);
if (in == "january" || in == "jan") return 1;
if (in == "february" || in == "feb") return 2;
if (in == "march" || in == "mar") return 3;
if (in == "april" || in == "apr") return 4;
if (in == "may" || in == "may") return 5;
if (in == "june" || in == "jun") return 6;
if (in == "july" || in == "jul") return 7;
if (in == "august" || in == "aug") return 8;
if (in == "september" || in == "sep") return 9;
if (in == "october" || in == "oct") return 10;
if (in == "november" || in == "nov") return 11;
if (in == "december" || in == "dec") return 12;
return -1;
}

View File

@@ -58,6 +58,7 @@ public:
static std::string dayName (int);
static int weekOfYear (const std::string&);
static int dayOfWeek (const std::string&);
static int monthOfYear (const std::string&);
int month () const;
int day () const;

View File

@@ -52,6 +52,9 @@
#include "Timer.h"
#include "text.h"
#include "util.h"
#include "Context.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Table::Table ()
@@ -868,6 +871,46 @@ void Table::sort (std::vector <int>& order)
}
break;
case ascendingDueDate:
{
if ((std::string)*left != "" && (std::string)*right == "")
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else
{
Date dl ((std::string)*left, context.config.get("reportdateformat",
context.config.get("dateformat","m/d/Y")));
Date dr ((std::string)*right, context.config.get("reportdateformat",
context.config.get("dateformat","m/d/Y")));
if (dl > dr)
SWAP
}
}
break;
case descendingDueDate:
{
if ((std::string)*left != "" && (std::string)*right == "")
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else
{
Date dl ((std::string)*left, context.config.get("reportdateformat",
context.config.get("dateformat","m/d/Y")));
Date dr ((std::string)*right, context.config.get("reportdateformat",
context.config.get("dateformat","m/d/Y")));
if (dl < dr)
SWAP
}
}
break;
case ascendingPriority:
if (((std::string)*left == "" && (std::string)*right != "") ||
((std::string)*left == "M" && (std::string)*right == "L") ||

View File

@@ -41,11 +41,13 @@ public:
ascendingCharacter,
ascendingPriority,
ascendingDate,
ascendingDueDate,
ascendingPeriod,
descendingNumeric,
descendingCharacter,
descendingPriority,
descendingDate,
descendingDueDate,
descendingPeriod};
enum sizing {minimum = -1, flexible = 0};

View File

@@ -555,9 +555,9 @@ int handleConfig (std::string &outs)
"color.due color.overdue color.pri.H color.pri.L color.pri.M color.pri.none "
"color.recurring color.tagged color.footnote color.header color.debug color.alternate "
"color.calendar.today color.calendar.due color.calendar.overdue color.calendar.weekend "
"confirmation curses data.location dateformat debug default.command default.priority "
"default.project defaultwidth due locale displayweeknumber echo.command "
"locking monthsperline nag next project shadow.command shadow.file "
"confirmation curses data.location dateformat reportdateformat debug default.command "
"default.priority default.project defaultwidth due locale displayweeknumber "
"echo.command locking monthsperline nag next project shadow.command shadow.file "
"shadow.notify weekstart editor import.synonym.id import.synonym.uuid "
"complete.all.projects complete.all.tags "
#ifdef FEATURE_SHELL

View File

@@ -356,12 +356,15 @@ int runCustomReport (
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
std::string due;
foreach (task, tasks)
table.addCell (row++, columnCount, getDueDate (*task));
table.addCell (row++, columnCount,
getDueDate (*task,
context.config.get ("reportdateformat",
context.config.get ("dateformat", "m/d/Y"))));
dueColumn = columnCount;
}
@@ -550,13 +553,19 @@ int runCustomReport (
Table::ascendingPriority :
Table::descendingPriority));
else if (column == "entry" || column == "start" || column == "due" ||
column == "wait" || column == "until" || column == "end")
else if (column == "entry" || column == "start" || column == "wait" ||
column == "until" || column == "end")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingDate :
Table::descendingDate));
else if (column == "due")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingDueDate :
Table::descendingDueDate));
else if (column == "recur")
table.sortOn (columnIndex[column],
(direction == '+' ?
@@ -571,10 +580,10 @@ int runCustomReport (
}
// Now auto colorize all rows.
Color color_due (context.config.get ("color.due", "yellow"));
std::string due;
Color color_due (context.config.get ("color.due", "green"));
Color color_overdue (context.config.get ("color.overdue", "red"));
std::string due;
bool imminent;
bool overdue;
for (unsigned int row = 0; row < tasks.size (); ++row)

View File

@@ -103,7 +103,7 @@ int handleReportCalendar (std::string &);
int handleReportStats (std::string &);
int handleReportTimesheet (std::string &);
std::string getFullDescription (Task&);
std::string getDueDate (Task&);
std::string getDueDate (Task&, const std::string&);
// custom.cpp
int handleCustomReport (const std::string&, std::string &);

View File

@@ -412,7 +412,7 @@ int handleInfo (std::string &outs)
table.addCell (row, 0, "Due");
Date dt (atoi (task->get ("due").c_str ()));
std::string due = getDueDate (*task);
std::string due = getDueDate (*task, context.config.get("reportdateformat", context.config.get("dateformat","m/d/Y")));
table.addCell (row, 1, due);
overdue = (dt < now) ? true : false;
@@ -1218,7 +1218,7 @@ int handleReportTimesheet (std::string &outs)
{
int row = completed.addRow ();
completed.addCell (row, 1, task->get ("project"));
completed.addCell (row, 2, getDueDate (*task));
completed.addCell (row, 2, getDueDate (*task,context.config.get("dateformat","m/d/Y")));
completed.addCell (row, 3, getFullDescription (*task));
if (color)
@@ -1274,7 +1274,7 @@ int handleReportTimesheet (std::string &outs)
{
int row = started.addRow ();
started.addCell (row, 1, task->get ("project"));
started.addCell (row, 2, getDueDate (*task));
started.addCell (row, 2, getDueDate (*task,context.config.get("dateformat","m/d/Y")));
started.addCell (row, 3, getFullDescription (*task));
if (color)
@@ -2124,13 +2124,15 @@ std::string getFullDescription (Task& task)
}
///////////////////////////////////////////////////////////////////////////////
std::string getDueDate (Task& task)
std::string getDueDate (Task& task, const std::string& format)
{
std::string due = task.get ("due");
if (due.length ())
{
Date d (atoi (due.c_str ()));
due = d.toString (context.config.get ("dateformat", "m/d/Y"));
due = d.toString (format);
//due = d.toString (context.config.get ("dateformat", "m/d/Y"));
//due = d.toString (context.config.get ("reportdateformat", context.config.get ("dateformat", "m/d/Y")));
}
return due;

View File

@@ -34,7 +34,7 @@ Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (102);
UnitTest t (111);
try
{
@@ -180,6 +180,21 @@ 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");
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");
t.is (fromString9.year (), 2008, "ctor (std::string) -> y");
Date fromString10 ("v01 Tue 2008-01-01", "vV 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");
// Relative dates.
Date r1 ("today");
t.ok (r1.sameDay (now), "today = now");

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 9;
use Test::More tests => 14;
# Create the rc file.
if (open my $fh, '>', 'date1.rc')
@@ -47,6 +47,16 @@ if (open my $fh, '>', 'date2.rc')
ok (-r 'date2.rc', 'Created date2.rc');
}
if (open my $fh, '>', 'date3.rc')
{
print $fh "data.location=.\n",
"dateformat=m/d/y\n",
"weekstart=Monday\n",
"reportdateformat=A D B Y (vV)\n";
close $fh;
ok (-r 'date3.rc', 'Created date3.rc');
}
qx{../task rc:date1.rc add foo due:20091231};
my $output = qx{../task rc:date1.rc info 1};
like ($output, qr/\b20091231\b/, 'date format YMD parsed');
@@ -58,6 +68,15 @@ qx{../task rc:date2.rc add foo due:12/1/09};
$output = qx{../task rc:date2.rc info 1};
like ($output, qr/\b12\/1\/09\b/, 'date format m/d/y parsed');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
qx{../task rc:date3.rc add foo due:4/8/10};
$output = qx{../task rc:date3.rc list};
like ($output, qr/Thursday 08 April 2010 \(v14\)/, 'date format A D B Y (vV) parsed');
$output = qx{../task rc:date3.rc rc.reportdateformat:"D b Y - a" list};
like ($output, qr/08 Apr 2010 - Thu/, 'date format D b Y - a parsed');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
@@ -71,5 +90,8 @@ ok (!-r 'date1.rc', 'Removed date1.rc');
unlink 'date2.rc';
ok (!-r 'date2.rc', 'Removed date2.rc');
unlink 'date3.rc';
ok (!-r 'date3.rc', 'Removed date3.rc');
exit 0;