2024-06-24 14:17:32 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
2024-06-25 15:06:08 +00:00
|
|
|
#include <AK/Array.h>
|
2024-06-24 14:17:32 +00:00
|
|
|
#include <AK/NonnullOwnPtr.h>
|
2024-06-25 15:06:08 +00:00
|
|
|
#include <AK/QuickSort.h>
|
2024-06-24 14:17:32 +00:00
|
|
|
#include <LibUnicode/ICU.h>
|
|
|
|
#include <LibUnicode/TimeZone.h>
|
|
|
|
|
|
|
|
#include <unicode/timezone.h>
|
2024-06-25 15:06:08 +00:00
|
|
|
#include <unicode/ucal.h>
|
2024-06-24 14:17:32 +00:00
|
|
|
|
|
|
|
namespace Unicode {
|
|
|
|
|
2024-08-24 16:07:24 +00:00
|
|
|
static Optional<String> cached_system_time_zone;
|
|
|
|
|
2024-08-26 14:20:24 +00:00
|
|
|
String current_time_zone()
|
2024-06-24 14:17:32 +00:00
|
|
|
{
|
2024-08-24 16:07:24 +00:00
|
|
|
if (cached_system_time_zone.has_value())
|
|
|
|
return *cached_system_time_zone;
|
|
|
|
|
2024-06-24 14:17:32 +00:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
auto time_zone = adopt_own_if_nonnull(icu::TimeZone::detectHostTimeZone());
|
|
|
|
if (!time_zone)
|
|
|
|
return "UTC"_string;
|
|
|
|
|
|
|
|
icu::UnicodeString time_zone_id;
|
|
|
|
time_zone->getID(time_zone_id);
|
|
|
|
|
|
|
|
icu::UnicodeString time_zone_name;
|
|
|
|
time_zone->getCanonicalID(time_zone_id, time_zone_name, status);
|
|
|
|
|
|
|
|
if (icu_failure(status))
|
|
|
|
return "UTC"_string;
|
|
|
|
|
2024-08-24 16:07:24 +00:00
|
|
|
cached_system_time_zone = icu_string_to_string(time_zone_name);
|
|
|
|
return *cached_system_time_zone;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_system_time_zone_cache()
|
|
|
|
{
|
|
|
|
cached_system_time_zone.clear();
|
2024-06-24 14:17:32 +00:00
|
|
|
}
|
|
|
|
|
2024-06-25 15:06:08 +00:00
|
|
|
// https://github.com/unicode-org/icu/blob/main/icu4c/source/tools/tzcode/icuzones
|
|
|
|
static constexpr bool is_legacy_non_iana_time_zone(StringView time_zone)
|
|
|
|
{
|
|
|
|
constexpr auto legacy_zones = to_array({
|
|
|
|
"ACT"sv,
|
|
|
|
"AET"sv,
|
|
|
|
"AGT"sv,
|
|
|
|
"ART"sv,
|
|
|
|
"AST"sv,
|
|
|
|
"BET"sv,
|
|
|
|
"BST"sv,
|
|
|
|
"Canada/East-Saskatchewan"sv,
|
|
|
|
"CAT"sv,
|
|
|
|
"CNT"sv,
|
|
|
|
"CST"sv,
|
|
|
|
"CTT"sv,
|
|
|
|
"EAT"sv,
|
|
|
|
"ECT"sv,
|
|
|
|
"IET"sv,
|
|
|
|
"IST"sv,
|
|
|
|
"JST"sv,
|
|
|
|
"MIT"sv,
|
|
|
|
"NET"sv,
|
|
|
|
"NST"sv,
|
|
|
|
"PLT"sv,
|
|
|
|
"PNT"sv,
|
|
|
|
"PRT"sv,
|
|
|
|
"PST"sv,
|
|
|
|
"SST"sv,
|
|
|
|
"US/Pacific-New"sv,
|
|
|
|
"VST"sv,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (time_zone.starts_with("SystemV/"sv))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return legacy_zones.contains_slow(time_zone);
|
|
|
|
}
|
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
static Vector<String> icu_available_time_zones(Optional<ByteString> const& region)
|
2024-06-25 15:06:08 +00:00
|
|
|
{
|
2024-06-25 15:33:26 +00:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
char const* icu_region = region.has_value() ? region->characters() : nullptr;
|
2024-06-25 15:06:08 +00:00
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
auto time_zone_enumerator = adopt_own_if_nonnull(icu::TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, icu_region, nullptr, status));
|
|
|
|
if (icu_failure(status))
|
|
|
|
return { "UTC"_string };
|
2024-06-25 15:06:08 +00:00
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
auto time_zones = icu_string_enumeration_to_list(move(time_zone_enumerator), [](char const* zone) {
|
|
|
|
return !is_legacy_non_iana_time_zone({ zone, strlen(zone) });
|
|
|
|
});
|
2024-06-25 15:06:08 +00:00
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
quick_sort(time_zones);
|
|
|
|
return time_zones;
|
|
|
|
}
|
2024-06-25 15:06:08 +00:00
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
Vector<String> const& available_time_zones()
|
|
|
|
{
|
|
|
|
static auto time_zones = icu_available_time_zones({});
|
2024-06-25 15:06:08 +00:00
|
|
|
return time_zones;
|
|
|
|
}
|
|
|
|
|
2024-06-25 15:33:26 +00:00
|
|
|
Vector<String> available_time_zones_in_region(StringView region)
|
|
|
|
{
|
|
|
|
return icu_available_time_zones(region);
|
|
|
|
}
|
|
|
|
|
2024-06-25 15:06:08 +00:00
|
|
|
Optional<String> resolve_primary_time_zone(StringView time_zone)
|
|
|
|
{
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
icu::UnicodeString iana_id;
|
|
|
|
icu::TimeZone::getIanaID(icu_string(time_zone), iana_id, status);
|
|
|
|
|
|
|
|
if (icu_failure(status))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
return icu_string_to_string(iana_id);
|
|
|
|
}
|
|
|
|
|
2024-06-25 19:27:20 +00:00
|
|
|
Optional<TimeZoneOffset> time_zone_offset(StringView time_zone, UnixDateTime time)
|
|
|
|
{
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
2024-09-03 15:20:20 +00:00
|
|
|
auto time_zone_data = TimeZoneData::for_time_zone(time_zone);
|
|
|
|
if (!time_zone_data.has_value())
|
2024-06-25 19:27:20 +00:00
|
|
|
return {};
|
|
|
|
|
|
|
|
i32 raw_offset = 0;
|
|
|
|
i32 dst_offset = 0;
|
|
|
|
|
2024-09-03 15:20:20 +00:00
|
|
|
time_zone_data->time_zone().getOffset(static_cast<UDate>(time.milliseconds_since_epoch()), 0, raw_offset, dst_offset, status);
|
2024-06-25 19:27:20 +00:00
|
|
|
if (icu_failure(status))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
return TimeZoneOffset {
|
2024-07-17 05:45:18 +00:00
|
|
|
.offset = AK::Duration::from_milliseconds(raw_offset + dst_offset),
|
2024-06-25 19:27:20 +00:00
|
|
|
.in_dst = dst_offset == 0 ? TimeZoneOffset::InDST::No : TimeZoneOffset::InDST::Yes,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-06-24 14:17:32 +00:00
|
|
|
}
|