mirror of
https://github.com/RGBCube/serenity
synced 2025-05-29 13:15:08 +00:00
LibTimeZone: Begin parsing and generating DaylightSavings rules
This commit is contained in:
parent
e9e8e2bdf4
commit
0e58b04b5c
4 changed files with 95 additions and 9 deletions
|
@ -23,6 +23,7 @@ struct DateTime {
|
||||||
|
|
||||||
Optional<u8> last_weekday;
|
Optional<u8> last_weekday;
|
||||||
Optional<u8> after_weekday;
|
Optional<u8> after_weekday;
|
||||||
|
Optional<u8> before_weekday;
|
||||||
|
|
||||||
Optional<u8> hour;
|
Optional<u8> hour;
|
||||||
Optional<u8> minute;
|
Optional<u8> minute;
|
||||||
|
@ -34,10 +35,20 @@ struct TimeZoneOffset {
|
||||||
Optional<DateTime> until;
|
Optional<DateTime> until;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DaylightSavingsOffset {
|
||||||
|
i64 offset { 0 };
|
||||||
|
u16 year_from { 0 };
|
||||||
|
u16 year_to { 0 };
|
||||||
|
DateTime in_effect;
|
||||||
|
};
|
||||||
|
|
||||||
struct TimeZoneData {
|
struct TimeZoneData {
|
||||||
HashMap<String, Vector<TimeZoneOffset>> time_zones;
|
HashMap<String, Vector<TimeZoneOffset>> time_zones;
|
||||||
Vector<String> time_zone_names;
|
Vector<String> time_zone_names;
|
||||||
Vector<Alias> time_zone_aliases;
|
Vector<Alias> time_zone_aliases;
|
||||||
|
|
||||||
|
HashMap<String, Vector<DaylightSavingsOffset>> dst_offsets;
|
||||||
|
Vector<String> dst_offset_names;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,12 +58,13 @@ struct AK::Formatter<DateTime> : Formatter<FormatString> {
|
||||||
ErrorOr<void> format(FormatBuilder& builder, DateTime const& date_time)
|
ErrorOr<void> format(FormatBuilder& builder, DateTime const& date_time)
|
||||||
{
|
{
|
||||||
return Formatter<FormatString>::format(builder,
|
return Formatter<FormatString>::format(builder,
|
||||||
"{{ {}, {}, {}, {}, {}, {}, {}, {} }}",
|
"{{ {}, {}, {}, {}, {}, {}, {}, {}, {} }}",
|
||||||
date_time.year,
|
date_time.year,
|
||||||
date_time.month.value_or(1),
|
date_time.month.value_or(1),
|
||||||
date_time.day.value_or(1),
|
date_time.day.value_or(1),
|
||||||
date_time.last_weekday.value_or(0),
|
date_time.last_weekday.value_or(0),
|
||||||
date_time.after_weekday.value_or(0),
|
date_time.after_weekday.value_or(0),
|
||||||
|
date_time.before_weekday.value_or(0),
|
||||||
date_time.hour.value_or(0),
|
date_time.hour.value_or(0),
|
||||||
date_time.minute.value_or(0),
|
date_time.minute.value_or(0),
|
||||||
date_time.second.value_or(0));
|
date_time.second.value_or(0));
|
||||||
|
@ -71,6 +83,19 @@ struct AK::Formatter<TimeZoneOffset> : Formatter<FormatString> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Formatter<DaylightSavingsOffset> : Formatter<FormatString> {
|
||||||
|
ErrorOr<void> format(FormatBuilder& builder, DaylightSavingsOffset const& dst_offset)
|
||||||
|
{
|
||||||
|
return Formatter<FormatString>::format(builder,
|
||||||
|
"{{ {}, {}, {}, {} }}",
|
||||||
|
dst_offset.offset,
|
||||||
|
dst_offset.year_from,
|
||||||
|
dst_offset.year_to,
|
||||||
|
dst_offset.in_effect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static Optional<DateTime> parse_date_time(Span<StringView const> segments)
|
static Optional<DateTime> parse_date_time(Span<StringView const> segments)
|
||||||
{
|
{
|
||||||
constexpr auto months = Array { "Jan"sv, "Feb"sv, "Mar"sv, "Apr"sv, "May"sv, "Jun"sv, "Jul"sv, "Aug"sv, "Sep"sv, "Oct"sv, "Nov"sv, "Dec"sv };
|
constexpr auto months = Array { "Jan"sv, "Feb"sv, "Mar"sv, "Apr"sv, "May"sv, "Jun"sv, "Jul"sv, "Aug"sv, "Sep"sv, "Oct"sv, "Nov"sv, "Dec"sv };
|
||||||
|
@ -98,6 +123,12 @@ static Optional<DateTime> parse_date_time(Span<StringView const> segments)
|
||||||
|
|
||||||
auto day = segments[2].substring_view(*index + ">="sv.length());
|
auto day = segments[2].substring_view(*index + ">="sv.length());
|
||||||
date_time.day = day.to_uint().value();
|
date_time.day = day.to_uint().value();
|
||||||
|
} else if (auto index = segments[2].find("<="sv); index.has_value()) {
|
||||||
|
auto weekday = segments[2].substring_view(0, *index);
|
||||||
|
date_time.before_weekday = find_index(weekdays.begin(), weekdays.end(), weekday);
|
||||||
|
|
||||||
|
auto day = segments[2].substring_view(*index + "<="sv.length());
|
||||||
|
date_time.day = day.to_uint().value();
|
||||||
} else {
|
} else {
|
||||||
date_time.day = segments[2].to_uint().value();
|
date_time.day = segments[2].to_uint().value();
|
||||||
}
|
}
|
||||||
|
@ -176,6 +207,35 @@ static void parse_link(StringView link_line, TimeZoneData& time_zone_data)
|
||||||
time_zone_data.time_zone_aliases.append({ target, alias });
|
time_zone_data.time_zone_aliases.append({ target, alias });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parse_rule(StringView rule_line, TimeZoneData& time_zone_data)
|
||||||
|
{
|
||||||
|
auto segments = rule_line.split_view_if([](char ch) { return (ch == '\t') || (ch == ' '); });
|
||||||
|
|
||||||
|
// Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
|
||||||
|
VERIFY(segments[0] == "Rule"sv);
|
||||||
|
auto name = segments[1];
|
||||||
|
|
||||||
|
DaylightSavingsOffset dst_offset {};
|
||||||
|
dst_offset.offset = parse_time_offset(segments[8]);
|
||||||
|
dst_offset.year_from = segments[2].to_uint().value();
|
||||||
|
|
||||||
|
if (segments[3] == "only")
|
||||||
|
dst_offset.year_to = dst_offset.year_from;
|
||||||
|
else if (segments[3] == "max"sv)
|
||||||
|
dst_offset.year_to = NumericLimits<u16>::max();
|
||||||
|
else
|
||||||
|
dst_offset.year_to = segments[3].to_uint().value();
|
||||||
|
|
||||||
|
auto in_effect = Array { "0"sv, segments[5], segments[6], segments[7] };
|
||||||
|
dst_offset.in_effect = parse_date_time(in_effect).release_value();
|
||||||
|
|
||||||
|
auto& dst_offsets = time_zone_data.dst_offsets.ensure(name);
|
||||||
|
dst_offsets.append(move(dst_offset));
|
||||||
|
|
||||||
|
if (!time_zone_data.dst_offset_names.contains_slow(name))
|
||||||
|
time_zone_data.dst_offset_names.append(name);
|
||||||
|
}
|
||||||
|
|
||||||
static ErrorOr<void> parse_time_zones(StringView time_zone_path, TimeZoneData& time_zone_data)
|
static ErrorOr<void> parse_time_zones(StringView time_zone_path, TimeZoneData& time_zone_data)
|
||||||
{
|
{
|
||||||
// For reference, the man page for `zic` has the best documentation of the TZDB file format.
|
// For reference, the man page for `zic` has the best documentation of the TZDB file format.
|
||||||
|
@ -197,6 +257,8 @@ static ErrorOr<void> parse_time_zones(StringView time_zone_path, TimeZoneData& t
|
||||||
|
|
||||||
if (line.starts_with("Link"sv))
|
if (line.starts_with("Link"sv))
|
||||||
parse_link(line, time_zone_data);
|
parse_link(line, time_zone_data);
|
||||||
|
else if (line.starts_with("Rule"sv))
|
||||||
|
parse_rule(line, time_zone_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +304,7 @@ namespace TimeZone {
|
||||||
)~~~");
|
)~~~");
|
||||||
|
|
||||||
generate_enum(generator, format_identifier, "TimeZone"sv, {}, time_zone_data.time_zone_names, time_zone_data.time_zone_aliases);
|
generate_enum(generator, format_identifier, "TimeZone"sv, {}, time_zone_data.time_zone_names, time_zone_data.time_zone_aliases);
|
||||||
|
generate_enum(generator, format_identifier, "DaylightSavingsRule"sv, {}, time_zone_data.dst_offset_names);
|
||||||
|
|
||||||
generator.append(R"~~~(
|
generator.append(R"~~~(
|
||||||
}
|
}
|
||||||
|
@ -270,7 +333,7 @@ namespace TimeZone {
|
||||||
struct DateTime {
|
struct DateTime {
|
||||||
AK::Time time_since_epoch() const
|
AK::Time time_since_epoch() const
|
||||||
{
|
{
|
||||||
// FIXME: This implementation does not take last_weekday or after_weekday into account.
|
// FIXME: This implementation does not take last_weekday, after_weekday, or before_weekday into account.
|
||||||
return AK::Time::from_timestamp(year, month, day, hour, minute, second, 0);
|
return AK::Time::from_timestamp(year, month, day, hour, minute, second, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +343,7 @@ struct DateTime {
|
||||||
|
|
||||||
u8 last_weekday { 0 };
|
u8 last_weekday { 0 };
|
||||||
u8 after_weekday { 0 };
|
u8 after_weekday { 0 };
|
||||||
|
u8 before_weekday { 0 };
|
||||||
|
|
||||||
u8 hour { 0 };
|
u8 hour { 0 };
|
||||||
u8 minute { 0 };
|
u8 minute { 0 };
|
||||||
|
@ -292,18 +356,26 @@ struct TimeZoneOffset {
|
||||||
DateTime until {};
|
DateTime until {};
|
||||||
bool has_until { false };
|
bool has_until { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DaylightSavingsOffset {
|
||||||
|
i64 offset { 0 };
|
||||||
|
u16 year_from { 0 };
|
||||||
|
u16 year_to { 0 };
|
||||||
|
DateTime in_effect {};
|
||||||
|
};
|
||||||
)~~~");
|
)~~~");
|
||||||
|
|
||||||
auto append_time_zone_offsets = [&](auto const& name, auto const& time_zone_offsets) {
|
auto append_offsets = [&](auto const& name, auto type, auto const& offsets) {
|
||||||
generator.set("name", name);
|
generator.set("name", name);
|
||||||
generator.set("size", String::number(time_zone_offsets.size()));
|
generator.set("type", type);
|
||||||
|
generator.set("size", String::number(offsets.size()));
|
||||||
|
|
||||||
generator.append(R"~~~(
|
generator.append(R"~~~(
|
||||||
static constexpr Array<TimeZoneOffset, @size@> @name@ { {
|
static constexpr Array<@type@, @size@> @name@ { {
|
||||||
)~~~");
|
)~~~");
|
||||||
|
|
||||||
for (auto const& time_zone_offset : time_zone_offsets)
|
for (auto const& offset : offsets)
|
||||||
generator.append(String::formatted(" {},\n", time_zone_offset));
|
generator.append(String::formatted(" {},\n", offset));
|
||||||
|
|
||||||
generator.append("} };\n");
|
generator.append("} };\n");
|
||||||
};
|
};
|
||||||
|
@ -311,10 +383,16 @@ static constexpr Array<TimeZoneOffset, @size@> @name@ { {
|
||||||
generate_mapping(generator, time_zone_data.time_zone_names, "TimeZoneOffset"sv, "s_time_zone_offsets"sv, "s_time_zone_offsets_{}", format_identifier,
|
generate_mapping(generator, time_zone_data.time_zone_names, "TimeZoneOffset"sv, "s_time_zone_offsets"sv, "s_time_zone_offsets_{}", format_identifier,
|
||||||
[&](auto const& name, auto const& value) {
|
[&](auto const& name, auto const& value) {
|
||||||
auto const& time_zone_offsets = time_zone_data.time_zones.find(value)->value;
|
auto const& time_zone_offsets = time_zone_data.time_zones.find(value)->value;
|
||||||
append_time_zone_offsets(name, time_zone_offsets);
|
append_offsets(name, "TimeZoneOffset"sv, time_zone_offsets);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto append_string_conversions = [&](StringView enum_title, StringView enum_snake, auto const& values, auto const& aliases) {
|
generate_mapping(generator, time_zone_data.dst_offset_names, "DaylightSavingsOffset"sv, "s_dst_offsets"sv, "s_dst_offsets_{}", format_identifier,
|
||||||
|
[&](auto const& name, auto const& value) {
|
||||||
|
auto const& dst_offsets = time_zone_data.dst_offsets.find(value)->value;
|
||||||
|
append_offsets(name, "DaylightSavingsOffset"sv, dst_offsets);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto append_string_conversions = [&](StringView enum_title, StringView enum_snake, auto const& values, Vector<Alias> const& aliases = {}) {
|
||||||
HashValueMap<String> hashes;
|
HashValueMap<String> hashes;
|
||||||
hashes.ensure_capacity(values.size());
|
hashes.ensure_capacity(values.size());
|
||||||
|
|
||||||
|
@ -335,6 +413,7 @@ static constexpr Array<TimeZoneOffset, @size@> @name@ { {
|
||||||
};
|
};
|
||||||
|
|
||||||
append_string_conversions("TimeZone"sv, "time_zone"sv, time_zone_data.time_zone_names, time_zone_data.time_zone_aliases);
|
append_string_conversions("TimeZone"sv, "time_zone"sv, time_zone_data.time_zone_names, time_zone_data.time_zone_aliases);
|
||||||
|
append_string_conversions("DaylightSavingsRule"sv, "daylight_savings_rule"sv, time_zone_data.dst_offset_names);
|
||||||
|
|
||||||
generator.append(R"~~~(
|
generator.append(R"~~~(
|
||||||
Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time)
|
Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
namespace TimeZone {
|
namespace TimeZone {
|
||||||
|
|
||||||
|
enum class DaylightSavingsRule : u8;
|
||||||
enum class TimeZone : u16;
|
enum class TimeZone : u16;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,9 @@ Optional<StringView> canonicalize_time_zone(StringView time_zone)
|
||||||
return canonical_time_zone;
|
return canonical_time_zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<DaylightSavingsRule> __attribute__((weak)) daylight_savings_rule_from_string(StringView) { return {}; }
|
||||||
|
StringView __attribute__((weak)) daylight_savings_rule_to_string(DaylightSavingsRule) { return {}; }
|
||||||
|
|
||||||
Optional<i64> __attribute__((weak)) get_time_zone_offset([[maybe_unused]] TimeZone time_zone, AK::Time)
|
Optional<i64> __attribute__((weak)) get_time_zone_offset([[maybe_unused]] TimeZone time_zone, AK::Time)
|
||||||
{
|
{
|
||||||
#if !ENABLE_TIME_ZONE_DATA
|
#if !ENABLE_TIME_ZONE_DATA
|
||||||
|
|
|
@ -20,6 +20,9 @@ Optional<TimeZone> time_zone_from_string(StringView time_zone);
|
||||||
StringView time_zone_to_string(TimeZone time_zone);
|
StringView time_zone_to_string(TimeZone time_zone);
|
||||||
Optional<StringView> canonicalize_time_zone(StringView time_zone);
|
Optional<StringView> canonicalize_time_zone(StringView time_zone);
|
||||||
|
|
||||||
|
Optional<DaylightSavingsRule> daylight_savings_rule_from_string(StringView daylight_savings_rule);
|
||||||
|
StringView daylight_savings_rule_to_string(DaylightSavingsRule daylight_savings_rule);
|
||||||
|
|
||||||
Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time);
|
Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time);
|
||||||
Optional<i64> get_time_zone_offset(StringView time_zone, AK::Time time);
|
Optional<i64> get_time_zone_offset(StringView time_zone, AK::Time time);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue