summaryrefslogtreecommitdiffstats
path: root/intl/components/src/TimeZone.h
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/src/TimeZone.h')
-rw-r--r--intl/components/src/TimeZone.h292
1 files changed, 292 insertions, 0 deletions
diff --git a/intl/components/src/TimeZone.h b/intl/components/src/TimeZone.h
new file mode 100644
index 0000000000..9d0423ef13
--- /dev/null
+++ b/intl/components/src/TimeZone.h
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef intl_components_TimeZone_h_
+#define intl_components_TimeZone_h_
+
+// ICU doesn't provide a separate C API for time zone functions, but instead
+// requires to use UCalendar. This adds a measurable overhead when compared to
+// using ICU's C++ TimeZone API, therefore we prefer to use the C++ API when
+// possible. Due to the lack of a stable ABI in C++, it's only possible to use
+// the C++ API when we use our in-tree ICU copy.
+#if !MOZ_SYSTEM_ICU
+# define MOZ_INTL_USE_ICU_CPP_TIMEZONE 1
+#else
+# define MOZ_INTL_USE_ICU_CPP_TIMEZONE 0
+#endif
+
+#include <stdint.h>
+#include <utility>
+
+#include "unicode/ucal.h"
+#include "unicode/utypes.h"
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+# include "unicode/locid.h"
+# include "unicode/timezone.h"
+# include "unicode/unistr.h"
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/intl/ICU4CGlue.h"
+#include "mozilla/intl/ICUError.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla::intl {
+
+/**
+ * This component is a Mozilla-focused API for working with time zones in
+ * internationalization code. It is used in coordination with other operations
+ * such as datetime formatting.
+ */
+class TimeZone final {
+ public:
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+ explicit TimeZone(UniquePtr<icu::TimeZone> aTimeZone)
+ : mTimeZone(std::move(aTimeZone)) {
+ MOZ_ASSERT(mTimeZone);
+ }
+#else
+ explicit TimeZone(UCalendar* aCalendar) : mCalendar(aCalendar) {
+ MOZ_ASSERT(mCalendar);
+ }
+#endif
+
+ // Do not allow copy as this class owns the ICU resource. Move is not
+ // currently implemented, but a custom move operator could be created if
+ // needed.
+ TimeZone(const TimeZone&) = delete;
+ TimeZone& operator=(const TimeZone&) = delete;
+
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+ ~TimeZone() = default;
+#else
+ ~TimeZone();
+#endif
+
+ /**
+ * Create a TimeZone.
+ */
+ static Result<UniquePtr<TimeZone>, ICUError> TryCreate(
+ Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
+
+ /**
+ * A number indicating the raw offset from GMT in milliseconds.
+ */
+ Result<int32_t, ICUError> GetRawOffsetMs();
+
+ /**
+ * Return the daylight saving offset in milliseconds at the given UTC time.
+ */
+ Result<int32_t, ICUError> GetDSTOffsetMs(int64_t aUTCMilliseconds);
+
+ /**
+ * Return the local offset in milliseconds at the given UTC time.
+ */
+ Result<int32_t, ICUError> GetOffsetMs(int64_t aUTCMilliseconds);
+
+ /**
+ * Return the UTC offset in milliseconds at the given local time.
+ */
+ Result<int32_t, ICUError> GetUTCOffsetMs(int64_t aLocalMilliseconds);
+
+ enum class LocalOption {
+ /**
+ * The input is interpreted as local time before a time zone transition.
+ */
+ Former,
+
+ /**
+ * The input is interpreted as local time after a time zone transition.
+ */
+ Latter,
+ };
+
+ /**
+ * Return the UTC offset in milliseconds at the given local time.
+ *
+ * `aSkippedTime` and `aRepeatedTime` select how to interpret skipped and
+ * repeated local times.
+ */
+ Result<int32_t, ICUError> GetUTCOffsetMs(int64_t aLocalMilliseconds,
+ LocalOption aSkippedTime,
+ LocalOption aRepeatedTime);
+
+ /**
+ * Return the previous time zone transition before the given UTC time. If no
+ * transition was found, return Nothing.
+ */
+ Result<Maybe<int64_t>, ICUError> GetPreviousTransition(
+ int64_t aUTCMilliseconds);
+
+ /**
+ * Return the next time zone transition after the given UTC time. If no
+ * transition was found, return Nothing.
+ */
+ Result<Maybe<int64_t>, ICUError> GetNextTransition(int64_t aUTCMilliseconds);
+
+ enum class DaylightSavings : bool { No, Yes };
+
+ /**
+ * Return the display name for this time zone.
+ */
+ template <typename B>
+ ICUResult GetDisplayName(const char* aLocale,
+ DaylightSavings aDaylightSavings, B& aBuffer) {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+ icu::UnicodeString displayName;
+ mTimeZone->getDisplayName(static_cast<bool>(aDaylightSavings),
+ icu::TimeZone::LONG, icu::Locale(aLocale),
+ displayName);
+ return FillBuffer(displayName, aBuffer);
+#else
+ return FillBufferWithICUCall(
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
+ UCalendarDisplayNameType type =
+ static_cast<bool>(aDaylightSavings) ? UCAL_DST : UCAL_STANDARD;
+ return ucal_getTimeZoneDisplayName(mCalendar, type, aLocale, target,
+ length, status);
+ });
+#endif
+ }
+
+ /**
+ * Return the identifier for this time zone.
+ */
+ template <typename B>
+ ICUResult GetId(B& aBuffer) {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+ icu::UnicodeString id;
+ mTimeZone->getID(id);
+ return FillBuffer(id, aBuffer);
+#else
+ return FillBufferWithICUCall(
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
+ return ucal_getTimeZoneID(mCalendar, target, length, status);
+ });
+#endif
+ }
+
+ /**
+ * Fill the buffer with the system's default IANA time zone identifier, e.g.
+ * "America/Chicago".
+ */
+ template <typename B>
+ static ICUResult GetDefaultTimeZone(B& aBuffer) {
+ return FillBufferWithICUCall(aBuffer, ucal_getDefaultTimeZone);
+ }
+
+ /**
+ * Fill the buffer with the host system's default IANA time zone identifier,
+ * e.g. "America/Chicago".
+ *
+ * NOTE: This function is not thread-safe.
+ */
+ template <typename B>
+ static ICUResult GetHostTimeZone(B& aBuffer) {
+ return FillBufferWithICUCall(aBuffer, ucal_getHostTimeZone);
+ }
+
+ /**
+ * Set the default time zone.
+ */
+ static Result<bool, ICUError> SetDefaultTimeZone(Span<const char> aTimeZone);
+
+ /**
+ * Set the default time zone using the host system's time zone.
+ *
+ * NOTE: This function is not thread-safe.
+ */
+ static ICUResult SetDefaultTimeZoneFromHostTimeZone();
+
+ /**
+ * Return the tzdata version.
+ *
+ * The tzdata version is a string of the form "<year><release>", e.g. "2021a".
+ */
+ static Result<Span<const char>, ICUError> GetTZDataVersion();
+
+ /**
+ * Constant for the typical maximal length of a time zone identifier.
+ *
+ * At the time of this writing 32 characters fits every supported time zone:
+ *
+ * Intl.supportedValuesOf("timeZone")
+ * .reduce((acc, v) => Math.max(acc, v.length), 0)
+ */
+ static constexpr size_t TimeZoneIdentifierLength = 32;
+
+ /**
+ * Returns the canonical system time zone ID or the normalized custom time
+ * zone ID for the given time zone ID.
+ */
+ template <typename B>
+ static ICUResult GetCanonicalTimeZoneID(Span<const char16_t> inputTimeZone,
+ B& aBuffer) {
+ static_assert(std::is_same_v<typename B::CharType, char16_t>,
+ "Currently only UTF-16 buffers are supported.");
+
+ if (aBuffer.capacity() == 0) {
+ // ucal_getCanonicalTimeZoneID differs from other API calls and fails when
+ // passed a nullptr or 0 length result. Reserve some space initially so
+ // that a real pointer will be used in the API.
+ if (!aBuffer.reserve(TimeZoneIdentifierLength)) {
+ return Err(ICUError::OutOfMemory);
+ }
+ }
+
+ return FillBufferWithICUCall(
+ aBuffer,
+ [&inputTimeZone](UChar* target, int32_t length, UErrorCode* status) {
+ return ucal_getCanonicalTimeZoneID(
+ inputTimeZone.Elements(),
+ static_cast<int32_t>(inputTimeZone.Length()), target, length,
+ /* isSystemID */ nullptr, status);
+ });
+ }
+
+ /**
+ * Return an enumeration over all time zones commonly used in the given
+ * region.
+ */
+ static Result<SpanEnumeration<char>, ICUError> GetAvailableTimeZones(
+ const char* aRegion);
+
+ /**
+ * Return an enumeration over all available time zones.
+ */
+ static Result<SpanEnumeration<char>, ICUError> GetAvailableTimeZones();
+
+ private:
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+ template <typename B>
+ static ICUResult FillBuffer(const icu::UnicodeString& aString, B& aBuffer) {
+ int32_t length = aString.length();
+ if (!aBuffer.reserve(AssertedCast<size_t>(length))) {
+ return Err(ICUError::OutOfMemory);
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ int32_t written = aString.extract(aBuffer.data(), length, status);
+ if (!ICUSuccessForStringSpan(status)) {
+ return Err(ToICUError(status));
+ }
+ MOZ_ASSERT(written == length);
+
+ aBuffer.written(written);
+
+ return Ok{};
+ }
+
+ UniquePtr<icu::TimeZone> mTimeZone = nullptr;
+#else
+ UCalendar* mCalendar = nullptr;
+#endif
+};
+
+} // namespace mozilla::intl
+
+#endif