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

xlnt-community / xlnt / 796b5d63-faf9-48e9-946d-5f649cf3d172

05 Mar 2025 05:11AM UTC coverage: 82.27% (+0.4%) from 81.87%
796b5d63-faf9-48e9-946d-5f649cf3d172

push

circleci

web-flow
Fix workbook comparisons, cleanup of included headers (#59)

This PR does the following:
1. Fixes issue https://github.com/xlnt-community/xlnt/issues/58. Please
read the issue for detailed infos.
2. Changes / removes a few definitions of `XLNT_API(_INTERNAL)` which
were defined in the wrong places (e.g. `.cpp` file, or in the header
although the function was implemented in the header too).
3. Cleans up many unnecessary or missing headers which I found along the
way.
4. Added `operator!=` to many classes to ease such comparisons and
improve programming experience.

103 of 143 new or added lines in 20 files covered. (72.03%)

3 existing lines in 2 files now uncovered.

11554 of 14044 relevant lines covered (82.27%)

1176465.94 hits per line

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

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

27
#include <xlnt/utils/date.hpp>
28
#include <xlnt/utils/datetime.hpp>
29
#include <xlnt/utils/time.hpp>
30
#include <detail/serialization/parsers.hpp>
31

32
#include <xlnt/utils/optional.hpp>
33

34
namespace {
35

36
std::string fill(const std::string &string, std::size_t length = 2)
3,124✔
37
{
38
    if (string.size() >= length)
3,124✔
39
    {
40
        return string;
1,862✔
41
    }
42

43
    return std::string(length - string.size(), '0') + string;
2,524✔
44
}
45

46
} // namespace
47

48
namespace xlnt {
49

50
datetime datetime::from_number(double raw_time, calendar base_date)
47✔
51
{
52
    auto date_part = date::from_number(static_cast<int>(raw_time), base_date);
47✔
53
    auto time_part = time::from_number(raw_time);
47✔
54

55
    return datetime(date_part, time_part);
94✔
56
}
57

58
bool datetime::operator==(const datetime &comparand) const
1✔
59
{
60
    return year == comparand.year
1✔
61
        && month == comparand.month
1✔
62
        && day == comparand.day
1✔
63
        && hour == comparand.hour
1✔
64
        && minute == comparand.minute
1✔
65
        && second == comparand.second
1✔
66
        && microsecond == comparand.microsecond
1✔
67
        && _is_null == comparand._is_null;
2✔
68
}
69

NEW
70
bool datetime::operator!=(const datetime &comparand) const
×
71
{
NEW
72
    return !(*this == comparand);
×
73
}
74

75
double datetime::to_number(calendar base_date) const
8✔
76
{
77
    if (_is_null)
8✔
78
    {
79
        throw xlnt::invalid_attribute("cannot convert invalid/empty datetime to a number");
1✔
80
    }
81

82
    return date(year, month, day).to_number(base_date)
7✔
83
        + time(hour, minute, second, microsecond).to_number();
7✔
84
}
85

86
std::string datetime::to_string() const
3✔
87
{
88
    if (_is_null)
3✔
89
    {
90
        return {};
1✔
91
    }
92
    else
93
    {
94
        std::string str = std::to_string(year);
2✔
95
        str.push_back('/');
2✔
96
        str.append(std::to_string(month));
2✔
97
        str.push_back('/');
2✔
98
        str.append(std::to_string(day));
2✔
99
        str.push_back(' ');
2✔
100
        str.append(std::to_string(hour));
2✔
101
        str.push_back(':');
2✔
102
        str.append(std::to_string(minute));
2✔
103
        str.push_back(':');
2✔
104
        str.append(std::to_string(second));
2✔
105

106
        if (microsecond != 0)
2✔
107
        {
108
            str.push_back('.');
2✔
109
            str.append(fill(std::to_string(microsecond), 6));
2✔
110
        }
111

112
        return str;
2✔
113
    }
2✔
114
}
115

116
datetime datetime::now()
×
117
{
118
    return datetime(date::today(), time::now());
×
119
}
120

121
datetime datetime::today()
3✔
122
{
123
    return datetime(date::today(), time(0, 0, 0, 0));
3✔
124
}
125

126
datetime::datetime(int year_, int month_, int day_, int hour_, int minute_, int second_, int microsecond_)
814✔
127
    : year(year_), month(month_), day(day_), hour(hour_), minute(minute_), second(second_), microsecond(microsecond_), _is_null(false)
814✔
128
{
129
}
814✔
130

131
datetime::datetime(const date &d, const time &t)
50✔
132
    : hour(t.hour),
50✔
133
      minute(t.minute),
50✔
134
      second(t.second),
50✔
135
      microsecond(t.microsecond),
50✔
136
      _is_null(d.is_null())
50✔
137
{
138
    if (!d.is_null())
50✔
139
    {
140
        year = d.get_year();
50✔
141
        month = d.get_month();
50✔
142
        day = d.get_day();
50✔
143
    }
144
}
50✔
145

146
int datetime::weekday() const
3✔
147
{
148
    if (!_is_null)
3✔
149
    {
150
        return date(year, month, day).weekday();
2✔
151
    }
152
    else
153
    {
154
        return -1;
1✔
155
    }
156
}
157

158
int datetime::get_year() const
39✔
159
{
160
    if (_is_null)
39✔
161
    {
162
        throw xlnt::invalid_attribute("access to invalid/empty year of xlnt::datetime");
1✔
163
    }
164

165
    return year;
38✔
166
}
167

168
int datetime::get_month() const
35✔
169
{
170
    if (_is_null)
35✔
171
    {
172
        throw xlnt::invalid_attribute("access to invalid/empty month of xlnt::datetime");
1✔
173
    }
174

175
    return month;
34✔
176
}
177

178
int datetime::get_day() const
30✔
179
{
180
    if (_is_null)
30✔
181
    {
182
        throw xlnt::invalid_attribute("access to invalid/empty day of xlnt::datetime");
1✔
183
    }
184

185
    return day;
29✔
186
}
187

188
int datetime::get_hour() const
77✔
189
{
190
    if (_is_null)
77✔
191
    {
192
        throw xlnt::invalid_attribute("access to invalid/empty hour of xlnt::datetime");
1✔
193
    }
194

195
    return hour;
76✔
196
}
197

198
int datetime::get_minute() const
48✔
199
{
200
    if (_is_null)
48✔
201
    {
202
        throw xlnt::invalid_attribute("access to invalid/empty minute of xlnt::datetime");
1✔
203
    }
204

205
    return minute;
47✔
206
}
207

208
int datetime::get_second() const
39✔
209
{
210
    if (_is_null)
39✔
211
    {
212
        throw xlnt::invalid_attribute("access to invalid/empty second of xlnt::datetime");
1✔
213
    }
214

215
    return second;
38✔
216
}
217

218
int datetime::get_microsecond() const
35✔
219
{
220
    if (_is_null)
35✔
221
    {
222
        throw xlnt::invalid_attribute("access to invalid/empty microsecond of xlnt::datetime");
1✔
223
    }
224

225
    return microsecond;
34✔
226
}
227

228
datetime datetime::from_iso_string(const std::string &string)
75✔
229
{
230
    xlnt::datetime result(1900, 1, 1);
75✔
231

232
    bool ok = true;
75✔
233
    auto next_separator_index = string.find('-');
75✔
234
    ok = ok && detail::parse(string.substr(0, next_separator_index), result.year) == std::errc();
75✔
235
    auto previous_separator_index = next_separator_index;
75✔
236
    next_separator_index = ok ? string.find('-', previous_separator_index + 1) : next_separator_index;
75✔
237
    ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.month) == std::errc();
75✔
238
    previous_separator_index = next_separator_index;
75✔
239
    next_separator_index = ok ? string.find('T', previous_separator_index + 1) : next_separator_index;
75✔
240
    ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.day) == std::errc();
75✔
241
    previous_separator_index = next_separator_index;
75✔
242
    next_separator_index = ok ? string.find(':', previous_separator_index + 1) : next_separator_index;
75✔
243
    ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.hour) == std::errc();
75✔
244
    previous_separator_index = next_separator_index;
75✔
245
    next_separator_index = ok ? string.find(':', previous_separator_index + 1) : next_separator_index;
75✔
246
    ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.minute) == std::errc();
75✔
247
    previous_separator_index = next_separator_index;
75✔
248
    next_separator_index = ok ? string.find('.', previous_separator_index + 1) : next_separator_index;
75✔
249
    bool subseconds_available = next_separator_index != std::string::npos;
75✔
250
    if (subseconds_available)
75✔
251
    {
252
        // First parse the seconds.
253
        ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.second) == std::errc();
6✔
254
        previous_separator_index = next_separator_index;
6✔
255

256
    }
257
    next_separator_index = ok ? string.find('Z', previous_separator_index + 1) : next_separator_index;
75✔
258
    size_t num_characters_parsed = 0;
75✔
259
    ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), subseconds_available ? result.microsecond : result.second, &num_characters_parsed) == std::errc();
75✔
260

261
    if (subseconds_available)
75✔
262
    {
263
        constexpr size_t expected_digits = 6; // microseconds have 6 digits
6✔
264
        size_t actual_digits = num_characters_parsed;
6✔
265

266
        while (actual_digits > expected_digits)
12✔
267
        {
268
            result.microsecond /= 10;
6✔
269
            --actual_digits;
6✔
270
        }
271

272
        while (actual_digits < expected_digits)
12✔
273
        {
274
            result.microsecond *= 10;
6✔
275
            ++actual_digits;
6✔
276
        }
277
    }
278

279
    if (!ok)
75✔
280
    {
281
        throw xlnt::invalid_parameter("invalid ISO date");
1✔
282
    }
283

284
    return result;
148✔
285
}
286

287
std::string datetime::to_iso_string() const
625✔
288
{
289
    if (_is_null)
625✔
290
    {
291
        return {};
1✔
292
    }
293
    else
294
    {
295
        std::string iso = std::to_string(year);
624✔
296
        iso.push_back('-');
624✔
297
        iso.append(fill(std::to_string(month)));
624✔
298
        iso.push_back('-');
624✔
299
        iso.append(fill(std::to_string(day)));
624✔
300
        iso.push_back('T');
624✔
301
        iso.append(fill(std::to_string(hour)));
624✔
302
        iso.push_back(':');
624✔
303
        iso.append(fill(std::to_string(minute)));
624✔
304
        iso.push_back(':');
624✔
305
        iso.append(fill(std::to_string(second)));
624✔
306

307
        if (microsecond != 0)
624✔
308
        {
309
            iso.push_back('.');
2✔
310
            iso.append(fill(std::to_string(microsecond), 6));
2✔
311
        }
312

313
        iso.push_back('Z');
624✔
314

315
        return iso;
624✔
316
    }
624✔
317
}
318

319
} // namespace xlnt
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