1// Copyright 2016 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef CCTZ_CIVIL_TIME_DETAIL_H_
16#define CCTZ_CIVIL_TIME_DETAIL_H_
17
18#include <cstdint>
19#include <limits>
20#include <ostream>
21#include <type_traits>
22
23// Disable constexpr support unless we are using clang in C++14 mode.
24#if __clang__ && __cpp_constexpr >= 201304
25#define CONSTEXPR_D constexpr // data
26#define CONSTEXPR_F constexpr // function
27#define CONSTEXPR_M constexpr // member
28#else
29#define CONSTEXPR_D const
30#define CONSTEXPR_F inline
31#define CONSTEXPR_M
32#endif
33
34namespace cctz {
35
36// Support years that at least span the range of 64-bit time_t values.
37using year_t = std::int_fast64_t;
38
39// Type alias that indicates an argument is not normalized (e.g., the
40// constructor parameters and operands/results of addition/subtraction).
41using diff_t = std::int_fast64_t;
42
43namespace detail {
44
45// Type aliases that indicate normalized argument values.
46using month_t = std::int_fast8_t; // [1:12]
47using day_t = std::int_fast8_t; // [1:31]
48using hour_t = std::int_fast8_t; // [0:23]
49using minute_t = std::int_fast8_t; // [0:59]
50using second_t = std::int_fast8_t; // [0:59]
51
52// Normalized civil-time fields: Y-M-D HH:MM:SS.
53struct fields {
54 CONSTEXPR_M fields(year_t year, month_t month, day_t day,
55 hour_t hour, minute_t minute, second_t second)
56 : y(year), m(month), d(day), hh(hour), mm(minute), ss(second) {}
57 std::int_least64_t y;
58 std::int_least8_t m;
59 std::int_least8_t d;
60 std::int_least8_t hh;
61 std::int_least8_t mm;
62 std::int_least8_t ss;
63};
64
65struct second_tag {};
66struct minute_tag : second_tag {};
67struct hour_tag : minute_tag {};
68struct day_tag : hour_tag {};
69struct month_tag : day_tag {};
70struct year_tag : month_tag {};
71
72////////////////////////////////////////////////////////////////////////
73
74// Field normalization (without avoidable overflow).
75
76namespace impl {
77
78CONSTEXPR_F bool is_leap_year(year_t y) noexcept {
79 return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
80}
81CONSTEXPR_F int year_index(year_t y, month_t m) noexcept {
82 return (static_cast<int>((y + (m > 2)) % 400) + 400) % 400;
83}
84CONSTEXPR_F int days_per_century(year_t y, month_t m) noexcept {
85 const int yi = year_index(y, m);
86 return 36524 + (yi == 0 || yi > 300);
87}
88CONSTEXPR_F int days_per_4years(year_t y, month_t m) noexcept {
89 const int yi = year_index(y, m);
90 return 1460 + (yi == 0 || yi > 300 || (yi - 1) % 100 < 96);
91}
92CONSTEXPR_F int days_per_year(year_t y, month_t m) noexcept {
93 return is_leap_year(y + (m > 2)) ? 366 : 365;
94}
95CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept {
96 CONSTEXPR_D int k_days_per_month[1 + 12] = {
97 -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // non leap year
98 };
99 return k_days_per_month[m] + (m == 2 && is_leap_year(y));
100}
101
102CONSTEXPR_F fields n_day(year_t y, month_t m, diff_t d, diff_t cd,
103 hour_t hh, minute_t mm, second_t ss) noexcept {
104 y += (cd / 146097) * 400;
105 cd %= 146097;
106 if (cd < 0) {
107 y -= 400;
108 cd += 146097;
109 }
110 y += (d / 146097) * 400;
111 d = d % 146097 + cd;
112 if (d > 0) {
113 if (d > 146097) {
114 y += 400;
115 d -= 146097;
116 }
117 } else {
118 if (d > -365) {
119 // We often hit the previous year when stepping a civil time backwards,
120 // so special case it to avoid counting up by 100/4/1-year chunks.
121 y -= 1;
122 d += days_per_year(y, m);
123 } else {
124 y -= 400;
125 d += 146097;
126 }
127 }
128 if (d > 365) {
129 for (int n = days_per_century(y, m); d > n; n = days_per_century(y, m)) {
130 d -= n;
131 y += 100;
132 }
133 for (int n = days_per_4years(y, m); d > n; n = days_per_4years(y, m)) {
134 d -= n;
135 y += 4;
136 }
137 for (int n = days_per_year(y, m); d > n; n = days_per_year(y, m)) {
138 d -= n;
139 ++y;
140 }
141 }
142 if (d > 28) {
143 for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) {
144 d -= n;
145 if (++m > 12) {
146 ++y;
147 m = 1;
148 }
149 }
150 }
151 return fields(y, m, static_cast<day_t>(d), hh, mm, ss);
152}
153CONSTEXPR_F fields n_mon(year_t y, diff_t m, diff_t d, diff_t cd,
154 hour_t hh, minute_t mm, second_t ss) noexcept {
155 if (m != 12) {
156 y += m / 12;
157 m %= 12;
158 if (m <= 0) {
159 y -= 1;
160 m += 12;
161 }
162 }
163 return n_day(y, static_cast<month_t>(m), d, cd, hh, mm, ss);
164}
165CONSTEXPR_F fields n_hour(year_t y, diff_t m, diff_t d, diff_t cd,
166 diff_t hh, minute_t mm, second_t ss) noexcept {
167 cd += hh / 24;
168 hh %= 24;
169 if (hh < 0) {
170 cd -= 1;
171 hh += 24;
172 }
173 return n_mon(y, m, d, cd, static_cast<hour_t>(hh), mm, ss);
174}
175CONSTEXPR_F fields n_min(year_t y, diff_t m, diff_t d, diff_t hh, diff_t ch,
176 diff_t mm, second_t ss) noexcept {
177 ch += mm / 60;
178 mm %= 60;
179 if (mm < 0) {
180 ch -= 1;
181 mm += 60;
182 }
183 return n_hour(y, m, d, hh / 24 + ch / 24, hh % 24 + ch % 24,
184 static_cast<minute_t>(mm), ss);
185}
186CONSTEXPR_F fields n_sec(year_t y, diff_t m, diff_t d, diff_t hh, diff_t mm,
187 diff_t ss) noexcept {
188 // Optimization for when (non-constexpr) fields are already normalized.
189 if (0 <= ss && ss < 60) {
190 const second_t nss = static_cast<second_t>(ss);
191 if (0 <= mm && mm < 60) {
192 const minute_t nmm = static_cast<minute_t>(mm);
193 if (0 <= hh && hh < 24) {
194 const hour_t nhh = static_cast<hour_t>(hh);
195 if (1 <= d && d <= 28 && 1 <= m && m <= 12) {
196 const day_t nd = static_cast<day_t>(d);
197 const month_t nm = static_cast<month_t>(m);
198 return fields(y, nm, nd, nhh, nmm, nss);
199 }
200 return n_mon(y, m, d, 0, nhh, nmm, nss);
201 }
202 return n_hour(y, m, d, hh / 24, hh % 24, nmm, nss);
203 }
204 return n_min(y, m, d, hh, mm / 60, mm % 60, nss);
205 }
206 diff_t cm = ss / 60;
207 ss %= 60;
208 if (ss < 0) {
209 cm -= 1;
210 ss += 60;
211 }
212 return n_min(y, m, d, hh, mm / 60 + cm / 60, mm % 60 + cm % 60,
213 static_cast<second_t>(ss));
214}
215
216} // namespace impl
217
218////////////////////////////////////////////////////////////////////////
219
220// Increments the indicated (normalized) field by "n".
221CONSTEXPR_F fields step(second_tag, fields f, diff_t n) noexcept {
222 return impl::n_sec(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60);
223}
224CONSTEXPR_F fields step(minute_tag, fields f, diff_t n) noexcept {
225 return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss);
226}
227CONSTEXPR_F fields step(hour_tag, fields f, diff_t n) noexcept {
228 return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss);
229}
230CONSTEXPR_F fields step(day_tag, fields f, diff_t n) noexcept {
231 return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss);
232}
233CONSTEXPR_F fields step(month_tag, fields f, diff_t n) noexcept {
234 return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss);
235}
236CONSTEXPR_F fields step(year_tag, fields f, diff_t n) noexcept {
237 return fields(f.y + n, f.m, f.d, f.hh, f.mm, f.ss);
238}
239
240////////////////////////////////////////////////////////////////////////
241
242namespace impl {
243
244// Returns (v * f + a) but avoiding intermediate overflow when possible.
245CONSTEXPR_F diff_t scale_add(diff_t v, diff_t f, diff_t a) noexcept {
246 return (v < 0) ? ((v + 1) * f + a) - f : ((v - 1) * f + a) + f;
247}
248
249// Map a (normalized) Y/M/D to the number of days before/after 1970-01-01.
250// Probably overflows for years outside [-292277022656:292277026595].
251CONSTEXPR_F diff_t ymd_ord(year_t y, month_t m, day_t d) noexcept {
252 const diff_t eyear = (m <= 2) ? y - 1 : y;
253 const diff_t era = (eyear >= 0 ? eyear : eyear - 399) / 400;
254 const diff_t yoe = eyear - era * 400;
255 const diff_t doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
256 const diff_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
257 return era * 146097 + doe - 719468;
258}
259
260// Returns the difference in days between two normalized Y-M-D tuples.
261// ymd_ord() will encounter integer overflow given extreme year values,
262// yet the difference between two such extreme values may actually be
263// small, so we take a little care to avoid overflow when possible by
264// exploiting the 146097-day cycle.
265CONSTEXPR_F diff_t day_difference(year_t y1, month_t m1, day_t d1,
266 year_t y2, month_t m2, day_t d2) noexcept {
267 const diff_t a_c4_off = y1 % 400;
268 const diff_t b_c4_off = y2 % 400;
269 diff_t c4_diff = (y1 - a_c4_off) - (y2 - b_c4_off);
270 diff_t delta = ymd_ord(a_c4_off, m1, d1) - ymd_ord(b_c4_off, m2, d2);
271 if (c4_diff > 0 && delta < 0) {
272 delta += 2 * 146097;
273 c4_diff -= 2 * 400;
274 } else if (c4_diff < 0 && delta > 0) {
275 delta -= 2 * 146097;
276 c4_diff += 2 * 400;
277 }
278 return (c4_diff / 400 * 146097) + delta;
279}
280
281} // namespace impl
282
283// Returns the difference between fields structs using the indicated unit.
284CONSTEXPR_F diff_t difference(year_tag, fields f1, fields f2) noexcept {
285 return f1.y - f2.y;
286}
287CONSTEXPR_F diff_t difference(month_tag, fields f1, fields f2) noexcept {
288 return impl::scale_add(difference(year_tag{}, f1, f2), 12, (f1.m - f2.m));
289}
290CONSTEXPR_F diff_t difference(day_tag, fields f1, fields f2) noexcept {
291 return impl::day_difference(f1.y, f1.m, f1.d, f2.y, f2.m, f2.d);
292}
293CONSTEXPR_F diff_t difference(hour_tag, fields f1, fields f2) noexcept {
294 return impl::scale_add(difference(day_tag{}, f1, f2), 24, (f1.hh - f2.hh));
295}
296CONSTEXPR_F diff_t difference(minute_tag, fields f1, fields f2) noexcept {
297 return impl::scale_add(difference(hour_tag{}, f1, f2), 60, (f1.mm - f2.mm));
298}
299CONSTEXPR_F diff_t difference(second_tag, fields f1, fields f2) noexcept {
300 return impl::scale_add(difference(minute_tag{}, f1, f2), 60, f1.ss - f2.ss);
301}
302
303////////////////////////////////////////////////////////////////////////
304
305// Aligns the (normalized) fields struct to the indicated field.
306CONSTEXPR_F fields align(second_tag, fields f) noexcept {
307 return f;
308}
309CONSTEXPR_F fields align(minute_tag, fields f) noexcept {
310 return fields{f.y, f.m, f.d, f.hh, f.mm, 0};
311}
312CONSTEXPR_F fields align(hour_tag, fields f) noexcept {
313 return fields{f.y, f.m, f.d, f.hh, 0, 0};
314}
315CONSTEXPR_F fields align(day_tag, fields f) noexcept {
316 return fields{f.y, f.m, f.d, 0, 0, 0};
317}
318CONSTEXPR_F fields align(month_tag, fields f) noexcept {
319 return fields{f.y, f.m, 1, 0, 0, 0};
320}
321CONSTEXPR_F fields align(year_tag, fields f) noexcept {
322 return fields{f.y, 1, 1, 0, 0, 0};
323}
324
325////////////////////////////////////////////////////////////////////////
326
327template <typename T>
328class civil_time {
329 public:
330 explicit CONSTEXPR_M civil_time(year_t y, diff_t m = 1, diff_t d = 1,
331 diff_t hh = 0, diff_t mm = 0,
332 diff_t ss = 0) noexcept
333 : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {}
334
335 CONSTEXPR_M civil_time() noexcept : f_{1970, 1, 1, 0, 0, 0} {}
336 civil_time(const civil_time&) = default;
337 civil_time& operator=(const civil_time&) = default;
338
339 // Conversion between civil times of different alignment. Conversion to
340 // a more precise alignment is allowed implicitly (e.g., day -> hour),
341 // but conversion where information is discarded must be explicit
342 // (e.g., second -> minute).
343 template <typename U, typename S>
344 using preserves_data =
345 typename std::enable_if<std::is_base_of<U, S>::value>::type;
346 template <typename U>
347 CONSTEXPR_M civil_time(const civil_time<U>& ct,
348 preserves_data<T, U>* = nullptr) noexcept
349 : civil_time(ct.f_) {}
350 template <typename U>
351 explicit CONSTEXPR_M civil_time(const civil_time<U>& ct,
352 preserves_data<U, T>* = nullptr) noexcept
353 : civil_time(ct.f_) {}
354
355 // Factories for the maximum/minimum representable civil_time.
356 static civil_time max() {
357 const auto max_year = std::numeric_limits<std::int_least64_t>::max();
358 return civil_time(max_year, 12, 31, 23, 59, 59);
359 }
360 static civil_time min() {
361 const auto min_year = std::numeric_limits<std::int_least64_t>::min();
362 return civil_time(min_year, 1, 1, 0, 0, 0);
363 }
364
365 // Field accessors. Note: All but year() return an int.
366 CONSTEXPR_M year_t year() const noexcept { return f_.y; }
367 CONSTEXPR_M int month() const noexcept { return f_.m; }
368 CONSTEXPR_M int day() const noexcept { return f_.d; }
369 CONSTEXPR_M int hour() const noexcept { return f_.hh; }
370 CONSTEXPR_M int minute() const noexcept { return f_.mm; }
371 CONSTEXPR_M int second() const noexcept { return f_.ss; }
372
373 // Assigning arithmetic.
374 CONSTEXPR_M civil_time& operator+=(diff_t n) noexcept {
375 f_ = step(T{}, f_, n);
376 return *this;
377 }
378 CONSTEXPR_M civil_time& operator-=(diff_t n) noexcept {
379 if (n != std::numeric_limits<diff_t>::min()) {
380 f_ = step(T{}, f_, -n);
381 } else {
382 f_ = step(T{}, step(T{}, f_, -(n + 1)), 1);
383 }
384 return *this;
385 }
386 CONSTEXPR_M civil_time& operator++() noexcept {
387 return *this += 1;
388 }
389 CONSTEXPR_M civil_time operator++(int) noexcept {
390 const civil_time a = *this;
391 ++*this;
392 return a;
393 }
394 CONSTEXPR_M civil_time& operator--() noexcept {
395 return *this -= 1;
396 }
397 CONSTEXPR_M civil_time operator--(int) noexcept {
398 const civil_time a = *this;
399 --*this;
400 return a;
401 }
402
403 // Binary arithmetic operators.
404 inline friend CONSTEXPR_M civil_time operator+(civil_time a,
405 diff_t n) noexcept {
406 return a += n;
407 }
408 inline friend CONSTEXPR_M civil_time operator+(diff_t n,
409 civil_time a) noexcept {
410 return a += n;
411 }
412 inline friend CONSTEXPR_M civil_time operator-(civil_time a,
413 diff_t n) noexcept {
414 return a -= n;
415 }
416 inline friend CONSTEXPR_M diff_t operator-(const civil_time& lhs,
417 const civil_time& rhs) noexcept {
418 return difference(T{}, lhs.f_, rhs.f_);
419 }
420
421 private:
422 // All instantiations of this template are allowed to call the following
423 // private constructor and access the private fields member.
424 template <typename U>
425 friend class civil_time;
426
427 // The designated constructor that all others eventually call.
428 explicit CONSTEXPR_M civil_time(fields f) noexcept : f_(align(T{}, f)) {}
429
430 fields f_;
431};
432
433// Disallows difference between differently aligned types.
434// auto n = civil_day(...) - civil_hour(...); // would be confusing.
435template <typename Tag1, typename Tag2>
436CONSTEXPR_F diff_t operator-(civil_time<Tag1>, civil_time<Tag2>) = delete;
437
438using civil_year = civil_time<year_tag>;
439using civil_month = civil_time<month_tag>;
440using civil_day = civil_time<day_tag>;
441using civil_hour = civil_time<hour_tag>;
442using civil_minute = civil_time<minute_tag>;
443using civil_second = civil_time<second_tag>;
444
445////////////////////////////////////////////////////////////////////////
446
447// Relational operators that work with differently aligned objects.
448// Always compares all six fields.
449template <typename T1, typename T2>
450CONSTEXPR_F bool operator<(const civil_time<T1>& lhs,
451 const civil_time<T2>& rhs) noexcept {
452 return (lhs.year() < rhs.year() ||
453 (lhs.year() == rhs.year() &&
454 (lhs.month() < rhs.month() ||
455 (lhs.month() == rhs.month() &&
456 (lhs.day() < rhs.day() ||
457 (lhs.day() == rhs.day() &&
458 (lhs.hour() < rhs.hour() ||
459 (lhs.hour() == rhs.hour() &&
460 (lhs.minute() < rhs.minute() ||
461 (lhs.minute() == rhs.minute() &&
462 (lhs.second() < rhs.second())))))))))));
463}
464template <typename T1, typename T2>
465CONSTEXPR_F bool operator<=(const civil_time<T1>& lhs,
466 const civil_time<T2>& rhs) noexcept {
467 return !(rhs < lhs);
468}
469template <typename T1, typename T2>
470CONSTEXPR_F bool operator>=(const civil_time<T1>& lhs,
471 const civil_time<T2>& rhs) noexcept {
472 return !(lhs < rhs);
473}
474template <typename T1, typename T2>
475CONSTEXPR_F bool operator>(const civil_time<T1>& lhs,
476 const civil_time<T2>& rhs) noexcept {
477 return rhs < lhs;
478}
479template <typename T1, typename T2>
480CONSTEXPR_F bool operator==(const civil_time<T1>& lhs,
481 const civil_time<T2>& rhs) noexcept {
482 return lhs.year() == rhs.year() && lhs.month() == rhs.month() &&
483 lhs.day() == rhs.day() && lhs.hour() == rhs.hour() &&
484 lhs.minute() == rhs.minute() && lhs.second() == rhs.second();
485}
486template <typename T1, typename T2>
487CONSTEXPR_F bool operator!=(const civil_time<T1>& lhs,
488 const civil_time<T2>& rhs) noexcept {
489 return !(lhs == rhs);
490}
491
492////////////////////////////////////////////////////////////////////////
493
494enum class weekday {
495 monday,
496 tuesday,
497 wednesday,
498 thursday,
499 friday,
500 saturday,
501 sunday,
502};
503
504CONSTEXPR_F weekday get_weekday(const civil_day& cd) noexcept {
505 CONSTEXPR_D weekday k_weekday_by_sun_off[7] = {
506 weekday::sunday, weekday::monday, weekday::tuesday,
507 weekday::wednesday, weekday::thursday, weekday::friday,
508 weekday::saturday,
509 };
510 CONSTEXPR_D int k_weekday_offsets[1 + 12] = {
511 -1, 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4,
512 };
513 year_t wd = cd.year() - (cd.month() < 3);
514 if (wd >= 0) {
515 wd += wd / 4 - wd / 100 + wd / 400;
516 } else {
517 wd += (wd - 3) / 4 - (wd - 99) / 100 + (wd - 399) / 400;
518 }
519 wd += k_weekday_offsets[cd.month()] + cd.day();
520 return k_weekday_by_sun_off[(wd % 7 + 7) % 7];
521}
522
523////////////////////////////////////////////////////////////////////////
524
525CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) noexcept {
526 do { cd += 1; } while (get_weekday(cd) != wd);
527 return cd;
528}
529
530CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) noexcept {
531 do { cd -= 1; } while (get_weekday(cd) != wd);
532 return cd;
533}
534
535CONSTEXPR_F int get_yearday(const civil_day& cd) noexcept {
536 CONSTEXPR_D int k_month_offsets[1 + 12] = {
537 -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
538 };
539 const int feb29 = (cd.month() > 2 && impl::is_leap_year(cd.year()));
540 return k_month_offsets[cd.month()] + feb29 + cd.day();
541}
542
543////////////////////////////////////////////////////////////////////////
544
545std::ostream& operator<<(std::ostream& os, const civil_year& y);
546std::ostream& operator<<(std::ostream& os, const civil_month& m);
547std::ostream& operator<<(std::ostream& os, const civil_day& d);
548std::ostream& operator<<(std::ostream& os, const civil_hour& h);
549std::ostream& operator<<(std::ostream& os, const civil_minute& m);
550std::ostream& operator<<(std::ostream& os, const civil_second& s);
551std::ostream& operator<<(std::ostream& os, weekday wd);
552
553} // namespace detail
554} // namespace cctz
555
556#undef CONSTEXPR_M
557#undef CONSTEXPR_F
558#undef CONSTEXPR_D
559
560#endif // CCTZ_CIVIL_TIME_DETAIL_H_
561