diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2021-11-17 22:34:57 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2021-11-17 22:34:57 +0100 |
| commit | 6232d13f5321b87ddf12a1aa36b4545da45f173d (patch) | |
| tree | 23f3316470a14136debd9d02f9e920ca2b06f4cc /src/tz_str.cc | |
Travel3: Simple image and video display site
Reads the images and videos from filesystem and builds a site in
memroy.
Diffstat (limited to 'src/tz_str.cc')
| -rw-r--r-- | src/tz_str.cc | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/src/tz_str.cc b/src/tz_str.cc new file mode 100644 index 0000000..207f15d --- /dev/null +++ b/src/tz_str.cc @@ -0,0 +1,265 @@ +#include "common.hh" + +#include "tz_str.hh" + +#include <math.h> + +namespace tz { + +namespace { + +inline bool is_alpha(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +inline bool is_digit(char c) { + return c >= '0' && c <= '9'; +} + +inline bool is_alphnumeric(char c) { + return is_alpha(c) || is_digit(c); +} + +std::optional<std::string_view> read_abbr(std::string_view str, + size_t& offset) { + if (offset >= str.size()) + return std::nullopt; + auto const start = offset; + std::string_view ret; + if (str[offset] == '<') { + size_t end = str.find('>', offset + 1); + if (end == std::string_view::npos) + return std::nullopt; + offset = end + 1; + ret = str.substr(start + 1, end - (start + 1)); + for (auto const& c : ret) { + if (!(is_alphnumeric(c) || c == '+' || c == '-')) + return std::nullopt; + } + } else { + while (offset < str.size() && is_alpha(str[offset])) + ++offset; + ret = str.substr(start, offset - start); + } + if (ret.size() < 3) + return std::nullopt; + return ret; +} + +std::optional<uint32_t> read_num(std::string_view str, + size_t& offset) { + if (offset >= str.size() || !is_digit(str[offset])) + return std::nullopt; + uint32_t ret = str[offset++] - '0'; + while (offset < str.size() && is_digit(str[offset])) { + auto value = ret * 10 + (str[offset++] - '0'); + if (value < ret) + return std::nullopt; + ret = value; + } + return ret; +} + +std::optional<time_t> read_time(std::string_view str, + size_t& offset) { + auto hh = read_num(str, offset); + if (!hh) + return std::nullopt; + if (hh.value() > 24) + return std::nullopt; + time_t ret = hh.value() * 60 * 60; + if (offset < str.size() && str[offset] == ':') { + ++offset; + auto mm = read_num(str, offset); + if (!mm) + return std::nullopt; + if (mm.value() > 59) + return std::nullopt; + ret += mm.value() * 60; + if (offset < str.size() && str[offset] == ':') { + ++offset; + auto ss = read_num(str, offset); + if (!ss) + return std::nullopt; + if (ss.value() > 59) + return std::nullopt; + ret += ss.value(); + } + } + return ret; +} + +std::optional<time_t> read_offset(std::string_view str, + size_t& offset) { + bool negative; + if (offset < str.size() && (str[offset] == '+' || str[offset] == '-')) + negative = str[offset++] == '+'; // Yes, this is correct. ('-' is east) + else + negative = true; // Yes, this is correct. (default is west) + auto ret = read_time(str, offset); + if (!ret) + return std::nullopt; + return negative ? -ret.value() : ret.value(); +} + +inline bool leap_year(int32_t local_year) { + return (local_year % 4) == 0 && + ((local_year % 100) || ((local_year % 400) == 0)); +} + +uint8_t week_day_for_day_of_month(int32_t year, uint8_t month, + uint8_t day_of_month) { + auto k = static_cast<int>(day_of_month); + auto m = month >= 3 ? month - 2 : 10 + month; + auto C = year % 100; + auto Y = year / 100; + if (m > 10) + --Y; + return abs((k + static_cast<int>((2.6 * m - 0.2)) + - 2 * C + Y + (Y / 4) + (C / 4))) % 7; +} + +std::optional<time_t> read_date_and_time(std::string_view str, + int32_t local_year, + size_t& offset) { + if (offset >= str.size()) + return std::nullopt; + + time_t day_of_year; + if (str[offset] == 'J') { + ++offset; + auto julian_day = read_num(str, offset); + if (!julian_day || julian_day.value() < 1 || julian_day.value() > 365) + return std::nullopt; + + day_of_year = julian_day.value() - 1; + if (leap_year(local_year) && julian_day.value() >= 60) + ++day_of_year; + } else if (str[offset] == 'M') { + ++offset; + auto month = read_num(str, offset); + if (!month || month.value() < 1 || month.value() > 12) + return std::nullopt; + + if (offset >= str.size() || str[offset] != '.') + return std::nullopt; + + ++offset; + auto week_of_month = read_num(str, offset); + if (!week_of_month || week_of_month.value() < 1 || + week_of_month.value() > 5) + return std::nullopt; + + if (offset >= str.size() || str[offset] != '.') + return std::nullopt; + + ++offset; + auto day_of_week = read_num(str, offset); + if (!day_of_week || day_of_week.value() > 6) + return std::nullopt; + + day_of_year = 0; + for (size_t i = 1; i < month; ++i) { + day_of_year += (i <= 7) + ? ((i == 2) ? (leap_year(local_year) ? 29 : 28) : (i % 2 ? 31 : 30)) + : (i % 2 ? 30 : 31); + } + auto week_day_for_first_day_of_month = week_day_for_day_of_month( + local_year, month.value(), 1); + for (size_t i = 1; i < week_of_month; ++i) + day_of_year += 7; + if (day_of_week.value() < week_day_for_first_day_of_month) + day_of_year += 7 - week_day_for_first_day_of_month - day_of_week.value(); + else + day_of_year += day_of_week.value() - week_day_for_first_day_of_month; + } else { + auto julian_day = read_num(str, offset); + if (!julian_day || julian_day.value() > 365) + return std::nullopt; + + day_of_year = julian_day.value(); + } + + time_t ret = day_of_year * 24 * 60 * 60; + if (offset < str.size() && str[offset] == '/') { + ++offset; + auto time = read_time(str, offset); + if (!time) + return std::nullopt; + + ret += time.value(); + } else { + ret += 2 * 60 * 60; + } + return ret; +} + +} // namespace + +std::optional<time_t> get_local_time(std::string_view tz_str, + time_t utc_time) { + size_t offset = 0; + auto std = read_abbr(tz_str, offset); + if (!std) + return std::nullopt; + + auto std_offset = read_offset(tz_str, offset); + if (!std_offset) + return std::nullopt; + + auto local_time = utc_time + std_offset.value(); + if (offset == tz_str.size()) + return local_time; + auto dst = read_abbr(tz_str, offset); + if (!dst) + return std::nullopt; + + std::optional<time_t> dst_offset; + if (offset == tz_str.size() || tz_str[offset] == ',') { + dst_offset = std_offset.value() + 60 * 60; + } else { + dst_offset = read_offset(tz_str, offset); + if (!dst_offset) + return std::nullopt; + } + if (offset == tz_str.size()) { + // TODO: Can't figure out what the spec actually says about this. + // They are clearly optional but it doesn't specify what the default + // are. Assume no DST. + return local_time; + } + if (tz_str[offset] != ',') + return std::nullopt; + ++offset; + // TODO: This can't be 100% correct + auto local_year = 1970 + local_time / (365.25 * 24 * 60 * 60); + auto start = read_date_and_time(tz_str, local_year, offset); + if (!start) + return std::nullopt; + + if (tz_str[offset] != ',') + return std::nullopt; + + ++offset; + auto end = read_date_and_time(tz_str, local_year, offset); + if (!end) + return std::nullopt; + + // TODO: If local_year isn't correct this is definitly not. + auto local_time_in_year = + local_time % static_cast<time_t>((365.25 * 24 * 60 * 60)); + if (start.value() <= end.value()) { + if (start.value() < local_time_in_year && + local_time_in_year < end.value()) { + return utc_time + dst_offset.value(); + } + } else { + if (!(end.value() < local_time_in_year && + local_time_in_year < start.value())) { + return utc_time + dst_offset.value(); + } + } + return local_time; +} + +} // namespace tz |
