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

GothenburgBitFactory / taskwarrior / 19431446801

17 Nov 2025 01:34PM UTC coverage: 85.194% (+0.008%) from 85.186%
19431446801

push

github

web-flow
Bump src/taskchampion-cpp/corrosion from `df258b9` to `614508f` (#3990)

Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `df258b9` to `614508f`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](https://github.com/corrosion-rs/corrosion/compare/df258b94a...<a class=hub.com/GothenburgBitFactory/taskwarrior/commit/614508f2c3789b703f917e4f85310cc4252d07fe">614508f2c)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-version: 614508f2c3789b703f917e4f85310cc4252d07fe
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

19593 of 22998 relevant lines covered (85.19%)

23475.75 hits per line

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

84.71
/src/commands/CmdCalendar.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 <CmdCalendar.h>
31
#include <Context.h>
32
#include <Lexer.h>
33
#include <Table.h>
34
#include <format.h>
35
#include <shared.h>
36
#include <stdlib.h>
37
#include <utf8.h>
38
#include <util.h>
39

40
#include <iomanip>
41
#include <sstream>
42

43
////////////////////////////////////////////////////////////////////////////////
44
CmdCalendar::CmdCalendar() {
4,593✔
45
  _keyword = "calendar";
4,593✔
46
  _usage = "task          calendar [due|<month> <year>|<year>] [y]";
4,593✔
47
  _description = "Shows a calendar, with due tasks marked";
4,593✔
48
  _read_only = true;
4,593✔
49
  _displays_id = true;
4,593✔
50
  _needs_gc = true;
4,593✔
51
  _needs_recur_update = false;
4,593✔
52
  _uses_context = false;
4,593✔
53
  _accepts_filter = false;
4,593✔
54
  _accepts_modifications = false;
4,593✔
55
  _accepts_miscellaneous = true;
4,593✔
56
  _category = Command::Category::graphs;
4,593✔
57
}
4,593✔
58

59
////////////////////////////////////////////////////////////////////////////////
60
int CmdCalendar::execute(std::string& output) {
36✔
61
  int rc = 0;
36✔
62

63
  auto& config = Context::getContext().config;
36✔
64

65
  // Each month requires 28 text columns width.  See how many will actually
66
  // fit.  But if a preference is specified, and it fits, use it.
67
  auto width = Context::getContext().getWidth();
36✔
68
  int preferredMonthsPerLine;
69

70
  if (config.has("calendar.monthsperline"))
72✔
71
    preferredMonthsPerLine = config.getInteger("calendar.monthsperline");
×
72
  else
73
    // Legacy configuration variable value
74
    preferredMonthsPerLine = config.getInteger("monthsperline");
72✔
75

76
  auto monthsThatFit = width / 26;
36✔
77

78
  auto monthsPerLine = monthsThatFit;
36✔
79
  if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit)
36✔
80
    monthsPerLine = preferredMonthsPerLine;
×
81

82
  // Load the pending tasks.
83
  auto tasks = Context::getContext().tdb2.pending_tasks();
36✔
84

85
  Datetime today;
36✔
86
  auto getPendingDate = false;
36✔
87
  auto monthsToDisplay = 1;
36✔
88
  auto mFrom = today.month();
36✔
89
  auto yFrom = today.year();
36✔
90
  auto mTo = mFrom;
36✔
91
  auto yTo = yFrom;
36✔
92

93
  // Defaults.
94
  monthsToDisplay = monthsPerLine;
36✔
95
  mFrom = today.month();
36✔
96
  yFrom = today.year();
36✔
97

98
  // Set up a vector of commands (1), for autoComplete.
99
  std::vector<std::string> commandNames{"calendar"};
108✔
100

101
  // Set up a vector of keywords, for autoComplete.
102
  std::vector<std::string> keywordNames{"due"};
108✔
103

104
  // Set up a vector of months, for autoComplete.
105
  std::vector<std::string> monthNames;
36✔
106
  for (int i = 1; i <= 12; ++i) monthNames.push_back(Lexer::lowerCase(Datetime::monthName(i)));
468✔
107

108
  // For autoComplete results.
109
  std::vector<std::string> matches;
36✔
110

111
  // Look at all args, regardless of sequence.
112
  auto argMonth = 0;
36✔
113
  auto argYear = 0;
36✔
114
  auto argWholeYear = false;
36✔
115

116
  for (auto& arg : Context::getContext().cli2.getWords()) {
84✔
117
    // Some version of "calendar".
118
    if (autoComplete(Lexer::lowerCase(arg), commandNames, matches,
177✔
119
                     config.getInteger("abbreviation.minimum")) == 1)
59✔
120
      continue;
×
121

122
    // "due".
123
    else if (autoComplete(Lexer::lowerCase(arg), keywordNames, matches,
177✔
124
                          config.getInteger("abbreviation.minimum")) == 1)
59✔
125
      getPendingDate = true;
12✔
126

127
    // "y".
128
    else if (Lexer::lowerCase(arg) == "y")
47✔
129
      argWholeYear = true;
11✔
130

131
    // YYYY.
132
    else if (Lexer::isAllDigits(arg) && arg.length() == 4)
36✔
133
      argYear = strtol(arg.c_str(), nullptr, 10);
11✔
134

135
    // MM.
136
    else if (Lexer::isAllDigits(arg) && arg.length() <= 2) {
25✔
137
      argMonth = strtol(arg.c_str(), nullptr, 10);
12✔
138
      if (argMonth < 1 || argMonth > 12) throw format("Argument '{1}' is not a valid month.", arg);
12✔
139
    }
140

141
    // "January" etc.
142
    else if (autoComplete(Lexer::lowerCase(arg), monthNames, matches,
39✔
143
                          config.getInteger("abbreviation.minimum")) == 1) {
13✔
144
      argMonth = Datetime::monthOfYear(matches[0]);
2✔
145
      if (argMonth == -1) throw format("Argument '{1}' is not a valid month.", arg);
2✔
146
    }
147

148
    else
149
      throw format("Could not recognize argument '{1}'.", arg);
33✔
150
  }
36✔
151

152
  // Supported combinations:
153
  //
154
  //   Command line  monthsToDisplay  mFrom  yFrom  getPendingDate
155
  //   ------------  ---------------  -----  -----  --------------
156
  //   cal             monthsPerLine  today  today           false
157
  //   cal y                      12  today  today           false
158
  //   cal due         monthsPerLine  today  today            true
159
  //   cal YYYY                   12      1    arg           false
160
  //   cal due y                  12  today  today            true
161
  //   cal MM YYYY     monthsPerLine    arg    arg           false
162
  //   cal MM YYYY y              12    arg    arg           false
163

164
  if (argWholeYear || (argYear && !argMonth && !argWholeYear)) monthsToDisplay = 12;
25✔
165

166
  if (!argMonth && argYear)
25✔
167
    mFrom = 1;
3✔
168
  else if (argMonth && argYear)
22✔
169
    mFrom = argMonth;
4✔
170

171
  if (argYear) yFrom = argYear;
25✔
172

173
  // Now begin the data subset and rendering.
174
  if (getPendingDate == true) {
25✔
175
    // Find the oldest pending due date.
176
    Datetime oldest(9999, 12, 31);
8✔
177
    for (auto& task : tasks) {
8✔
178
      auto status = task.getStatus();
×
179
      if (status == Task::pending || status == Task::waiting) {
×
180
        if (task.has("due") && !task.hasTag("nocal")) {
×
181
          Datetime d(task.get("due"));
×
182
          if (d < oldest) oldest = d;
×
183
        }
184
      }
185
    }
186

187
    // Default to current month if no due date is present
188
    if (oldest != Datetime(9999, 12, 31)) {
8✔
189
      mFrom = oldest.month();
×
190
      yFrom = oldest.year();
×
191
    }
192
  }
193

194
  if (config.getBoolean("calendar.offset")) {
50✔
195
    auto moffset = config.getInteger("calendar.offset.value") % 12;
2✔
196
    auto yoffset = config.getInteger("calendar.offset.value") / 12;
1✔
197
    mFrom += moffset;
1✔
198
    yFrom += yoffset;
1✔
199
    if (mFrom < 1) {
1✔
200
      mFrom += 12;
×
201
      yFrom--;
×
202
    } else if (mFrom > 12) {
1✔
203
      mFrom -= 12;
×
204
      yFrom++;
×
205
    }
206
  }
207

208
  mTo = mFrom + monthsToDisplay - 1;
25✔
209
  yTo = yFrom;
25✔
210
  if (mTo > 12) {
25✔
211
    mTo -= 12;
19✔
212
    yTo++;
19✔
213
  }
214

215
  auto details_yFrom = yFrom;
25✔
216
  auto details_mFrom = mFrom;
25✔
217

218
  std::stringstream out;
25✔
219
  out << '\n';
25✔
220

221
  while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) {
74✔
222
    auto nextM = mFrom;
49✔
223
    auto nextY = yFrom;
49✔
224

225
    // Print month headers (cheating on the width settings, yes)
226
    for (int i = 0; i < monthsPerLine; i++) {
196✔
227
      auto month = Datetime::monthName(nextM);
147✔
228

229
      //    12345678901234567890123456 = 26 chars wide
230
      //                ^^             = center
231
      //    <------->                  = 13 - (month.length / 2) + 1
232
      //                      <------> = 26 - above
233
      //   +--------------------------+
234
      //   |         July 2009        |
235
      //   |     Mo Tu We Th Fr Sa Su |
236
      //   |  27        1  2  3  4  5 |
237
      //   |  28  6  7  8  9 10 11 12 |
238
      //   |  29 13 14 15 16 17 18 19 |
239
      //   |  30 20 21 22 23 24 25 26 |
240
      //   |  31 27 28 29 30 31       |
241
      //   +--------------------------+
242

243
      auto totalWidth = 26;
147✔
244
      auto labelWidth = month.length() + 5;  // 5 = " 2009"
147✔
245
      auto leftGap = (totalWidth / 2) - (labelWidth / 2);
147✔
246
      auto rightGap = totalWidth - leftGap - labelWidth;
147✔
247

248
      out << std::setw(leftGap) << ' ' << month << ' ' << nextY << std::setw(rightGap) << ' ';
147✔
249

250
      if (++nextM > 12) {
147✔
251
        nextM = 1;
22✔
252
        nextY++;
22✔
253
      }
254
    }
147✔
255

256
    out << '\n'
257
        << optionalBlankLine() << renderMonths(mFrom, yFrom, today, tasks, monthsPerLine) << '\n';
49✔
258

259
    mFrom += monthsPerLine;
49✔
260
    if (mFrom > 12) {
49✔
261
      mFrom -= 12;
22✔
262
      ++yFrom;
22✔
263
    }
264
  }
265

266
  Color color_today(config.get("color.calendar.today"));
50✔
267
  Color color_due(config.get("color.calendar.due"));
50✔
268
  Color color_duetoday(config.get("color.calendar.due.today"));
50✔
269
  Color color_overdue(config.get("color.calendar.overdue"));
50✔
270
  Color color_weekend(config.get("color.calendar.weekend"));
50✔
271
  Color color_holiday(config.get("color.calendar.holiday"));
50✔
272
  Color color_scheduled(config.get("color.calendar.scheduled"));
50✔
273
  Color color_weeknumber(config.get("color.calendar.weeknumber"));
25✔
274

275
  if (Context::getContext().color() && config.getBoolean("calendar.legend")) {
29✔
276
    out << "Legend: " << color_today.colorize("today") << ", " << color_weekend.colorize("weekend")
10✔
277
        << ", ";
6✔
278

279
    // If colorizing due dates, print legend
280
    if (config.get("calendar.details") != "none")
4✔
281
      out << color_due.colorize("due") << ", " << color_duetoday.colorize("due-today") << ", "
10✔
282
          << color_overdue.colorize("overdue") << ", " << color_scheduled.colorize("scheduled")
10✔
283
          << ", ";
8✔
284

285
    // If colorizing holidays, print legend
286
    if (config.get("calendar.holidays") != "none") out << color_holiday.colorize("holiday") << ", ";
4✔
287

288
    out << color_weeknumber.colorize("weeknumber") << '.' << optionalBlankLine() << '\n';
4✔
289
  }
290

291
  if (config.get("calendar.details") == "full" || config.get("calendar.holidays") == "full") {
121✔
292
    --details_mFrom;
5✔
293
    if (details_mFrom == 0) {
5✔
294
      details_mFrom = 12;
×
295
      --details_yFrom;
×
296
    }
297
    int details_dFrom = Datetime::daysInMonth(details_yFrom, details_mFrom);
5✔
298

299
    ++mTo;
5✔
300
    if (mTo == 13) {
5✔
301
      mTo = 1;
×
302
      ++yTo;
×
303
    }
304

305
    Datetime date_after(details_yFrom, details_mFrom, details_dFrom);
5✔
306
    auto after = date_after.toString(config.get("dateformat"));
5✔
307

308
    Datetime date_before(yTo, mTo, 1);
5✔
309
    auto before = date_before.toString(config.get("dateformat"));
10✔
310

311
    // Table with due date information
312
    if (config.get("calendar.details") == "full") {
10✔
313
      // Assert that 'report' is a valid report.
314
      auto report = config.get("calendar.details.report");
2✔
315
      if (Context::getContext().commands.find(report) == Context::getContext().commands.end())
2✔
316
        throw std::string(
317
            "The setting 'calendar.details.report' must contain a single report name.");
×
318

319
      // TODO Fix this:  cal      --> task
320
      //                 calendar --> taskendar
321

322
      // If the executable was "cal" or equivalent, replace it with "task".
323
      auto executable = Context::getContext().cli2._original_args[0].attribute("raw");
4✔
324
      auto cal = executable.find("cal");
2✔
325
      if (cal != std::string::npos) executable = executable.substr(0, cal) + PACKAGE;
2✔
326

327
      std::vector<std::string> args;
2✔
328
      args.push_back("rc:" + Context::getContext().rc_file._data);
2✔
329
      args.push_back("rc.due:0");
4✔
330
      args.push_back("rc.verbose:label,affected,blank");
2✔
331
      if (Context::getContext().color()) args.push_back("rc._forcecolor:on");
4✔
332
      args.push_back("due.after:" + after);
2✔
333
      args.push_back("due.before:" + before);
2✔
334
      args.push_back("-nocal");
2✔
335
      args.push_back(report);
2✔
336

337
      std::string output;
2✔
338
      ::execute(executable, args, "", output);
2✔
339
      out << output;
2✔
340
    }
2✔
341

342
    // Table with holiday information
343
    if (config.get("calendar.holidays") == "full") {
10✔
344
      Table holTable;
3✔
345
      holTable.width(Context::getContext().getWidth());
3✔
346
      holTable.add("Date");
6✔
347
      holTable.add("Holiday");
3✔
348
      setHeaderUnderline(holTable);
3✔
349

350
      auto dateFormat = config.get("dateformat.holiday");
3✔
351

352
      std::map<time_t, std::vector<std::string>> hm;  // we need to store multiple holidays per day
3✔
353
      for (auto& it : config)
740✔
354
        if (it.first.substr(0, 8) == "holiday.")
737✔
355
          if (it.first.substr(it.first.size() - 4) == "name") {
4✔
356
            auto holName = it.second;
2✔
357
            auto date = config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".date");
2✔
358
            auto start =
359
                config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".start");
2✔
360
            auto end = config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".end");
2✔
361
            if (!date.empty()) {
2✔
362
              Datetime holDate(date.c_str(), dateFormat);
×
363

364
              if (date_after < holDate && holDate < date_before)
×
365
                hm[holDate.toEpoch()].push_back(holName);
×
366
            }
367
            if (!start.empty() && !end.empty()) {
2✔
368
              Datetime holStart(start.c_str(), dateFormat);
2✔
369
              Datetime holEnd(end.c_str(), dateFormat);
1✔
370

371
              if (date_after < holStart && holStart < date_before)
1✔
372
                hm[holStart.toEpoch()].push_back("Start of " + holName);
1✔
373
              if (date_after < holEnd && holEnd < date_before)
1✔
374
                hm[holEnd.toEpoch()].push_back("End of " + holName);
1✔
375
            }
376
          }
2✔
377

378
      auto format = config.get("report." + config.get("calendar.details.report") + ".dateformat");
3✔
379
      if (format == "") format = config.get("dateformat.report");
9✔
380
      if (format == "") format = config.get("dateformat");
9✔
381

382
      for (auto& hm_it : hm) {
5✔
383
        auto v = hm_it.second;
2✔
384
        Datetime hDate(hm_it.first);
2✔
385
        auto d = hDate.toString(format);
2✔
386
        for (const auto& i : v) {
4✔
387
          auto row = holTable.addRow();
2✔
388
          holTable.set(row, 0, d);
2✔
389
          holTable.set(row, 1, i);
2✔
390
        }
391
      }
2✔
392

393
      out << optionalBlankLine() << holTable.render() << '\n';
3✔
394
    }
3✔
395
  }
5✔
396

397
  output = out.str();
25✔
398
  return rc;
25✔
399
}
260✔
400

401
////////////////////////////////////////////////////////////////////////////////
402
std::string CmdCalendar::renderMonths(int firstMonth, int firstYear, const Datetime& today,
49✔
403
                                      std::vector<Task>& all, int monthsPerLine) {
404
  auto& config = Context::getContext().config;
49✔
405
  auto weekStart = Datetime::weekstart;
49✔
406

407
  // Build table for the number of months to be displayed.
408
  Table view;
49✔
409
  setHeaderUnderline(view);
49✔
410
  view.width(Context::getContext().getWidth());
49✔
411
  for (int i = 0; i < (monthsPerLine * 8); i += 8) {
196✔
412
    if (weekStart == 1) {
147✔
413
      view.add("", false);
3✔
414
      view.add(utf8_substr(Datetime::dayName(1), 0, 2), false);
3✔
415
      view.add(utf8_substr(Datetime::dayName(2), 0, 2), false);
3✔
416
      view.add(utf8_substr(Datetime::dayName(3), 0, 2), false);
3✔
417
      view.add(utf8_substr(Datetime::dayName(4), 0, 2), false);
3✔
418
      view.add(utf8_substr(Datetime::dayName(5), 0, 2), false);
3✔
419
      view.add(utf8_substr(Datetime::dayName(6), 0, 2), false);
3✔
420
      view.add(utf8_substr(Datetime::dayName(0), 0, 2), false);
3✔
421
    } else {
422
      view.add("", false);
144✔
423
      view.add(utf8_substr(Datetime::dayName(0), 0, 2), false);
144✔
424
      view.add(utf8_substr(Datetime::dayName(1), 0, 2), false);
144✔
425
      view.add(utf8_substr(Datetime::dayName(2), 0, 2), false);
144✔
426
      view.add(utf8_substr(Datetime::dayName(3), 0, 2), false);
144✔
427
      view.add(utf8_substr(Datetime::dayName(4), 0, 2), false);
144✔
428
      view.add(utf8_substr(Datetime::dayName(5), 0, 2), false);
144✔
429
      view.add(utf8_substr(Datetime::dayName(6), 0, 2), false);
144✔
430
    }
431
  }
432

433
  // At most, we need 6 rows.
434
  view.addRow();
49✔
435
  view.addRow();
49✔
436
  view.addRow();
49✔
437
  view.addRow();
49✔
438
  view.addRow();
49✔
439
  view.addRow();
49✔
440

441
  // Set number of days per month, months to render, and years to render.
442
  std::vector<int> years;
49✔
443
  std::vector<int> months;
49✔
444
  std::vector<int> daysInMonth;
49✔
445
  int thisYear = firstYear;
49✔
446
  int thisMonth = firstMonth;
49✔
447
  for (int i = 0; i < monthsPerLine; i++) {
196✔
448
    if (thisMonth < 13) {
147✔
449
      years.push_back(thisYear);
128✔
450
    } else {
451
      thisMonth -= 12;
19✔
452
      years.push_back(++thisYear);
19✔
453
    }
454
    months.push_back(thisMonth);
147✔
455
    daysInMonth.push_back(Datetime::daysInMonth(thisYear, thisMonth++));
147✔
456
  }
457

458
  auto row = 0;
49✔
459

460
  Color color_today(config.get("color.calendar.today"));
98✔
461
  Color color_due(config.get("color.calendar.due"));
98✔
462
  Color color_duetoday(config.get("color.calendar.due.today"));
98✔
463
  Color color_overdue(config.get("color.calendar.overdue"));
98✔
464
  Color color_weekend(config.get("color.calendar.weekend"));
98✔
465
  Color color_holiday(config.get("color.calendar.holiday"));
98✔
466
  Color color_scheduled(config.get("color.calendar.scheduled"));
98✔
467
  Color color_weeknumber(config.get("color.calendar.weeknumber"));
49✔
468

469
  // Loop through months to be added on this line.
470
  for (int mpl = 0; mpl < monthsPerLine; mpl++) {
196✔
471
    // Reset row counter for subsequent months
472
    if (mpl != 0) row = 0;
147✔
473

474
    // Loop through days in month and add to table.
475
    for (int d = 1; d <= daysInMonth[mpl]; ++d) {
4,630✔
476
      Datetime date(years[mpl], months[mpl], d);
4,483✔
477
      auto dow = date.dayOfWeek();
4,483✔
478
      auto woy = date.week();
4,483✔
479

480
      if (config.getBoolean("displayweeknumber"))
8,966✔
481
        view.set(row, (8 * mpl),
4,483✔
482
                 // Make sure the week number is always 4 columns, space-padded.
483
                 format((woy < 10 ? "   {1}" : "  {1}"), woy), color_weeknumber);
13,449✔
484

485
      // Calculate column id.
486
      auto thisCol = dow +                       // 0 = Sunday
4,483✔
487
                     (weekStart == 1 ? 0 : 1) +  // Offset for weekStart
4,483✔
488
                     (8 * mpl);                  // Columns in 1 month
4,483✔
489

490
      if (thisCol == (8 * mpl)) thisCol += 7;
4,483✔
491

492
      view.set(row, thisCol, d);
4,483✔
493

494
      if (Context::getContext().color()) {
4,483✔
495
        Color cellColor;
184✔
496

497
        // colorize weekends
498
        if (dow == 0 || dow == 6) cellColor.blend(color_weekend);
184✔
499

500
        // colorize holidays
501
        if (config.get("calendar.holidays") != "none") {
368✔
502
          auto dateFormat = config.get("dateformat.holiday");
×
503
          for (auto& hol : config) {
×
504
            if (hol.first.substr(0, 8) == "holiday.") {
×
505
              if (hol.first.substr(hol.first.size() - 4) == "date") {
×
506
                auto value = hol.second;
×
507
                Datetime holDate(value.c_str(), dateFormat);
×
508
                if (holDate.sameDay(date)) cellColor.blend(color_holiday);
×
509
              }
×
510

511
              if (hol.first.substr(hol.first.size() - 5) == "start" &&
×
512
                  config.has("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end")) {
×
513
                auto start = hol.second;
×
514
                auto end =
515
                    config.get("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end");
×
516
                Datetime holStart(start.c_str(), dateFormat);
×
517
                Datetime holEnd(end.c_str(), dateFormat);
×
518
                if (holStart <= date && date <= holEnd) cellColor.blend(color_holiday);
×
519
              }
×
520
            }
521
          }
522
        }
×
523

524
        // colorize today
525
        if (today.sameDay(date)) cellColor.blend(color_today);
184✔
526

527
        // colorize due and scheduled tasks
528
        if (config.get("calendar.details") != "none") {
368✔
529
          config.set("due", 0);
368✔
530
          config.set("scheduled", 0);
184✔
531
          // if a date has a task that is due on that day, the due color
532
          // takes precedence over the scheduled color
533
          bool coloredWithDue = false;
184✔
534
          for (auto& task : all) {
276✔
535
            auto status = task.getStatus();
92✔
536
            if ((status == Task::pending || status == Task::waiting) && !task.hasTag("nocal")) {
276✔
537
              if (task.has("scheduled") && !coloredWithDue) {
276✔
538
                std::string scheduled = task.get("scheduled");
×
539
                Datetime scheduleddmy(strtoll(scheduled.c_str(), nullptr, 10));
×
540

541
                if (scheduleddmy.sameDay(date)) {
×
542
                  cellColor.blend(color_scheduled);
×
543
                }
544
              }
×
545
              if (task.has("due")) {
184✔
546
                std::string due = task.get("due");
92✔
547
                Datetime duedmy(strtoll(due.c_str(), nullptr, 10));
92✔
548

549
                if (duedmy.sameDay(date)) {
92✔
550
                  coloredWithDue = true;
1✔
551
                  switch (task.getDateState("due")) {
2✔
552
                    case Task::dateNotDue:
×
553
                      break;
×
554

555
                    case Task::dateAfterToday:
1✔
556
                      cellColor.blend(color_due);
1✔
557
                      break;
1✔
558

559
                    case Task::dateLaterToday:
×
560
                      cellColor.blend(color_duetoday);
×
561
                      break;
×
562

563
                    case Task::dateEarlierToday:
×
564
                    case Task::dateBeforeToday:
565
                      cellColor.blend(color_overdue);
×
566
                      break;
×
567
                  }
568
                }
569
              }
92✔
570
            }
571
          }
572
        }
573

574
        view.set(row, thisCol, cellColor);
184✔
575
      }
576

577
      // Check for end of week, and...
578
      int eow = 6;
4,483✔
579
      if (weekStart == 1) eow = 0;
4,483✔
580
      if (dow == eow && d < daysInMonth[mpl]) row++;
4,483✔
581
    }
582
  }
583

584
  return view.render();
98✔
585
}
49✔
586

587
////////////////////////////////////////////////////////////////////////////////
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