| 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,
|
|
|