diff --git a/icu4c/source/i18n/messageformat2_formattable.cpp b/icu4c/source/i18n/messageformat2_formattable.cpp index c26038bd819..41a5b6d88e2 100644 --- a/icu4c/source/i18n/messageformat2_formattable.cpp +++ b/icu4c/source/i18n/messageformat2_formattable.cpp @@ -11,6 +11,7 @@ #include "unicode/messageformat2_formattable.h" #include "unicode/smpdtfmt.h" +#include "messageformat2_allocation.h" #include "messageformat2_macros.h" #include "limits.h" @@ -227,19 +228,50 @@ namespace message2 { return df.orphan(); } - void formatDateWithDefaults(const Locale& locale, const GregorianCalendar& cal, UnicodeString& result, UErrorCode& errorCode) { + GregorianCalendar* DateInfo::createGregorianCalendar(UErrorCode& errorCode) const { + NULL_ON_ERROR(errorCode); + + LocalPointer cal(new GregorianCalendar(errorCode)); + if (!cal.isValid()) { + errorCode = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + // Copy info from this + // Non-Gregorian calendars not implemented yet + U_ASSERT(calendarName.length() == 0); + cal->setTime(date, errorCode); + // If time zone is present... + if (zoneName.length() > 0) { + if (zoneName == UnicodeString("UTC")) { + cal->setTimeZone(*TimeZone::getGMT()); + } else { + LocalPointer tz(TimeZone::createTimeZone(zoneName)); + if (!tz.isValid()) { + errorCode = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + cal->setTimeZone(*tz); + } + } + return cal.orphan(); + } + + void formatDateWithDefaults(const Locale& locale, + const DateInfo& dateInfo, + UnicodeString& result, + UErrorCode& errorCode) { CHECK_ERROR(errorCode); LocalPointer df(defaultDateTimeInstance(locale, errorCode)); CHECK_ERROR(errorCode); // We have to copy the `const` reference that was passed in, // because DateFormat::format() takes a non-const reference. - LocalPointer copied(new GregorianCalendar(cal)); - if (!copied.isValid()) { - errorCode = U_MEMORY_ALLOCATION_ERROR; - return; - } - df->format(*copied, result, nullptr, errorCode); + + // Non-Gregorian calendars not supported yet + U_ASSERT(dateInfo.calendarName.length() == 0); + LocalPointer cal(dateInfo.createGregorianCalendar(errorCode)); + CHECK_ERROR(errorCode); + df->format(*cal, result, nullptr, errorCode); } // Called when output is required and the contents are an unevaluated `Formattable`; @@ -270,9 +302,9 @@ namespace message2 { switch (type) { case UFMT_DATE: { UnicodeString result; - const GregorianCalendar* cal = toFormat.getDate(status); + const DateInfo* dateInfo = toFormat.getDate(status); U_ASSERT(U_SUCCESS(status)); - formatDateWithDefaults(locale, *cal, result, status); + formatDateWithDefaults(locale, *dateInfo, result, status); return FormattedPlaceholder(input, FormattedValue(std::move(result))); } case UFMT_DOUBLE: { diff --git a/icu4c/source/i18n/messageformat2_function_registry.cpp b/icu4c/source/i18n/messageformat2_function_registry.cpp index 8e786cdd21d..d01f3511a37 100644 --- a/icu4c/source/i18n/messageformat2_function_registry.cpp +++ b/icu4c/source/i18n/messageformat2_function_registry.cpp @@ -1067,7 +1067,10 @@ SimpleTimeZone* createTimeZonePart(const UnicodeString& offsetStr, UErrorCode& e return nullptr; } int32_t offset = sign * (static_cast(tzHour) * 60 + static_cast(tzMin)) * 60 * 1000; - LocalPointer tz(new SimpleTimeZone(offset, UnicodeString("offset"))); + LocalPointer tz(new SimpleTimeZone(offset, UnicodeString("GMT") + offsetStr)); +/* +Use tz.getIanaID() to get the time zone name +*/ if (!tz.isValid()) { errorCode = U_MEMORY_ALLOCATION_ERROR; } @@ -1084,6 +1087,8 @@ static bool hasTzOffset(const UnicodeString& sourceStr) { && sourceStr[len - 3] == COLON); } +// Note: `calendar` option to :datetime not implemented yet; +// Gregorian calendar is assumed static GregorianCalendar* createCalendarFromDateString(const UnicodeString& sourceStr, UErrorCode& errorCode) { NULL_ON_ERROR(errorCode); @@ -1327,7 +1332,6 @@ FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&& U_ASSERT(U_SUCCESS(errorCode)); LocalPointer cal(createCalendarFromDateString(sourceStr, errorCode)); - if (U_FAILURE(errorCode)) { errorCode = U_MF_OPERAND_MISMATCH_ERROR; return {}; @@ -1337,20 +1341,32 @@ FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&& // in the returned FormattedPlaceholder; this is necessary // so the date can be re-formatted df->format(*cal, result, 0, errorCode); - toFormat = FormattedPlaceholder(message2::Formattable(std::move(*cal)), + + // Construct DateInfo from Date + UDate absoluteDate = cal->getTime(errorCode); + const TimeZone& tz = cal->getTimeZone(); + UnicodeString timeZoneId; + tz.getID(timeZoneId); + UnicodeString timeZoneName; + // This doesn't work for offsets -- TODO + // tz.getIanaID(timeZoneId, timeZoneName, errorCode); + // Empty string for Gregorian calendar (default); + // `:datetime` `calendar` option not implemented yet, + // so other calendars aren't implemented + DateInfo dateInfo = {absoluteDate, timeZoneId, {}}; // Should be timeZoneName, but getIanaID() doesn't work for strings like GMT+03:30 + toFormat = FormattedPlaceholder(message2::Formattable(std::move(dateInfo)), toFormat.getFallback()); break; } case UFMT_DATE: { - const GregorianCalendar* cal = source.getDate(errorCode); + const DateInfo* dateInfo = source.getDate(errorCode); U_ASSERT(U_SUCCESS(errorCode)); - U_ASSERT(cal != nullptr); - LocalPointer copied(new GregorianCalendar(*cal)); - if (!copied.isValid()) { - errorCode = U_MEMORY_ALLOCATION_ERROR; + + LocalPointer cal(dateInfo->createGregorianCalendar(errorCode)); + if (U_FAILURE(errorCode)) { return {}; } - df->format(*copied, result, 0, errorCode); + df->format(*cal, result, 0, errorCode); if (U_FAILURE(errorCode)) { if (errorCode == U_ILLEGAL_ARGUMENT_ERROR) { errorCode = U_MF_OPERAND_MISMATCH_ERROR; diff --git a/icu4c/source/i18n/unicode/messageformat2_formattable.h b/icu4c/source/i18n/unicode/messageformat2_formattable.h index 6e89f0165dd..7d9461534da 100644 --- a/icu4c/source/i18n/unicode/messageformat2_formattable.h +++ b/icu4c/source/i18n/unicode/messageformat2_formattable.h @@ -68,6 +68,7 @@ namespace message2 { virtual ~FormattableObject(); }; // class FormattableObject + struct DateInfo; class Formattable; } // namespace message2 @@ -86,7 +87,7 @@ template class U_I18N_API std::_Variant_storage_>; #endif @@ -95,7 +96,7 @@ template class U_I18N_API std::variant; #endif @@ -104,6 +105,19 @@ template class U_I18N_API std::variant(&contents); + return std::get_if(&contents); } status = U_ILLEGAL_ARGUMENT_ERROR; } @@ -360,13 +374,13 @@ namespace message2 { /** * Date constructor. * - * @param c A GregorianCalendar value representing a date, + * @param d A DateInfo struct representing a date, * to wrap as a Formattable. * Passed by move * @internal ICU 75 technology preview * @deprecated This API is for technology preview only. */ - Formattable(GregorianCalendar&& c) : contents(std::move(c)) {} + Formattable(DateInfo&& d) : contents(std::move(d)) {} /** * Creates a Formattable object of an appropriate numeric type from a * a decimal number in string form. The Formattable will retain the @@ -426,7 +440,7 @@ namespace message2 { int64_t, UnicodeString, icu::Formattable, // represents a Decimal - GregorianCalendar, + DateInfo, const FormattableObject*, std::pair> contents; UnicodeString bogusString; // :(((( @@ -435,7 +449,7 @@ namespace message2 { return std::holds_alternative(contents); } UBool isDate() const { - return std::holds_alternative(contents); + return std::holds_alternative(contents); } }; // class Formattable diff --git a/icu4c/source/test/intltest/messageformat2test.cpp b/icu4c/source/test/intltest/messageformat2test.cpp index b251c9d79db..eb18f1190eb 100644 --- a/icu4c/source/test/intltest/messageformat2test.cpp +++ b/icu4c/source/test/intltest/messageformat2test.cpp @@ -163,7 +163,10 @@ void TestMessageFormat2::testAPISimple() { cal.set(2136, Calendar::OCTOBER, 28, 8, 39, 12); argsBuilder.clear(); - argsBuilder["today"] = message2::Formattable(std::move(cal)); + DateInfo dateInfo = { cal.getTime(errorCode), + "Pacific Standard Time", + "" }; + argsBuilder["today"] = message2::Formattable(std::move(dateInfo)); args = MessageArguments(argsBuilder, errorCode); result = mf.formatToString(args, errorCode); assertEquals("testAPI", "Today is Sunday, October 28, 2136.", result); @@ -210,7 +213,7 @@ void TestMessageFormat2::testAPI() { test = testBuilder.setName("testAPI") .setPattern("Today is {$today :date style=full}.") - .setDateArgument("today", date, errorCode) + .setDateArgument("today", date) .setExpected("Today is Sunday, October 28, 2136.") .setLocale("en_US") .build(); diff --git a/icu4c/source/test/intltest/messageformat2test_utils.h b/icu4c/source/test/intltest/messageformat2test_utils.h index 489f12b042e..65675b72882 100644 --- a/icu4c/source/test/intltest/messageformat2test_utils.h +++ b/icu4c/source/test/intltest/messageformat2test_utils.h @@ -112,16 +112,13 @@ class TestCase : public UMemory { arguments[k] = Formattable(val); return *this; } - Builder& setDateArgument(const UnicodeString& k, UDate date, UErrorCode& errorCode) { + Builder& setDateArgument(const UnicodeString& k, UDate date) { // This ignores time zones; the data-driven tests represent date/time values // as a datestamp, so this code suffices to handle those. // Date/time literal strings would be handled using `setArgument()` with a string // argument. - GregorianCalendar cal(errorCode); - THIS_ON_ERROR(errorCode); - cal.setTime(date, errorCode); - THIS_ON_ERROR(errorCode); - arguments[k] = Formattable(std::move(cal)); + DateInfo dateInfo = { date, {}, {} }; // No time zone or calendar name + arguments[k] = Formattable(std::move(dateInfo)); return *this; } Builder& setDecimalArgument(const UnicodeString& k, std::string_view decimal, UErrorCode& errorCode) {