• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 20532341662

27 Dec 2025 01:15AM UTC coverage: 85.288% (+0.07%) from 85.223%
20532341662

Pull #4014

github

web-flow
Merge 14440aa85 into a061bdf26
Pull Request #4014: UDADuration: add `age` and `countdown` format styles

18 of 18 new or added lines in 2 files covered. (100.0%)

5 existing lines in 3 files now uncovered.

19630 of 23016 relevant lines covered (85.29%)

23480.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.13
/src/columns/ColTypeDate.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included
13
// in all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
// SOFTWARE.
22
//
23
// https://www.opensource.org/licenses/mit-license.php
24
//
25
////////////////////////////////////////////////////////////////////////////////
26

27
#include <cmake.h>
28
// cmake.h include header must come first
29

30
#include <ColTypeDate.h>
31
#include <Context.h>
32
#include <Datetime.h>
33
#include <Duration.h>
34
#include <Eval.h>
35
#include <Filter.h>
36
#include <Variant.h>
37
#include <format.h>
38

39
////////////////////////////////////////////////////////////////////////////////
40
ColumnTypeDate::ColumnTypeDate() {
40,003✔
41
  _name = "";
40,003✔
42
  _type = "date";
40,003✔
43
  _style = "formatted";
40,003✔
44
  _label = "";
40,003✔
45
  _styles = {"formatted", "julian", "epoch", "iso", "age", "relative", "remaining", "countdown"};
360,027✔
46

47
  Datetime now;
40,003✔
48
  now -= 125;  // So that "age" is non-zero.
40,003✔
49
  _examples = {now.toString(Context::getContext().config.get("dateformat")),
480,036✔
50
               format(now.toJulian(), 13, 12),
51
               now.toEpochString(),
52
               now.toISO(),
53
               Duration(Datetime() - now).formatVague(true),
40,003✔
54
               '-' + Duration(Datetime() - now).formatVague(true),
80,006✔
55
               "",
56
               Duration(Datetime() - now).format()};
440,033✔
57
}
360,027✔
58

59
////////////////////////////////////////////////////////////////////////////////
60
// Set the minimum and maximum widths for the value.
61
void ColumnTypeDate::measure(Task& task, unsigned int& minimum, unsigned int& maximum) {
4,847✔
62
  minimum = maximum = 0;
4,847✔
63
  if (task.has(_name)) {
4,847✔
64
    Datetime date(task.get_date(_name));
1,382✔
65

66
    if (_style == "default" || _style == "formatted") {
1,382✔
67
      // Determine the output date format, which uses a hierarchy of definitions.
68
      //   rc.report.<report>.dateformat
69
      //   rc.dateformat.report
70
      //   rc.dateformat.
71
      std::string format = Context::getContext().config.get("report." + _report + ".dateformat");
383✔
72
      if (format == "") format = Context::getContext().config.get("dateformat.report");
1,111✔
73
      if (format == "") format = Context::getContext().config.get("dateformat");
1,111✔
74

75
      minimum = maximum = Datetime::length(format);
383✔
76
    } else if (_style == "countdown") {
1,382✔
77
      Datetime now;
84✔
78
      if (date > now) minimum = maximum = Duration(date - now).format().length();
84✔
79
    } else if (_style == "julian") {
915✔
80
      minimum = maximum = format(date.toJulian(), 13, 12).length();
2✔
81
    } else if (_style == "epoch") {
913✔
82
      minimum = maximum = date.toEpochString().length();
2✔
83
    } else if (_style == "iso") {
911✔
84
      minimum = maximum = date.toISO().length();
2✔
85
    } else if (_style == "age") {
909✔
86
      Datetime now;
852✔
87
      if (now > date)
852✔
88
        minimum = maximum = Duration(now - date).formatVague(true).length();
492✔
89
      else
90
        minimum = maximum = Duration(date - now).formatVague(true).length() + 1;
360✔
91
    } else if (_style == "relative") {
57✔
92
      Datetime now;
19✔
93
      if (now < date)
19✔
94
        minimum = maximum = Duration(date - now).formatVague(true).length();
9✔
95
      else
96
        minimum = maximum = Duration(now - date).formatVague(true).length() + 1;
10✔
97
    } else if (_style == "remaining") {
38✔
98
      Datetime now;
38✔
99
      if (date > now) minimum = maximum = Duration(date - now).formatVague(true).length();
38✔
100
    }
101
  }
102
}
4,847✔
103

104
////////////////////////////////////////////////////////////////////////////////
105
void ColumnTypeDate::render(std::vector<std::string>& lines, Task& task, int width, Color& color) {
1,791✔
106
  if (task.has(_name)) {
1,791✔
107
    Datetime date(task.get_date(_name));
1,363✔
108

109
    if (_style == "default" || _style == "formatted") {
1,363✔
110
      // Determine the output date format, which uses a hierarchy of definitions.
111
      //   rc.report.<report>.dateformat
112
      //   rc.dateformat.report
113
      //   rc.dateformat
114
      std::string format = Context::getContext().config.get("report." + _report + ".dateformat");
383✔
115
      if (format == "") {
383✔
116
        format = Context::getContext().config.get("dateformat.report");
728✔
117
        if (format == "") format = Context::getContext().config.get("dateformat");
1,092✔
118
      }
119

120
      renderStringLeft(lines, width, color, date.toString(format));
383✔
121
    } else if (_style == "countdown") {
1,363✔
122
      Datetime now;
68✔
123
      if (date > now)
68✔
124
        renderStringRight(lines, width, color, Duration(date - now).format());
39✔
125
    } else if (_style == "julian")
912✔
126
      renderStringRight(lines, width, color, format(date.toJulian(), 13, 12));
2✔
127

128
    else if (_style == "epoch")
910✔
129
      renderStringRight(lines, width, color, date.toEpochString());
2✔
130

131
    else if (_style == "iso")
908✔
132
      renderStringLeft(lines, width, color, date.toISO());
2✔
133

134
    else if (_style == "age") {
906✔
135
      Datetime now;
852✔
136
      if (now > date)
852✔
137
        renderStringRight(lines, width, color, Duration(now - date).formatVague(true));
492✔
138
      else
139
        renderStringRight(lines, width, color, '-' + Duration(date - now).formatVague(true));
360✔
140
    } else if (_style == "relative") {
54✔
141
      Datetime now;
19✔
142
      if (now < date)
19✔
143
        renderStringRight(lines, width, color, Duration(date - now).formatVague(true));
9✔
144
      else
145
        renderStringRight(lines, width, color, '-' + Duration(now - date).formatVague(true));
10✔
146
    }
147

148
    else if (_style == "remaining") {
35✔
149
      Datetime now;
35✔
150
      if (date > now)
35✔
151
        renderStringRight(lines, width, color, Duration(date - now).formatVague(true));
34✔
152
    }
153
  }
154
}
1,791✔
155

156
////////////////////////////////////////////////////////////////////////////////
157
bool ColumnTypeDate::validate(const std::string& input) const {
9✔
158
  return input.length() ? true : false;
9✔
159
}
160

161
////////////////////////////////////////////////////////////////////////////////
162
void ColumnTypeDate::modify(Task& task, const std::string& value) {
736✔
163
  // Try to evaluate 'value'.  It might work.
164
  Variant evaluatedValue;
736✔
165
  try {
166
    Eval e;
736✔
167
    e.addSource(domSource);
736✔
168
    e.evaluateInfixExpression(value, evaluatedValue);
736✔
169
  }
736✔
170

171
  catch (...) {
124✔
172
    evaluatedValue = Variant(value);
124✔
173
  }
124✔
174

175
  // If v is duration, we need to convert it to date (and implicitly add now),
176
  // else store as date.
177
  std::string label = "   [1;37;43mMODIFICATION [0m ";
736✔
178
  if (evaluatedValue.type() == Variant::type_duration) {
736✔
179
    Context::getContext().debug(label + _name + " <-- '" +
256✔
180
                                format("{1}", format(evaluatedValue.get_duration())) + "' <-- '" +
384✔
181
                                (std::string)evaluatedValue + "' <-- '" + value + '\'');
256✔
182
    evaluatedValue.cast(Variant::type_date);
64✔
183
  } else {
184
    evaluatedValue.cast(Variant::type_date);
672✔
185
    Context::getContext().debug(label + _name + " <-- '" +
1,152✔
186
                                format("{1}", evaluatedValue.get_date()) + "' <-- '" +
1,440✔
187
                                (std::string)evaluatedValue + "' <-- '" + value + '\'');
1,152✔
188
  }
189

190
  // If a date doesn't parse (2/29/2014) then it evaluates to zero.
191
  if (value != "" && evaluatedValue.get_date() == 0)
352✔
UNCOV
192
    throw format("'{1}' is not a valid date in the '{2}' format.", value, Variant::dateFormat);
×
193

194
  time_t epoch = evaluatedValue.get_date();
352✔
195
  if (epoch < EPOCH_MIN_VALUE || epoch >= EPOCH_MAX_VALUE) {
352✔
UNCOV
196
    throw format("'{1}' is not a valid date.", value);
×
197
  }
198
  task.set(_name, epoch);
352✔
199
}
1,120✔
200

201
////////////////////////////////////////////////////////////////////////////////
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc