1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/base/l10n/time_format.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/scoped_vector.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/time/time.h"
17 #include "grit/ui_strings.h"
18 #include "third_party/icu/source/common/unicode/locid.h"
19 #include "third_party/icu/source/i18n/unicode/datefmt.h"
20 #include "third_party/icu/source/i18n/unicode/plurfmt.h"
21 #include "third_party/icu/source/i18n/unicode/plurrule.h"
22 #include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/l10n/l10n_util_plurals.h"
27 using base::TimeDelta;
31 static const char kFallbackFormatSuffixShort[] = "}";
32 static const char kFallbackFormatSuffixLeft[] = " left}";
33 static const char kFallbackFormatSuffixAgo[] = " ago}";
35 // Contains message IDs for various time units and pluralities.
37 // There are 4 different time units and 6 different pluralities.
41 // Message IDs for different time formats.
42 static const MessageIDs kTimeShortMessageIDs = { {
44 IDS_TIME_SECS_DEFAULT, IDS_TIME_SEC_SINGULAR, IDS_TIME_SECS_ZERO,
45 IDS_TIME_SECS_TWO, IDS_TIME_SECS_FEW, IDS_TIME_SECS_MANY
48 IDS_TIME_MINS_DEFAULT, IDS_TIME_MIN_SINGULAR, IDS_TIME_MINS_ZERO,
49 IDS_TIME_MINS_TWO, IDS_TIME_MINS_FEW, IDS_TIME_MINS_MANY
52 IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR, IDS_TIME_HOURS_ZERO,
53 IDS_TIME_HOURS_TWO, IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY
56 IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR, IDS_TIME_DAYS_ZERO,
57 IDS_TIME_DAYS_TWO, IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY
61 static const MessageIDs kTimeRemainingMessageIDs = { {
63 IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
64 IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
65 IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
68 IDS_TIME_REMAINING_MINS_DEFAULT, IDS_TIME_REMAINING_MIN_SINGULAR,
69 IDS_TIME_REMAINING_MINS_ZERO, IDS_TIME_REMAINING_MINS_TWO,
70 IDS_TIME_REMAINING_MINS_FEW, IDS_TIME_REMAINING_MINS_MANY
73 IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
74 IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
75 IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
78 IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
79 IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
80 IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
84 static const MessageIDs kTimeRemainingLongMessageIDs = { {
86 IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
87 IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
88 IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
91 IDS_TIME_REMAINING_LONG_MINS_DEFAULT, IDS_TIME_REMAINING_LONG_MIN_SINGULAR,
92 IDS_TIME_REMAINING_LONG_MINS_ZERO, IDS_TIME_REMAINING_LONG_MINS_TWO,
93 IDS_TIME_REMAINING_LONG_MINS_FEW, IDS_TIME_REMAINING_LONG_MINS_MANY
96 IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
97 IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
98 IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
101 IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
102 IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
103 IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
107 static const MessageIDs kTimeDurationLongMessageIDs = { {
109 IDS_TIME_DURATION_LONG_SECS_DEFAULT, IDS_TIME_DURATION_LONG_SEC_SINGULAR,
110 IDS_TIME_DURATION_LONG_SECS_ZERO, IDS_TIME_DURATION_LONG_SECS_TWO,
111 IDS_TIME_DURATION_LONG_SECS_FEW, IDS_TIME_DURATION_LONG_SECS_MANY
114 IDS_TIME_DURATION_LONG_MINS_DEFAULT, IDS_TIME_DURATION_LONG_MIN_SINGULAR,
115 IDS_TIME_DURATION_LONG_MINS_ZERO, IDS_TIME_DURATION_LONG_MINS_TWO,
116 IDS_TIME_DURATION_LONG_MINS_FEW, IDS_TIME_DURATION_LONG_MINS_MANY
119 IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR,
120 IDS_TIME_HOURS_ZERO, IDS_TIME_HOURS_TWO,
121 IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY
124 IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR,
125 IDS_TIME_DAYS_ZERO, IDS_TIME_DAYS_TWO,
126 IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY
130 static const MessageIDs kTimeElapsedMessageIDs = { {
132 IDS_TIME_ELAPSED_SECS_DEFAULT, IDS_TIME_ELAPSED_SEC_SINGULAR,
133 IDS_TIME_ELAPSED_SECS_ZERO, IDS_TIME_ELAPSED_SECS_TWO,
134 IDS_TIME_ELAPSED_SECS_FEW, IDS_TIME_ELAPSED_SECS_MANY
137 IDS_TIME_ELAPSED_MINS_DEFAULT, IDS_TIME_ELAPSED_MIN_SINGULAR,
138 IDS_TIME_ELAPSED_MINS_ZERO, IDS_TIME_ELAPSED_MINS_TWO,
139 IDS_TIME_ELAPSED_MINS_FEW, IDS_TIME_ELAPSED_MINS_MANY
142 IDS_TIME_ELAPSED_HOURS_DEFAULT, IDS_TIME_ELAPSED_HOUR_SINGULAR,
143 IDS_TIME_ELAPSED_HOURS_ZERO, IDS_TIME_ELAPSED_HOURS_TWO,
144 IDS_TIME_ELAPSED_HOURS_FEW, IDS_TIME_ELAPSED_HOURS_MANY
147 IDS_TIME_ELAPSED_DAYS_DEFAULT, IDS_TIME_ELAPSED_DAY_SINGULAR,
148 IDS_TIME_ELAPSED_DAYS_ZERO, IDS_TIME_ELAPSED_DAYS_TWO,
149 IDS_TIME_ELAPSED_DAYS_FEW, IDS_TIME_ELAPSED_DAYS_MANY
153 // Different format types.
157 FORMAT_REMAINING_LONG,
158 FORMAT_DURATION_LONG,
162 class TimeFormatter {
164 const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
165 switch (format_type) {
167 return short_formatter_.get();
168 case FORMAT_REMAINING:
169 return time_left_formatter_.get();
170 case FORMAT_REMAINING_LONG:
171 return time_left_long_formatter_.get();
172 case FORMAT_DURATION_LONG:
173 return time_duration_long_formatter_.get();
175 return time_elapsed_formatter_.get();
178 return short_formatter_.get();
182 static const MessageIDs& GetMessageIDs(FormatType format_type) {
183 switch (format_type) {
185 return kTimeShortMessageIDs;
186 case FORMAT_REMAINING:
187 return kTimeRemainingMessageIDs;
188 case FORMAT_REMAINING_LONG:
189 return kTimeRemainingLongMessageIDs;
190 case FORMAT_DURATION_LONG:
191 return kTimeDurationLongMessageIDs;
193 return kTimeElapsedMessageIDs;
196 return kTimeShortMessageIDs;
200 static const char* GetFallbackFormatSuffix(FormatType format_type) {
201 switch (format_type) {
203 return kFallbackFormatSuffixShort;
204 case FORMAT_REMAINING:
205 case FORMAT_REMAINING_LONG:
206 return kFallbackFormatSuffixLeft;
208 return kFallbackFormatSuffixAgo;
211 return kFallbackFormatSuffixShort;
216 BuildFormats(FORMAT_SHORT, &short_formatter_);
217 BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
218 BuildFormats(FORMAT_REMAINING_LONG, &time_left_long_formatter_);
219 BuildFormats(FORMAT_DURATION_LONG, &time_duration_long_formatter_);
220 BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
224 friend struct base::DefaultLazyInstanceTraits<TimeFormatter>;
226 ScopedVector<icu::PluralFormat> short_formatter_;
227 ScopedVector<icu::PluralFormat> time_left_formatter_;
228 ScopedVector<icu::PluralFormat> time_left_long_formatter_;
229 ScopedVector<icu::PluralFormat> time_duration_long_formatter_;
230 ScopedVector<icu::PluralFormat> time_elapsed_formatter_;
231 static void BuildFormats(FormatType format_type,
232 ScopedVector<icu::PluralFormat>* time_formats);
233 static icu::PluralFormat* createFallbackFormat(
234 const icu::PluralRules& rules, int index, FormatType format_type);
236 DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
239 static base::LazyInstance<TimeFormatter> g_time_formatter =
240 LAZY_INSTANCE_INITIALIZER;
242 void TimeFormatter::BuildFormats(
243 FormatType format_type, ScopedVector<icu::PluralFormat>* time_formats) {
244 const MessageIDs& message_ids = GetMessageIDs(format_type);
246 for (int i = 0; i < 4; ++i) {
247 icu::UnicodeString pattern;
248 std::vector<int> ids;
249 for (size_t j = 0; j < arraysize(message_ids.ids[i]); ++j) {
250 ids.push_back(message_ids.ids[i][j]);
252 scoped_ptr<icu::PluralFormat> format = l10n_util::BuildPluralFormat(ids);
254 time_formats->push_back(format.release());
256 scoped_ptr<icu::PluralRules> rules(l10n_util::BuildPluralRules());
257 time_formats->push_back(createFallbackFormat(*rules, i, format_type));
262 // Create a hard-coded fallback plural format. This will never be called
263 // unless translators make a mistake.
264 icu::PluralFormat* TimeFormatter::createFallbackFormat(
265 const icu::PluralRules& rules, int index, FormatType format_type) {
266 const icu::UnicodeString kUnits[4][2] = {
267 { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") },
268 { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") },
269 { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") },
270 { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") }
272 icu::UnicodeString suffix(GetFallbackFormatSuffix(format_type), -1, US_INV);
273 icu::UnicodeString pattern;
274 if (rules.isKeyword(UNICODE_STRING_SIMPLE("one"))) {
275 pattern += UNICODE_STRING_SIMPLE("one{# ") + kUnits[index][0] + suffix;
277 pattern += UNICODE_STRING_SIMPLE(" other{# ") + kUnits[index][1] + suffix;
278 UErrorCode err = U_ZERO_ERROR;
279 icu::PluralFormat* format = new icu::PluralFormat(rules, pattern, err);
280 DCHECK(U_SUCCESS(err));
284 string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
285 if (delta.ToInternalValue() < 0) {
286 NOTREACHED() << "Negative duration";
292 const std::vector<icu::PluralFormat*>& formatters =
293 g_time_formatter.Get().formatter(format_type);
295 UErrorCode error = U_ZERO_ERROR;
296 icu::UnicodeString time_string;
297 // Less than a minute gets "X seconds left"
298 if (delta.ToInternalValue() < Time::kMicrosecondsPerMinute) {
299 number = static_cast<int>(delta.ToInternalValue() /
300 Time::kMicrosecondsPerSecond);
301 time_string = formatters[0]->format(number, error);
303 // Less than 1 hour gets "X minutes left".
304 } else if (delta.ToInternalValue() < Time::kMicrosecondsPerHour) {
305 number = static_cast<int>(delta.ToInternalValue() /
306 Time::kMicrosecondsPerMinute);
307 time_string = formatters[1]->format(number, error);
309 // Less than 1 day remaining gets "X hours left"
310 } else if (delta.ToInternalValue() < Time::kMicrosecondsPerDay) {
311 number = static_cast<int>(delta.ToInternalValue() /
312 Time::kMicrosecondsPerHour);
313 time_string = formatters[2]->format(number, error);
315 // Anything bigger gets "X days left"
317 number = static_cast<int>(delta.ToInternalValue() /
318 Time::kMicrosecondsPerDay);
319 time_string = formatters[3]->format(number, error);
322 // With the fallback added, this should never fail.
323 DCHECK(U_SUCCESS(error));
324 int capacity = time_string.length() + 1;
325 DCHECK_GT(capacity, 1);
327 time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
329 DCHECK(U_SUCCESS(error));
338 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
339 return FormatTimeImpl(delta, FORMAT_ELAPSED);
343 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
344 return FormatTimeImpl(delta, FORMAT_REMAINING);
348 string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
349 return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
353 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
354 return FormatTimeImpl(delta, FORMAT_SHORT);
358 string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) {
359 return FormatTimeImpl(delta, FORMAT_DURATION_LONG);
363 string16 TimeFormat::RelativeDate(
365 const Time* optional_midnight_today) {
366 Time midnight_today = optional_midnight_today ? *optional_midnight_today :
367 Time::Now().LocalMidnight();
368 TimeDelta day = TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay);
369 Time tomorrow = midnight_today + day;
370 Time yesterday = midnight_today - day;
371 if (time >= tomorrow)
373 else if (time >= midnight_today)
374 return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
375 else if (time >= yesterday)
376 return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);