summaryrefslogtreecommitdiffstats
path: root/intl/components/src/DateIntervalFormat.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/src/DateIntervalFormat.cpp')
-rw-r--r--intl/components/src/DateIntervalFormat.cpp266
1 files changed, 266 insertions, 0 deletions
diff --git a/intl/components/src/DateIntervalFormat.cpp b/intl/components/src/DateIntervalFormat.cpp
new file mode 100644
index 0000000000..0097668f8b
--- /dev/null
+++ b/intl/components/src/DateIntervalFormat.cpp
@@ -0,0 +1,266 @@
+/* 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/. */
+
+#include "DateTimeFormat.h" // for DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
+#include "DateTimeFormatUtils.h"
+#include "ScopedICUObject.h"
+
+#include "mozilla/intl/Calendar.h"
+#include "mozilla/intl/DateIntervalFormat.h"
+
+namespace mozilla::intl {
+
+/**
+ * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11.
+ *
+ * Examine the formatted value to see if any interval span field is present.
+ *
+ * https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
+ */
+static ICUResult DateFieldsPracticallyEqual(
+ const UFormattedValue* aFormattedValue, bool* aEqual) {
+ if (!aFormattedValue) {
+ return Err(ICUError::InternalError);
+ }
+
+ MOZ_ASSERT(aEqual);
+ *aEqual = false;
+ UErrorCode status = U_ZERO_ERROR;
+ UConstrainedFieldPosition* fpos = ucfpos_open(&status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+ ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
+
+ // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields.
+ ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ bool hasSpan = ufmtval_nextPosition(aFormattedValue, fpos, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ // When no date interval span field was found, both dates are "practically
+ // equal" per PartitionDateTimeRangePattern.
+ *aEqual = !hasSpan;
+ return Ok();
+}
+
+/* static */
+Result<UniquePtr<DateIntervalFormat>, ICUError> DateIntervalFormat::TryCreate(
+ Span<const char> aLocale, Span<const char16_t> aSkeleton,
+ Span<const char16_t> aTimeZone) {
+ UErrorCode status = U_ZERO_ERROR;
+ UDateIntervalFormat* dif =
+ udtitvfmt_open(IcuLocale(aLocale), aSkeleton.data(),
+ AssertedCast<int32_t>(aSkeleton.size()), aTimeZone.data(),
+ AssertedCast<int32_t>(aTimeZone.size()), &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ return UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif));
+}
+
+DateIntervalFormat::~DateIntervalFormat() {
+ MOZ_ASSERT(mDateIntervalFormat);
+ udtitvfmt_close(mDateIntervalFormat.GetMut());
+}
+
+#if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
+// We reach inside the UFormattedValue and modify its internal string. (It's
+// crucial that this is just an in-place replacement that doesn't alter any
+// field positions, etc., )
+static void ReplaceSpecialSpaces(const UFormattedValue* aValue) {
+ UErrorCode status = U_ZERO_ERROR;
+ int32_t len;
+ const UChar* str = ufmtval_getString(aValue, &len, &status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ for (const auto& c : Span(str, len)) {
+ if (IsSpecialSpace(c)) {
+ const_cast<UChar&>(c) = ' ';
+ }
+ }
+}
+#endif
+
+ICUResult DateIntervalFormat::TryFormatCalendar(
+ const Calendar& aStart, const Calendar& aEnd,
+ AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const {
+ MOZ_ASSERT(aFormatted.IsValid());
+
+ UErrorCode status = U_ZERO_ERROR;
+ udtitvfmt_formatCalendarToResult(mDateIntervalFormat.GetConst(),
+ aStart.GetUCalendar(), aEnd.GetUCalendar(),
+ aFormatted.GetFormatted(), &status);
+
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+#if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
+ ReplaceSpecialSpaces(aFormatted.Value());
+#endif
+
+ MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
+ return Ok();
+}
+
+ICUResult DateIntervalFormat::TryFormatDateTime(
+ double aStart, double aEnd, AutoFormattedDateInterval& aFormatted,
+ bool* aPracticallyEqual) const {
+ MOZ_ASSERT(aFormatted.IsValid());
+
+ UErrorCode status = U_ZERO_ERROR;
+ udtitvfmt_formatToResult(mDateIntervalFormat.GetConst(), aStart, aEnd,
+ aFormatted.GetFormatted(), &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+#if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
+ ReplaceSpecialSpaces(aFormatted.Value());
+#endif
+
+ MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
+ return Ok();
+}
+
+ICUResult DateIntervalFormat::TryFormattedToParts(
+ const AutoFormattedDateInterval& aFormatted,
+ DateTimePartVector& aParts) const {
+ MOZ_ASSERT(aFormatted.IsValid());
+ const UFormattedValue* value = aFormatted.Value();
+ if (!value) {
+ return Err(ICUError::InternalError);
+ }
+
+ size_t lastEndIndex = 0;
+ auto AppendPart = [&](DateTimePartType type, size_t endIndex,
+ DateTimePartSource source) {
+ if (!aParts.emplaceBack(type, endIndex, source)) {
+ return false;
+ }
+
+ lastEndIndex = endIndex;
+ return true;
+ };
+
+ UErrorCode status = U_ZERO_ERROR;
+ UConstrainedFieldPosition* fpos = ucfpos_open(&status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+ ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
+
+ size_t categoryEndIndex = 0;
+ DateTimePartSource source = DateTimePartSource::Shared;
+
+ while (true) {
+ bool hasMore = ufmtval_nextPosition(value, fpos, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+ if (!hasMore) {
+ break;
+ }
+
+ int32_t category = ucfpos_getCategory(fpos, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ int32_t field = ucfpos_getField(fpos, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ int32_t beginIndexInt, endIndexInt;
+ ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
+ if (U_FAILURE(status)) {
+ return Err(ToICUError(status));
+ }
+
+ MOZ_ASSERT(beginIndexInt <= endIndexInt,
+ "field iterator returning invalid range");
+
+ size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
+ size_t endIndex = AssertedCast<size_t>(endIndexInt);
+
+ // Indices are guaranteed to be returned in order (from left to right).
+ MOZ_ASSERT(lastEndIndex <= beginIndex,
+ "field iteration didn't return fields in order start to "
+ "finish as expected");
+
+ if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
+ // Append any remaining literal parts before changing the source kind.
+ if (lastEndIndex < beginIndex) {
+ if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
+ return Err(ICUError::InternalError);
+ }
+ }
+
+ // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only
+ // two allowed values (0 or 1), indicating the begin of the start- resp.
+ // end-date.
+ MOZ_ASSERT(field == 0 || field == 1,
+ "span category has unexpected value");
+
+ source = field == 0 ? DateTimePartSource::StartRange
+ : DateTimePartSource::EndRange;
+ categoryEndIndex = endIndex;
+ continue;
+ }
+
+ // Ignore categories other than UFIELD_CATEGORY_DATE.
+ if (category != UFIELD_CATEGORY_DATE) {
+ continue;
+ }
+
+ DateTimePartType type =
+ ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(field));
+ if (lastEndIndex < beginIndex) {
+ if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
+ return Err(ICUError::InternalError);
+ }
+ }
+
+ if (!AppendPart(type, endIndex, source)) {
+ return Err(ICUError::InternalError);
+ }
+
+ if (endIndex == categoryEndIndex) {
+ // Append any remaining literal parts before changing the source kind.
+ if (lastEndIndex < endIndex) {
+ if (!AppendPart(DateTimePartType::Literal, endIndex, source)) {
+ return Err(ICUError::InternalError);
+ }
+ }
+
+ source = DateTimePartSource::Shared;
+ }
+ }
+
+ // Append any final literal.
+ auto spanResult = aFormatted.ToSpan();
+ if (spanResult.isErr()) {
+ return spanResult.propagateErr();
+ }
+ size_t formattedSize = spanResult.unwrap().size();
+ if (lastEndIndex < formattedSize) {
+ if (!AppendPart(DateTimePartType::Literal, formattedSize, source)) {
+ return Err(ICUError::InternalError);
+ }
+ }
+
+ return Ok();
+}
+
+} // namespace mozilla::intl