mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Date Formatting
- Some bad inefficiencies in date formatting were noticed, and when addressed, caused a bug to surface. The length of a formatted date can be calculated from the dateformat, but was done incorrectly. Very, very incorrectly. - Added unit tests. - Promoted date column-specific "countdown" size measurements up to the ColDate base class. This neatly falls out from work on #1218. - Noted a potential I18N problem in Date.cpp.
This commit is contained in:
parent
d895c4a249
commit
656e350291
8 changed files with 79 additions and 122 deletions
17
src/Date.cpp
17
src/Date.cpp
|
@ -237,9 +237,9 @@ const std::string Date::toString (
|
||||||
case 'y': sprintf (buffer, "%02d", this->year () % 100); break;
|
case 'y': sprintf (buffer, "%02d", this->year () % 100); break;
|
||||||
case 'Y': sprintf (buffer, "%d", this->year ()); break;
|
case 'Y': sprintf (buffer, "%d", this->year ()); break;
|
||||||
case 'a': sprintf (buffer, "%.3s", Date::dayName (dayOfWeek ()).c_str ()); break;
|
case 'a': sprintf (buffer, "%.3s", Date::dayName (dayOfWeek ()).c_str ()); break;
|
||||||
case 'A': sprintf (buffer, "%s", Date::dayName (dayOfWeek ()).c_str ()); break;
|
case 'A': sprintf (buffer, "%.10s", Date::dayName (dayOfWeek ()).c_str ()); break;
|
||||||
case 'b': sprintf (buffer, "%.3s", Date::monthName (month ()).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 'B': sprintf (buffer, "%.10s", Date::monthName (month ()).c_str ()); break;
|
||||||
case 'v': sprintf (buffer, "%d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break;
|
case 'v': sprintf (buffer, "%d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break;
|
||||||
case 'V': sprintf (buffer, "%02d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break;
|
case 'V': sprintf (buffer, "%02d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break;
|
||||||
case 'h': sprintf (buffer, "%d", this->hour ()); break;
|
case 'h': sprintf (buffer, "%d", this->hour ()); break;
|
||||||
|
@ -527,16 +527,23 @@ int Date::length (const std::string& format)
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'D':
|
case 'D':
|
||||||
case 'y':
|
case 'y':
|
||||||
case 'A':
|
case 'v':
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
case 'V':
|
case 'V':
|
||||||
case 'h':
|
case 'h':
|
||||||
case 'H':
|
case 'H':
|
||||||
|
case 'n':
|
||||||
case 'N':
|
case 'N':
|
||||||
|
case 's':
|
||||||
case 'S': total += 2; break;
|
case 'S': total += 2; break;
|
||||||
|
case 'b':
|
||||||
|
case 'j':
|
||||||
|
case 'J':
|
||||||
case 'a': total += 3; break;
|
case 'a': total += 3; break;
|
||||||
case 'Y': total += 4; break;
|
case 'Y': total += 4; break;
|
||||||
|
case 'A':
|
||||||
|
case 'B': total += 10; break;
|
||||||
|
|
||||||
|
// TODO This should be a calculated character width, not necessarily 1.
|
||||||
default: total += 1; break;
|
default: total += 1; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,13 @@ void ColumnDate::measure (Task& task, unsigned int& minimum, unsigned int& maxim
|
||||||
if (format == "")
|
if (format == "")
|
||||||
format = context.config.get ("dateformat");
|
format = context.config.get ("dateformat");
|
||||||
|
|
||||||
minimum = maximum = date.toString (format).length ();
|
minimum = maximum = Date::length (format);
|
||||||
|
}
|
||||||
|
else if (_style == "countdown")
|
||||||
|
{
|
||||||
|
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
||||||
|
Date now;
|
||||||
|
minimum = maximum = Duration (now - date).format ().length ();
|
||||||
}
|
}
|
||||||
else if (_style == "julian")
|
else if (_style == "julian")
|
||||||
{
|
{
|
||||||
|
@ -134,7 +140,7 @@ void ColumnDate::render (
|
||||||
// Determine the output date format, which uses a hierarchy of definitions.
|
// Determine the output date format, which uses a hierarchy of definitions.
|
||||||
// rc.report.<report>.dateformat
|
// rc.report.<report>.dateformat
|
||||||
// rc.dateformat.report
|
// rc.dateformat.report
|
||||||
// rc.dateformat.
|
// rc.dateformat
|
||||||
std::string format = context.config.get ("report." + _report + ".dateformat");
|
std::string format = context.config.get ("report." + _report + ".dateformat");
|
||||||
if (format == "")
|
if (format == "")
|
||||||
format = context.config.get ("dateformat.report");
|
format = context.config.get ("dateformat.report");
|
||||||
|
@ -147,6 +153,16 @@ void ColumnDate::render (
|
||||||
Date ((time_t) strtol (task.get (_name).c_str (), NULL, 10))
|
Date ((time_t) strtol (task.get (_name).c_str (), NULL, 10))
|
||||||
.toString (format), width)));
|
.toString (format), width)));
|
||||||
}
|
}
|
||||||
|
else if (_style == "countdown")
|
||||||
|
{
|
||||||
|
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
||||||
|
Date now;
|
||||||
|
|
||||||
|
lines.push_back (
|
||||||
|
color.colorize (
|
||||||
|
rightJustify (
|
||||||
|
Duration (now - date).format (), width)));
|
||||||
|
}
|
||||||
else if (_style == "julian")
|
else if (_style == "julian")
|
||||||
{
|
{
|
||||||
lines.push_back (
|
lines.push_back (
|
||||||
|
|
|
@ -73,46 +73,3 @@ void ColumnDue::setStyle (const std::string& value)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Set the minimum and maximum widths for the value.
|
|
||||||
void ColumnDue::measure (Task& task, unsigned int& minimum, unsigned int& maximum)
|
|
||||||
{
|
|
||||||
minimum = maximum = 0;
|
|
||||||
|
|
||||||
if (task.has (_name))
|
|
||||||
{
|
|
||||||
if (_style == "countdown")
|
|
||||||
{
|
|
||||||
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
|
||||||
Date now;
|
|
||||||
minimum = maximum = Duration (now - date).format ().length ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ColumnDate::measure (task, minimum, maximum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
void ColumnDue::render (
|
|
||||||
std::vector <std::string>& lines,
|
|
||||||
Task& task,
|
|
||||||
int width,
|
|
||||||
Color& color)
|
|
||||||
{
|
|
||||||
if (task.has (_name))
|
|
||||||
{
|
|
||||||
if (_style == "countdown")
|
|
||||||
{
|
|
||||||
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
|
||||||
Date now;
|
|
||||||
|
|
||||||
lines.push_back (
|
|
||||||
color.colorize (
|
|
||||||
rightJustify (
|
|
||||||
Duration (now - date).format (), width)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ColumnDate::render (lines, task, width, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
|
@ -39,8 +39,6 @@ public:
|
||||||
|
|
||||||
bool validate (std::string&);
|
bool validate (std::string&);
|
||||||
void setStyle (const std::string&);
|
void setStyle (const std::string&);
|
||||||
void measure (Task&, unsigned int&, unsigned int&);
|
|
||||||
void render (std::vector <std::string>&, Task&, int, Color&);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -73,46 +73,3 @@ void ColumnScheduled::setStyle (const std::string& value)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Set the minimum and maximum widths for the value.
|
|
||||||
void ColumnScheduled::measure (Task& task, unsigned int& minimum, unsigned int& maximum)
|
|
||||||
{
|
|
||||||
minimum = maximum = 0;
|
|
||||||
|
|
||||||
if (task.has (_name))
|
|
||||||
{
|
|
||||||
if (_style == "countdown")
|
|
||||||
{
|
|
||||||
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
|
||||||
Date now;
|
|
||||||
minimum = maximum = Duration (now - date).format ().length ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ColumnDate::measure (task, minimum, maximum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
void ColumnScheduled::render (
|
|
||||||
std::vector <std::string>& lines,
|
|
||||||
Task& task,
|
|
||||||
int width,
|
|
||||||
Color& color)
|
|
||||||
{
|
|
||||||
if (task.has (_name))
|
|
||||||
{
|
|
||||||
if (_style == "countdown")
|
|
||||||
{
|
|
||||||
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
|
|
||||||
Date now;
|
|
||||||
|
|
||||||
lines.push_back (
|
|
||||||
color.colorize (
|
|
||||||
rightJustify (
|
|
||||||
Duration (now - date).format (), width)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ColumnDate::render (lines, task, width, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
|
@ -39,8 +39,6 @@ public:
|
||||||
|
|
||||||
bool validate (std::string&);
|
bool validate (std::string&);
|
||||||
void setStyle (const std::string&);
|
void setStyle (const std::string&);
|
||||||
void measure (Task&, unsigned int&, unsigned int&);
|
|
||||||
void render (std::vector <std::string>&, Task&, int, Color&);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -92,7 +92,7 @@ void ColumnUDA::measure (Task& task, unsigned int& minimum, unsigned int& maximu
|
||||||
// Determine the output date format, which uses a hierarchy of definitions.
|
// Determine the output date format, which uses a hierarchy of definitions.
|
||||||
// rc.report.<report>.dateformat
|
// rc.report.<report>.dateformat
|
||||||
// rc.dateformat.report
|
// rc.dateformat.report
|
||||||
// rc.dateformat.
|
// rc.dateformat
|
||||||
Date date ((time_t) strtol (value.c_str (), NULL, 10));
|
Date date ((time_t) strtol (value.c_str (), NULL, 10));
|
||||||
std::string format = context.config.get ("report." + _report + ".dateformat");
|
std::string format = context.config.get ("report." + _report + ".dateformat");
|
||||||
if (format == "")
|
if (format == "")
|
||||||
|
@ -100,7 +100,7 @@ void ColumnUDA::measure (Task& task, unsigned int& minimum, unsigned int& maximu
|
||||||
if (format == "")
|
if (format == "")
|
||||||
format = context.config.get ("dateformat");
|
format = context.config.get ("dateformat");
|
||||||
|
|
||||||
minimum = maximum = utf8_width (date.toString (format));
|
minimum = maximum = Date::length (format);
|
||||||
}
|
}
|
||||||
else if (_type == "duration")
|
else if (_type == "duration")
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,7 +35,7 @@ Context context;
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int main (int argc, char** argv)
|
int main (int argc, char** argv)
|
||||||
{
|
{
|
||||||
UnitTest t (184);
|
UnitTest t (205);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -407,6 +407,30 @@ int main (int argc, char** argv)
|
||||||
Date r29 (3, 13, 2010, 23, 59, 59);
|
Date r29 (3, 13, 2010, 23, 59, 59);
|
||||||
r29++;
|
r29++;
|
||||||
t.is (r29.toString ("YMDHNS"), "20100314235959", "increment across spring DST boundary");
|
t.is (r29.toString ("YMDHNS"), "20100314235959", "increment across spring DST boundary");
|
||||||
|
|
||||||
|
// int Date::length (const std::string&);
|
||||||
|
t.is (Date::length ("m"), 2, "length 'm' --> 2");
|
||||||
|
t.is (Date::length ("M"), 2, "length 'M' --> 2");
|
||||||
|
t.is (Date::length ("d"), 2, "length 'd' --> 2");
|
||||||
|
t.is (Date::length ("D"), 2, "length 'D' --> 2");
|
||||||
|
t.is (Date::length ("y"), 2, "length 'y' --> 2");
|
||||||
|
t.is (Date::length ("Y"), 4, "length 'Y' --> 4");
|
||||||
|
t.is (Date::length ("a"), 3, "length 'a' --> 3");
|
||||||
|
t.is (Date::length ("A"), 10, "length 'A' --> 10");
|
||||||
|
t.is (Date::length ("b"), 3, "length 'b' --> 3");
|
||||||
|
t.is (Date::length ("B"), 10, "length 'B' --> 10");
|
||||||
|
t.is (Date::length ("v"), 2, "length 'v' --> 2");
|
||||||
|
t.is (Date::length ("V"), 2, "length 'V' --> 2");
|
||||||
|
t.is (Date::length ("h"), 2, "length 'h' --> 2");
|
||||||
|
t.is (Date::length ("H"), 2, "length 'H' --> 2");
|
||||||
|
t.is (Date::length ("n"), 2, "length 'n' --> 2");
|
||||||
|
t.is (Date::length ("N"), 2, "length 'N' --> 2");
|
||||||
|
t.is (Date::length ("s"), 2, "length 's' --> 2");
|
||||||
|
t.is (Date::length ("S"), 2, "length 'S' --> 2");
|
||||||
|
t.is (Date::length ("j"), 3, "length 'j' --> 3");
|
||||||
|
t.is (Date::length ("J"), 3, "length 'J' --> 3");
|
||||||
|
|
||||||
|
t.is (Date::length (" "), 1, "length ' ' --> 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (const std::string& e)
|
catch (const std::string& e)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue