mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 05:57:44 +00:00
LibTimeZone: Parse and generate time zone coordinate data
This commit is contained in:
parent
7b41a09540
commit
ea814a3ce6
4 changed files with 141 additions and 3 deletions
|
@ -35,6 +35,9 @@ set(TZDB_NORTH_AMERICA_PATH "${TZDB_PATH}/${TZDB_NORTH_AMERICA_SOURCE}")
|
||||||
set(TZDB_SOUTH_AMERICA_SOURCE southamerica)
|
set(TZDB_SOUTH_AMERICA_SOURCE southamerica)
|
||||||
set(TZDB_SOUTH_AMERICA_PATH "${TZDB_PATH}/${TZDB_SOUTH_AMERICA_SOURCE}")
|
set(TZDB_SOUTH_AMERICA_PATH "${TZDB_PATH}/${TZDB_SOUTH_AMERICA_SOURCE}")
|
||||||
|
|
||||||
|
set(TZDB_ZONE_1970_SOURCE zone1970.tab)
|
||||||
|
set(TZDB_ZONE_1970_PATH "${TZDB_PATH}/${TZDB_ZONE_1970_SOURCE}")
|
||||||
|
|
||||||
function(extract_tzdb_file source path)
|
function(extract_tzdb_file source path)
|
||||||
if(EXISTS "${TZDB_ZIP_PATH}" AND NOT EXISTS "${path}")
|
if(EXISTS "${TZDB_ZIP_PATH}" AND NOT EXISTS "${path}")
|
||||||
message(STATUS "Extracting TZDB ${source} from ${TZDB_ZIP_PATH}...")
|
message(STATUS "Extracting TZDB ${source} from ${TZDB_ZIP_PATH}...")
|
||||||
|
@ -58,6 +61,7 @@ if (ENABLE_TIME_ZONE_DATABASE_DOWNLOAD)
|
||||||
extract_tzdb_file("${TZDB_EUROPE_SOURCE}" "${TZDB_EUROPE_PATH}")
|
extract_tzdb_file("${TZDB_EUROPE_SOURCE}" "${TZDB_EUROPE_PATH}")
|
||||||
extract_tzdb_file("${TZDB_NORTH_AMERICA_SOURCE}" "${TZDB_NORTH_AMERICA_PATH}")
|
extract_tzdb_file("${TZDB_NORTH_AMERICA_SOURCE}" "${TZDB_NORTH_AMERICA_PATH}")
|
||||||
extract_tzdb_file("${TZDB_SOUTH_AMERICA_SOURCE}" "${TZDB_SOUTH_AMERICA_PATH}")
|
extract_tzdb_file("${TZDB_SOUTH_AMERICA_SOURCE}" "${TZDB_SOUTH_AMERICA_PATH}")
|
||||||
|
extract_tzdb_file("${TZDB_ZONE_1970_SOURCE}" "${TZDB_ZONE_1970_PATH}")
|
||||||
|
|
||||||
set(TIME_ZONE_DATA_HEADER LibTimeZone/TimeZoneData.h)
|
set(TIME_ZONE_DATA_HEADER LibTimeZone/TimeZoneData.h)
|
||||||
set(TIME_ZONE_DATA_IMPLEMENTATION LibTimeZone/TimeZoneData.cpp)
|
set(TIME_ZONE_DATA_IMPLEMENTATION LibTimeZone/TimeZoneData.cpp)
|
||||||
|
@ -78,7 +82,7 @@ if (ENABLE_TIME_ZONE_DATABASE_DOWNLOAD)
|
||||||
"${TIME_ZONE_META_TARGET_PREFIX}"
|
"${TIME_ZONE_META_TARGET_PREFIX}"
|
||||||
"${TIME_ZONE_DATA_HEADER}"
|
"${TIME_ZONE_DATA_HEADER}"
|
||||||
"${TIME_ZONE_DATA_IMPLEMENTATION}"
|
"${TIME_ZONE_DATA_IMPLEMENTATION}"
|
||||||
arguments "${TZDB_AFRICA_PATH}" "${TZDB_ANTARCTICA_PATH}" "${TZDB_ASIA_PATH}" "${TZDB_AUSTRALASIA_PATH}" "${TZDB_BACKWARD_PATH}" "${TZDB_ETCETERA_PATH}" "${TZDB_EUROPE_PATH}" "${TZDB_NORTH_AMERICA_PATH}" "${TZDB_SOUTH_AMERICA_PATH}"
|
arguments -z "${TZDB_ZONE_1970_PATH}" "${TZDB_AFRICA_PATH}" "${TZDB_ANTARCTICA_PATH}" "${TZDB_ASIA_PATH}" "${TZDB_AUSTRALASIA_PATH}" "${TZDB_BACKWARD_PATH}" "${TZDB_ETCETERA_PATH}" "${TZDB_EUROPE_PATH}" "${TZDB_NORTH_AMERICA_PATH}" "${TZDB_SOUTH_AMERICA_PATH}"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TIME_ZONE_DATA_SOURCES
|
set(TIME_ZONE_DATA_SOURCES
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
|
#include <LibTimeZone/TimeZone.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -63,6 +64,8 @@ struct TimeZoneData {
|
||||||
|
|
||||||
HashMap<String, Vector<DaylightSavingsOffset>> dst_offsets;
|
HashMap<String, Vector<DaylightSavingsOffset>> dst_offsets;
|
||||||
Vector<String> dst_offset_names;
|
Vector<String> dst_offset_names;
|
||||||
|
|
||||||
|
HashMap<String, TimeZone::Location> time_zone_coordinates;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -115,6 +118,29 @@ struct AK::Formatter<DaylightSavingsOffset> : Formatter<FormatString> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Formatter<TimeZone::Coordinate> : Formatter<FormatString> {
|
||||||
|
ErrorOr<void> format(FormatBuilder& builder, TimeZone::Coordinate const& coordinate)
|
||||||
|
{
|
||||||
|
return Formatter<FormatString>::format(builder,
|
||||||
|
"{{ {}, {}, {} }}",
|
||||||
|
coordinate.degrees,
|
||||||
|
coordinate.minutes,
|
||||||
|
coordinate.seconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Formatter<TimeZone::Location> : Formatter<FormatString> {
|
||||||
|
ErrorOr<void> format(FormatBuilder& builder, TimeZone::Location const& location)
|
||||||
|
{
|
||||||
|
return Formatter<FormatString>::format(builder,
|
||||||
|
"{{ {}, {} }}",
|
||||||
|
location.latitude,
|
||||||
|
location.longitude);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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 };
|
||||||
|
@ -312,6 +338,56 @@ static ErrorOr<void> parse_time_zones(StringView time_zone_path, TimeZoneData& t
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parse_time_zone_coordinates(Core::File& file, TimeZoneData& time_zone_data)
|
||||||
|
{
|
||||||
|
auto parse_coordinate = [](auto coordinate) {
|
||||||
|
VERIFY(coordinate.substring_view(0, 1).is_one_of("+"sv, "-"sv));
|
||||||
|
TimeZone::Coordinate parsed {};
|
||||||
|
|
||||||
|
if (coordinate.length() == 5) {
|
||||||
|
// ±DDMM
|
||||||
|
parsed.degrees = coordinate.substring_view(0, 3).to_int().value();
|
||||||
|
parsed.minutes = coordinate.substring_view(3).to_int().value();
|
||||||
|
} else if (coordinate.length() == 6) {
|
||||||
|
// ±DDDMM
|
||||||
|
parsed.degrees = coordinate.substring_view(0, 4).to_int().value();
|
||||||
|
parsed.minutes = coordinate.substring_view(4).to_int().value();
|
||||||
|
} else if (coordinate.length() == 7) {
|
||||||
|
// ±DDMMSS
|
||||||
|
parsed.degrees = coordinate.substring_view(0, 3).to_int().value();
|
||||||
|
parsed.minutes = coordinate.substring_view(3, 2).to_int().value();
|
||||||
|
parsed.seconds = coordinate.substring_view(5).to_int().value();
|
||||||
|
} else if (coordinate.length() == 8) {
|
||||||
|
// ±DDDDMMSS
|
||||||
|
parsed.degrees = coordinate.substring_view(0, 4).to_int().value();
|
||||||
|
parsed.minutes = coordinate.substring_view(4, 2).to_int().value();
|
||||||
|
parsed.seconds = coordinate.substring_view(6).to_int().value();
|
||||||
|
} else {
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
while (file.can_read_line()) {
|
||||||
|
auto line = file.read_line();
|
||||||
|
if (line.is_empty() || line.trim_whitespace(TrimMode::Left).starts_with('#'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto segments = line.split_view('\t');
|
||||||
|
auto coordinates = segments[1];
|
||||||
|
auto zone = segments[2];
|
||||||
|
|
||||||
|
VERIFY(time_zone_data.time_zones.contains(zone));
|
||||||
|
|
||||||
|
auto index = coordinates.find_any_of("+-"sv, StringView::SearchDirection::Backward).value();
|
||||||
|
auto latitude = parse_coordinate(coordinates.substring_view(0, index));
|
||||||
|
auto longitude = parse_coordinate(coordinates.substring_view(index));
|
||||||
|
|
||||||
|
time_zone_data.time_zone_coordinates.set(zone, { latitude, longitude });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void set_dst_rule_indices(TimeZoneData& time_zone_data)
|
static void set_dst_rule_indices(TimeZoneData& time_zone_data)
|
||||||
{
|
{
|
||||||
for (auto& time_zone : time_zone_data.time_zones) {
|
for (auto& time_zone : time_zone_data.time_zones) {
|
||||||
|
@ -473,6 +549,18 @@ static constexpr Array<@type@, @size@> @name@ { {
|
||||||
append_offsets(name, "DaylightSavingsOffset"sv, dst_offsets);
|
append_offsets(name, "DaylightSavingsOffset"sv, dst_offsets);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
generator.set("size", String::number(time_zone_data.time_zone_names.size()));
|
||||||
|
generator.append(R"~~~(
|
||||||
|
static constexpr Array<Location, @size@> s_time_zone_locations { {
|
||||||
|
)~~~");
|
||||||
|
|
||||||
|
for (auto const& time_zone : time_zone_data.time_zone_names) {
|
||||||
|
auto location = time_zone_data.time_zone_coordinates.get(time_zone).value_or({});
|
||||||
|
|
||||||
|
generator.append(String::formatted(" {},\n", location));
|
||||||
|
}
|
||||||
|
generator.append("} };\n");
|
||||||
|
|
||||||
auto append_string_conversions = [&](StringView enum_title, StringView enum_snake, auto const& values, Vector<Alias> const& aliases = {}) {
|
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());
|
||||||
|
@ -619,6 +707,19 @@ Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(TimeZone time_zone,
|
||||||
|
|
||||||
return named_offsets;
|
return named_offsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<Location> get_time_zone_location(TimeZone time_zone)
|
||||||
|
{
|
||||||
|
auto is_valid_coordinate = [](auto const& coordinate) {
|
||||||
|
return (coordinate.degrees != 0) || (coordinate.minutes != 0) || (coordinate.seconds != 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const& location = s_time_zone_locations[to_underlying(time_zone)];
|
||||||
|
|
||||||
|
if (is_valid_coordinate(location.latitude) && is_valid_coordinate(location.longitude))
|
||||||
|
return location;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
)~~~");
|
)~~~");
|
||||||
|
|
||||||
generate_available_values(generator, "all_time_zones"sv, time_zone_data.time_zone_names);
|
generate_available_values(generator, "all_time_zones"sv, time_zone_data.time_zone_names);
|
||||||
|
@ -635,30 +736,35 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
{
|
{
|
||||||
StringView generated_header_path;
|
StringView generated_header_path;
|
||||||
StringView generated_implementation_path;
|
StringView generated_implementation_path;
|
||||||
|
StringView time_zone_coordinates_path;
|
||||||
Vector<StringView> time_zone_paths;
|
Vector<StringView> time_zone_paths;
|
||||||
|
|
||||||
Core::ArgsParser args_parser;
|
Core::ArgsParser args_parser;
|
||||||
args_parser.add_option(generated_header_path, "Path to the time zone data header file to generate", "generated-header-path", 'h', "generated-header-path");
|
args_parser.add_option(generated_header_path, "Path to the time zone data header file to generate", "generated-header-path", 'h', "generated-header-path");
|
||||||
args_parser.add_option(generated_implementation_path, "Path to the time zone data implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
|
args_parser.add_option(generated_implementation_path, "Path to the time zone data implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
|
||||||
|
args_parser.add_option(time_zone_coordinates_path, "Path to the time zone data coordinates file", "time-zone-coordinates-path", 'z', "time-zone-coordinates-path");
|
||||||
args_parser.add_positional_argument(time_zone_paths, "Paths to the time zone database files", "time-zone-paths");
|
args_parser.add_positional_argument(time_zone_paths, "Paths to the time zone database files", "time-zone-paths");
|
||||||
args_parser.parse(arguments);
|
args_parser.parse(arguments);
|
||||||
|
|
||||||
auto open_file = [&](StringView path) -> ErrorOr<NonnullRefPtr<Core::File>> {
|
auto open_file = [&](StringView path, Core::OpenMode mode = Core::OpenMode::ReadWrite) -> ErrorOr<NonnullRefPtr<Core::File>> {
|
||||||
if (path.is_empty()) {
|
if (path.is_empty()) {
|
||||||
args_parser.print_usage(stderr, arguments.argv[0]);
|
args_parser.print_usage(stderr, arguments.argv[0]);
|
||||||
return Error::from_string_literal("Must provide all command line options"sv);
|
return Error::from_string_literal("Must provide all command line options"sv);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Core::File::open(path, Core::OpenMode::ReadWrite);
|
return Core::File::open(path, mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto generated_header_file = TRY(open_file(generated_header_path));
|
auto generated_header_file = TRY(open_file(generated_header_path));
|
||||||
auto generated_implementation_file = TRY(open_file(generated_implementation_path));
|
auto generated_implementation_file = TRY(open_file(generated_implementation_path));
|
||||||
|
auto time_zone_coordinates_file = TRY(open_file(time_zone_coordinates_path, Core::OpenMode::ReadOnly));
|
||||||
|
|
||||||
TimeZoneData time_zone_data {};
|
TimeZoneData time_zone_data {};
|
||||||
for (auto time_zone_path : time_zone_paths)
|
for (auto time_zone_path : time_zone_paths)
|
||||||
TRY(parse_time_zones(time_zone_path, time_zone_data));
|
TRY(parse_time_zones(time_zone_path, time_zone_data));
|
||||||
|
|
||||||
|
parse_time_zone_coordinates(time_zone_coordinates_file, time_zone_data);
|
||||||
|
|
||||||
generate_time_zone_data_header(generated_header_file, time_zone_data);
|
generate_time_zone_data_header(generated_header_file, time_zone_data);
|
||||||
generate_time_zone_data_implementation(generated_implementation_file, time_zone_data);
|
generate_time_zone_data_implementation(generated_implementation_file, time_zone_data);
|
||||||
|
|
||||||
|
|
|
@ -183,4 +183,13 @@ Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(StringView time_zone
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<Location> __attribute__((weak)) get_time_zone_location(TimeZone) { return {}; }
|
||||||
|
|
||||||
|
Optional<Location> get_time_zone_location(StringView time_zone)
|
||||||
|
{
|
||||||
|
if (auto maybe_time_zone = time_zone_from_string(time_zone); maybe_time_zone.has_value())
|
||||||
|
return get_time_zone_location(*maybe_time_zone);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,22 @@ struct NamedOffset : public Offset {
|
||||||
String name;
|
String name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Coordinate {
|
||||||
|
constexpr float decimal_coordinate() const
|
||||||
|
{
|
||||||
|
return static_cast<float>(degrees) + (static_cast<float>(minutes) / 60.0f) + (static_cast<float>(seconds) / 3'600.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
i16 degrees { 0 };
|
||||||
|
u8 minutes { 0 };
|
||||||
|
u8 seconds { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Location {
|
||||||
|
Coordinate latitude;
|
||||||
|
Coordinate longitude;
|
||||||
|
};
|
||||||
|
|
||||||
StringView system_time_zone();
|
StringView system_time_zone();
|
||||||
StringView current_time_zone();
|
StringView current_time_zone();
|
||||||
ErrorOr<void> change_time_zone(StringView time_zone);
|
ErrorOr<void> change_time_zone(StringView time_zone);
|
||||||
|
@ -49,4 +65,7 @@ Optional<Offset> get_time_zone_offset(StringView time_zone, AK::Time time);
|
||||||
Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(TimeZone time_zone, AK::Time time);
|
Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(TimeZone time_zone, AK::Time time);
|
||||||
Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(StringView time_zone, AK::Time time);
|
Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(StringView time_zone, AK::Time time);
|
||||||
|
|
||||||
|
Optional<Location> get_time_zone_location(TimeZone time_zone);
|
||||||
|
Optional<Location> get_time_zone_location(StringView time_zone);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue