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

@ -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;