TimeZone.h (8931B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 #ifndef intl_components_TimeZone_h_ 5 #define intl_components_TimeZone_h_ 6 7 // ICU doesn't provide a separate C API for time zone functions, but instead 8 // requires to use UCalendar. This adds a measurable overhead when compared to 9 // using ICU's C++ TimeZone API, therefore we prefer to use the C++ API when 10 // possible. Due to the lack of a stable ABI in C++, it's only possible to use 11 // the C++ API when we use our in-tree ICU copy. 12 #if !MOZ_SYSTEM_ICU 13 # define MOZ_INTL_USE_ICU_CPP_TIMEZONE 1 14 #else 15 # define MOZ_INTL_USE_ICU_CPP_TIMEZONE 0 16 #endif 17 18 #include <stdint.h> 19 #include <utility> 20 21 #include "unicode/ucal.h" 22 #include "unicode/utypes.h" 23 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 24 # include "unicode/locid.h" 25 # include "unicode/timezone.h" 26 # include "unicode/unistr.h" 27 #endif 28 29 #include "mozilla/Assertions.h" 30 #include "mozilla/Casting.h" 31 #include "mozilla/intl/ICU4CGlue.h" 32 #include "mozilla/intl/ICUError.h" 33 #include "mozilla/Maybe.h" 34 #include "mozilla/Result.h" 35 #include "mozilla/Span.h" 36 #include "mozilla/UniquePtr.h" 37 38 namespace mozilla::intl { 39 40 /** 41 * This component is a Mozilla-focused API for working with time zones in 42 * internationalization code. It is used in coordination with other operations 43 * such as datetime formatting. 44 */ 45 class TimeZone final { 46 public: 47 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 48 explicit TimeZone(UniquePtr<icu::TimeZone> aTimeZone) 49 : mTimeZone(std::move(aTimeZone)) { 50 MOZ_ASSERT(mTimeZone); 51 } 52 #else 53 explicit TimeZone(UCalendar* aCalendar) : mCalendar(aCalendar) { 54 MOZ_ASSERT(mCalendar); 55 } 56 #endif 57 58 // Do not allow copy as this class owns the ICU resource. Move is not 59 // currently implemented, but a custom move operator could be created if 60 // needed. 61 TimeZone(const TimeZone&) = delete; 62 TimeZone& operator=(const TimeZone&) = delete; 63 64 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 65 ~TimeZone() = default; 66 #else 67 ~TimeZone(); 68 #endif 69 70 /** 71 * Create a TimeZone. 72 */ 73 static Result<UniquePtr<TimeZone>, ICUError> TryCreate( 74 Maybe<Span<const char>> aTimeZoneOverride = Nothing{}); 75 76 /** 77 * A number indicating the raw offset from GMT in milliseconds. 78 */ 79 Result<int32_t, ICUError> GetRawOffsetMs(); 80 81 /** 82 * Return the daylight saving offset in milliseconds at the given UTC time. 83 */ 84 Result<int32_t, ICUError> GetDSTOffsetMs(int64_t aUTCMilliseconds); 85 86 /** 87 * Return the local offset in milliseconds at the given UTC time. 88 */ 89 Result<int32_t, ICUError> GetOffsetMs(int64_t aUTCMilliseconds); 90 91 /** 92 * Return the UTC offset in milliseconds at the given local time. 93 */ 94 Result<int32_t, ICUError> GetUTCOffsetMs(int64_t aLocalMilliseconds); 95 96 enum class LocalOption { 97 /** 98 * The input is interpreted as local time before a time zone transition. 99 */ 100 Former, 101 102 /** 103 * The input is interpreted as local time after a time zone transition. 104 */ 105 Latter, 106 }; 107 108 /** 109 * Return the UTC offset in milliseconds at the given local time. 110 * 111 * `aSkippedTime` and `aRepeatedTime` select how to interpret skipped and 112 * repeated local times. 113 */ 114 Result<int32_t, ICUError> GetUTCOffsetMs(int64_t aLocalMilliseconds, 115 LocalOption aSkippedTime, 116 LocalOption aRepeatedTime); 117 118 /** 119 * Return the previous time zone transition before the given UTC time. If no 120 * transition was found, return Nothing. 121 */ 122 Result<Maybe<int64_t>, ICUError> GetPreviousTransition( 123 int64_t aUTCMilliseconds); 124 125 /** 126 * Return the next time zone transition after the given UTC time. If no 127 * transition was found, return Nothing. 128 */ 129 Result<Maybe<int64_t>, ICUError> GetNextTransition(int64_t aUTCMilliseconds); 130 131 enum class DaylightSavings : bool { No, Yes }; 132 133 /** 134 * Return the display name for this time zone. 135 */ 136 template <typename B> 137 ICUResult GetDisplayName(const char* aLocale, 138 DaylightSavings aDaylightSavings, B& aBuffer) { 139 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 140 icu::UnicodeString displayName; 141 mTimeZone->getDisplayName(static_cast<bool>(aDaylightSavings), 142 icu::TimeZone::LONG, icu::Locale(aLocale), 143 displayName); 144 return FillBuffer(displayName, aBuffer); 145 #else 146 return FillBufferWithICUCall( 147 aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { 148 UCalendarDisplayNameType type = 149 static_cast<bool>(aDaylightSavings) ? UCAL_DST : UCAL_STANDARD; 150 return ucal_getTimeZoneDisplayName(mCalendar, type, aLocale, target, 151 length, status); 152 }); 153 #endif 154 } 155 156 /** 157 * Return the identifier for this time zone. 158 */ 159 template <typename B> 160 ICUResult GetId(B& aBuffer) { 161 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 162 icu::UnicodeString id; 163 mTimeZone->getID(id); 164 return FillBuffer(id, aBuffer); 165 #else 166 return FillBufferWithICUCall( 167 aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { 168 return ucal_getTimeZoneID(mCalendar, target, length, status); 169 }); 170 #endif 171 } 172 173 /** 174 * Fill the buffer with the system's default IANA time zone identifier, e.g. 175 * "America/Chicago". 176 */ 177 template <typename B> 178 static ICUResult GetDefaultTimeZone(B& aBuffer) { 179 return FillBufferWithICUCall(aBuffer, ucal_getDefaultTimeZone); 180 } 181 182 /** 183 * Fill the buffer with the host system's default IANA time zone identifier, 184 * e.g. "America/Chicago". 185 * 186 * NOTE: This function is not thread-safe. 187 */ 188 template <typename B> 189 static ICUResult GetHostTimeZone(B& aBuffer) { 190 return FillBufferWithICUCall(aBuffer, ucal_getHostTimeZone); 191 } 192 193 /** 194 * Set the default time zone. 195 */ 196 static Result<bool, ICUError> SetDefaultTimeZone(Span<const char> aTimeZone); 197 198 /** 199 * Set the default time zone using the host system's time zone. 200 * 201 * NOTE: This function is not thread-safe. 202 */ 203 static ICUResult SetDefaultTimeZoneFromHostTimeZone(); 204 205 /** 206 * Return the tzdata version. 207 * 208 * The tzdata version is a string of the form "<year><release>", e.g. "2021a". 209 */ 210 static Result<Span<const char>, ICUError> GetTZDataVersion(); 211 212 /** 213 * Constant for the typical maximal length of a time zone identifier. 214 * 215 * At the time of this writing 32 characters fits every supported time zone: 216 * 217 * Intl.supportedValuesOf("timeZone") 218 * .reduce((acc, v) => Math.max(acc, v.length), 0) 219 */ 220 static constexpr size_t TimeZoneIdentifierLength = 32; 221 222 /** 223 * Returns the canonical system time zone ID or the normalized custom time 224 * zone ID for the given time zone ID. 225 */ 226 template <typename B> 227 static ICUResult GetCanonicalTimeZoneID(Span<const char16_t> inputTimeZone, 228 B& aBuffer) { 229 static_assert(std::is_same_v<typename B::CharType, char16_t>, 230 "Currently only UTF-16 buffers are supported."); 231 232 if (aBuffer.capacity() == 0) { 233 // ucal_getCanonicalTimeZoneID differs from other API calls and fails when 234 // passed a nullptr or 0 length result. Reserve some space initially so 235 // that a real pointer will be used in the API. 236 if (!aBuffer.reserve(TimeZoneIdentifierLength)) { 237 return Err(ICUError::OutOfMemory); 238 } 239 } 240 241 return FillBufferWithICUCall( 242 aBuffer, 243 [&inputTimeZone](UChar* target, int32_t length, UErrorCode* status) { 244 return ucal_getCanonicalTimeZoneID( 245 inputTimeZone.Elements(), 246 static_cast<int32_t>(inputTimeZone.Length()), target, length, 247 /* isSystemID */ nullptr, status); 248 }); 249 } 250 251 /** 252 * Return an enumeration over all time zones commonly used in the given 253 * region. 254 */ 255 static Result<SpanEnumeration<char>, ICUError> GetAvailableTimeZones( 256 const char* aRegion); 257 258 /** 259 * Return an enumeration over all available time zones. 260 */ 261 static Result<SpanEnumeration<char>, ICUError> GetAvailableTimeZones(); 262 263 private: 264 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE 265 template <typename B> 266 static ICUResult FillBuffer(const icu::UnicodeString& aString, B& aBuffer) { 267 int32_t length = aString.length(); 268 if (!aBuffer.reserve(AssertedCast<size_t>(length))) { 269 return Err(ICUError::OutOfMemory); 270 } 271 272 UErrorCode status = U_ZERO_ERROR; 273 int32_t written = aString.extract(aBuffer.data(), length, status); 274 if (!ICUSuccessForStringSpan(status)) { 275 return Err(ToICUError(status)); 276 } 277 MOZ_ASSERT(written == length); 278 279 aBuffer.written(written); 280 281 return Ok{}; 282 } 283 284 UniquePtr<icu::TimeZone> mTimeZone = nullptr; 285 #else 286 UCalendar* mCalendar = nullptr; 287 #endif 288 }; 289 290 } // namespace mozilla::intl 291 292 #endif