TimeZone.cpp 3.7 KB

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