diff options
Diffstat (limited to 'intl/components/src/TimeZone.h')
-rw-r--r-- | intl/components/src/TimeZone.h | 292 |
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 |