void testParse ( UnitTest& t, const std::string& input, int in_start, int in_year, int in_month, int in_week, int in_weekday, int in_julian, int in_day, int in_seconds, int in_offset, bool in_utc, time_t in_date) { std::string label = std::string ("parse (\"") + input + "\") --> "; ISO8601d iso; std::string::size_type start = 0; t.ok (iso.parse (input, start), label + "true"); t.is ((int) start, in_start, label + "[]"); t.is (iso._year, in_year, label + "_year"); t.is (iso._month, in_month, label + "_month"); t.is (iso._week, in_week, label + "_week"); t.is (iso._weekday, in_weekday, label + "_weekday"); t.is (iso._julian, in_julian, label + "_julian"); t.is (iso._day, in_day, label + "_day"); t.is (iso._seconds, in_seconds, label + "_seconds"); t.is (iso._offset, in_offset, label + "_offset"); t.is (iso._utc, in_utc, label + "_utc"); t.is ((size_t) iso._date, (size_t) in_date, label + "_date"); }
//////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. void ColumnDate::measure (Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; if (task.has (_name)) { ISO8601d date (task.get_date (_name)); if (_style == "default" || _style == "formatted") { // Determine the output date format, which uses a hierarchy of definitions. // rc.report.<report>.dateformat // rc.dateformat.report // rc.dateformat. std::string format = context.config.get ("report." + _report + ".dateformat"); if (format == "") format = context.config.get ("dateformat.report"); if (format == "") format = context.config.get ("dateformat"); minimum = maximum = ISO8601d::length (format); } else if (_style == "countdown") { ISO8601d now; minimum = maximum = ISO8601p (now - date).formatVague ().length (); } else if (_style == "julian") { minimum = maximum = format (date.toJulian (), 13, 12).length (); } else if (_style == "epoch") { minimum = maximum = date.toEpochString ().length (); } else if (_style == "iso") { minimum = maximum = date.toISO ().length (); } else if (_style == "age") { ISO8601d now; minimum = maximum = ISO8601p (now - date).formatVague ().length (); } else if (_style == "remaining") { ISO8601d now; if (date > now) minimum = maximum = ISO8601p (date - now).formatVague ().length (); } else throw format (STRING_COLUMN_BAD_FORMAT, _name, _style); } }
std::string CmdEdit::formatDate ( Task& task, const std::string& attribute, const std::string& dateformat) { std::string value = task.get (attribute); if (value.length ()) { ISO8601d dt (value); value = dt.toString (dateformat); } return value; }
//////////////////////////////////////////////////////////////////////////////// // Lexer::Type::date // <ISO8601d> bool Lexer::isDate (std::string& token, Lexer::Type& type) { // Try an ISO date parse. std::size_t iso_i = 0; ISO8601d iso; if (iso.parse (_text.substr (_cursor), iso_i, Lexer::dateFormat)) { type = Lexer::Type::date; token = _text.substr (_cursor, iso_i); _cursor += iso_i; return true; } return false; }
//////////////////////////////////////////////////////////////////////////////// // Lexer::Type::date // <ISO8601d> | <Date> bool Lexer::isDate (std::string& token, Lexer::Type& type) { // Try an ISO date parse. if (Lexer::isoEnabled) { std::size_t iso_i = 0; ISO8601d iso; iso.ambiguity (_ambiguity); if (iso.parse (_text.substr (_cursor), iso_i)) { type = Lexer::Type::date; token = _text.substr (_cursor, iso_i); _cursor += iso_i; return true; } } // Try a legacy rc.dateformat parse here. if (Lexer::dateFormat != "") { try { std::size_t legacy_i = 0; Date legacyDate (_text.substr (_cursor), legacy_i, Lexer::dateFormat, false, false); type = Lexer::Type::date; token = _text.substr (_cursor, legacy_i); _cursor += legacy_i; return true; } catch (...) { /* Never mind. */ } } return false; }
ColumnDate::ColumnDate () { _name = ""; _type = "date"; _style = "formatted"; _label = ""; _styles = {"formatted", "julian", "epoch", "iso", "age", "remaining", "countdown"}; ISO8601d now; now -= 125; // So that "age" is non-zero. _examples = {now.toString (context.config.get ("dateformat")), format (now.toJulian (), 13, 12), now.toEpochString (), now.toISO (), ISO8601p (ISO8601d () - now).formatVague (), "", ISO8601p (ISO8601d () - now).format ()}; }
void ColumnDescription::render ( std::vector <std::string>& lines, Task& task, int width, Color& color) { std::string description = task.get (_name); // This is a description // <date> <anno> // ... if (_style == "default" || _style == "combined") { std::map <std::string, std::string> annos; task.getAnnotations (annos); if (annos.size ()) { for (auto& i : annos) { ISO8601d dt (strtol (i.first.substr (11).c_str (), NULL, 10)); description += "\n" + std::string (_indent, ' ') + dt.toString (_dateformat) + " " + i.second; } } std::vector <std::string> raw; wrapText (raw, description, width, _hyphenate); for (auto& i : raw) lines.push_back (color.colorize (leftJustify (i, width))); } // This is a description else if (_style == "desc") { std::vector <std::string> raw; wrapText (raw, description, width, _hyphenate); for (auto& i : raw) lines.push_back (color.colorize (leftJustify (i, width))); } // This is a description <date> <anno> ... else if (_style == "oneline") { std::map <std::string, std::string> annos; task.getAnnotations (annos); if (annos.size ()) { for (auto& i : annos) { ISO8601d dt (strtol (i.first.substr (11).c_str (), NULL, 10)); description += " " + dt.toString (_dateformat) + " " + i.second; } } std::vector <std::string> raw; wrapText (raw, description, width, _hyphenate); for (auto& i : raw) lines.push_back (color.colorize (leftJustify (i, width))); } // This is a des... else if (_style == "truncated") { int len = utf8_width (description); if (len > width) lines.push_back (color.colorize (description.substr (0, width - 3) + "...")); else lines.push_back (color.colorize (leftJustify (description, width))); } // This is a description [2] else if (_style == "count") { std::map <std::string, std::string> annos; task.getAnnotations (annos); if (annos.size ()) description += " [" + format ((int) annos.size ()) + "]"; std::vector <std::string> raw; wrapText (raw, description, width, _hyphenate); for (auto& i : raw) lines.push_back (color.colorize (leftJustify (i, width))); } // This is a des... [2] else if (_style == "truncated_count") { std::map <std::string, std::string> annos; task.getAnnotations (annos); int len = utf8_width (description); std::string annos_count; int len_annos = 0; if (annos.size ()) { annos_count = " [" + format ((int) annos.size ()) + "]"; len_annos = utf8_width (annos_count); len += len_annos; } if (len > width) lines.push_back (color.colorize (description.substr (0, width - len_annos - 3) + "..." + annos_count)); else lines.push_back (color.colorize (leftJustify (description + annos_count, width))); } }
void CmdEdit::parseTask (Task& task, const std::string& after, const std::string& dateformat) { // project std::string value = findValue (after, "\n Project:"); if (task.get ("project") != value) { if (value != "") { context.footnote (STRING_EDIT_PROJECT_MOD); task.set ("project", value); } else { context.footnote (STRING_EDIT_PROJECT_DEL); task.remove ("project"); } } // tags value = findValue (after, "\n Tags:"); std::vector <std::string> tags; split (tags, value, ' '); task.remove ("tags"); task.addTags (tags); // description. value = findMultilineValue (after, "\n Description:", "\n Created:"); if (task.get ("description") != value) { if (value != "") { context.footnote (STRING_EDIT_DESC_MOD); task.set ("description", value); } else throw std::string (STRING_EDIT_DESC_REMOVE_ERR); } // entry value = findValue (after, "\n Created:"); if (value != "") { std::string formatted = formatDate (task, "entry", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_ENTRY_MOD); task.set ("entry", ISO8601d (value, dateformat).toEpochString ()); } } else throw std::string (STRING_EDIT_ENTRY_REMOVE_ERR); // start value = findValue (after, "\n Started:"); if (value != "") { if (task.get ("start") != "") { std::string formatted = formatDate (task, "start", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_START_MOD); task.set ("start", ISO8601d (value, dateformat).toEpochString ()); } } else { context.footnote (STRING_EDIT_START_MOD); task.set ("start", ISO8601d (value, dateformat).toEpochString ()); } } else { if (task.get ("start") != "") { context.footnote (STRING_EDIT_START_DEL); task.remove ("start"); } } // end value = findValue (after, "\n Ended:"); if (value != "") { if (task.get ("end") != "") { std::string formatted = formatDate (task, "end", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_END_MOD); task.set ("end", ISO8601d (value, dateformat).toEpochString ()); } } else if (task.getStatus () != Task::deleted) throw std::string (STRING_EDIT_END_SET_ERR); } else { if (task.get ("end") != "") { context.footnote (STRING_EDIT_END_DEL); task.setStatus (Task::pending); task.remove ("end"); } } // scheduled value = findValue (after, "\n Scheduled:"); if (value != "") { if (task.get ("scheduled") != "") { std::string formatted = formatDate (task, "scheduled", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_SCHED_MOD); task.set ("scheduled", ISO8601d (value, dateformat).toEpochString ()); } } else { context.footnote (STRING_EDIT_SCHED_MOD); task.set ("scheduled", ISO8601d (value, dateformat).toEpochString ()); } } else { if (task.get ("scheduled") != "") { context.footnote (STRING_EDIT_SCHED_DEL); task.setStatus (Task::pending); task.remove ("scheduled"); } } // due value = findValue (after, "\n Due:"); if (value != "") { if (task.get ("due") != "") { std::string formatted = formatDate (task, "due", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_DUE_MOD); task.set ("due", ISO8601d (value, dateformat).toEpochString ()); } } else { context.footnote (STRING_EDIT_DUE_MOD); task.set ("due", ISO8601d (value, dateformat).toEpochString ()); } } else { if (task.get ("due") != "") { if (task.getStatus () == Task::recurring || task.get ("parent") != "") { context.footnote (STRING_EDIT_DUE_DEL_ERR); } else { context.footnote (STRING_EDIT_DUE_DEL); task.remove ("due"); } } } // until value = findValue (after, "\n Until:"); if (value != "") { if (task.get ("until") != "") { std::string formatted = formatDate (task, "until", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_UNTIL_MOD); task.set ("until", ISO8601d (value, dateformat).toEpochString ()); } } else { context.footnote (STRING_EDIT_UNTIL_MOD); task.set ("until", ISO8601d (value, dateformat).toEpochString ()); } } else { if (task.get ("until") != "") { context.footnote (STRING_EDIT_UNTIL_DEL); task.remove ("until"); } } // recur value = findValue (after, "\n Recur:"); if (value != task.get ("recur")) { if (value != "") { ISO8601p p; std::string::size_type idx = 0; if (p.parse (value, idx)) { context.footnote (STRING_EDIT_RECUR_MOD); if (task.get ("due") != "") { task.set ("recur", value); task.setStatus (Task::recurring); } else throw std::string (STRING_EDIT_RECUR_DUE_ERR); } else throw std::string (STRING_EDIT_RECUR_ERR); } else { context.footnote (STRING_EDIT_RECUR_DEL); task.setStatus (Task::pending); task.remove ("recur"); task.remove ("until"); task.remove ("mask"); task.remove ("imask"); } } // wait value = findValue (after, "\n Wait until:"); if (value != "") { if (task.get ("wait") != "") { std::string formatted = formatDate (task, "wait", dateformat); if (formatted != value) { context.footnote (STRING_EDIT_WAIT_MOD); task.set ("wait", ISO8601d (value, dateformat).toEpochString ()); task.setStatus (Task::waiting); } } else { context.footnote (STRING_EDIT_WAIT_MOD); task.set ("wait", ISO8601d (value, dateformat).toEpochString ()); task.setStatus (Task::waiting); } } else { if (task.get ("wait") != "") { context.footnote (STRING_EDIT_WAIT_DEL); task.remove ("wait"); task.setStatus (Task::pending); } } // parent value = findValue (after, "\n Parent:"); if (value != task.get ("parent")) { if (value != "") { context.footnote (STRING_EDIT_PARENT_MOD); task.set ("parent", value); } else { context.footnote (STRING_EDIT_PARENT_DEL); task.remove ("parent"); } } // Annotations std::map <std::string, std::string> annotations; std::string::size_type found = 0; while ((found = after.find ("\n Annotation:", found)) != std::string::npos) { found += 14; // Length of "\n Annotation:". auto eol = after.find ("\n", found + 1); if (eol != std::string::npos) { std::string value = trim (after.substr ( found, eol - found), "\t "); auto gap = value.find (" -- "); if (gap != std::string::npos) { // TODO keeping the initial dates even if dateformat approximates them // is complex as finding the correspondence between each original line // and edited line may be impossible (bug #705). It would be simpler if // each annotation was put on a line with a distinguishable id (then // for each line: if the annotation is the same, then it is copied; if // the annotation is modified, then its original date may be kept; and // if there is no corresponding id, then a new unique date is created). ISO8601d when (value.substr (0, gap), dateformat); // If the map already contains a annotation for a given timestamp // we need to increment until we find an unused key int timestamp = (int) when.toEpoch (); std::stringstream name; do { name.str (""); // Clear name << "annotation_" << timestamp; timestamp++; } while (annotations.find (name.str ()) != annotations.end ()); std::string text = trim (value.substr (gap + 4), "\t "); annotations.insert (std::make_pair (name.str (), json::decode (text))); } } } task.setAnnotations (annotations); // Dependencies value = findValue (after, "\n Dependencies:"); std::vector <std::string> dependencies; split (dependencies, value, ","); task.remove ("depends"); for (auto& dep : dependencies) { if (dep.length () >= 7) task.addDependency (dep); else task.addDependency ((int) strtol (dep.c_str (), NULL, 10)); } // UDAs for (auto& col : context.columns) { std::string type = context.config.get ("uda." + col.first + ".type"); if (type != "") { std::string value = findValue (after, "\n UDA " + col.first + ":"); if ((task.get (col.first) != value) && (type != "date" || (task.get (col.first) != ISO8601d (value, dateformat).toEpochString ())) && (type != "duration" || (task.get (col.first) != (std::string) ISO8601p (value)))) { if (value != "") { context.footnote (format (STRING_EDIT_UDA_MOD, col.first)); if (type == "string") { task.set (col.first, value); } else if (type == "numeric") { Nibbler n (value); double d; if (n.getNumber (d) && n.depleted ()) task.set (col.first, value); else throw format (STRING_UDA_NUMERIC, value); } else if (type == "date") { task.set (col.first, ISO8601d (value, dateformat).toEpochString ()); } else if (type == "duration") { task.set (col.first, (time_t) ISO8601p (value)); } } else { context.footnote (format (STRING_EDIT_UDA_DEL, col.first)); task.remove (col.first); } } } } // UDA orphans std::vector <std::string> orphanValues = findValues (after, "\n UDA Orphan "); for (auto& orphan : orphanValues) { auto colon = orphan.find (':'); if (colon != std::string::npos) { std::string name = trim (orphan.substr (0, colon), "\t "); std::string value = trim (orphan.substr (colon + 1), "\t "); if (value != "") task.set (name, value); else task.remove (name); } } }
std::string CmdEdit::formatTask (Task task, const std::string& dateformat) { std::stringstream before; bool verbose = context.verbose ("edit"); if (verbose) before << "# " << STRING_EDIT_HEADER_1 << "\n" << "# " << STRING_EDIT_HEADER_2 << "\n" << "# " << STRING_EDIT_HEADER_3 << "\n" << "# " << STRING_EDIT_HEADER_4 << "\n" << "# " << STRING_EDIT_HEADER_5 << "\n" << "# " << STRING_EDIT_HEADER_6 << "\n" << "#\n" << "# " << STRING_EDIT_HEADER_7 << "\n" << "# " << STRING_EDIT_HEADER_8 << "\n" << "# " << STRING_EDIT_HEADER_9 << "\n" << "#\n" << "# " << STRING_EDIT_HEADER_10 << "\n" << "# " << STRING_EDIT_HEADER_11 << "\n" << "# " << STRING_EDIT_HEADER_12 << "\n" << "#\n"; before << "# " << STRING_EDIT_TABLE_HEADER_1 << "\n" << "# " << STRING_EDIT_TABLE_HEADER_2 << "\n" << "# ID: " << task.id << "\n" << "# UUID: " << task.get ("uuid") << "\n" << "# Status: " << ucFirst (Task::statusToText (task.getStatus ())) << "\n" // L10N safe ucFirst. << "# Mask: " << task.get ("mask") << "\n" << "# iMask: " << task.get ("imask") << "\n" << " Project: " << task.get ("project") << "\n"; std::vector <std::string> tags; task.getTags (tags); std::string allTags; join (allTags, " ", tags); if (verbose) before << "# " << STRING_EDIT_TAG_SEP << "\n"; before << " Tags: " << allTags << "\n" << " Description: " << task.get ("description") << "\n" << " Created: " << formatDate (task, "entry", dateformat) << "\n" << " Started: " << formatDate (task, "start", dateformat) << "\n" << " Ended: " << formatDate (task, "end", dateformat) << "\n" << " Scheduled: " << formatDate (task, "scheduled", dateformat) << "\n" << " Due: " << formatDate (task, "due", dateformat) << "\n" << " Until: " << formatDate (task, "until", dateformat) << "\n" << " Recur: " << task.get ("recur") << "\n" << " Wait until: " << formatDate (task, "wait", dateformat) << "\n" << "# Modified: " << formatDate (task, "modified", dateformat) << "\n" << " Parent: " << task.get ("parent") << "\n"; if (verbose) before << "# " << STRING_EDIT_HEADER_13 << "\n" << "# " << STRING_EDIT_HEADER_14 << "\n" << "# " << STRING_EDIT_HEADER_15 << "\n"; std::map <std::string, std::string> annotations; task.getAnnotations (annotations); for (auto& anno : annotations) { ISO8601d dt (strtol (anno.first.substr (11).c_str (), NULL, 10)); before << " Annotation: " << dt.toString (dateformat) << " -- " << json::encode (anno.second) << "\n"; } ISO8601d now; before << " Annotation: " << now.toString (dateformat) << " -- \n"; // Add dependencies here. std::vector <std::string> dependencies; task.getDependencies (dependencies); std::stringstream allDeps; for (unsigned int i = 0; i < dependencies.size (); ++i) { if (i) allDeps << ","; Task t; context.tdb2.get (dependencies[i], t); if (t.getStatus () == Task::pending || t.getStatus () == Task::waiting) allDeps << t.id; else allDeps << dependencies[i]; } if (verbose) before << "# " << STRING_EDIT_DEP_SEP << "\n"; before << " Dependencies: " << allDeps.str () << "\n"; // UDAs std::vector <std::string> udas; for (auto& col : context.columns) if (context.config.get ("uda." + col.first + ".type") != "") udas.push_back (col.first); if (udas.size ()) { before << "# " << STRING_EDIT_UDA_SEP << "\n"; std::sort (udas.begin (), udas.end ()); for (auto& uda : udas) { int pad = 13 - uda.length (); std::string padding = ""; if (pad > 0) padding = std::string (pad, ' '); std::string type = context.config.get ("uda." + uda + ".type"); if (type == "string" || type == "numeric") before << " UDA " << uda << ": " << padding << task.get (uda) << "\n"; else if (type == "date") before << " UDA " << uda << ": " << padding << formatDate (task, uda, dateformat) << "\n"; else if (type == "duration") before << " UDA " << uda << ": " << padding << formatDuration (task, uda) << "\n"; } } // UDA orphans std::vector <std::string> orphans; task.getUDAOrphans (orphans); if (orphans.size ()) { before << "# " << STRING_EDIT_UDA_ORPHAN_SEP << "\n"; std::sort (orphans.begin (), orphans.end ()); for (auto& orphan : orphans) { int pad = 6 - orphan.length (); std::string padding = ""; if (pad > 0) padding = std::string (pad, ' '); before << " UDA Orphan " << orphan << ": " << padding << task.get (orphan) << "\n"; } } before << "# " << STRING_EDIT_END << "\n"; return before.str (); }
void TDB2::show_diff ( const std::string& current, const std::string& prior, const std::string& when) { ISO8601d lastChange (strtol (when.c_str (), NULL, 10)); // Set the colors. Color color_red (context.color () ? context.config.get ("color.undo.before") : ""); Color color_green (context.color () ? context.config.get ("color.undo.after") : ""); if (context.config.get ("undo.style") == "side") { std::cout << "\n" << format (STRING_TDB2_LAST_MOD, lastChange.toString ()) << "\n"; // Attributes are all there is, so figure the different attribute names // between before and after. ViewText view; view.width (context.getWidth ()); view.intraPadding (2); view.add (Column::factory ("string", "")); view.add (Column::factory ("string", STRING_TDB2_UNDO_PRIOR)); view.add (Column::factory ("string", STRING_TDB2_UNDO_CURRENT)); Color label (context.config.get ("color.label")); view.colorHeader (label); Task after (current); if (prior != "") { Task before (prior); std::vector <std::string> beforeAtts; for (auto& att : before) beforeAtts.push_back (att.first); std::vector <std::string> afterAtts; for (auto& att : after) afterAtts.push_back (att.first); std::vector <std::string> beforeOnly; std::vector <std::string> afterOnly; listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); int row; for (auto& name : beforeOnly) { row = view.addRow (); view.set (row, 0, name); view.set (row, 1, renderAttribute (name, before.get (name)), color_red); } for (auto& att : before) { std::string priorValue = before.get (att.first); std::string currentValue = after.get (att.first); if (currentValue != "") { row = view.addRow (); view.set (row, 0, att.first); view.set (row, 1, renderAttribute (att.first, priorValue), (priorValue != currentValue ? color_red : Color ())); view.set (row, 2, renderAttribute (att.first, currentValue), (priorValue != currentValue ? color_green : Color ())); } } for (auto& name : afterOnly) { row = view.addRow (); view.set (row, 0, name); view.set (row, 2, renderAttribute (name, after.get (name)), color_green); } } else { int row; for (auto& att : after) { row = view.addRow (); view.set (row, 0, att.first); view.set (row, 2, renderAttribute (att.first, after.get (att.first)), color_green); } } std::cout << "\n" << view.render () << "\n"; } // This style looks like this: // --- before 2009-07-04 00:00:25.000000000 +0200 // +++ after 2009-07-04 00:00:45.000000000 +0200 // // - name: old // att deleted // + name: // // - name: old // att changed // + name: new // // - name: // + name: new // att added // else if (context.config.get ("undo.style") == "diff") { // Create reference tasks. Task before; if (prior != "") before.parse (prior); Task after (current); // Generate table header. ViewText view; view.width (context.getWidth ()); view.intraPadding (2); view.add (Column::factory ("string", "")); view.add (Column::factory ("string", "")); int row = view.addRow (); view.set (row, 0, STRING_TDB2_DIFF_PREV, color_red); view.set (row, 1, STRING_TDB2_DIFF_PREV_DESC, color_red); row = view.addRow (); view.set (row, 0, STRING_TDB2_DIFF_CURR, color_green); // Note trailing space. view.set (row, 1, format (STRING_TDB2_DIFF_CURR_DESC, lastChange.toString (context.config.get ("dateformat"))), color_green); view.addRow (); // Add rows to table showing diffs. std::vector <std::string> all = context.getColumns (); // Now factor in the annotation attributes. for (auto& it : before) if (it.first.substr (0, 11) == "annotation_") all.push_back (it.first); for (auto& it : after) if (it.first.substr (0, 11) == "annotation_") all.push_back (it.first); // Now render all the attributes. std::sort (all.begin (), all.end ()); std::string before_att; std::string after_att; std::string last_att; for (auto& a : all) { if (a != last_att) // Skip duplicates. { last_att = a; before_att = before.get (a); after_att = after.get (a); // Don't report different uuid. // Show nothing if values are the unchanged. if (a == "uuid" || before_att == after_att) { // Show nothing - no point displaying that which did not change. // row = view.addRow (); // view.set (row, 0, *a + ":"); // view.set (row, 1, before_att); } // Attribute deleted. else if (before_att != "" && after_att == "") { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); view.set (row, 1, before_att, color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); } // Attribute added. else if (before_att == "" && after_att != "") { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); view.set (row, 1, after_att, color_green); } // Attribute changed. else { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); view.set (row, 1, before_att, color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); view.set (row, 1, after_att, color_green); } } } std::cout << "\n" << view.render () << "\n"; } }
void ColumnDate::render ( std::vector <std::string>& lines, Task& task, int width, Color& color) { if (task.has (_name)) { ISO8601d date (task.get_date (_name)); if (_style == "default" || _style == "formatted") { // Determine the output date format, which uses a hierarchy of definitions. // rc.report.<report>.dateformat // rc.dateformat.report // rc.dateformat std::string format = context.config.get ("report." + _report + ".dateformat"); if (format == "") format = context.config.get ("dateformat.report"); if (format == "") format = context.config.get ("dateformat"); lines.push_back ( color.colorize ( leftJustify ( date.toString (format), width))); } else if (_style == "countdown") { ISO8601d now; lines.push_back ( color.colorize ( rightJustify ( ISO8601p (now - date).formatVague (), width))); } else if (_style == "julian") { lines.push_back ( color.colorize ( rightJustify ( format (date.toJulian (), 13, 12), width))); } else if (_style == "epoch") { lines.push_back ( color.colorize ( rightJustify ( date.toEpochString (), width))); } else if (_style == "iso") { lines.push_back ( color.colorize ( leftJustify ( date.toISO (), width))); } else if (_style == "age") { ISO8601d now; lines.push_back ( color.colorize ( leftJustify ( ISO8601p (now - date).formatVague (), width))); } else if (_style == "remaining") { ISO8601d now; if (date > now) lines.push_back ( color.colorize ( rightJustify ( ISO8601p (date - now).formatVague (), width))); } } }
int main (int, char**) { UnitTest t (966); ISO8601d iso; std::string::size_type start = 0; t.notok (iso.parse ("foo", start), "foo --> false"); t.is ((int)start, 0, "foo[0]"); // Determine local and UTC time. time_t now = time (NULL); struct tm* local_now = localtime (&now); int local_s = (local_now->tm_hour * 3600) + (local_now->tm_min * 60) + local_now->tm_sec; local_now->tm_hour = 0; local_now->tm_min = 0; local_now->tm_sec = 0; local_now->tm_isdst = -1; time_t local = mktime (local_now); std::cout << "# local midnight today " << local << "\n"; local_now->tm_year = 2013 - 1900; local_now->tm_mon = 12 - 1; local_now->tm_mday = 6; local_now->tm_isdst = 0; time_t local6 = mktime (local_now); std::cout << "# local midnight 2013-12-06 " << local6 << "\n"; local_now->tm_year = 2013 - 1900; local_now->tm_mon = 12 - 1; local_now->tm_mday = 1; local_now->tm_isdst = 0; time_t local1 = mktime (local_now); std::cout << "# local midnight 2013-12-01 " << local1 << "\n"; struct tm* utc_now = gmtime (&now); int utc_s = (utc_now->tm_hour * 3600) + (utc_now->tm_min * 60) + utc_now->tm_sec; utc_now->tm_hour = 0; utc_now->tm_min = 0; utc_now->tm_sec = 0; utc_now->tm_isdst = -1; time_t utc = timegm (utc_now); std::cout << "# utc midnight today " << utc << "\n"; utc_now->tm_year = 2013 - 1900; utc_now->tm_mon = 12 - 1; utc_now->tm_mday = 6; utc_now->tm_isdst = 0; time_t utc6 = timegm (utc_now); std::cout << "# utc midnight 2013-12-06 " << utc6 << "\n"; utc_now->tm_year = 2013 - 1900; utc_now->tm_mon = 12 - 1; utc_now->tm_mday = 1; utc_now->tm_isdst = 0; time_t utc1 = timegm (utc_now); std::cout << "# utc midnight 2013-12-01 " << utc1 << "\n"; int hms = (12 * 3600) + (34 * 60) + 56; // The time 12:34:56 in seconds. int hm = (12 * 3600) + (34 * 60); // The time 12:34:00 in seconds. int z = 3600; // TZ offset. int ld = local_s > hms ? 86400 : 0; // Local extra day if now > hms. int ud = utc_s > hms ? 86400 : 0; // UTC extra day if now > hms. std::cout << "# ld " << ld << "\n"; std::cout << "# ud " << ud << "\n"; // Aggregated. // input i Year Mo Wk WD Jul Da Secs TZ UTC time_t testParse (t, "12:34:56 ", 8, 0, 0, 0, 0, 0, 0, hms, 0, false, local+hms+ld ); // time-ext // input i Year Mo Wk WD Jul Da Secs TZ UTC time_t testParse (t, "12:34:56Z", 9, 0, 0, 0, 0, 0, 0, hms, 0, true, utc+hms+ud ); testParse (t, "12:34Z", 6, 0, 0, 0, 0, 0, 0, hm, 0, true, utc+hm+ud ); testParse (t, "12:34:56+01:00", 14, 0, 0, 0, 0, 0, 0, hms, 3600, false, utc+hms-z+ud ); testParse (t, "12:34:56+01", 11, 0, 0, 0, 0, 0, 0, hms, 3600, false, utc+hms-z+ud ); testParse (t, "12:34+01:00", 11, 0, 0, 0, 0, 0, 0, hm, 3600, false, utc+hm-z+ud ); testParse (t, "12:34+01", 8, 0, 0, 0, 0, 0, 0, hm, 3600, false, utc+hm-z+ud ); testParse (t, "12:34:56", 8, 0, 0, 0, 0, 0, 0, hms, 0, false, local+hms+ld ); testParse (t, "12:34", 5, 0, 0, 0, 0, 0, 0, hm, 0, false, local+hm+ld ); // datetime-ext // input i Year Mo Wk WD Jul Da Secs TZ UTC time_t testParse (t, "2013-12-06", 10, 2013, 12, 0, 0, 0, 6, 0, 0, false, local6 ); testParse (t, "2013-340", 8, 2013, 0, 0, 0, 340, 0, 0, 0, false, local6 ); testParse (t, "2013-W49-5", 10, 2013, 0, 49, 5, 0, 0, 0, 0, false, local6 ); testParse (t, "2013-W49", 8, 2013, 0, 49, 0, 0, 0, 0, 0, false, local1 ); testParse (t, "2013-12-06T12:34:56", 19, 2013, 12, 0, 0, 0, 6, hms, 0, false, local6+hms); testParse (t, "2013-12-06T12:34", 16, 2013, 12, 0, 0, 0, 6, hm, 0, false, local6+hm ); testParse (t, "2013-340T12:34:56", 17, 2013, 0, 0, 0, 340, 0, hms, 0, false, local6+hms); testParse (t, "2013-340T12:34", 14, 2013, 0, 0, 0, 340, 0, hm, 0, false, local6+hm ); testParse (t, "2013-W49-5T12:34:56", 19, 2013, 0, 49, 5, 0, 0, hms, 0, false, local6+hms); testParse (t, "2013-W49-5T12:34", 16, 2013, 0, 49, 5, 0, 0, hm, 0, false, local6+hm ); testParse (t, "2013-W49T12:34:56", 17, 2013, 0, 49, 0, 0, 0, hms, 0, false, local1+hms); testParse (t, "2013-W49T12:34", 14, 2013, 0, 49, 0, 0, 0, hm, 0, false, local1+hm ); testParse (t, "2013-12-06T12:34:56Z", 20, 2013, 12, 0, 0, 0, 6, hms, 0, true, utc6+hms ); testParse (t, "2013-12-06T12:34Z", 17, 2013, 12, 0, 0, 0, 6, hm, 0, true, utc6+hm ); testParse (t, "2013-340T12:34:56Z", 18, 2013, 0, 0, 0, 340, 0, hms, 0, true, utc6+hms ); testParse (t, "2013-340T12:34Z", 15, 2013, 0, 0, 0, 340, 0, hm, 0, true, utc6+hm ); testParse (t, "2013-W49-5T12:34:56Z", 20, 2013, 0, 49, 5, 0, 0, hms, 0, true, utc6+hms ); testParse (t, "2013-W49-5T12:34Z", 17, 2013, 0, 49, 5, 0, 0, hm, 0, true, utc6+hm ); testParse (t, "2013-W49T12:34:56Z", 18, 2013, 0, 49, 0, 0, 0, hms, 0, true, utc1+hms ); testParse (t, "2013-W49T12:34Z", 15, 2013, 0, 49, 0, 0, 0, hm, 0, true, utc1+hm ); testParse (t, "2013-12-06T12:34:56+01:00", 25, 2013, 12, 0, 0, 0, 6, hms, 3600, false, utc6+hms-z); testParse (t, "2013-12-06T12:34:56+01", 22, 2013, 12, 0, 0, 0, 6, hms, 3600, false, utc6+hms-z); testParse (t, "2013-12-06T12:34:56-01:00", 25, 2013, 12, 0, 0, 0, 6, hms, -3600, false, utc6+hms+z); testParse (t, "2013-12-06T12:34:56-01", 22, 2013, 12, 0, 0, 0, 6, hms, -3600, false, utc6+hms+z); testParse (t, "2013-12-06T12:34+01:00", 22, 2013, 12, 0, 0, 0, 6, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-12-06T12:34+01", 19, 2013, 12, 0, 0, 0, 6, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-12-06T12:34-01:00", 22, 2013, 12, 0, 0, 0, 6, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-12-06T12:34-01", 19, 2013, 12, 0, 0, 0, 6, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-340T12:34:56+01:00", 23, 2013, 0, 0, 0, 340, 0, hms, 3600, false, utc6+hms-z); testParse (t, "2013-340T12:34:56+01", 20, 2013, 0, 0, 0, 340, 0, hms, 3600, false, utc6+hms-z); testParse (t, "2013-340T12:34:56-01:00", 23, 2013, 0, 0, 0, 340, 0, hms, -3600, false, utc6+hms+z); testParse (t, "2013-340T12:34:56-01", 20, 2013, 0, 0, 0, 340, 0, hms, -3600, false, utc6+hms+z); testParse (t, "2013-340T12:34+01:00", 20, 2013, 0, 0, 0, 340, 0, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-340T12:34+01", 17, 2013, 0, 0, 0, 340, 0, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-340T12:34-01:00", 20, 2013, 0, 0, 0, 340, 0, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-340T12:34-01", 17, 2013, 0, 0, 0, 340, 0, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-W49-5T12:34:56+01:00", 25, 2013, 0, 49, 5, 0, 0, hms, 3600, false, utc6+hms-z); testParse (t, "2013-W49-5T12:34:56+01", 22, 2013, 0, 49, 5, 0, 0, hms, 3600, false, utc6+hms-z); testParse (t, "2013-W49-5T12:34:56-01:00", 25, 2013, 0, 49, 5, 0, 0, hms, -3600, false, utc6+hms+z); testParse (t, "2013-W49-5T12:34:56-01", 22, 2013, 0, 49, 5, 0, 0, hms, -3600, false, utc6+hms+z); testParse (t, "2013-W49-5T12:34+01:00", 22, 2013, 0, 49, 5, 0, 0, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-W49-5T12:34+01", 19, 2013, 0, 49, 5, 0, 0, hm, 3600, false, utc6+hm-z ); testParse (t, "2013-W49-5T12:34-01:00", 22, 2013, 0, 49, 5, 0, 0, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-W49-5T12:34-01", 19, 2013, 0, 49, 5, 0, 0, hm, -3600, false, utc6+hm+z ); testParse (t, "2013-W49T12:34:56+01:00", 23, 2013, 0, 49, 0, 0, 0, hms, 3600, false, utc1+hms-z); testParse (t, "2013-W49T12:34:56+01", 20, 2013, 0, 49, 0, 0, 0, hms, 3600, false, utc1+hms-z); testParse (t, "2013-W49T12:34:56-01:00", 23, 2013, 0, 49, 0, 0, 0, hms, -3600, false, utc1+hms+z); testParse (t, "2013-W49T12:34:56-01", 20, 2013, 0, 49, 0, 0, 0, hms, -3600, false, utc1+hms+z); testParse (t, "2013-W49T12:34+01:00", 20, 2013, 0, 49, 0, 0, 0, hm, 3600, false, utc1+hm-z ); testParse (t, "2013-W49T12:34+01", 17, 2013, 0, 49, 0, 0, 0, hm, 3600, false, utc1+hm-z ); testParse (t, "2013-W49T12:34-01:00", 20, 2013, 0, 49, 0, 0, 0, hm, -3600, false, utc1+hm+z ); testParse (t, "2013-W49T12:34-01", 17, 2013, 0, 49, 0, 0, 0, hm, -3600, false, utc1+hm+z ); // The only non-extended forms. testParse (t, "20131206T123456Z", 16, 2013, 12, 0, 0, 0, 6, hms, 0, true, utc6+hms ); testParse (t, "20131206T123456", 15, 2013, 12, 0, 0, 0, 6, hms, 0, false, local6+hms); try { ISO8601d now; t.ok (now.toISO ().find ("1969") == std::string::npos, "'now' != 1969"); ISO8601d yesterday; yesterday -= 86400; ISO8601d tomorrow; tomorrow += 86400; 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"); t.ok (tomorrow >= now, "tomorrow >= now"); t.ok (tomorrow > now, "tomorrow > now"); t.notok (tomorrow == now, "!(tomorrow == now)"); t.ok (tomorrow != now, "tomorrow != now"); t.ok (now <= tomorrow, "now <= tomorrow"); t.ok (now < tomorrow, "now < tomorrow"); // ctor ("now") context.config.set ("weekstart", "monday"); ISO8601d relative_now; t.ok (relative_now.sameHour (now), "ISO8601d ().sameHour (ISO8601d (now))"); t.ok (relative_now.sameDay (now), "ISO8601d ().sameDay (ISO8601d (now))"); t.ok (relative_now.sameWeek (now), "ISO8601d ().sameWeek (ISO8601d (now))"); t.ok (relative_now.sameMonth (now), "ISO8601d ().sameMonth (ISO8601d (now))"); t.ok (relative_now.sameYear (now), "ISO8601d ().sameYear (ISO8601d (now))"); // Loose comparisons. ISO8601d left ("7/4/2008", "m/d/Y"); ISO8601d comp1 ("7/4/2008", "m/d/Y"); t.ok (left.sameDay (comp1), "7/4/2008 is on the same day as 7/4/2008"); t.ok (left.sameWeek (comp1), "7/4/2008 is on the same week as 7/4/2008"); t.ok (left.sameMonth (comp1), "7/4/2008 is in the same month as 7/4/2008"); t.ok (left.sameYear (comp1), "7/4/2008 is in the same year as 7/4/2008"); ISO8601d comp2 ("7/5/2008", "m/d/Y"); t.notok (left.sameDay (comp2), "7/4/2008 is not on the same day as 7/5/2008"); t.ok (left.sameMonth (comp2), "7/4/2008 is in the same month as 7/5/2008"); t.ok (left.sameYear (comp2), "7/4/2008 is in the same year as 7/5/2008"); ISO8601d comp3 ("8/4/2008", "m/d/Y"); t.notok (left.sameDay (comp3), "7/4/2008 is not on the same day as 8/4/2008"); t.notok (left.sameWeek (comp3), "7/4/2008 is not on the same week as 8/4/2008"); t.notok (left.sameMonth (comp3), "7/4/2008 is not in the same month as 8/4/2008"); t.ok (left.sameYear (comp3), "7/4/2008 is in the same year as 8/4/2008"); ISO8601d comp4 ("7/4/2009", "m/d/Y"); t.notok (left.sameDay (comp4), "7/4/2008 is not on the same day as 7/4/2009"); t.notok (left.sameWeek (comp3), "7/4/2008 is not on the same week as 7/4/2009"); t.notok (left.sameMonth (comp4), "7/4/2008 is not in the same month as 7/4/2009"); t.notok (left.sameYear (comp4), "7/4/2008 is not in the same year as 7/4/2009"); // Validity. t.ok (ISO8601d::valid (2, 29, 2008), "valid: 2/29/2008"); t.notok (ISO8601d::valid (2, 29, 2007), "invalid: 2/29/2007"); t.ok (ISO8601d::valid ("2/29/2008", "m/d/Y"), "valid: 2/29/2008"); t.notok (ISO8601d::valid ("2/29/2007", "m/d/Y"), "invalid: 2/29/2007"); t.ok (ISO8601d::valid (366, 2008), "valid: 366 days in 2008"); t.notok (ISO8601d::valid (366, 2007), "invalid: 366 days in 2007"); // Time validity. t.ok (ISO8601d::valid (2, 28, 2010, 0, 0, 0), "valid 2/28/2010 0:00:00"); t.ok (ISO8601d::valid (2, 28, 2010, 23, 59, 59), "valid 2/28/2010 23:59:59"); t.notok (ISO8601d::valid (2, 28, 2010, 24, 59, 59), "valid 2/28/2010 24:59:59"); t.notok (ISO8601d::valid (2, 28, 2010, -1, 0, 0), "valid 2/28/2010 -1:00:00"); // Leap year. t.ok (ISO8601d::leapYear (2008), "2008 is a leap year"); t.notok (ISO8601d::leapYear (2007), "2007 is not a leap year"); t.ok (ISO8601d::leapYear (2000), "2000 is a leap year"); t.notok (ISO8601d::leapYear (1900), "1900 is not a leap year"); // Days in year. t.is (ISO8601d::daysInYear (2016), 366, "366 days in 2016"); t.is (ISO8601d::daysInYear (2015), 365, "365 days in 2015"); // Days in month. t.is (ISO8601d::daysInMonth (2, 2008), 29, "29 days in February 2008"); t.is (ISO8601d::daysInMonth (2, 2007), 28, "28 days in February 2007"); // Names. t.is (ISO8601d::monthName (1), "January", "1 = January"); t.is (ISO8601d::monthName (2), "February", "2 = February"); t.is (ISO8601d::monthName (3), "March", "3 = March"); t.is (ISO8601d::monthName (4), "April", "4 = April"); t.is (ISO8601d::monthName (5), "May", "5 = May"); t.is (ISO8601d::monthName (6), "June", "6 = June"); t.is (ISO8601d::monthName (7), "July", "7 = July"); t.is (ISO8601d::monthName (8), "August", "8 = August"); t.is (ISO8601d::monthName (9), "September", "9 = September"); t.is (ISO8601d::monthName (10), "October", "10 = October"); t.is (ISO8601d::monthName (11), "November", "11 = November"); t.is (ISO8601d::monthName (12), "December", "12 = December"); // Names. t.is (ISO8601d::monthOfYear ("January"), 1, "January = 1"); t.is (ISO8601d::monthOfYear ("February"), 2, "February = 2"); t.is (ISO8601d::monthOfYear ("March"), 3, "March = 3"); t.is (ISO8601d::monthOfYear ("April"), 4, "April = 4"); t.is (ISO8601d::monthOfYear ("May"), 5, "May = 5"); t.is (ISO8601d::monthOfYear ("June"), 6, "June = 6"); t.is (ISO8601d::monthOfYear ("July"), 7, "July = 7"); t.is (ISO8601d::monthOfYear ("August"), 8, "August = 8"); t.is (ISO8601d::monthOfYear ("September"), 9, "September = 9"); t.is (ISO8601d::monthOfYear ("October"), 10, "October = 10"); t.is (ISO8601d::monthOfYear ("November"), 11, "November = 11"); t.is (ISO8601d::monthOfYear ("December"), 12, "December = 12"); t.is (ISO8601d::dayName (0), "Sunday", "0 == Sunday"); t.is (ISO8601d::dayName (1), "Monday", "1 == Monday"); t.is (ISO8601d::dayName (2), "Tuesday", "2 == Tuesday"); t.is (ISO8601d::dayName (3), "Wednesday", "3 == Wednesday"); t.is (ISO8601d::dayName (4), "Thursday", "4 == Thursday"); t.is (ISO8601d::dayName (5), "Friday", "5 == Friday"); t.is (ISO8601d::dayName (6), "Saturday", "6 == Saturday"); t.is (ISO8601d::dayOfWeek ("SUNDAY"), 0, "SUNDAY == 0"); t.is (ISO8601d::dayOfWeek ("sunday"), 0, "sunday == 0"); t.is (ISO8601d::dayOfWeek ("Sunday"), 0, "Sunday == 0"); t.is (ISO8601d::dayOfWeek ("Monday"), 1, "Monday == 1"); t.is (ISO8601d::dayOfWeek ("Tuesday"), 2, "Tuesday == 2"); t.is (ISO8601d::dayOfWeek ("Wednesday"), 3, "Wednesday == 3"); t.is (ISO8601d::dayOfWeek ("Thursday"), 4, "Thursday == 4"); t.is (ISO8601d::dayOfWeek ("Friday"), 5, "Friday == 5"); t.is (ISO8601d::dayOfWeek ("Saturday"), 6, "Saturday == 6"); ISO8601d 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"); t.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"); ISO8601d epoch (9, 8, 2001); t.ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000"); epoch += 172800; t.ok ((int)epoch.toEpoch () > 1000000000, "9/10/2001 > 1,000,000,000"); ISO8601d fromEpoch (epoch.toEpoch ()); t.is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)"); ISO8601d iso (1000000000); t.is (iso.toISO (), "20010909T014640Z", "1,000,000,000 -> 20010909T014640Z"); // Quantization. ISO8601d quant (1234526400); t.is (quant.startOfDay ().toString ("YMDHNS"), "20090213000000", "1234526400 -> 2/13/2009 12:00:00 UTC -> 2/13/2009 0:00:00"); t.is (quant.startOfWeek ().toString ("YMDHNS"), "20090208000000", "1234526400 -> 2/13/2009 12:00:00 UTC -> 2/8/2009 0:00:00"); t.is (quant.startOfMonth ().toString ("YMDHNS"), "20090201000000", "1234526400 -> 2/13/2009 12:00:00 UTC -> 2/1/2009 0:00:00"); t.is (quant.startOfYear ().toString ("YMDHNS"), "20090101000000", "1234526400 -> 2/13/2009 12:00:00 UTC -> 1/1/2009 0:00:00"); // Format parsing. ISO8601d fromString1 ("1/1/2008", "m/d/Y"); t.is (fromString1.month (), 1, "ctor (std::string) -> m"); t.is (fromString1.day (), 1, "ctor (std::string) -> d"); t.is (fromString1.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString2 ("20080101", "YMD"); t.is (fromString2.month (), 1, "ctor (std::string) -> m"); t.is (fromString2.day (), 1, "ctor (std::string) -> d"); t.is (fromString2.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString3 ("12/31/2007", "m/d/Y"); t.is (fromString3.month (), 12, "ctor (std::string) -> m"); t.is (fromString3.day (), 31, "ctor (std::string) -> d"); t.is (fromString3.year (), 2007, "ctor (std::string) -> y"); ISO8601d fromString4 ("01/01/2008", "m/d/Y"); t.is (fromString4.month (), 1, "ctor (std::string) -> m"); t.is (fromString4.day (), 1, "ctor (std::string) -> d"); t.is (fromString4.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString5 ("Tue 05 Feb 2008 (06)", "a D b Y (V)"); t.is (fromString5.month (), 2, "ctor (std::string) -> m"); t.is (fromString5.day (), 5, "ctor (std::string) -> d"); t.is (fromString5.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString6 ("Tuesday, February 5, 2008", "A, B d, Y"); t.is (fromString6.month (), 2, "ctor (std::string) -> m"); t.is (fromString6.day (), 5, "ctor (std::string) -> d"); t.is (fromString6.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString7 ("w01 Tue 2008-01-01", "wV a Y-M-D"); t.is (fromString7.month (), 1, "ctor (std::string) -> m"); t.is (fromString7.day (), 1, "ctor (std::string) -> d"); t.is (fromString7.year (), 2008, "ctor (std::string) -> y"); ISO8601d fromString8 ("6/7/2010 1:23:45", "m/d/Y h:N:S"); t.is (fromString8.month (), 6, "ctor (std::string) -> m"); t.is (fromString8.day (), 7, "ctor (std::string) -> d"); t.is (fromString8.year (), 2010, "ctor (std::string) -> Y"); t.is (fromString8.hour (), 1, "ctor (std::string) -> h"); t.is (fromString8.minute (), 23, "ctor (std::string) -> N"); t.is (fromString8.second (), 45, "ctor (std::string) -> S"); ISO8601d fromString9 ("6/7/2010 01:23:45", "m/d/Y H:N:S"); t.is (fromString9.month (), 6, "ctor (std::string) -> m"); t.is (fromString9.day (), 7, "ctor (std::string) -> d"); t.is (fromString9.year (), 2010, "ctor (std::string) -> Y"); t.is (fromString9.hour (), 1, "ctor (std::string) -> h"); t.is (fromString9.minute (), 23, "ctor (std::string) -> N"); t.is (fromString9.second (), 45, "ctor (std::string) -> S"); ISO8601d fromString10 ("6/7/2010 12:34:56", "m/d/Y H:N:S"); t.is (fromString10.month (), 6, "ctor (std::string) -> m"); t.is (fromString10.day (), 7, "ctor (std::string) -> d"); t.is (fromString10.year (), 2010, "ctor (std::string) -> Y"); t.is (fromString10.hour (), 12, "ctor (std::string) -> h"); t.is (fromString10.minute (), 34, "ctor (std::string) -> N"); t.is (fromString10.second (), 56, "ctor (std::string) -> S"); // Day of year t.is (ISO8601d ("1/1/2011", "m/d/Y").dayOfYear (), 1, "dayOfYear (1/1/2011) -> 1"); t.is (ISO8601d ("5/1/2011", "m/d/Y").dayOfYear (), 121, "dayOfYear (5/1/2011) -> 121"); t.is (ISO8601d ("12/31/2011", "m/d/Y").dayOfYear (), 365, "dayOfYear (12/31/2011) -> 365"); // Relative dates. ISO8601d r1 ("today"); t.ok (r1.sameDay (now), "today = now"); ISO8601d r4 ("sunday"); if (now.dayOfWeek () >= 0) t.ok (r4.sameDay (now + (0 - now.dayOfWeek () + 7) * 86400), "next sunday"); else t.ok (r4.sameDay (now + (0 - now.dayOfWeek ()) * 86400), "next sunday");; ISO8601d r5 ("monday"); if (now.dayOfWeek () >= 1) t.ok (r5.sameDay (now + (1 - now.dayOfWeek () + 7) * 86400), "next monday"); else t.ok (r5.sameDay (now + (1 - now.dayOfWeek ()) * 86400), "next monday");; ISO8601d r6 ("tuesday"); if (now.dayOfWeek () >= 2) t.ok (r6.sameDay (now + (2 - now.dayOfWeek () + 7) * 86400), "next tuesday"); else t.ok (r6.sameDay (now + (2 - now.dayOfWeek ()) * 86400), "next tuesday");; ISO8601d r7 ("wednesday"); if (now.dayOfWeek () >= 3) t.ok (r7.sameDay (now + (3 - now.dayOfWeek () + 7) * 86400), "next wednesday"); else t.ok (r7.sameDay (now + (3 - now.dayOfWeek ()) * 86400), "next wednesday");; ISO8601d r8 ("thursday"); if (now.dayOfWeek () >= 4) t.ok (r8.sameDay (now + (4 - now.dayOfWeek () + 7) * 86400), "next thursday"); else t.ok (r8.sameDay (now + (4 - now.dayOfWeek ()) * 86400), "next thursday");; ISO8601d r9 ("friday"); if (now.dayOfWeek () >= 5) t.ok (r9.sameDay (now + (5 - now.dayOfWeek () + 7) * 86400), "next friday"); else t.ok (r9.sameDay (now + (5 - now.dayOfWeek ()) * 86400), "next friday");; ISO8601d r10 ("saturday"); if (now.dayOfWeek () >= 6) t.ok (r10.sameDay (now + (6 - now.dayOfWeek () + 7) * 86400), "next saturday"); else t.ok (r10.sameDay (now + (6 - now.dayOfWeek ()) * 86400), "next saturday");; ISO8601d r11 ("eow"); t.ok (r11 < now + (8 * 86400), "eow < 7 days away"); ISO8601d r12 ("eocw"); t.ok (r12 > now - (8 * 86400), "eocw < 7 days in the past"); ISO8601d r13 ("eom"); t.ok (r13.sameMonth (now), "eom in same month as now"); ISO8601d r14 ("eocm"); t.ok (r14.sameMonth (now), "eocm in same month as now"); ISO8601d r15 ("eoy"); t.ok (r15.sameYear (now), "eoy in same year as now"); ISO8601d r16 ("sow"); t.ok (r16 < now + (8 * 86400), "sow < 7 days away"); ISO8601d r23 ("socw"); t.ok (r23 > now - (8 * 86400), "sow < 7 days in the past"); ISO8601d r17 ("som"); t.notok (r17.sameMonth (now), "som not in same month as now"); ISO8601d r18 ("socm"); t.ok (r18.sameMonth (now), "socm in same month as now"); ISO8601d r19 ("soy"); t.notok (r19.sameYear (now), "soy not in same year as now"); ISO8601d first ("1st"); t.notok (first.sameMonth (now), "1st not in same month as now"); t.is (first.day (), 1, "1st day is 1"); ISO8601d later ("later"); t.is (later.month (), 1, "later -> m = 1"); t.is (later.day (), 18, "later -> d = 18"); t.is (later.year (), 2038, "later -> y = 2038"); // Quarters ISO8601d soq ("soq"); ISO8601d eoq ("eoq"); t.is (soq.day (), 1, "soq is the first day of a month"); t.is (eoq.day () / 10, 3, "eoq is the 30th or 31th of a month"); t.is (soq.month () % 3, 1, "soq month is 1, 4, 7 or 10"); t.is (eoq.month () % 3, 0, "eoq month is 3, 6, 9 or 12"); // Note: these fail during the night of daylight savings end. t.ok (soq.sameYear (now) || (now.month () >= 10 && soq.year () == now.year () + 1), "soq is in same year as now"); t.ok (eoq.sameYear (now), "eoq is in same year as now"); // ISO8601d::sameHour ISO8601d r20 ("6/7/2010 01:00:00", "m/d/Y H:N:S"); ISO8601d r21 ("6/7/2010 01:59:59", "m/d/Y H:N:S"); t.ok (r20.sameHour (r21), "two dates within the same hour"); ISO8601d r22 ("6/7/2010 00:59:59", "m/d/Y H:N:S"); t.notok (r20.sameHour (r22), "two dates not within the same hour"); // ISO8601d::operator- ISO8601d r25 (1234567890); t.is ((r25 - 1).toEpoch (), 1234567889, "1234567890 - 1 = 1234567889"); // ISO8601d::operator-- ISO8601d r26 (11, 7, 2010, 23, 59, 59); r26--; t.is (r26.toString ("YMDHNS"), "20101106235959", "decrement across fall DST boundary"); ISO8601d r27 (3, 14, 2010, 23, 59, 59); r27--; t.is (r27.toString ("YMDHNS"), "20100313235959", "decrement across spring DST boundary"); // ISO8601d::operator++ ISO8601d r28 (11, 6, 2010, 23, 59, 59); r28++; t.is (r28.toString ("YMDHNS"), "20101107235959", "increment across fall DST boundary"); ISO8601d r29 (3, 13, 2010, 23, 59, 59); r29++; t.is (r29.toString ("YMDHNS"), "20100314235959", "increment across spring DST boundary"); // int ISO8601d::length (const std::string&); t.is (ISO8601d::length ("m"), 2, "length 'm' --> 2"); t.is (ISO8601d::length ("M"), 2, "length 'M' --> 2"); t.is (ISO8601d::length ("d"), 2, "length 'd' --> 2"); t.is (ISO8601d::length ("D"), 2, "length 'D' --> 2"); t.is (ISO8601d::length ("y"), 2, "length 'y' --> 2"); t.is (ISO8601d::length ("Y"), 4, "length 'Y' --> 4"); t.is (ISO8601d::length ("a"), 3, "length 'a' --> 3"); t.is (ISO8601d::length ("A"), 10, "length 'A' --> 10"); t.is (ISO8601d::length ("b"), 3, "length 'b' --> 3"); t.is (ISO8601d::length ("B"), 10, "length 'B' --> 10"); t.is (ISO8601d::length ("v"), 2, "length 'v' --> 2"); t.is (ISO8601d::length ("V"), 2, "length 'V' --> 2"); t.is (ISO8601d::length ("h"), 2, "length 'h' --> 2"); t.is (ISO8601d::length ("H"), 2, "length 'H' --> 2"); t.is (ISO8601d::length ("n"), 2, "length 'n' --> 2"); t.is (ISO8601d::length ("N"), 2, "length 'N' --> 2"); t.is (ISO8601d::length ("s"), 2, "length 's' --> 2"); t.is (ISO8601d::length ("S"), 2, "length 'S' --> 2"); t.is (ISO8601d::length ("j"), 3, "length 'j' --> 3"); t.is (ISO8601d::length ("J"), 3, "length 'J' --> 3"); t.is (ISO8601d::length (" "), 1, "length ' ' --> 1"); // Depletion requirement. ISO8601d r30 ("Mon Jun 30 2014", "a b D Y"); t.is (r30.toString ("YMDHNS"), "20140630000000", "Depletion required on complex format with spaces"); ISO8601d r31 ("Mon Jun 30 2014 xxx", "a b D Y"); t.is (r31.toString ("YMDHNS"), "20140630000000", "Depletion not required on complex format with spaces"); } catch (const std::string& e) { t.fail ("Exception thrown."); t.diag (e); } return 0; }
bool Nibbler::getDate (const std::string& format, time_t& t) { auto i = _cursor; int month = -1; // So we can check later. int day = -1; int year = -1; int hour = -1; 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': case 'M': if (! parseDigits(i, month, 2, format[f] == 'M')) return false; break; case 'd': case 'D': if (! parseDigits(i, day, 2, format[f] == 'D')) return false; break; case 'y': 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 'n': case 'N': if (! parseDigits(i, minute, 2, format[f] == 'N')) return false; break; case 's': case 'S': if (! parseDigits(i, second, 2, format[f] == 'S')) return false; break; // Merely parse, not extract. case 'v': case 'V': if (! parseDigits(i, week, 2, format[f] == 'V')) return false; break; // Merely parse, not extract. case 'a': case 'A': if (i + 3 <= _length && ! Lexer::isDigit ((*_input)[i + 0]) && ! Lexer::isDigit ((*_input)[i + 1]) && ! Lexer::isDigit ((*_input)[i + 2])) { wday = ISO8601d::dayOfWeek (_input->substr (i, 3).c_str ()); i += (format[f] == 'a') ? 3 : ISO8601d::dayName (wday).size (); } else return false; break; case 'b': case 'B': if (i + 3 <= _length && ! Lexer::isDigit ((*_input)[i + 0]) && ! Lexer::isDigit ((*_input)[i + 1]) && ! Lexer::isDigit ((*_input)[i + 2])) { if (month != -1) return false; month = ISO8601d::monthOfYear (_input->substr (i, 3).c_str()); i += (format[f] == 'b') ? 3 : ISO8601d::monthName (month).size (); } else return false; break; default: if (i + 1 <= _length && (*_input)[i] == format[f]) ++i; else return false; break; } } // By default, the most global date variables that are undefined are put to // the current date (for instance, the year to the current year for formats // that lack Y/y). If even 'second' is undefined, then the date is parsed as // now. if (year == -1) { ISO8601d now; year = now.year (); if (month == -1) { month = now.month (); if (day == -1) { day = now.day (); if (hour == -1) { hour = now.hour (); if (minute == -1) { minute = now.minute (); if (second == -1) second = now.second (); } } } } } // Put all remaining undefined date variables to their default values (0 or // 1). month = (month == -1) ? 1 : month; day = (day == -1) ? 1 : day; hour = (hour == -1) ? 0 : hour; minute = (minute == -1) ? 0 : minute; second = (second == -1) ? 0 : second; // Check that values are correct if (! ISO8601d::valid (month, day, year, hour, minute, second)) return false; // Convert to epoch. struct tm tms {}; tms.tm_isdst = -1; // Requests that mktime determine summer time effect. tms.tm_mon = month - 1; tms.tm_mday = day; tms.tm_year = year - 1900; tms.tm_hour = hour; tms.tm_min = minute; tms.tm_sec = second; t = mktime (&tms); _cursor = i; return true; }