Tests pass with new DateInfo struct

This commit is contained in:
Tim Chevalier 2024-06-18 11:31:57 -07:00
parent f8c67ece8b
commit c42fe17a3a
5 changed files with 98 additions and 36 deletions

View file

@ -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<GregorianCalendar> 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<TimeZone> 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<DateFormat> 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<GregorianCalendar> 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<GregorianCalendar> 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: {

View file

@ -1067,7 +1067,10 @@ SimpleTimeZone* createTimeZonePart(const UnicodeString& offsetStr, UErrorCode& e
return nullptr;
}
int32_t offset = sign * (static_cast<int32_t>(tzHour) * 60 + static_cast<int32_t>(tzMin)) * 60 * 1000;
LocalPointer<SimpleTimeZone> tz(new SimpleTimeZone(offset, UnicodeString("offset")));
LocalPointer<SimpleTimeZone> 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<GregorianCalendar> 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<GregorianCalendar> copied(new GregorianCalendar(*cal));
if (!copied.isValid()) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
LocalPointer<GregorianCalendar> 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;

View file

@ -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_<false,
int64_t,
icu::UnicodeString,
icu::Formattable,
icu::GregorianCalendar,
icu::message2::DateInfo;
const icu::message2::FormattableObject *,
std::pair<const icu::message2::Formattable *,int32_t>>;
#endif
@ -95,7 +96,7 @@ template class U_I18N_API std::variant<double,
int64_t,
icu::UnicodeString,
icu::Formattable,
icu::GregorianCalendar,
icu::message2::DateInfo,
const icu::message2::FormattableObject*,
P>;
#endif
@ -104,6 +105,19 @@ template class U_I18N_API std::variant<double,
U_NAMESPACE_BEGIN
namespace message2 {
// TODO: doc comments
struct U_I18N_API DateInfo {
// Milliseconds since Unix epoch
UDate date;
// IANA time zone name; "UTC" if UTC; empty string if value is floating
UnicodeString zoneName;
// Empty if not specified; proleptic Gregorian (8601) calendar is default
UnicodeString calendarName;
GregorianCalendar* createGregorianCalendar(UErrorCode&) const;
};
/**
* The `Formattable` class represents a typed value that can be formatted,
* originating either from a message argument or a literal in the code.
@ -232,20 +246,20 @@ namespace message2 {
}
/**
* Gets the calendar representing the date value of this object.
* Gets the struct representing the date value of this object.
* If this object is not of type kDate then the result is
* undefined and the error code is set.
*
* @param status Input/output error code.
* @return A non-owned pointer to a GregorianCalendar object
* @return A non-owned pointer to a DateInfo object
* representing the underlying date of this object.
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
const GregorianCalendar* getDate(UErrorCode& status) const {
const DateInfo* getDate(UErrorCode& status) const {
if (U_SUCCESS(status)) {
if (isDate()) {
return std::get_if<GregorianCalendar>(&contents);
return std::get_if<DateInfo>(&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<const Formattable*, int32_t>> contents;
UnicodeString bogusString; // :((((
@ -435,7 +449,7 @@ namespace message2 {
return std::holds_alternative<icu::Formattable>(contents);
}
UBool isDate() const {
return std::holds_alternative<GregorianCalendar>(contents);
return std::holds_alternative<DateInfo>(contents);
}
}; // class Formattable

View file

@ -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();

View file

@ -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) {