diff --git a/ChangeLog b/ChangeLog index d93e6da2e..8c80948a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,7 +6,7 @@ where the x represents a major version number, or architecture. The y represents a feature release, and the z represents a patch. 1.2.0 (?) - - + + Bug: "dateformat" configuration variable used to display dates, but not parse them. ------ reality ----------------------------------- @@ -127,6 +127,7 @@ represents a feature release, and the z represents a patch. + File locking + retain deleted tasks + "task info ID" report showing all metadata + + File format v2 [Development hiatus while planning for T, TDB API, new features and the future of the project. Seeded to two testers for feedback, suggestions.] diff --git a/src/Date.cpp b/src/Date.cpp index 202d5d144..502dec718 100644 --- a/src/Date.cpp +++ b/src/Date.cpp @@ -57,29 +57,114 @@ Date::Date (const int m, const int d, const int y) } //////////////////////////////////////////////////////////////////////////////// -Date::Date (const std::string& mdy) +Date::Date (const std::string& mdy, const std::string format /* = "m/d/Y" */) { - size_t firstSlash = mdy.find ("/"); - size_t secondSlash = mdy.find ("/", firstSlash + 1); - if (firstSlash != std::string::npos && - secondSlash != std::string::npos) - { - int m = ::atoi (mdy.substr (0, firstSlash ).c_str ()); - int d = ::atoi (mdy.substr (firstSlash + 1, secondSlash - firstSlash).c_str ()); - int y = ::atoi (mdy.substr (secondSlash + 1, std::string::npos ).c_str ()); - if (!valid (m, d, y)) - throw std::string ("\"") + mdy + "\" is not a valid date."; + int month = 0; + int day = 0; + int year = 0; - // Duplicate Date::Date (const int, const int, const int); - struct tm t = {0}; - t.tm_mday = d; - t.tm_mon = m - 1; - t.tm_year = y - 1900; + unsigned int i = 0; // Index into mdy. + + for (unsigned int f = 0; f < format.length (); ++f) + { + switch (format[f]) + { + // Single digit. + case 'm': + if (i >= mdy.length () || + ! ::isdigit (mdy[i])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + month = ::atoi (mdy.substr (i, 1).c_str ()); + ++i; + break; + + case 'd': + if (i >= mdy.length () || + ! ::isdigit (mdy[i])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + day = ::atoi (mdy.substr (i, 1).c_str ()); + ++i; + break; + + // Double digit. + case 'y': + if (i + 1 >= mdy.length () || + ! ::isdigit (mdy[i + 0]) || + ! ::isdigit (mdy[i + 1])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + year = ::atoi (mdy.substr (i, 2).c_str ()) + 2000; + i += 2; + break; + + case 'M': + if (i + 1 >= mdy.length () || + ! ::isdigit (mdy[i + 0]) || + ! ::isdigit (mdy[i + 1])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + month = ::atoi (mdy.substr (i, 2).c_str ()); + i += 2; + break; + + case 'D': + if (i + 1 >= mdy.length () || + ! ::isdigit (mdy[i + 0]) || + ! ::isdigit (mdy[i + 1])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + day = ::atoi (mdy.substr (i, 2).c_str ()); + i += 2; + break; + + // Quadruple digit. + case 'Y': + if (i + 3 >= mdy.length () || + ! ::isdigit (mdy[i + 0]) || + ! ::isdigit (mdy[i + 1]) || + ! ::isdigit (mdy[i + 2]) || + ! ::isdigit (mdy[i + 3])) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + + year = ::atoi (mdy.substr (i, 4).c_str ()); + i += 4; + break; + + default: + if (i >= mdy.length () || + mdy[i] != format[f]) + { + throw std::string ("\"") + mdy + "\" is not a valid date."; + } + ++i; + break; + } + } + + if (!valid (month, day, year)) + throw std::string ("\"") + mdy + "\" is not a valid date."; + + // Duplicate Date::Date (const int, const int, const int); + struct tm t = {0}; + t.tm_mday = day; + t.tm_mon = month - 1; + t.tm_year = year - 1900; mT = mktime (&t); - } - else - throw std::string ("\"") + mdy + "\" is not a valid date."; } //////////////////////////////////////////////////////////////////////////////// @@ -118,15 +203,6 @@ void Date::toMDY (int& m, int& d, int& y) //////////////////////////////////////////////////////////////////////////////// std::string Date::toString (const std::string& format /*= "m/d/Y"*/) { -/* - int m, d, y; - toMDY (m, d, y); - - char formatted [11]; - sprintf (formatted, "%d/%d/%d", m, d, y); - return std::string (formatted); -*/ - std::string formatted; for (unsigned int i = 0; i < format.length (); ++i) { diff --git a/src/Date.h b/src/Date.h index e8a61203f..dc68f482b 100644 --- a/src/Date.h +++ b/src/Date.h @@ -37,7 +37,7 @@ public: Date (); Date (time_t); Date (const int, const int, const int); - Date (const std::string&); + Date (const std::string&, const std::string format = "m/d/Y"); Date (const Date&); virtual ~Date (); diff --git a/src/T.cpp b/src/T.cpp index e17052f02..118724e46 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -275,20 +275,8 @@ const std::string T::compose () const int count = 0; foreach (i, mAttributes) { - std::string converted = i->second; - - // Date attributes may need conversion to epoch. - if (i->first == "due" || - i->first == "start" || - i->first == "entry" || - i->first == "end") - { - if (i->second.find ("/") != std::string::npos) - validDate (converted); - } - line += (count > 0 ? " " : ""); - line += i->first + ":" + converted; + line += i->first + ":" + i->second; ++count; } diff --git a/src/Table.cpp b/src/Table.cpp index 8cf403582..89f65bdf9 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -659,6 +659,12 @@ void Table::suppressWS () mSuppressWS = true; } +//////////////////////////////////////////////////////////////////////////////// +void Table::setDateFormat (const std::string& dateFormat) +{ + mDateFormat = dateFormat; +} + //////////////////////////////////////////////////////////////////////////////// int Table::rowCount () { @@ -771,8 +777,8 @@ void Table::sort (std::vector & order) else { - Date dl ((std::string)*left); - Date dr ((std::string)*right); + Date dl ((std::string)*left, mDateFormat); + Date dr ((std::string)*right, mDateFormat); if (dl > dr) SWAP } @@ -789,8 +795,8 @@ void Table::sort (std::vector & order) else { - Date dl ((std::string)*left); - Date dr ((std::string)*right); + Date dl ((std::string)*left, mDateFormat); + Date dr ((std::string)*right, mDateFormat); if (dl < dr) SWAP } diff --git a/src/Table.h b/src/Table.h index a90aaafbc..7428453ec 100644 --- a/src/Table.h +++ b/src/Table.h @@ -79,6 +79,7 @@ public: void setCellBg (int, int, Text::color); void suppressWS (); + void setDateFormat (const std::string&); int rowCount (); int columnCount (); @@ -128,6 +129,7 @@ private: // Misc... bool mSuppressWS; + std::string mDateFormat; }; #endif diff --git a/src/parse.cpp b/src/parse.cpp index 99f9dd8d3..9dcdaec69 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -185,29 +185,13 @@ static bool isCommand (const std::string& candidate) } //////////////////////////////////////////////////////////////////////////////// -bool validDate (std::string& date) +bool validDate (std::string& date, Config& conf) { - size_t firstSlash = date.find ("/"); - size_t secondSlash = date.find ("/", firstSlash + 1); - if (firstSlash != std::string::npos && - secondSlash != std::string::npos) - { - int m = ::atoi (date.substr (0, firstSlash ).c_str ()); - int d = ::atoi (date.substr (firstSlash + 1, secondSlash - firstSlash).c_str ()); - int y = ::atoi (date.substr (secondSlash + 1, std::string::npos ).c_str ()); - if (!Date::valid (m, d, y)) - throw std::string ("\"") + date + "\" is not a valid date."; + Date test (date, conf.get ("dateformat", "m/d/Y")); - // Convert to epoch form. - Date dt (m, d, y); - time_t t; - dt.toEpoch (t); - char converted[12]; - sprintf (converted, "%u", (unsigned int) t); - date = converted; - } - else - throw std::string ("Badly formed date - use the MM/DD/YYYY format"); + char epoch[12]; + sprintf (epoch, "%d", (int) test.toEpoch ()); + date = epoch; return true; } @@ -227,7 +211,7 @@ static bool validPriority (std::string& input) } //////////////////////////////////////////////////////////////////////////////// -static bool validAttribute (std::string& name, std::string& value) +static bool validAttribute (std::string& name, std::string& value, Config& conf) { guess ("attribute", attributes, name); @@ -235,7 +219,7 @@ static bool validAttribute (std::string& name, std::string& value) guess ("color", colors, value); else if (name == "due" && value != "") - validDate (value); + validDate (value, conf); else if (name == "priority") { @@ -335,7 +319,8 @@ static bool validSubstitution ( void parse ( std::vector & args, std::string& command, - T& task) + T& task, + Config& conf) { command = ""; @@ -369,7 +354,7 @@ void parse ( std::string name = arg.substr (0, colon); std::string value = arg.substr (colon + 1, std::string::npos); - if (validAttribute (name, value)) + if (validAttribute (name, value, conf)) task.setAttribute (name, value); } diff --git a/src/task.cpp b/src/task.cpp index 9039ddadc..ddc73e660 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -70,6 +70,7 @@ void usage (Config& conf) table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::flexible); table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); int row = table.addRow (); table.addCell (row, 0, "Usage:"); @@ -242,7 +243,7 @@ int main (int argc, char** argv) std::string command; T task; - parse (args, command, task); + parse (args, command, task, conf); if (command == "add") handleAdd (tdb, task, conf); else if (command == "projects") handleProjects (tdb, task, conf); @@ -326,6 +327,7 @@ void handleProjects (const TDB& tdb, T& task, Config& conf) table.setColumnUnderline (1); table.setColumnJustification (1, Table::right); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); foreach (i, unique) { @@ -442,6 +444,8 @@ void handleList (const TDB& tdb, T& task, Config& conf) table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); + // Split any description specified into words. std::vector descWords; split (descWords, task.getDescription (), ' '); @@ -581,6 +585,7 @@ void handleSmallList (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); @@ -737,6 +742,7 @@ void handleCompleted (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Done"); table.addColumn ("Project"); table.addColumn ("Description"); @@ -852,6 +858,7 @@ void handleInfo (const TDB& tdb, T& task, Config& conf) Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Name"); table.addColumn ("Value"); @@ -1024,6 +1031,7 @@ void handleLongList (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); @@ -1279,6 +1287,7 @@ void handleReportSummary (const TDB& tdb, T& task, Config& conf) table.setColumnJustification (3, Table::right); table.sortOn (0, Table::ascendingCharacter); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); int barWidth = 30; foreach (i, allProjects) @@ -1390,6 +1399,7 @@ void handleReportNext (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); @@ -1638,6 +1648,7 @@ void handleReportHistory (const TDB& tdb, T& task, Config& conf) // Now build the table. Table table; + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Year"); table.addColumn ("Month"); table.addColumn ("Added"); @@ -1740,7 +1751,7 @@ void handleReportUsage (const TDB& tdb, T& task, Config& conf) { T task; std::string commandName; - parse (args, commandName, task); + parse (args, commandName, task, conf); usage[commandName]++; } @@ -1759,6 +1770,7 @@ void handleReportUsage (const TDB& tdb, T& task, Config& conf) table.setColumnUnderline (1); table.setColumnJustification (1, Table::right); table.sortOn (1, Table::descendingNumeric); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); foreach (i, usage) { @@ -1788,6 +1800,7 @@ std::string renderMonth ( Config& conf) { Table table; + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn (" "); table.addColumn ("Su"); table.addColumn ("Mo"); @@ -1934,6 +1947,7 @@ void handleReportActive (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); @@ -2052,6 +2066,7 @@ void handleReportOverdue (const TDB& tdb, T& task, Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); @@ -2241,6 +2256,7 @@ void handleVersion (Config& conf) // Create a table for output. Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Config variable"); table.addColumn ("Value"); table.setColumnUnderline (0); diff --git a/src/task.h b/src/task.h index cf43ab6a8..3aa89df58 100644 --- a/src/task.h +++ b/src/task.h @@ -49,8 +49,8 @@ for (typeof (c) *foreach_p = & (c); \ ++i) // parse.cpp -void parse (std::vector &, std::string&, T&); -bool validDate (std::string&); +void parse (std::vector &, std::string&, T&, Config&); +bool validDate (std::string&, Config&); // task.cpp void handleAdd (const TDB&, T&, Config&); diff --git a/src/tests/.gitignore b/src/tests/.gitignore index e0f390805..d77228426 100644 --- a/src/tests/.gitignore +++ b/src/tests/.gitignore @@ -1,5 +1,6 @@ t.t tdb.t +date.t pending.data completed.data diff --git a/src/tests/date.t.cpp b/src/tests/date.t.cpp index a83d7d31e..519fa495b 100644 --- a/src/tests/date.t.cpp +++ b/src/tests/date.t.cpp @@ -9,77 +9,87 @@ //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (46); + plan (52); Date now; Date yesterday; yesterday -= 1; - t.ok (yesterday <= now, "yesterday <= now"); - t.ok (yesterday < now, "yesterday < now"); - t.notok (yesterday == now, "!(yesterday == now)"); - t.ok (yesterday != now, "yesterday != now"); - t.ok (now >= yesterday, "now >= yesterday"); - t.ok (now > yesterday, "now > yesterday"); + ok (yesterday <= now, "yesterday <= now"); + ok (yesterday < now, "yesterday < now"); + notok (yesterday == now, "!(yesterday == now)"); + ok (yesterday != now, "yesterday != now"); + ok (now >= yesterday, "now >= yesterday"); + ok (now > yesterday, "now > yesterday"); - t.ok (Date::valid (2, 29, 2008), "valid: 2/29/2008"); - t.notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007"); + ok (Date::valid (2, 29, 2008), "valid: 2/29/2008"); + notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007"); - t.ok (Date::leapYear (2008), "2008 is a leap year"); - t.notok (Date::leapYear (2007), "2007 is not a leap year"); + ok (Date::leapYear (2008), "2008 is a leap year"); + notok (Date::leapYear (2007), "2007 is not a leap year"); - t.is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008"); - t.is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007"); + is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008"); + is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007"); - t.is (Date::monthName (1), "January", "1 = January"); - t.is (Date::monthName (2), "February", "2 = February"); - t.is (Date::monthName (3), "March", "3 = March"); - t.is (Date::monthName (4), "April", "4 = April"); - t.is (Date::monthName (5), "May", "5 = May"); - t.is (Date::monthName (6), "June", "6 = June"); - t.is (Date::monthName (7), "July", "7 = July"); - t.is (Date::monthName (8), "August", "8 = August"); - t.is (Date::monthName (9), "September", "9 = September"); - t.is (Date::monthName (10), "October", "10 = October"); - t.is (Date::monthName (11), "November", "11 = November"); - t.is (Date::monthName (12), "December", "12 = December"); + is (Date::monthName (1), "January", "1 = January"); + is (Date::monthName (2), "February", "2 = February"); + is (Date::monthName (3), "March", "3 = March"); + is (Date::monthName (4), "April", "4 = April"); + is (Date::monthName (5), "May", "5 = May"); + is (Date::monthName (6), "June", "6 = June"); + is (Date::monthName (7), "July", "7 = July"); + is (Date::monthName (8), "August", "8 = August"); + is (Date::monthName (9), "September", "9 = September"); + is (Date::monthName (10), "October", "10 = October"); + is (Date::monthName (11), "November", "11 = November"); + is (Date::monthName (12), "December", "12 = December"); - t.is (Date::dayName (0), "Sunday", "0 == Sunday"); - t.is (Date::dayName (1), "Monday", "1 == Monday"); - t.is (Date::dayName (2), "Tuesday", "2 == Tuesday"); - t.is (Date::dayName (3), "Wednesday", "3 == Wednesday"); - t.is (Date::dayName (4), "Thursday", "4 == Thursday"); - t.is (Date::dayName (5), "Friday", "5 == Friday"); - t.is (Date::dayName (6), "Saturday", "6 == Saturday"); + is (Date::dayName (0), "Sunday", "0 == Sunday"); + is (Date::dayName (1), "Monday", "1 == Monday"); + is (Date::dayName (2), "Tuesday", "2 == Tuesday"); + is (Date::dayName (3), "Wednesday", "3 == Wednesday"); + is (Date::dayName (4), "Thursday", "4 == Thursday"); + is (Date::dayName (5), "Friday", "5 == Friday"); + is (Date::dayName (6), "Saturday", "6 == Saturday"); Date happyNewYear (1, 1, 2008); - t.is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday"); - t.is (happyNewYear.month (), 1, "1/1/2008 == January"); - t.is (happyNewYear.day (), 1, "1/1/2008 == 1"); - t.is (happyNewYear.year (), 2008, "1/1/2008 == 2008"); + is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday"); + is (happyNewYear.month (), 1, "1/1/2008 == January"); + is (happyNewYear.day (), 1, "1/1/2008 == 1"); + is (happyNewYear.year (), 2008, "1/1/2008 == 2008"); - t.is (now - yesterday, 1, "today - yesterday == 1"); + is (now - yesterday, 1, "today - yesterday == 1"); - t.is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008"); + is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008"); int m, d, y; happyNewYear.toMDY (m, d, y); - t.is (m, 1, "1/1/2008 == January"); - t.is (d, 1, "1/1/2008 == 1"); - t.is (y, 2008, "1/1/2008 == 2008"); + is (m, 1, "1/1/2008 == January"); + is (d, 1, "1/1/2008 == 1"); + is (y, 2008, "1/1/2008 == 2008"); Date epoch (9, 8, 2001); - t.ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000"); + ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000"); epoch += 86400; - t.ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000"); + ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000"); Date fromEpoch (epoch.toEpoch ()); - t.is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)"); + is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)"); - Date fromString ("1/1/2008"); - t.is (fromString.month (), 1, "ctor (std::string) -> m"); - t.is (fromString.day (), 1, "ctor (std::string) -> d"); - t.is (fromString.year (), 2008, "ctor (std::string) -> y"); + Date fromString1 ("1/1/2008"); + is (fromString1.month (), 1, "ctor (std::string) -> m"); + is (fromString1.day (), 1, "ctor (std::string) -> d"); + is (fromString1.year (), 2008, "ctor (std::string) -> y"); + + Date fromString2 ("1/1/2008", "m/d/Y"); + is (fromString2.month (), 1, "ctor (std::string) -> m"); + is (fromString2.day (), 1, "ctor (std::string) -> d"); + is (fromString2.year (), 2008, "ctor (std::string) -> y"); + + Date fromString3 ("20080101", "YMD"); + is (fromString3.month (), 1, "ctor (std::string) -> m"); + is (fromString3.day (), 1, "ctor (std::string) -> d"); + is (fromString3.year (), 2008, "ctor (std::string) -> y"); return 0; }