From 4a3a4742241f0843867e76cbc49694b9e4146b2f Mon Sep 17 00:00:00 2001 From: Adrien Destugues Date: Tue, 7 Oct 2014 12:46:51 +0200 Subject: [PATCH] Reimplement BDateTimeFormat using ICU support * Avoid hardcoding the format to "date, time" * Allows using DateTimePatternGenerator to create custom formats from a set of fields. --- headers/os/locale/DateFormat.h | 12 +-- headers/os/locale/DateTimeFormat.h | 31 +++++-- headers/os/locale/FormattingConventions.h | 13 +++ headers/os/locale/TimeFormat.h | 2 +- src/kits/locale/DateTimeFormat.cpp | 104 ++++++++++++---------- src/kits/locale/FormattingConventions.cpp | 77 +++++++++++++--- src/tests/kits/locale/DateFormatTest.cpp | 25 ++++-- 7 files changed, 177 insertions(+), 87 deletions(-) diff --git a/headers/os/locale/DateFormat.h b/headers/os/locale/DateFormat.h index e788551484..b05227cf1c 100644 --- a/headers/os/locale/DateFormat.h +++ b/headers/os/locale/DateFormat.h @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -22,17 +23,6 @@ class BString; class BTimeZone; -enum BDateElement { - B_DATE_ELEMENT_INVALID = B_BAD_DATA, - B_DATE_ELEMENT_YEAR = 0, - B_DATE_ELEMENT_MONTH, - B_DATE_ELEMENT_DAY, - B_DATE_ELEMENT_AM_PM, - B_DATE_ELEMENT_HOUR, - B_DATE_ELEMENT_MINUTE, - B_DATE_ELEMENT_SECOND -}; - enum BWeekday { B_WEEKDAY_MONDAY = 1, B_WEEKDAY_TUESDAY, diff --git a/headers/os/locale/DateTimeFormat.h b/headers/os/locale/DateTimeFormat.h index 7906d7ec07..1b13d676c4 100644 --- a/headers/os/locale/DateTimeFormat.h +++ b/headers/os/locale/DateTimeFormat.h @@ -6,21 +6,42 @@ #define _B_DATE_TIME_FORMAT_H_ -#include #include #include -#include class BString; +class BTimeZone; + + +enum BDateElement { + B_DATE_ELEMENT_INVALID = 0, + B_DATE_ELEMENT_YEAR = 1 << 0, + B_DATE_ELEMENT_MONTH = 1 << 1, + B_DATE_ELEMENT_WEEKDAY = 1 << 2, + B_DATE_ELEMENT_DAY = 1 << 3, + B_DATE_ELEMENT_AM_PM = 1 << 4, + B_DATE_ELEMENT_HOUR = 1 << 5, + B_DATE_ELEMENT_MINUTE = 1 << 6, + B_DATE_ELEMENT_SECOND = 1 << 7, + B_DATE_ELEMENT_TIMEZONE = 1 << 8 +}; + class BDateTimeFormat : public BFormat { public: - BDateTimeFormat(); + BDateTimeFormat( + const BLanguage* const language = NULL, + const BFormattingConventions* const format + = NULL); BDateTimeFormat(const BDateTimeFormat &other); virtual ~BDateTimeFormat(); + void SetDateTimeFormat(BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, + int32 elements); + // formatting ssize_t Format(char* target, const size_t maxSize, @@ -33,9 +54,7 @@ public: const BTimeZone* timeZone = NULL) const; private: - icu::DateFormat* _CreateDateFormatter( - const BString& format) const; - icu::DateFormat* _CreateTimeFormatter( + icu::DateFormat* _CreateDateTimeFormatter( const BString& format) const; }; diff --git a/headers/os/locale/FormattingConventions.h b/headers/os/locale/FormattingConventions.h index 63ab21cbc7..b526ef3906 100644 --- a/headers/os/locale/FormattingConventions.h +++ b/headers/os/locale/FormattingConventions.h @@ -83,6 +83,9 @@ public: BString& outFormat) const; status_t GetTimeFormat(BTimeFormatStyle style, BString& outFormat) const; + status_t GetDateTimeFormat(BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, + BString& outFormat) const; status_t GetNumericFormat(BString& outFormat) const; status_t GetMonetaryFormat(BString& outFormat) const; @@ -90,6 +93,10 @@ public: const BString& format); void SetExplicitTimeFormat(BTimeFormatStyle style, const BString& format); + void SetExplicitDateTimeFormat( + BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, + const BString& format); void SetExplicitNumericFormat(const BString& format); void SetExplicitMonetaryFormat( const BString& format); @@ -112,12 +119,18 @@ private: mutable BString fCachedDateFormats[B_DATE_FORMAT_STYLE_COUNT]; mutable BString fCachedTimeFormats[B_TIME_FORMAT_STYLE_COUNT]; + mutable BString fCachedDateTimeFormats + [B_DATE_FORMAT_STYLE_COUNT] + [B_TIME_FORMAT_STYLE_COUNT]; mutable BString fCachedNumericFormat; mutable BString fCachedMonetaryFormat; mutable int8 fCachedUse24HourClock; BString fExplicitDateFormats[B_DATE_FORMAT_STYLE_COUNT]; BString fExplicitTimeFormats[B_TIME_FORMAT_STYLE_COUNT]; + BString fExplicitDateTimeFormats + [B_DATE_FORMAT_STYLE_COUNT] + [B_TIME_FORMAT_STYLE_COUNT]; BString fExplicitNumericFormat; BString fExplicitMonetaryFormat; int8 fExplicitUse24HourClock; diff --git a/headers/os/locale/TimeFormat.h b/headers/os/locale/TimeFormat.h index df6bb8ff59..563a1da7fb 100644 --- a/headers/os/locale/TimeFormat.h +++ b/headers/os/locale/TimeFormat.h @@ -9,7 +9,7 @@ class BString; -class BTimeZone; + class BTimeFormat : public BFormat { public: diff --git a/src/kits/locale/DateTimeFormat.cpp b/src/kits/locale/DateTimeFormat.cpp index 419ef1b51c..f21ec3615a 100644 --- a/src/kits/locale/DateTimeFormat.cpp +++ b/src/kits/locale/DateTimeFormat.cpp @@ -17,12 +17,18 @@ #include #include +#include #include -BDateTimeFormat::BDateTimeFormat() - : BFormat() +BDateTimeFormat::BDateTimeFormat(const BLanguage* const language, + const BFormattingConventions* const conventions) { + if (conventions != NULL) + fConventions = *conventions; + + if (language != NULL) + fLanguage = *language; } @@ -37,6 +43,45 @@ BDateTimeFormat::~BDateTimeFormat() } +void +BDateTimeFormat::SetDateTimeFormat(BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, int32 elements) { + UErrorCode error = U_ZERO_ERROR; + DateTimePatternGenerator* generator + = DateTimePatternGenerator::createInstance(error); + + BString skeleton; + if (elements & B_DATE_ELEMENT_YEAR) + skeleton << "yyyy"; + if (elements & B_DATE_ELEMENT_MONTH) + skeleton << "MM"; + if (elements & B_DATE_ELEMENT_WEEKDAY) + skeleton << "eee"; + if (elements & B_DATE_ELEMENT_DAY) + skeleton << "dd"; + if (elements & B_DATE_ELEMENT_AM_PM) + skeleton << "a"; + if (elements & B_DATE_ELEMENT_HOUR) + skeleton << "jj"; + if (elements & B_DATE_ELEMENT_MINUTE) + skeleton << "mm"; + if (elements & B_DATE_ELEMENT_SECOND) + skeleton << "ss"; + if (elements & B_DATE_ELEMENT_TIMEZONE) + skeleton << "V"; + + UnicodeString pattern = generator->getBestPattern( + UnicodeString::fromUTF8(skeleton.String()), error); + + BString buffer; + BStringByteSink stringConverter(&buffer); + pattern.toUTF8(stringConverter); + fConventions.SetExplicitDateTimeFormat(dateStyle, timeStyle, buffer); + + delete generator; +} + + // #pragma mark - Formatting @@ -49,23 +94,14 @@ BDateTimeFormat::Format(char* target, size_t maxSize, time_t time, return B_ERROR; BString format; - fConventions.GetDateFormat(dateStyle, format); - ObjectDeleter dateFormatter(_CreateDateFormatter(format)); + fConventions.GetDateTimeFormat(dateStyle, timeStyle, format); + ObjectDeleter dateFormatter(_CreateDateTimeFormatter(format)); if (dateFormatter.Get() == NULL) return B_NO_MEMORY; - fConventions.GetTimeFormat(timeStyle, format); - ObjectDeleter timeFormatter(_CreateTimeFormatter(format)); - if (timeFormatter.Get() == NULL) - return B_NO_MEMORY; - UnicodeString icuString; dateFormatter->format((UDate)time * 1000, icuString); - icuString.append(UnicodeString::fromUTF8(", ")); - - timeFormatter->format((UDate)time * 1000, icuString); - CheckedArrayByteSink stringConverter(target, maxSize); icuString.toUTF8(stringConverter); @@ -86,28 +122,21 @@ BDateTimeFormat::Format(BString& target, const time_t time, return B_ERROR; BString format; - fConventions.GetDateFormat(dateStyle, format); - ObjectDeleter dateFormatter(_CreateDateFormatter(format)); + fConventions.GetDateTimeFormat(dateStyle, timeStyle, format); + ObjectDeleter dateFormatter(_CreateDateTimeFormatter(format)); if (dateFormatter.Get() == NULL) return B_NO_MEMORY; - fConventions.GetTimeFormat(timeStyle, format); - ObjectDeleter timeFormatter(_CreateTimeFormatter(format)); - if (timeFormatter.Get() == NULL) - return B_NO_MEMORY; - if (timeZone != NULL) { ObjectDeleter icuTimeZone( TimeZone::createTimeZone(timeZone->ID().String())); if (icuTimeZone.Get() == NULL) return B_NO_MEMORY; - timeFormatter->setTimeZone(*icuTimeZone.Get()); + dateFormatter->setTimeZone(*icuTimeZone.Get()); } UnicodeString icuString; dateFormatter->format((UDate)time * 1000, icuString); - icuString.append(UnicodeString::fromUTF8(", ")); - timeFormatter->format((UDate)time * 1000, icuString); target.Truncate(0); BStringByteSink stringConverter(&target); @@ -118,15 +147,15 @@ BDateTimeFormat::Format(BString& target, const time_t time, DateFormat* -BDateTimeFormat::_CreateDateFormatter(const BString& format) const +BDateTimeFormat::_CreateDateTimeFormatter(const BString& format) const { Locale* icuLocale = fConventions.UseStringsFromPreferredLanguage() ? BLanguage::Private(&fLanguage).ICULocale() : BFormattingConventions::Private(&fConventions).ICULocale(); - icu::DateFormat* dateFormatter - = icu::DateFormat::createDateInstance(DateFormat::kShort, *icuLocale); + icu::DateFormat* dateFormatter = icu::DateFormat::createDateTimeInstance( + DateFormat::kDefault, DateFormat::kDefault, *icuLocale); if (dateFormatter == NULL) return NULL; @@ -138,26 +167,3 @@ BDateTimeFormat::_CreateDateFormatter(const BString& format) const return dateFormatter; } - - -DateFormat* -BDateTimeFormat::_CreateTimeFormatter(const BString& format) const -{ - Locale* icuLocale - = fConventions.UseStringsFromPreferredLanguage() - ? BLanguage::Private(&fLanguage).ICULocale() - : BFormattingConventions::Private(&fConventions).ICULocale(); - - icu::DateFormat* timeFormatter - = icu::DateFormat::createTimeInstance(DateFormat::kShort, *icuLocale); - if (timeFormatter == NULL) - return NULL; - - SimpleDateFormat* timeFormatterImpl - = static_cast(timeFormatter); - - UnicodeString pattern(format.String()); - timeFormatterImpl->applyPattern(pattern); - - return timeFormatter; -} diff --git a/src/kits/locale/FormattingConventions.cpp b/src/kits/locale/FormattingConventions.cpp index 67c0dadeee..75353001cb 100644 --- a/src/kits/locale/FormattingConventions.cpp +++ b/src/kits/locale/FormattingConventions.cpp @@ -205,14 +205,19 @@ BFormattingConventions::BFormattingConventions( fUseStringsFromPreferredLanguage(other.fUseStringsFromPreferredLanguage), fICULocale(new icu::Locale(*other.fICULocale)) { - for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) + for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { fCachedDateFormats[s] = other.fCachedDateFormats[s]; - for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) - fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; - for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; - for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) + + for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) { + fCachedDateTimeFormats[s][t] = other.fCachedDateFormats[s][t]; + fExplicitDateFormats[s][t] = other.fExplicitDateFormats[s][t]; + } + } + for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { + fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; + } } @@ -259,18 +264,23 @@ BFormattingConventions::operator=(const BFormattingConventions& other) if (this == &other) return *this; - for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) + for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { fCachedDateFormats[s] = other.fCachedDateFormats[s]; - for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) + fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; + for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) { + fCachedDateTimeFormats[s][t] = other.fCachedDateTimeFormats[s][t]; + fExplicitDateTimeFormats[s][t] + = other.fExplicitDateTimeFormats[s][t]; + } + } + for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; + fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; + } fCachedNumericFormat = other.fCachedNumericFormat; fCachedMonetaryFormat = other.fCachedMonetaryFormat; fCachedUse24HourClock = other.fCachedUse24HourClock; - for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) - fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; - for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) - fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; fExplicitNumericFormat = other.fExplicitNumericFormat; fExplicitMonetaryFormat = other.fExplicitMonetaryFormat; fExplicitUse24HourClock = other.fExplicitUse24HourClock; @@ -489,6 +499,43 @@ BFormattingConventions::GetTimeFormat(BTimeFormatStyle style, } +status_t +BFormattingConventions::GetDateTimeFormat(BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, BString& outFormat) const +{ + if (dateStyle < 0 || dateStyle >= B_DATE_FORMAT_STYLE_COUNT) + return B_BAD_VALUE; + + if (timeStyle < 0 || timeStyle >= B_TIME_FORMAT_STYLE_COUNT) + return B_BAD_VALUE; + + outFormat = fExplicitDateTimeFormats[dateStyle][timeStyle].Length() + ? fExplicitDateTimeFormats[dateStyle][timeStyle] + : fCachedDateTimeFormats[dateStyle][timeStyle]; + + if (outFormat.Length() > 0) + return B_OK; + + ObjectDeleter dateFormatter( + DateFormat::createDateTimeInstance((DateFormat::EStyle)dateStyle, + (DateFormat::EStyle)timeStyle, *fICULocale)); + if (dateFormatter.Get() == NULL) + return B_NO_MEMORY; + + SimpleDateFormat* dateFormatterImpl + = static_cast(dateFormatter.Get()); + + UnicodeString icuString; + dateFormatterImpl->toPattern(icuString); + BStringByteSink stringConverter(&outFormat); + icuString.toUTF8(stringConverter); + + fCachedDateTimeFormats[dateStyle][timeStyle] = outFormat; + + return B_OK; +} + + status_t BFormattingConventions::GetNumericFormat(BString& outFormat) const { @@ -521,6 +568,14 @@ BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style, } +void +BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle, + BTimeFormatStyle timeStyle, const BString& format) +{ + fExplicitDateTimeFormats[dateStyle][timeStyle] = format; +} + + void BFormattingConventions::SetExplicitNumericFormat(const BString& format) { diff --git a/src/tests/kits/locale/DateFormatTest.cpp b/src/tests/kits/locale/DateFormatTest.cpp index 2575eafb60..d7e00afd33 100644 --- a/src/tests/kits/locale/DateFormatTest.cpp +++ b/src/tests/kits/locale/DateFormatTest.cpp @@ -29,22 +29,23 @@ void DateFormatTest::TestFormat() { struct Value { - char* language; - char* convention; + const char* language; + const char* convention; time_t time; - char* shortDate; - char* longDate; - char* shortTime; - char* longTime; + const char* shortDate; + const char* longDate; + const char* shortTime; + const char* longTime; + const char* shortDateTime; }; static const Value values[] = { {"en", "en_US", 12345, "1/1/70", "January 1, 1970", - "4:25 AM", "4:25:45 AM"}, + "4:25 AM", "4:25:45 AM", "1/1/70, 4:25 AM"}, {"fr", "fr_FR", 12345, "01/01/1970", "1 janvier 1970", - "04:25", "04:25:45"}, + "04:25", "04:25:45", "01/01/1970 04:25"}, {"fr", "fr_FR", 12345678, "23/05/1970", "23 mai 1970", - "22:21", "22:21:18"}, + "22:21", "22:21:18", "23/05/1970 22:21"}, {NULL} }; @@ -58,6 +59,7 @@ DateFormatTest::TestFormat() BFormattingConventions formatting(values[i].convention); BDateFormat dateFormat(&language, &formatting); BTimeFormat timeFormat(&language, &formatting); + BDateTimeFormat dateTimeFormat(&language, &formatting); result = dateFormat.Format(output, values[i].time, B_SHORT_DATE_FORMAT); CPPUNIT_ASSERT_EQUAL(B_OK, result); @@ -74,6 +76,11 @@ DateFormatTest::TestFormat() result = timeFormat.Format(output, values[i].time, B_MEDIUM_TIME_FORMAT); CPPUNIT_ASSERT_EQUAL(B_OK, result); CPPUNIT_ASSERT_EQUAL(BString(values[i].longTime), output); + + result = dateTimeFormat.Format(output, values[i].time, + B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT); + CPPUNIT_ASSERT_EQUAL(B_OK, result); + CPPUNIT_ASSERT_EQUAL(BString(values[i].shortDateTime), output); } }