Skip to content

Commit

Permalink
Add FMT_ASSERT and validation of values of struct tm members
Browse files Browse the repository at this point in the history
  • Loading branch information
phprus committed Oct 22, 2021
1 parent 623cc58 commit 0bed636
Showing 1 changed file with 117 additions and 75 deletions.
192 changes: 117 additions & 75 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -1394,8 +1394,10 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {

template <typename OutputIt, typename Char> class tm_writer {
private:
static constexpr int days_per_week = 7;
static constexpr unsigned days_per_week = 7;

// At least 3 characters.
static constexpr const Char invalid[] = {'?', '?', '?', '\0'};
static constexpr const Char day_of_week[][10] = {
{'S', 'u', 'n', 'd', 'a', 'y', '\0', '\0', '\0', '\0'},
{'M', 'o', 'n', 'd', 'a', 'y', '\0', '\0', '\0', '\0'},
Expand Down Expand Up @@ -1423,21 +1425,72 @@ template <typename OutputIt, typename Char> class tm_writer {
OutputIt out_;
const std::tm& tm_;

auto tm_year() const noexcept -> int { return 1900 + tm_.tm_year; }
auto tm_sec() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 60,
"tm_sec not in range [0, 60]");
return to_unsigned(tm_.tm_sec);
}
auto tm_min() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59,
"tm_min not in range [0, 59]");
return to_unsigned(tm_.tm_min);
}
auto tm_hour() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23,
"tm_hour not in range [0, 23]");
return to_unsigned(tm_.tm_hour);
}
auto tm_mday() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31,
"tm_mday not in range [1, 31]");
return to_unsigned(tm_.tm_mday);
}
auto tm_mon() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11,
"tm_mon not in range [0, 11]");
return to_unsigned(tm_.tm_mon);
}
auto tm_year() const noexcept -> int {
return 1900 + tm_.tm_year;
}
auto tm_wday() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6,
"tm_wday not in range [0, 6]");
return to_unsigned(tm_.tm_wday);
}
auto tm_yday() const noexcept -> unsigned {
FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365,
"tm_yday not in range [0, 365]");
return to_unsigned(tm_.tm_yday);
}

auto tm_hour12() const noexcept -> unsigned {
auto hour = tm_hour() % 12;
return hour == 0 ? 12 : hour;
}

FMT_CONSTEXPR const Char* tm_day_of_week() const noexcept {
auto wday = tm_wday();
return (wday >= 0 && wday <= 6) ? day_of_week[wday] : invalid;
}
FMT_CONSTEXPR const Char* tm_month() const noexcept {
auto mon = tm_mon();
return (mon >= 0 && mon <= 11) ? month[mon] : invalid;
}

// POSIX and the C Standard are unclear or inconsistent about what %C and %y
// do if the year is negative or exceeds 9999. Use the convention that %C
// concatenated with %y yields the same output as %Y, and that %Y contains at
// least 4 characters, with more only if necessary.
auto split_year_lower(int year) const noexcept -> int {
auto split_year_lower(int year) const noexcept -> unsigned {
auto l = year % 100;
if (l < 0) l = -l; // l in [0, 99]
return l;
return to_unsigned(l);
}

// Algorithm:
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date
auto iso_year_weeks(const int year) const noexcept -> int {
auto iso_year_weeks(const int year) const noexcept -> unsigned {
const long long curr_year = year;
const long long prev_year = curr_year - 1;
const int curr_p = static_cast<int>(
Expand All @@ -1446,57 +1499,55 @@ template <typename OutputIt, typename Char> class tm_writer {
const int prev_p = static_cast<int>(
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
days_per_week);
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
return 52u + ((curr_p == 4 || prev_p == 3) ? 1u : 0u);
}
auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int {
auto iso_week_num(unsigned tm_yday, unsigned tm_wday) const noexcept
-> unsigned {
return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) /
days_per_week;
}
auto tm_iso_week_year() const noexcept -> int {
const auto year = tm_year();
const int w = iso_week_num(tm_.tm_yday, tm_.tm_wday);
const unsigned w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return year - 1;
if (w > iso_year_weeks(year)) return year + 1;
return year;
}
auto tm_iso_week_of_year() const noexcept -> int {
auto tm_iso_week_of_year() const noexcept -> unsigned {
const auto year = tm_year();
const int w = iso_week_num(tm_.tm_yday, tm_.tm_wday);
const unsigned w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return iso_year_weeks(year - 1);
if (w > iso_year_weeks(year)) return 1;
return w;
}

auto tm_hour12() const noexcept -> int {
auto hour = tm_.tm_hour % 12;
return hour == 0 ? 12 : hour;
}

void write1(int value) { *out_++ = static_cast<char>('0' + value % 10); }
void write2(int value) {
const char* d = digits2(to_unsigned(value));
void write1(unsigned value) { *out_++ = static_cast<char>('0' + value % 10); }
void write2(unsigned value) {
const char* d = digits2(value % 100);
*out_++ = *d++;
*out_++ = *d;
}

void write_year_extended(int year) {
// At least 4 characters.
int width = 4;
if (year < 0) {
*out_++ = '-';
year = 0 - year;
--width;
}
uint32_or_64_or_128_t<int> n =
to_unsigned(to_nonnegative_int(year, max_value<int>()));
const int num_digits = count_digits(n);
if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
out_ = format_decimal<Char>(out_, n, num_digits).end;
}
void write_year(int year) {
if (year >= 0 && year < 10000) {
write2(year / 100);
write2(year % 100);
} else {
// At least 4 characters.
int width = 4;
if (year < 0) {
*out_++ = '-';
year = 0 - year;
--width;
}
uint32_or_64_or_128_t<int> n =
to_unsigned(to_nonnegative_int(year, max_value<int>()));
const int num_digits = count_digits(n);
if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
out_ = format_decimal<Char>(out_, n, num_digits).end;
}
write2(to_unsigned(year / 100));
write2(to_unsigned(year % 100));
} else
write_year_extended(year);
}

void format_localized(char format, char modifier = 0) {
Expand All @@ -1517,43 +1568,36 @@ template <typename OutputIt, typename Char> class tm_writer {
}

void on_abbr_weekday() {
FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday < 7,
"tm_wday not in range [0, 6]");
if (is_classic_)
out_ = write(out_, basic_string_view<Char>(day_of_week[tm_.tm_wday], 3));
out_ = write(out_, basic_string_view<Char>(tm_day_of_week(), 3));
else
format_localized('a');
}
void on_full_weekday() {
FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday < 7,
"tm_wday not in range [0, 6]");
if (is_classic_)
out_ = write(out_, day_of_week[tm_.tm_wday]);
out_ = write(out_, tm_day_of_week());
else
format_localized('A');
}
void on_dec0_weekday(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('w', 'O');
write1(tm_.tm_wday);
write1(tm_wday());
}
void on_dec1_weekday(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('u', 'O');
write1(tm_.tm_wday == 0 ? days_per_week : tm_.tm_wday);
auto wday = tm_wday();
write1(wday == 0 ? days_per_week : wday);
}

void on_abbr_month() {
FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon < 12,
"tm_mon not in range [0, 11]");
if (is_classic_)
out_ = write(out_, basic_string_view<Char>(month[tm_.tm_mon], 3));
out_ = write(out_, basic_string_view<Char>(tm_month(), 3));
else
format_localized('b');
}
void on_full_month() {
FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon < 12,
"tm_mon not in range [0, 11]");
if (is_classic_)
out_ = write(out_, month[tm_.tm_mon]);
out_ = write(out_, tm_month());
else
format_localized('B');
}
Expand Down Expand Up @@ -1592,9 +1636,8 @@ template <typename OutputIt, typename Char> class tm_writer {
}
void on_us_date() {
char buf[8];
write_digit2_separated(buf, to_unsigned(tm_.tm_mon + 1),
to_unsigned(tm_.tm_mday),
to_unsigned(split_year_lower(tm_year())), '/');
write_digit2_separated(buf, tm_mon() + 1, tm_mday(),
split_year_lower(tm_year()), '/');
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
}
void on_iso_date() {
Expand All @@ -1605,11 +1648,10 @@ template <typename OutputIt, typename Char> class tm_writer {
copy2(buf, digits2(to_unsigned(year / 100)));
} else {
offset = 4;
write_year(year);
write_year_extended(year);
year = 0;
}
write_digit2_separated(buf + 2, year % 100, to_unsigned(tm_.tm_mon + 1),
to_unsigned(tm_.tm_mday), '-');
write_digit2_separated(buf + 2, year % 100, tm_mon() + 1, tm_mday(), '-');
out_ = copy_str<Char>(std::begin(buf) + offset, std::end(buf), out_);
}

Expand All @@ -1635,25 +1677,26 @@ template <typename OutputIt, typename Char> class tm_writer {
*out_++ = '-';
*out_++ = '0';
} else if (upper >= 0 && upper < 100) {
write2(upper);
write2(to_unsigned(upper));
} else {
out_ = write<Char>(out_, upper);
}
}

void on_dec_month(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('m', 'O');
write2(tm_.tm_mon + 1);
write2(tm_mon() + 1);
}

void on_dec0_week_of_year(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('U', 'O');
write2((tm_.tm_yday + days_per_week - tm_.tm_wday) / days_per_week);
write2((tm_yday() + days_per_week - tm_wday()) / days_per_week);
}
void on_dec1_week_of_year(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('W', 'O');
write2((tm_.tm_yday + days_per_week -
(tm_.tm_wday == 0 ? (days_per_week - 1) : (tm_.tm_wday - 1))) /
auto wday = tm_wday();
write2((tm_yday() + days_per_week -
(wday == 0 ? (days_per_week - 1) : (wday - 1))) /
days_per_week);
}
void on_iso_week_of_year(numeric_system ns) {
Expand All @@ -1667,36 +1710,36 @@ template <typename OutputIt, typename Char> class tm_writer {
}

void on_day_of_year() {
auto yday = tm_.tm_yday + 1;
auto yday = tm_yday() + 1;
write1(yday / 100);
write2(yday % 100);
}
void on_day_of_month(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('d', 'O');
write2(tm_.tm_mday);
write2(tm_mday());
}
void on_day_of_month_space(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('e', 'O');
const char* d2 = digits2(to_unsigned(tm_.tm_mday));
*out_++ = tm_.tm_mday < 10 ? ' ' : d2[0];
const char* d2 = digits2(tm_mday());
*out_++ = tm_mday() < 10 ? ' ' : d2[0];
*out_++ = d2[1];
}

void on_24_hour(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('H', 'O');
write2(tm_.tm_hour);
write2(tm_hour());
}
void on_12_hour(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('I', 'O');
write2(tm_hour12());
}
void on_minute(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('M', 'O');
write2(tm_.tm_min);
write2(tm_min());
}
void on_second(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('S', 'O');
write2(tm_.tm_sec);
write2(tm_sec());
}

void on_12_hour_time() {
Expand All @@ -1705,9 +1748,7 @@ template <typename OutputIt, typename Char> class tm_writer {
on_iso_time();
#else
char buf[8];
write_digit2_separated(buf, to_unsigned(tm_hour12()),
to_unsigned(tm_.tm_min), to_unsigned(tm_.tm_sec),
':');
write_digit2_separated(buf, tm_hour12(), tm_min(), tm_sec(), ':');
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
*out_++ = ' ';
on_am_pm();
Expand All @@ -1716,21 +1757,19 @@ template <typename OutputIt, typename Char> class tm_writer {
format_localized('r');
}
void on_24_hour_time() {
write2(tm_.tm_hour);
write2(tm_hour());
*out_++ = ':';
write2(tm_.tm_min);
write2(tm_min());
}
void on_iso_time() {
char buf[8];
write_digit2_separated(buf, to_unsigned(tm_.tm_hour),
to_unsigned(tm_.tm_min), to_unsigned(tm_.tm_sec),
':');
write_digit2_separated(buf, tm_hour(), tm_min(), tm_sec(), ':');
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
}

void on_am_pm() {
if (is_classic_) {
*out_++ = tm_.tm_hour < 12 ? 'A' : 'P';
*out_++ = tm_hour() < 12 ? 'A' : 'P';
*out_++ = 'M';
} else
format_localized('p');
Expand All @@ -1741,6 +1780,9 @@ template <typename OutputIt, typename Char> class tm_writer {
void on_duration_unit() {}
};

template <typename OutputIt, typename Char>
constexpr const Char tm_writer<OutputIt, Char>::invalid[];

template <typename OutputIt, typename Char>
constexpr const Char tm_writer<OutputIt, Char>::day_of_week[][10];

Expand Down

0 comments on commit 0bed636

Please sign in to comment.