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

GothenburgBitFactory / taskwarrior / 11420355627

19 Oct 2024 08:00PM UTC coverage: 84.853% (+0.6%) from 84.223%
11420355627

push

github

web-flow
Pass rc.weekstart to libshared for ISO8601 weeknum parsing if "monday" (#3654)

* libshared: bump for weekstart, epoch defines, eopww fix

mainly those visible changes, and miscellaneous others

see GothenburgBitFactory/taskwarrior#3623 (weekstart)
see GothenburgBitFactory/taskwarrior#3651 (epoch limit defines)
see GothenburgBitFactory/libshared#73 (eopww fix)

* Initialize libshared's weekstart from user's rc.weekstart config

This enables use of newer libshared code that can parse week numbers
according to ISO8601 instead of existing code which is always using
Sunday-based weeks.  To get ISO behavior, set rc.weekstart=monday.
Default is still Sunday / old algorithm, as before, since Sunday is in
the hardcoded default rcfile.

Weekstart does not yet fix week-relative shortcuts, which will still
always use Monday.

See #3623 for further details.

4 of 6 new or added lines in 2 files covered. (66.67%)

993 existing lines in 25 files now uncovered.

19019 of 22414 relevant lines covered (84.85%)

23067.98 hits per line

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

86.09
/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"))
36✔
71
    preferredMonthsPerLine = config.getInteger("calendar.monthsperline");
×
72
  else
73
    // Legacy configuration variable value
74
    preferredMonthsPerLine = config.getInteger("monthsperline");
36✔
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"};
144✔
102

103
  // Set up a vector of keywords, for autoComplete.
104
  std::vector<std::string> keywordNames{"due"};
144✔
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,
118✔
121
                     config.getInteger("abbreviation.minimum")) == 1)
59✔
122
      continue;
×
123

124
    // "due".
125
    else if (autoComplete(Lexer::lowerCase(arg), keywordNames, matches,
118✔
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,
26✔
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);
11✔
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")) {
25✔
197
    auto moffset = config.getInteger("calendar.offset.value") % 12;
1✔
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"));
25✔
269
  Color color_due(config.get("color.calendar.due"));
25✔
270
  Color color_duetoday(config.get("color.calendar.due.today"));
25✔
271
  Color color_overdue(config.get("color.calendar.overdue"));
25✔
272
  Color color_weekend(config.get("color.calendar.weekend"));
25✔
273
  Color color_holiday(config.get("color.calendar.holiday"));
25✔
274
  Color color_scheduled(config.get("color.calendar.scheduled"));
25✔
275
  Color color_weeknumber(config.get("color.calendar.weeknumber"));
25✔
276

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

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

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

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

293
  if (config.get("calendar.details") == "full" || config.get("calendar.holidays") == "full") {
25✔
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"));
10✔
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") {
5✔
315
      // Assert that 'report' is a valid report.
316
      auto report = config.get("calendar.details.report");
4✔
317
      if (Context::getContext().commands.find(report) == Context::getContext().commands.end())
2✔
UNCOV
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");
2✔
332
      args.push_back("rc.verbose:label,affected,blank");
2✔
333
      if (Context::getContext().color()) args.push_back("rc._forcecolor:on");
2✔
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") {
5✔
346
      Table holTable;
3✔
347
      holTable.width(Context::getContext().getWidth());
3✔
348
      holTable.add("Date");
3✔
349
      holTable.add("Holiday");
3✔
350
      setHeaderUnderline(holTable);
3✔
351

352
      auto dateFormat = config.get("dateformat.holiday");
6✔
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");
4✔
360
            auto start =
361
                config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".start");
4✔
362
            auto end = config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".end");
4✔
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);
1✔
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");
6✔
381
      if (format == "") format = config.get("dateformat.report");
3✔
382
      if (format == "") format = config.get("dateformat");
3✔
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
}
80✔
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
  auto weekStart = Datetime::weekstart;
49✔
408

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

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

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

460
  auto row = 0;
49✔
461

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

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

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

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

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

492
      if (thisCol == (8 * mpl)) thisCol += 7;
4,485✔
493

494
      view.set(row, thisCol, d);
4,485✔
495

496
      if (Context::getContext().color()) {
4,485✔
497
        Color cellColor;
184✔
498

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

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

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

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

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

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

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

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

561
                    case Task::dateLaterToday:
×
562
                      cellColor.blend(color_duetoday);
×
563
                      break;
×
564

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

576
        view.set(row, thisCol, cellColor);
184✔
577
      }
578

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

586
  return view.render();
98✔
587
}
49✔
588

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