Index: ui/base/l10n/time_format.cc |
diff --git a/ui/base/l10n/time_format.cc b/ui/base/l10n/time_format.cc |
index 1a5282c7f5d320aaab7decce15213a8b993428fc..f7fa096485cf8368201f31abbbf3b89e92cf53ae 100644 |
--- a/ui/base/l10n/time_format.cc |
+++ b/ui/base/l10n/time_format.cc |
@@ -4,369 +4,117 @@ |
#include "ui/base/l10n/time_format.h" |
-#include <vector> |
+#include <limits> |
#include "base/lazy_instance.h" |
#include "base/logging.h" |
-#include "base/memory/scoped_ptr.h" |
-#include "base/memory/scoped_vector.h" |
-#include "base/stl_util.h" |
-#include "base/strings/string16.h" |
-#include "base/strings/utf_string_conversions.h" |
+#include "base/strings/string_util.h" |
#include "base/time/time.h" |
#include "grit/ui_strings.h" |
-#include "third_party/icu/source/common/unicode/locid.h" |
-#include "third_party/icu/source/i18n/unicode/datefmt.h" |
-#include "third_party/icu/source/i18n/unicode/plurfmt.h" |
-#include "third_party/icu/source/i18n/unicode/plurrule.h" |
-#include "third_party/icu/source/i18n/unicode/smpdtfmt.h" |
+#include "third_party/icu/source/common/unicode/unistr.h" |
+#include "ui/base/l10n/formatter.h" |
#include "ui/base/l10n/l10n_util.h" |
-#include "ui/base/l10n/l10n_util_plurals.h" |
+#include "ui/base/ui_base_export.h" |
using base::Time; |
using base::TimeDelta; |
+using ui::TimeFormat; |
-namespace { |
- |
-static const char kFallbackFormatSuffixAgo[] = " ago}"; |
-static const char kFallbackFormatSuffixLeft[] = " left}"; |
-static const char kFallbackFormatSuffixDuration[] = "}"; |
- |
-// Contains message IDs for various time units and pluralities. |
-struct MessageIDs { |
- // There are 4 different time units and 6 different pluralities. |
- int ids[4][6]; |
-}; |
- |
-// Message IDs for different time formats. |
-static const MessageIDs kTimeElapsedMessageIDs = { { |
- { |
- IDS_TIME_ELAPSED_SECS_DEFAULT, IDS_TIME_ELAPSED_SEC_SINGULAR, |
- IDS_TIME_ELAPSED_SECS_ZERO, IDS_TIME_ELAPSED_SECS_TWO, |
- IDS_TIME_ELAPSED_SECS_FEW, IDS_TIME_ELAPSED_SECS_MANY |
- }, |
- { |
- IDS_TIME_ELAPSED_MINS_DEFAULT, IDS_TIME_ELAPSED_MIN_SINGULAR, |
- IDS_TIME_ELAPSED_MINS_ZERO, IDS_TIME_ELAPSED_MINS_TWO, |
- IDS_TIME_ELAPSED_MINS_FEW, IDS_TIME_ELAPSED_MINS_MANY |
- }, |
- { |
- IDS_TIME_ELAPSED_HOURS_DEFAULT, IDS_TIME_ELAPSED_HOUR_SINGULAR, |
- IDS_TIME_ELAPSED_HOURS_ZERO, IDS_TIME_ELAPSED_HOURS_TWO, |
- IDS_TIME_ELAPSED_HOURS_FEW, IDS_TIME_ELAPSED_HOURS_MANY |
- }, |
- { |
- IDS_TIME_ELAPSED_DAYS_DEFAULT, IDS_TIME_ELAPSED_DAY_SINGULAR, |
- IDS_TIME_ELAPSED_DAYS_ZERO, IDS_TIME_ELAPSED_DAYS_TWO, |
- IDS_TIME_ELAPSED_DAYS_FEW, IDS_TIME_ELAPSED_DAYS_MANY |
- } |
-} }; |
- |
-static const MessageIDs kTimeRemainingMessageIDs = { { |
- { |
- IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR, |
- IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO, |
- IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_MINS_DEFAULT, IDS_TIME_REMAINING_MIN_SINGULAR, |
- IDS_TIME_REMAINING_MINS_ZERO, IDS_TIME_REMAINING_MINS_TWO, |
- IDS_TIME_REMAINING_MINS_FEW, IDS_TIME_REMAINING_MINS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR, |
- IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO, |
- IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR, |
- IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO, |
- IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY |
- } |
-} }; |
- |
-static const MessageIDs kTimeRemainingLongMessageIDs = { { |
- { |
- IDS_TIME_REMAINING_LONG_SECS_DEFAULT, IDS_TIME_REMAINING_LONG_SEC_SINGULAR, |
- IDS_TIME_REMAINING_LONG_SECS_ZERO, IDS_TIME_REMAINING_LONG_SECS_TWO, |
- IDS_TIME_REMAINING_LONG_SECS_FEW, IDS_TIME_REMAINING_LONG_SECS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_LONG_MINS_DEFAULT, IDS_TIME_REMAINING_LONG_MIN_SINGULAR, |
- IDS_TIME_REMAINING_LONG_MINS_ZERO, IDS_TIME_REMAINING_LONG_MINS_TWO, |
- IDS_TIME_REMAINING_LONG_MINS_FEW, IDS_TIME_REMAINING_LONG_MINS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR, |
- IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO, |
- IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY |
- }, |
- { |
- IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR, |
- IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO, |
- IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY |
- } |
-} }; |
- |
-static const MessageIDs kTimeDurationShortMessageIDs = { { |
- { |
- IDS_TIME_SECS_DEFAULT, IDS_TIME_SEC_SINGULAR, IDS_TIME_SECS_ZERO, |
- IDS_TIME_SECS_TWO, IDS_TIME_SECS_FEW, IDS_TIME_SECS_MANY |
- }, |
- { |
- IDS_TIME_MINS_DEFAULT, IDS_TIME_MIN_SINGULAR, IDS_TIME_MINS_ZERO, |
- IDS_TIME_MINS_TWO, IDS_TIME_MINS_FEW, IDS_TIME_MINS_MANY |
- }, |
- { |
- IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR, IDS_TIME_HOURS_ZERO, |
- IDS_TIME_HOURS_TWO, IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY |
- }, |
- { |
- IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR, IDS_TIME_DAYS_ZERO, |
- IDS_TIME_DAYS_TWO, IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY |
- } |
-} }; |
- |
-static const MessageIDs kTimeDurationLongMessageIDs = { { |
- { |
- IDS_TIME_DURATION_LONG_SECS_DEFAULT, IDS_TIME_DURATION_LONG_SEC_SINGULAR, |
- IDS_TIME_DURATION_LONG_SECS_ZERO, IDS_TIME_DURATION_LONG_SECS_TWO, |
- IDS_TIME_DURATION_LONG_SECS_FEW, IDS_TIME_DURATION_LONG_SECS_MANY |
- }, |
- { |
- IDS_TIME_DURATION_LONG_MINS_DEFAULT, IDS_TIME_DURATION_LONG_MIN_SINGULAR, |
- IDS_TIME_DURATION_LONG_MINS_ZERO, IDS_TIME_DURATION_LONG_MINS_TWO, |
- IDS_TIME_DURATION_LONG_MINS_FEW, IDS_TIME_DURATION_LONG_MINS_MANY |
- }, |
- { |
- IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR, |
- IDS_TIME_HOURS_ZERO, IDS_TIME_HOURS_TWO, |
- IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY |
- }, |
- { |
- IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR, |
- IDS_TIME_DAYS_ZERO, IDS_TIME_DAYS_TWO, |
- IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY |
- } |
-} }; |
- |
-// Different format types. |
-enum FormatType { |
- FORMAT_ELAPSED, |
- FORMAT_REMAINING, |
- FORMAT_REMAINING_LONG, |
- FORMAT_DURATION_SHORT, |
- FORMAT_DURATION_LONG, |
-}; |
- |
-class TimeFormatter { |
- public: |
- const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) { |
- switch (format_type) { |
- case FORMAT_ELAPSED: |
- return time_elapsed_formatter_.get(); |
- case FORMAT_REMAINING: |
- return time_left_formatter_.get(); |
- case FORMAT_REMAINING_LONG: |
- return time_left_long_formatter_.get(); |
- case FORMAT_DURATION_SHORT: |
- return time_duration_short_formatter_.get(); |
- case FORMAT_DURATION_LONG: |
- return time_duration_long_formatter_.get(); |
- default: |
- NOTREACHED(); |
- return time_duration_short_formatter_.get(); |
- } |
- } |
- private: |
- static const MessageIDs& GetMessageIDs(FormatType format_type) { |
- switch (format_type) { |
- case FORMAT_ELAPSED: |
- return kTimeElapsedMessageIDs; |
- case FORMAT_REMAINING: |
- return kTimeRemainingMessageIDs; |
- case FORMAT_REMAINING_LONG: |
- return kTimeRemainingLongMessageIDs; |
- case FORMAT_DURATION_SHORT: |
- return kTimeDurationShortMessageIDs; |
- case FORMAT_DURATION_LONG: |
- return kTimeDurationLongMessageIDs; |
- default: |
- NOTREACHED(); |
- return kTimeDurationShortMessageIDs; |
- } |
- } |
- |
- static const char* GetFallbackFormatSuffix(FormatType format_type) { |
- switch (format_type) { |
- case FORMAT_ELAPSED: |
- return kFallbackFormatSuffixAgo; |
- case FORMAT_REMAINING: |
- case FORMAT_REMAINING_LONG: |
- return kFallbackFormatSuffixLeft; |
- case FORMAT_DURATION_SHORT: |
- case FORMAT_DURATION_LONG: |
- return kFallbackFormatSuffixDuration; |
- default: |
- NOTREACHED(); |
- return kFallbackFormatSuffixDuration; |
- } |
- } |
- |
- TimeFormatter() { |
- BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_); |
- BuildFormats(FORMAT_REMAINING, &time_left_formatter_); |
- BuildFormats(FORMAT_REMAINING_LONG, &time_left_long_formatter_); |
- BuildFormats(FORMAT_DURATION_SHORT, &time_duration_short_formatter_); |
- BuildFormats(FORMAT_DURATION_LONG, &time_duration_long_formatter_); |
- } |
- ~TimeFormatter() { |
- } |
- friend struct base::DefaultLazyInstanceTraits<TimeFormatter>; |
- |
- ScopedVector<icu::PluralFormat> time_elapsed_formatter_; |
- ScopedVector<icu::PluralFormat> time_left_formatter_; |
- ScopedVector<icu::PluralFormat> time_left_long_formatter_; |
- ScopedVector<icu::PluralFormat> time_duration_short_formatter_; |
- ScopedVector<icu::PluralFormat> time_duration_long_formatter_; |
- static void BuildFormats(FormatType format_type, |
- ScopedVector<icu::PluralFormat>* time_formats); |
- static icu::PluralFormat* createFallbackFormat( |
- const icu::PluralRules& rules, int index, FormatType format_type); |
- |
- DISALLOW_COPY_AND_ASSIGN(TimeFormatter); |
-}; |
+namespace ui { |
-static base::LazyInstance<TimeFormatter> g_time_formatter = |
+UI_BASE_EXPORT base::LazyInstance<FormatterContainer> g_container = |
LAZY_INSTANCE_INITIALIZER; |
-void TimeFormatter::BuildFormats( |
- FormatType format_type, ScopedVector<icu::PluralFormat>* time_formats) { |
- const MessageIDs& message_ids = GetMessageIDs(format_type); |
- |
- for (int i = 0; i < 4; ++i) { |
- icu::UnicodeString pattern; |
- std::vector<int> ids; |
- for (size_t j = 0; j < arraysize(message_ids.ids[i]); ++j) { |
- ids.push_back(message_ids.ids[i][j]); |
- } |
- scoped_ptr<icu::PluralFormat> format = l10n_util::BuildPluralFormat(ids); |
- if (format) { |
- time_formats->push_back(format.release()); |
- } else { |
- scoped_ptr<icu::PluralRules> rules(l10n_util::BuildPluralRules()); |
- time_formats->push_back(createFallbackFormat(*rules, i, format_type)); |
- } |
- } |
-} |
- |
-// Create a hard-coded fallback plural format. This will never be called |
-// unless translators make a mistake. |
-icu::PluralFormat* TimeFormatter::createFallbackFormat( |
- const icu::PluralRules& rules, int index, FormatType format_type) { |
- const icu::UnicodeString kUnits[4][2] = { |
- { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") }, |
- { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") }, |
- { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") }, |
- { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") } |
- }; |
- icu::UnicodeString suffix(GetFallbackFormatSuffix(format_type), -1, US_INV); |
- icu::UnicodeString pattern; |
- if (rules.isKeyword(UNICODE_STRING_SIMPLE("one"))) { |
- pattern += UNICODE_STRING_SIMPLE("one{# ") + kUnits[index][0] + suffix; |
- } |
- pattern += UNICODE_STRING_SIMPLE(" other{# ") + kUnits[index][1] + suffix; |
- UErrorCode err = U_ZERO_ERROR; |
- icu::PluralFormat* format = new icu::PluralFormat(rules, pattern, err); |
- DCHECK(U_SUCCESS(err)); |
- return format; |
+// static |
+base::string16 TimeFormat::Simple(TimeFormat::Format format, |
+ TimeFormat::Length length, |
+ const base::TimeDelta& delta) { |
+ return Detailed(format, length, 0, delta); |
} |
-base::string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) { |
+// static |
+base::string16 TimeFormat::Detailed(TimeFormat::Format format, |
+ TimeFormat::Length length, |
+ int cutoff, |
+ const base::TimeDelta& delta) { |
if (delta < TimeDelta::FromSeconds(0)) { |
NOTREACHED() << "Negative duration"; |
return base::string16(); |
} |
- const std::vector<icu::PluralFormat*>& formatters = |
- g_time_formatter.Get().formatter(format_type); |
- |
- UErrorCode error = U_ZERO_ERROR; |
- icu::UnicodeString time_string; |
+ // Negative cutoff: always use two-value format. |
+ if (cutoff < 0) |
+ cutoff = std::numeric_limits<int>::max(); |
const TimeDelta one_minute(TimeDelta::FromMinutes(1)); |
const TimeDelta one_hour(TimeDelta::FromHours(1)); |
const TimeDelta one_day(TimeDelta::FromDays(1)); |
- |
const TimeDelta half_second(TimeDelta::FromMilliseconds(500)); |
const TimeDelta half_minute(TimeDelta::FromSeconds(30)); |
const TimeDelta half_hour(TimeDelta::FromMinutes(30)); |
const TimeDelta half_day(TimeDelta::FromHours(12)); |
- // Less than 59.5 seconds gets "X seconds left", anything larger is |
- // rounded to minutes. |
+ // Rationale: Start by determining major (first) unit, then add minor (second) |
+ // unit if mandated by |cutoff|. |
+ icu::UnicodeString time_string; |
+ const Formatter* formatter = g_container.Get().Get(format, length); |
if (delta < one_minute - half_second) { |
+ // Anything up to 59.500 seconds is formatted as seconds. |
const int seconds = static_cast<int>((delta + half_second).InSeconds()); |
- time_string = formatters[0]->format(seconds, error); |
- |
- // Less than 59.5 minutes gets "X minutes left", anything larger is |
- // rounded to hours. |
- } else if (delta < one_hour - half_minute) { |
- const int minutes = (delta + half_minute).InMinutes(); |
- time_string = formatters[1]->format(minutes, error); |
+ formatter->Format(Formatter::UNIT_SEC, seconds, time_string); |
+ |
+ } else if (delta < one_hour - (cutoff < 60 ? half_minute : half_second)) { |
+ // Anything up to 59.5 minutes (respectively 59:59.500 when |cutoff| permits |
+ // two-value output) is formatted as minutes (respectively minutes and |
+ // seconds). |
+ if (delta >= cutoff * one_minute - half_second) { |
+ const int minutes = (delta + half_minute).InMinutes(); |
+ formatter->Format(Formatter::UNIT_MIN, minutes, time_string); |
+ } else { |
+ const int minutes = (delta + half_second).InMinutes(); |
+ const int seconds = static_cast<int>( |
+ (delta + half_second).InSeconds() % 60); |
+ formatter->Format(Formatter::TWO_UNITS_MIN_SEC, |
+ minutes, seconds, time_string); |
+ } |
- // Less than 23.5 hours gets "X hours left", anything larger is |
- // rounded to days. |
- } else if (delta < one_day - half_hour) { |
- const int hours = (delta + half_hour).InHours(); |
- time_string = formatters[2]->format(hours, error); |
+ } else if (delta < one_day - (cutoff < 24 ? half_hour : half_minute)) { |
+ // Anything up to 23.5 hours (respectively 23:59:30.000 when |cutoff| |
+ // permits two-value output) is formatted as hours (respectively hours and |
+ // minutes). |
+ if (delta >= cutoff * one_hour - half_minute) { |
+ const int hours = (delta + half_hour).InHours(); |
+ formatter->Format(Formatter::UNIT_HOUR, hours, time_string); |
+ } else { |
+ const int hours = (delta + half_minute).InHours(); |
+ const int minutes = (delta + half_minute).InMinutes() % 60; |
+ formatter->Format(Formatter::TWO_UNITS_HOUR_MIN, |
+ hours, minutes, time_string); |
+ } |
- // Anything bigger gets "X days left". |
} else { |
- const int days = (delta + half_day).InDays(); |
- time_string = formatters[3]->format(days, error); |
+ // Anything bigger is formatted as days (respectively days and hours). |
+ if (delta >= cutoff * one_day - half_hour) { |
+ const int days = (delta + half_day).InDays(); |
+ formatter->Format(Formatter::UNIT_DAY, days, time_string); |
+ } else { |
+ const int days = (delta + half_hour).InDays(); |
+ const int hours = (delta + half_hour).InHours() % 24; |
+ formatter->Format(Formatter::TWO_UNITS_DAY_HOUR, |
+ days, hours, time_string); |
+ } |
} |
- // With the fallback added, this should never fail. |
- DCHECK(U_SUCCESS(error)); |
- int capacity = time_string.length() + 1; |
+ const int capacity = time_string.length() + 1; |
DCHECK_GT(capacity, 1); |
base::string16 result; |
+ UErrorCode error = U_ZERO_ERROR; |
time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)), |
capacity, error); |
DCHECK(U_SUCCESS(error)); |
return result; |
} |
-} // namespace |
- |
-namespace ui { |
- |
-// static |
-base::string16 TimeFormat::TimeElapsed(const TimeDelta& delta) { |
- return FormatTimeImpl(delta, FORMAT_ELAPSED); |
-} |
- |
-// static |
-base::string16 TimeFormat::TimeRemaining(const TimeDelta& delta) { |
- return FormatTimeImpl(delta, FORMAT_REMAINING); |
-} |
- |
-// static |
-base::string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) { |
- return FormatTimeImpl(delta, FORMAT_REMAINING_LONG); |
-} |
- |
-// static |
-base::string16 TimeFormat::TimeDurationShort(const TimeDelta& delta) { |
- return FormatTimeImpl(delta, FORMAT_DURATION_SHORT); |
-} |
- |
-// static |
-base::string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) { |
- return FormatTimeImpl(delta, FORMAT_DURATION_LONG); |
-} |
- |
// static |
base::string16 TimeFormat::RelativeDate( |
const Time& time, |