Browse Source

LibTimeZone: Parse and generate time zone coordinate data

Timothy Flynn 3 years ago
parent
commit
ea814a3ce6

+ 5 - 1
Meta/CMake/time_zone_data.cmake

@@ -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

+ 108 - 2
Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp

@@ -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);
 
 

+ 9 - 0
Userland/Libraries/LibTimeZone/TimeZone.cpp

@@ -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 {};
+}
+
 }
 }

+ 19 - 0
Userland/Libraries/LibTimeZone/TimeZone.h

@@ -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);
+
 }
 }