TimeZone.cpp 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /*
  2. * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #define AK_DONT_REPLACE_STD
  7. #include <AK/Array.h>
  8. #include <AK/NonnullOwnPtr.h>
  9. #include <AK/QuickSort.h>
  10. #include <LibUnicode/ICU.h>
  11. #include <LibUnicode/TimeZone.h>
  12. #include <unicode/timezone.h>
  13. #include <unicode/ucal.h>
  14. namespace Unicode {
  15. String current_time_zone()
  16. {
  17. UErrorCode status = U_ZERO_ERROR;
  18. auto time_zone = adopt_own_if_nonnull(icu::TimeZone::detectHostTimeZone());
  19. if (!time_zone)
  20. return "UTC"_string;
  21. icu::UnicodeString time_zone_id;
  22. time_zone->getID(time_zone_id);
  23. icu::UnicodeString time_zone_name;
  24. time_zone->getCanonicalID(time_zone_id, time_zone_name, status);
  25. if (icu_failure(status))
  26. return "UTC"_string;
  27. return icu_string_to_string(time_zone_name);
  28. }
  29. // https://github.com/unicode-org/icu/blob/main/icu4c/source/tools/tzcode/icuzones
  30. static constexpr bool is_legacy_non_iana_time_zone(StringView time_zone)
  31. {
  32. constexpr auto legacy_zones = to_array({
  33. "ACT"sv,
  34. "AET"sv,
  35. "AGT"sv,
  36. "ART"sv,
  37. "AST"sv,
  38. "BET"sv,
  39. "BST"sv,
  40. "Canada/East-Saskatchewan"sv,
  41. "CAT"sv,
  42. "CNT"sv,
  43. "CST"sv,
  44. "CTT"sv,
  45. "EAT"sv,
  46. "ECT"sv,
  47. "IET"sv,
  48. "IST"sv,
  49. "JST"sv,
  50. "MIT"sv,
  51. "NET"sv,
  52. "NST"sv,
  53. "PLT"sv,
  54. "PNT"sv,
  55. "PRT"sv,
  56. "PST"sv,
  57. "SST"sv,
  58. "US/Pacific-New"sv,
  59. "VST"sv,
  60. });
  61. if (time_zone.starts_with("SystemV/"sv))
  62. return true;
  63. return legacy_zones.contains_slow(time_zone);
  64. }
  65. static Vector<String> icu_available_time_zones(Optional<ByteString> const& region)
  66. {
  67. UErrorCode status = U_ZERO_ERROR;
  68. char const* icu_region = region.has_value() ? region->characters() : nullptr;
  69. auto time_zone_enumerator = adopt_own_if_nonnull(icu::TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, icu_region, nullptr, status));
  70. if (icu_failure(status))
  71. return { "UTC"_string };
  72. auto time_zones = icu_string_enumeration_to_list(move(time_zone_enumerator), [](char const* zone) {
  73. return !is_legacy_non_iana_time_zone({ zone, strlen(zone) });
  74. });
  75. quick_sort(time_zones);
  76. return time_zones;
  77. }
  78. Vector<String> const& available_time_zones()
  79. {
  80. static auto time_zones = icu_available_time_zones({});
  81. return time_zones;
  82. }
  83. Vector<String> available_time_zones_in_region(StringView region)
  84. {
  85. return icu_available_time_zones(region);
  86. }
  87. Optional<String> resolve_primary_time_zone(StringView time_zone)
  88. {
  89. UErrorCode status = U_ZERO_ERROR;
  90. icu::UnicodeString iana_id;
  91. icu::TimeZone::getIanaID(icu_string(time_zone), iana_id, status);
  92. if (icu_failure(status))
  93. return {};
  94. return icu_string_to_string(iana_id);
  95. }
  96. Optional<TimeZoneOffset> time_zone_offset(StringView time_zone, UnixDateTime time)
  97. {
  98. UErrorCode status = U_ZERO_ERROR;
  99. auto icu_time_zone = adopt_own_if_nonnull(icu::TimeZone::createTimeZone(icu_string(time_zone)));
  100. if (!icu_time_zone || *icu_time_zone == icu::TimeZone::getUnknown())
  101. return {};
  102. i32 raw_offset = 0;
  103. i32 dst_offset = 0;
  104. icu_time_zone->getOffset(static_cast<UDate>(time.milliseconds_since_epoch()), 0, raw_offset, dst_offset, status);
  105. if (icu_failure(status))
  106. return {};
  107. return TimeZoneOffset {
  108. .offset = AK::Duration::from_milliseconds(raw_offset + dst_offset),
  109. .in_dst = dst_offset == 0 ? TimeZoneOffset::InDST::No : TimeZoneOffset::InDST::Yes,
  110. };
  111. }
  112. }