Share DateFormat objects across threads

This commit is contained in:
Tim Chevalier 2024-09-10 19:04:01 -07:00
parent b2ae9f8b5d
commit ea93684e24
3 changed files with 54 additions and 34 deletions

View file

@ -27,6 +27,7 @@
#include "hash.h"
#include "mutex.h"
#include "number_types.h"
#include "ucln_in.h"
#include "uvector.h" // U_ASSERT
// The C99 standard suggested that C++ implementations not define PRId64 etc. constants
@ -1002,34 +1003,64 @@ static DateFormat::EStyle stringToStyle(UnicodeString option, UErrorCode& errorC
Formatter* StandardFunctions::DateTimeFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
NULL_ON_ERROR(errorCode);
Formatter* result = new StandardFunctions::DateTime(locale, *this, type);
Formatter* result = new StandardFunctions::DateTime(locale, type);
if (result == nullptr) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
}
return result;
}
static UMutex gDateTimeFactoryMutex;
// DateFormat parsers that are shared across threads
static DateFormat* dateParser = nullptr;
static DateFormat* dateTimeParser = nullptr;
static DateFormat* dateTimeUTCParser = nullptr;
static DateFormat* dateTimeZoneParser = nullptr;
static icu::UInitOnce gMF2DateParsersInitOnce {};
// Lazily initialize DateFormat objects used for parsing date literals
void StandardFunctions::DateTimeFactory::initDateParsers(UErrorCode& errorCode) {
CHECK_ERROR(errorCode);
Mutex lock(&gDateTimeFactoryMutex);
if (dateParser.isValid()) {
// Already initialized
return;
// Clean up shared DateFormat objects
static UBool mf2_date_parsers_cleanup() {
if (dateParser != nullptr) {
delete dateParser;
dateParser = nullptr;
}
if (dateTimeParser != nullptr) {
delete dateTimeParser;
dateTimeParser = nullptr;
}
if (dateTimeUTCParser != nullptr) {
delete dateTimeUTCParser;
dateTimeUTCParser = nullptr;
}
if (dateTimeZoneParser != nullptr) {
delete dateTimeZoneParser;
dateTimeZoneParser = nullptr;
}
return true;
}
// Initialize DateFormat objects used for parsing date literals
static void initDateParsersOnce(UErrorCode& errorCode) {
U_ASSERT(dateParser == nullptr);
U_ASSERT(dateTimeParser == nullptr);
U_ASSERT(dateTimeUTCParser == nullptr);
U_ASSERT(dateTimeZoneParser == nullptr);
// Handles ISO 8601 date
dateParser.adoptInstead(new SimpleDateFormat(UnicodeString("YYYY-MM-dd"), errorCode));
dateParser = new SimpleDateFormat(UnicodeString("YYYY-MM-dd"), errorCode);
// Handles ISO 8601 datetime without time zone
dateTimeParser.adoptInstead(new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:ss"), errorCode));
dateTimeParser = new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:ss"), errorCode);
// Handles ISO 8601 datetime with 'Z' to denote UTC
dateTimeUTCParser.adoptInstead(new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:ssZ"), errorCode));
dateTimeUTCParser = new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:ssZ"), errorCode);
// Handles ISO 8601 datetime with timezone offset; 'zzzz' denotes timezone offset
dateTimeZoneParser.adoptInstead(new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:sszzzz"), errorCode));
dateTimeZoneParser = new SimpleDateFormat(UnicodeString("YYYY-MM-dd'T'HH:mm:sszzzz"), errorCode);
ucln_i18n_registerCleanup(UCLN_I18N_MF2_DATE_PARSERS, mf2_date_parsers_cleanup);
}
// Lazily initialize DateFormat objects used for parsing date literals
static void initDateParsers(UErrorCode& errorCode) {
CHECK_ERROR(errorCode);
umtx_initOnce(gMF2DateParsersInitOnce, &initDateParsersOnce, errorCode);
}
// From https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md#date-and-time-operands :
@ -1043,10 +1074,10 @@ UDate StandardFunctions::DateTime::tryPatterns(const UnicodeString& sourceStr,
// Handle ISO 8601 datetime (tryTimeZonePatterns() handles the case
// where a timezone offset follows)
if (sourceStr.length() > 10) {
return parent.dateTimeParser->parse(sourceStr, errorCode);
return dateTimeParser->parse(sourceStr, errorCode);
}
// Handle ISO 8601 date
return parent.dateParser->parse(sourceStr, errorCode);
return dateParser->parse(sourceStr, errorCode);
}
// See comment on tryPatterns() for spec reference
@ -1057,9 +1088,9 @@ UDate StandardFunctions::DateTime::tryTimeZonePatterns(const UnicodeString& sour
}
int32_t len = sourceStr.length();
if (len > 0 && sourceStr[len] == 'Z') {
return parent.dateTimeUTCParser->parse(sourceStr, errorCode);
return dateTimeUTCParser->parse(sourceStr, errorCode);
}
return parent.dateTimeZoneParser->parse(sourceStr, errorCode);
return dateTimeZoneParser->parse(sourceStr, errorCode);
}
static TimeZone* createTimeZone(const DateInfo& dateInfo, UErrorCode& errorCode) {
@ -1355,7 +1386,7 @@ FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&&
switch (source.getType()) {
case UFMT_STRING: {
// Lazily initialize date parsers used for parsing date literals
parent.initDateParsers(errorCode);
initDateParsers(errorCode);
if (U_FAILURE(errorCode)) {
return {};
}

View file

@ -64,15 +64,6 @@ namespace message2 {
DateTimeType type;
DateTimeFactory(DateTimeType t) : type(t) {}
// Lazily initialize cached date parsers
void initDateParsers(UErrorCode&);
// Cached parsers; lazily initialized
LocalPointer<DateFormat> dateParser;
LocalPointer<DateFormat> dateTimeParser;
LocalPointer<DateFormat> dateTimeUTCParser;
LocalPointer<DateFormat> dateTimeZoneParser;
};
class DateTime : public Formatter {
@ -84,13 +75,10 @@ namespace message2 {
const Locale& locale;
const DateTimeFactory::DateTimeType type;
friend class DateTimeFactory;
DateTime(const Locale& l, DateTimeFactory& p, DateTimeFactory::DateTimeType t)
: locale(l), type(t), parent(p) {}
DateTime(const Locale& l, DateTimeFactory::DateTimeType t)
: locale(l), type(t) {}
const LocalPointer<icu::DateFormat> icuFormatter;
// Stores the cached DateFormat objects for parsing date literals
DateTimeFactory& parent;
// Methods for parsing date literals
UDate tryPatterns(const UnicodeString&, UErrorCode&) const;
UDate tryTimeZonePatterns(const UnicodeString&, UErrorCode&) const;

View file

@ -64,6 +64,7 @@ typedef enum ECleanupI18NType {
UCLN_I18N_LIST_FORMATTER,
UCLN_I18N_NUMSYS,
UCLN_I18N_MF2_UNISETS,
UCLN_I18N_MF2_DATE_PARSERS,
UCLN_I18N_COUNT /* This must be last */
} ECleanupI18NType;