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

GothenburgBitFactory / taskwarrior / 11335495770

14 Oct 2024 09:47PM UTC coverage: 84.223% (-0.6%) from 84.776%
11335495770

push

github

web-flow
[pre-commit.ci] pre-commit autoupdate (#3650)

updates:
- [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

19005 of 22565 relevant lines covered (84.22%)

23473.55 hits per line

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

85.13
/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 <main.h>
36
#include <shared.h>
37
#include <stdlib.h>
38
#include <utf8.h>
39
#include <util.h>
40

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

44
////////////////////////////////////////////////////////////////////////////////
45
CmdCalendar::CmdCalendar() {
4,497✔
46
  _keyword = "calendar";
4,497✔
47
  _usage = "task          calendar [due|<month> <year>|<year>] [y]";
4,497✔
48
  _description = "Shows a calendar, with due tasks marked";
4,497✔
49
  _read_only = true;
4,497✔
50
  _displays_id = true;
4,497✔
51
  _needs_gc = true;
4,497✔
52
  _uses_context = false;
4,497✔
53
  _accepts_filter = false;
4,497✔
54
  _accepts_modifications = false;
4,497✔
55
  _accepts_miscellaneous = true;
4,497✔
56
  _category = Command::Category::graphs;
4,497✔
57
}
4,497✔
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
  handleUntil();
36✔
84
  handleRecurrence();
36✔
85
  auto tasks = Context::getContext().tdb2.pending_tasks();
36✔
86

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

166
  if (argWholeYear || (argYear && !argMonth && !argWholeYear)) monthsToDisplay = 12;
25✔
167

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

173
  if (argYear) yFrom = argYear;
25✔
174

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

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

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

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

217
  auto details_yFrom = yFrom;
25✔
218
  auto details_mFrom = mFrom;
25✔
219

220
  std::stringstream out;
25✔
221
  out << '\n';
25✔
222

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

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

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

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

250
      out << std::setw(leftGap) << ' ' << month << ' ' << nextY << std::setw(rightGap) << ' ';
147✔
251

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

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

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

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

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

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

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

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

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

301
    ++mTo;
5✔
302
    if (mTo == 13) {
5✔
303
      mTo = 1;
5✔
304
      ++yTo;
5✔
305
    }
306

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

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

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

321
      // TODO Fix this:  cal      --> task
322
      //                 calendar --> taskendar
323

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

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

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

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

352
      auto dateFormat = config.get("dateformat.holiday");
3✔
353

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

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

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

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

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

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

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

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

408
  // What day of the week does the user consider the first?
409
  auto weekStart = Datetime::dayOfWeek(config.get("weekstart"));
49✔
410
  if (weekStart != 0 && weekStart != 1)
49✔
411
    throw std::string(
412
        "The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'.");
×
413

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

440
  // At most, we need 6 rows.
441
  view.addRow();
49✔
442
  view.addRow();
49✔
443
  view.addRow();
49✔
444
  view.addRow();
49✔
445
  view.addRow();
49✔
446
  view.addRow();
49✔
447

448
  // Set number of days per month, months to render, and years to render.
449
  std::vector<int> years;
49✔
450
  std::vector<int> months;
49✔
451
  std::vector<int> daysInMonth;
49✔
452
  int thisYear = firstYear;
49✔
453
  int thisMonth = firstMonth;
49✔
454
  for (int i = 0; i < monthsPerLine; i++) {
196✔
455
    if (thisMonth < 13) {
147✔
456
      years.push_back(thisYear);
145✔
457
    } else {
458
      thisMonth -= 12;
2✔
459
      years.push_back(++thisYear);
2✔
460
    }
461
    months.push_back(thisMonth);
147✔
462
    daysInMonth.push_back(Datetime::daysInMonth(thisYear, thisMonth++));
147✔
463
  }
464

465
  auto row = 0;
49✔
466

467
  Color color_today(config.get("color.calendar.today"));
98✔
468
  Color color_due(config.get("color.calendar.due"));
98✔
469
  Color color_duetoday(config.get("color.calendar.due.today"));
98✔
470
  Color color_overdue(config.get("color.calendar.overdue"));
98✔
471
  Color color_weekend(config.get("color.calendar.weekend"));
98✔
472
  Color color_holiday(config.get("color.calendar.holiday"));
98✔
473
  Color color_scheduled(config.get("color.calendar.scheduled"));
98✔
474
  Color color_weeknumber(config.get("color.calendar.weeknumber"));
49✔
475

476
  // Loop through months to be added on this line.
477
  for (int mpl = 0; mpl < monthsPerLine; mpl++) {
196✔
478
    // Reset row counter for subsequent months
479
    if (mpl != 0) row = 0;
147✔
480

481
    // Loop through days in month and add to table.
482
    for (int d = 1; d <= daysInMonth[mpl]; ++d) {
4,632✔
483
      Datetime date(years[mpl], months[mpl], d);
4,485✔
484
      auto dow = date.dayOfWeek();
4,485✔
485
      auto woy = date.week();
4,485✔
486

487
      if (config.getBoolean("displayweeknumber"))
8,970✔
488
        view.set(row, (8 * mpl),
4,485✔
489
                 // Make sure the week number is always 4 columns, space-padded.
490
                 format((woy < 10 ? "   {1}" : "  {1}"), woy), color_weeknumber);
13,455✔
491

492
      // Calculate column id.
493
      auto thisCol = dow +                       // 0 = Sunday
4,485✔
494
                     (weekStart == 1 ? 0 : 1) +  // Offset for weekStart
4,485✔
495
                     (8 * mpl);                  // Columns in 1 month
4,485✔
496

497
      if (thisCol == (8 * mpl)) thisCol += 7;
4,485✔
498

499
      view.set(row, thisCol, d);
4,485✔
500

501
      if (Context::getContext().color()) {
4,485✔
502
        Color cellColor;
184✔
503

504
        // colorize weekends
505
        if (dow == 0 || dow == 6) cellColor.blend(color_weekend);
184✔
506

507
        // colorize holidays
508
        if (config.get("calendar.holidays") != "none") {
368✔
509
          auto dateFormat = config.get("dateformat.holiday");
×
510
          for (auto& hol : config) {
×
511
            if (hol.first.substr(0, 8) == "holiday.") {
×
512
              if (hol.first.substr(hol.first.size() - 4) == "date") {
×
513
                auto value = hol.second;
×
514
                Datetime holDate(value.c_str(), dateFormat);
×
515
                if (holDate.sameDay(date)) cellColor.blend(color_holiday);
×
516
              }
×
517

518
              if (hol.first.substr(hol.first.size() - 5) == "start" &&
×
519
                  config.has("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end")) {
×
520
                auto start = hol.second;
×
521
                auto end =
522
                    config.get("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end");
×
523
                Datetime holStart(start.c_str(), dateFormat);
×
524
                Datetime holEnd(end.c_str(), dateFormat);
×
525
                if (holStart <= date && date <= holEnd) cellColor.blend(color_holiday);
×
526
              }
×
527
            }
528
          }
529
        }
×
530

531
        // colorize today
532
        if (today.sameDay(date)) cellColor.blend(color_today);
184✔
533

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

548
                if (scheduleddmy.sameDay(date)) {
×
549
                  cellColor.blend(color_scheduled);
×
550
                }
551
              }
×
552
              if (task.has("due")) {
184✔
553
                std::string due = task.get("due");
92✔
554
                Datetime duedmy(strtoll(due.c_str(), nullptr, 10));
92✔
555

556
                if (duedmy.sameDay(date)) {
92✔
557
                  coloredWithDue = true;
1✔
558
                  switch (task.getDateState("due")) {
2✔
559
                    case Task::dateNotDue:
×
560
                      break;
×
561

562
                    case Task::dateAfterToday:
1✔
563
                      cellColor.blend(color_due);
1✔
564
                      break;
1✔
565

566
                    case Task::dateLaterToday:
×
567
                      cellColor.blend(color_duetoday);
×
568
                      break;
×
569

570
                    case Task::dateEarlierToday:
×
571
                    case Task::dateBeforeToday:
572
                      cellColor.blend(color_overdue);
×
573
                      break;
×
574
                  }
575
                }
576
              }
92✔
577
            }
578
          }
579
        }
580

581
        view.set(row, thisCol, cellColor);
184✔
582
      }
583

584
      // Check for end of week, and...
585
      int eow = 6;
4,485✔
586
      if (weekStart == 1) eow = 0;
4,485✔
587
      if (dow == eow && d < daysInMonth[mpl]) row++;
4,485✔
588
    }
589
  }
590

591
  return view.render();
98✔
592
}
49✔
593

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

© 2025 Coveralls, Inc