summaryrefslogtreecommitdiffstats
path: root/chrome/common/time_format.cc
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-08-06 12:13:06 +0100
committerBen Murdoch <benm@google.com>2010-08-18 15:49:13 +0100
commit06741cbc25cd4227a9fba40dfd0273bfcc1a587a (patch)
treeca6f21dec86a8c4f6d3c50e78628c0cf31da0353 /chrome/common/time_format.cc
parentaa0bf16ed53445f227734aee4274c7aef056f032 (diff)
downloadexternal_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.zip
external_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.tar.gz
external_chromium-06741cbc25cd4227a9fba40dfd0273bfcc1a587a.tar.bz2
Add chrome/common @ 52593
Needed by autofill Change-Id: Ibfea9ab92382af0bd0cfc6e94d21e4baa4b9d896
Diffstat (limited to 'chrome/common/time_format.cc')
-rw-r--r--chrome/common/time_format.cc338
1 files changed, 338 insertions, 0 deletions
diff --git a/chrome/common/time_format.cc b/chrome/common/time_format.cc
new file mode 100644
index 0000000..4ca5187
--- /dev/null
+++ b/chrome/common/time_format.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/time_format.h"
+
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "base/i18n/time_formatting.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "base/stl_util-inl.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "grit/generated_resources.h"
+#include "unicode/datefmt.h"
+#include "unicode/locid.h"
+#include "unicode/plurfmt.h"
+#include "unicode/plurrule.h"
+#include "unicode/smpdtfmt.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace {
+
+static const char kFallbackFormatSuffixShort[] = "}";
+static const char kFallbackFormatSuffixLeft[] = " left}";
+static const char kFallbackFormatSuffixAgo[] = " ago}";
+
+// 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 kTimeShortMessageIDs = { {
+ {
+ 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 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 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
+ }
+} };
+
+// Different format types.
+enum FormatType {
+ FORMAT_SHORT,
+ FORMAT_REMAINING,
+ FORMAT_ELAPSED,
+};
+
+} // namespace
+
+class TimeFormatter {
+ public:
+ const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
+ switch (format_type) {
+ case FORMAT_SHORT:
+ return short_formatter_;
+ case FORMAT_REMAINING:
+ return time_left_formatter_;
+ case FORMAT_ELAPSED:
+ return time_elapsed_formatter_;
+ default:
+ NOTREACHED();
+ return short_formatter_;
+ }
+ }
+ private:
+ static const MessageIDs& GetMessageIDs(FormatType format_type) {
+ switch (format_type) {
+ case FORMAT_SHORT:
+ return kTimeShortMessageIDs;
+ case FORMAT_REMAINING:
+ return kTimeRemainingMessageIDs;
+ case FORMAT_ELAPSED:
+ return kTimeElapsedMessageIDs;
+ default:
+ NOTREACHED();
+ return kTimeShortMessageIDs;
+ }
+ }
+
+ static const char* GetFallbackFormatSuffix(FormatType format_type) {
+ switch (format_type) {
+ case FORMAT_SHORT:
+ return kFallbackFormatSuffixShort;
+ case FORMAT_REMAINING:
+ return kFallbackFormatSuffixLeft;
+ case FORMAT_ELAPSED:
+ return kFallbackFormatSuffixAgo;
+ default:
+ NOTREACHED();
+ return kFallbackFormatSuffixShort;
+ }
+ }
+
+ TimeFormatter() {
+ BuildFormats(FORMAT_SHORT, &short_formatter_);
+ BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
+ BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
+ }
+ ~TimeFormatter() {
+ STLDeleteContainerPointers(short_formatter_.begin(),
+ short_formatter_.end());
+ STLDeleteContainerPointers(time_left_formatter_.begin(),
+ time_left_formatter_.end());
+ STLDeleteContainerPointers(time_elapsed_formatter_.begin(),
+ time_elapsed_formatter_.end());
+ }
+ friend class Singleton<TimeFormatter>;
+ friend struct DefaultSingletonTraits<TimeFormatter>;
+
+ std::vector<icu::PluralFormat*> short_formatter_;
+ std::vector<icu::PluralFormat*> time_left_formatter_;
+ std::vector<icu::PluralFormat*> time_elapsed_formatter_;
+ static void BuildFormats(FormatType format_type,
+ std::vector<icu::PluralFormat*>* time_formats);
+ static icu::PluralFormat* createFallbackFormat(
+ const icu::PluralRules& rules, int index, FormatType format_type);
+
+ DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
+};
+
+void TimeFormatter::BuildFormats(
+ FormatType format_type, std::vector<icu::PluralFormat*>* time_formats) {
+ static const icu::UnicodeString kKeywords[] = {
+ UNICODE_STRING_SIMPLE("other"), UNICODE_STRING_SIMPLE("one"),
+ UNICODE_STRING_SIMPLE("zero"), UNICODE_STRING_SIMPLE("two"),
+ UNICODE_STRING_SIMPLE("few"), UNICODE_STRING_SIMPLE("many")
+ };
+ UErrorCode err = U_ZERO_ERROR;
+ scoped_ptr<icu::PluralRules> rules(
+ icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
+ if (U_FAILURE(err)) {
+ err = U_ZERO_ERROR;
+ icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
+ rules.reset(icu::PluralRules::createRules(fallback_rules, err));
+ DCHECK(U_SUCCESS(err));
+ }
+
+ const MessageIDs& message_ids = GetMessageIDs(format_type);
+
+ for (int i = 0; i < 4; ++i) {
+ icu::UnicodeString pattern;
+ for (size_t j = 0; j < arraysize(kKeywords); ++j) {
+ int msg_id = message_ids.ids[i][j];
+ std::string sub_pattern = WideToUTF8(l10n_util::GetString(msg_id));
+ // NA means this keyword is not used in the current locale.
+ // Even if a translator translated for this keyword, we do not
+ // use it unless it's 'other' (j=0) or it's defined in the rules
+ // for the current locale. Special-casing of 'other' will be removed
+ // once ICU's isKeyword is fixed to return true for isKeyword('other').
+ if (sub_pattern.compare("NA") != 0 &&
+ (j == 0 || rules->isKeyword(kKeywords[j]))) {
+ pattern += kKeywords[j];
+ pattern += UNICODE_STRING_SIMPLE("{");
+ pattern += icu::UnicodeString(sub_pattern.c_str(), "UTF-8");
+ pattern += UNICODE_STRING_SIMPLE("}");
+ }
+ }
+ icu::PluralFormat* format = new icu::PluralFormat(*rules, pattern, err);
+ if (U_SUCCESS(err)) {
+ time_formats->push_back(format);
+ } else {
+ delete format;
+ time_formats->push_back(createFallbackFormat(*rules, i, format_type));
+ // Reset it so that next ICU call can proceed.
+ err = U_ZERO_ERROR;
+ }
+ }
+}
+
+// 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) {
+ static 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;
+}
+
+Singleton<TimeFormatter> time_formatter;
+
+static std::wstring FormatTimeImpl(const TimeDelta& delta,
+ FormatType format_type) {
+ if (delta.ToInternalValue() < 0) {
+ NOTREACHED() << "Negative duration";
+ return std::wstring();
+ }
+
+ int number;
+
+ const std::vector<icu::PluralFormat*>& formatters =
+ time_formatter->formatter(format_type);
+
+ UErrorCode error = U_ZERO_ERROR;
+ icu::UnicodeString time_string;
+ // Less than a minute gets "X seconds left"
+ if (delta.ToInternalValue() < Time::kMicrosecondsPerMinute) {
+ number = static_cast<int>(delta.ToInternalValue() /
+ Time::kMicrosecondsPerSecond);
+ time_string = formatters[0]->format(number, error);
+
+ // Less than 1 hour gets "X minutes left".
+ } else if (delta.ToInternalValue() < Time::kMicrosecondsPerHour) {
+ number = static_cast<int>(delta.ToInternalValue() /
+ Time::kMicrosecondsPerMinute);
+ time_string = formatters[1]->format(number, error);
+
+ // Less than 1 day remaining gets "X hours left"
+ } else if (delta.ToInternalValue() < Time::kMicrosecondsPerDay) {
+ number = static_cast<int>(delta.ToInternalValue() /
+ Time::kMicrosecondsPerHour);
+ time_string = formatters[2]->format(number, error);
+
+ // Anything bigger gets "X days left"
+ } else {
+ number = static_cast<int>(delta.ToInternalValue() /
+ Time::kMicrosecondsPerDay);
+ time_string = formatters[3]->format(number, error);
+ }
+
+ // With the fallback added, this should never fail.
+ DCHECK(U_SUCCESS(error));
+ int capacity = time_string.length() + 1;
+ string16 result_utf16;
+ time_string.extract(static_cast<UChar*>(
+ WriteInto(&result_utf16, capacity)),
+ capacity, error);
+ DCHECK(U_SUCCESS(error));
+ return UTF16ToWide(result_utf16);
+}
+
+// static
+std::wstring TimeFormat::TimeElapsed(const TimeDelta& delta) {
+ return FormatTimeImpl(delta, FORMAT_ELAPSED);
+}
+
+// static
+std::wstring TimeFormat::TimeRemaining(const TimeDelta& delta) {
+ return FormatTimeImpl(delta, FORMAT_REMAINING);
+}
+
+// static
+std::wstring TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
+ return FormatTimeImpl(delta, FORMAT_SHORT);
+}
+
+// static
+std::wstring TimeFormat::RelativeDate(
+ const Time& time,
+ const Time* optional_midnight_today) {
+ Time midnight_today = optional_midnight_today ? *optional_midnight_today :
+ Time::Now().LocalMidnight();
+
+ // Filter out "today" and "yesterday"
+ if (time >= midnight_today)
+ return l10n_util::GetString(IDS_PAST_TIME_TODAY);
+ else if (time >= midnight_today -
+ TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay))
+ return l10n_util::GetString(IDS_PAST_TIME_YESTERDAY);
+
+ return std::wstring();
+}