summaryrefslogtreecommitdiff
path: root/src/tz_str.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
committerJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
commit6232d13f5321b87ddf12a1aa36b4545da45f173d (patch)
tree23f3316470a14136debd9d02f9e920ca2b06f4cc /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.cc265
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