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 "chrome/common/time_format.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/stl_util.h"
13 #include "base/string16.h"
14 #include "base/time.h"
15 #include "base/utf_string_conversions.h"
16 #include "grit/generated_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "unicode/datefmt.h"
19 #include "unicode/locid.h"
20 #include "unicode/plurfmt.h"
21 #include "unicode/plurrule.h"
22 #include "unicode/smpdtfmt.h"
25 using base::TimeDelta
;
29 static const char kFallbackFormatSuffixShort
[] = "}";
30 static const char kFallbackFormatSuffixLeft
[] = " left}";
31 static const char kFallbackFormatSuffixAgo
[] = " ago}";
33 // Contains message IDs for various time units and pluralities.
35 // There are 4 different time units and 6 different pluralities.
39 // Message IDs for different time formats.
40 static const MessageIDs kTimeShortMessageIDs
= { {
42 IDS_TIME_SECS_DEFAULT
, IDS_TIME_SEC_SINGULAR
, IDS_TIME_SECS_ZERO
,
43 IDS_TIME_SECS_TWO
, IDS_TIME_SECS_FEW
, IDS_TIME_SECS_MANY
46 IDS_TIME_MINS_DEFAULT
, IDS_TIME_MIN_SINGULAR
, IDS_TIME_MINS_ZERO
,
47 IDS_TIME_MINS_TWO
, IDS_TIME_MINS_FEW
, IDS_TIME_MINS_MANY
50 IDS_TIME_HOURS_DEFAULT
, IDS_TIME_HOUR_SINGULAR
, IDS_TIME_HOURS_ZERO
,
51 IDS_TIME_HOURS_TWO
, IDS_TIME_HOURS_FEW
, IDS_TIME_HOURS_MANY
54 IDS_TIME_DAYS_DEFAULT
, IDS_TIME_DAY_SINGULAR
, IDS_TIME_DAYS_ZERO
,
55 IDS_TIME_DAYS_TWO
, IDS_TIME_DAYS_FEW
, IDS_TIME_DAYS_MANY
59 static const MessageIDs kTimeRemainingMessageIDs
= { {
61 IDS_TIME_REMAINING_SECS_DEFAULT
, IDS_TIME_REMAINING_SEC_SINGULAR
,
62 IDS_TIME_REMAINING_SECS_ZERO
, IDS_TIME_REMAINING_SECS_TWO
,
63 IDS_TIME_REMAINING_SECS_FEW
, IDS_TIME_REMAINING_SECS_MANY
66 IDS_TIME_REMAINING_MINS_DEFAULT
, IDS_TIME_REMAINING_MIN_SINGULAR
,
67 IDS_TIME_REMAINING_MINS_ZERO
, IDS_TIME_REMAINING_MINS_TWO
,
68 IDS_TIME_REMAINING_MINS_FEW
, IDS_TIME_REMAINING_MINS_MANY
71 IDS_TIME_REMAINING_HOURS_DEFAULT
, IDS_TIME_REMAINING_HOUR_SINGULAR
,
72 IDS_TIME_REMAINING_HOURS_ZERO
, IDS_TIME_REMAINING_HOURS_TWO
,
73 IDS_TIME_REMAINING_HOURS_FEW
, IDS_TIME_REMAINING_HOURS_MANY
76 IDS_TIME_REMAINING_DAYS_DEFAULT
, IDS_TIME_REMAINING_DAY_SINGULAR
,
77 IDS_TIME_REMAINING_DAYS_ZERO
, IDS_TIME_REMAINING_DAYS_TWO
,
78 IDS_TIME_REMAINING_DAYS_FEW
, IDS_TIME_REMAINING_DAYS_MANY
82 static const MessageIDs kTimeRemainingLongMessageIDs
= { {
84 IDS_TIME_REMAINING_SECS_DEFAULT
, IDS_TIME_REMAINING_SEC_SINGULAR
,
85 IDS_TIME_REMAINING_SECS_ZERO
, IDS_TIME_REMAINING_SECS_TWO
,
86 IDS_TIME_REMAINING_SECS_FEW
, IDS_TIME_REMAINING_SECS_MANY
89 IDS_TIME_REMAINING_LONG_MINS_DEFAULT
, IDS_TIME_REMAINING_LONG_MIN_SINGULAR
,
90 IDS_TIME_REMAINING_LONG_MINS_ZERO
, IDS_TIME_REMAINING_LONG_MINS_TWO
,
91 IDS_TIME_REMAINING_LONG_MINS_FEW
, IDS_TIME_REMAINING_LONG_MINS_MANY
94 IDS_TIME_REMAINING_HOURS_DEFAULT
, IDS_TIME_REMAINING_HOUR_SINGULAR
,
95 IDS_TIME_REMAINING_HOURS_ZERO
, IDS_TIME_REMAINING_HOURS_TWO
,
96 IDS_TIME_REMAINING_HOURS_FEW
, IDS_TIME_REMAINING_HOURS_MANY
99 IDS_TIME_REMAINING_DAYS_DEFAULT
, IDS_TIME_REMAINING_DAY_SINGULAR
,
100 IDS_TIME_REMAINING_DAYS_ZERO
, IDS_TIME_REMAINING_DAYS_TWO
,
101 IDS_TIME_REMAINING_DAYS_FEW
, IDS_TIME_REMAINING_DAYS_MANY
105 static const MessageIDs kTimeElapsedMessageIDs
= { {
107 IDS_TIME_ELAPSED_SECS_DEFAULT
, IDS_TIME_ELAPSED_SEC_SINGULAR
,
108 IDS_TIME_ELAPSED_SECS_ZERO
, IDS_TIME_ELAPSED_SECS_TWO
,
109 IDS_TIME_ELAPSED_SECS_FEW
, IDS_TIME_ELAPSED_SECS_MANY
112 IDS_TIME_ELAPSED_MINS_DEFAULT
, IDS_TIME_ELAPSED_MIN_SINGULAR
,
113 IDS_TIME_ELAPSED_MINS_ZERO
, IDS_TIME_ELAPSED_MINS_TWO
,
114 IDS_TIME_ELAPSED_MINS_FEW
, IDS_TIME_ELAPSED_MINS_MANY
117 IDS_TIME_ELAPSED_HOURS_DEFAULT
, IDS_TIME_ELAPSED_HOUR_SINGULAR
,
118 IDS_TIME_ELAPSED_HOURS_ZERO
, IDS_TIME_ELAPSED_HOURS_TWO
,
119 IDS_TIME_ELAPSED_HOURS_FEW
, IDS_TIME_ELAPSED_HOURS_MANY
122 IDS_TIME_ELAPSED_DAYS_DEFAULT
, IDS_TIME_ELAPSED_DAY_SINGULAR
,
123 IDS_TIME_ELAPSED_DAYS_ZERO
, IDS_TIME_ELAPSED_DAYS_TWO
,
124 IDS_TIME_ELAPSED_DAYS_FEW
, IDS_TIME_ELAPSED_DAYS_MANY
128 // Different format types.
132 FORMAT_REMAINING_LONG
,
138 class TimeFormatter
{
140 const std::vector
<icu::PluralFormat
*>& formatter(FormatType format_type
) {
141 switch (format_type
) {
143 return short_formatter_
;
144 case FORMAT_REMAINING
:
145 return time_left_formatter_
;
146 case FORMAT_REMAINING_LONG
:
147 return time_left_long_formatter_
;
149 return time_elapsed_formatter_
;
152 return short_formatter_
;
156 static const MessageIDs
& GetMessageIDs(FormatType format_type
) {
157 switch (format_type
) {
159 return kTimeShortMessageIDs
;
160 case FORMAT_REMAINING
:
161 return kTimeRemainingMessageIDs
;
162 case FORMAT_REMAINING_LONG
:
163 return kTimeRemainingLongMessageIDs
;
165 return kTimeElapsedMessageIDs
;
168 return kTimeShortMessageIDs
;
172 static const char* GetFallbackFormatSuffix(FormatType format_type
) {
173 switch (format_type
) {
175 return kFallbackFormatSuffixShort
;
176 case FORMAT_REMAINING
:
177 case FORMAT_REMAINING_LONG
:
178 return kFallbackFormatSuffixLeft
;
180 return kFallbackFormatSuffixAgo
;
183 return kFallbackFormatSuffixShort
;
188 BuildFormats(FORMAT_SHORT
, &short_formatter_
);
189 BuildFormats(FORMAT_REMAINING
, &time_left_formatter_
);
190 BuildFormats(FORMAT_REMAINING_LONG
, &time_left_long_formatter_
);
191 BuildFormats(FORMAT_ELAPSED
, &time_elapsed_formatter_
);
194 STLDeleteContainerPointers(short_formatter_
.begin(),
195 short_formatter_
.end());
196 STLDeleteContainerPointers(time_left_formatter_
.begin(),
197 time_left_formatter_
.end());
198 STLDeleteContainerPointers(time_left_long_formatter_
.begin(),
199 time_left_long_formatter_
.end());
200 STLDeleteContainerPointers(time_elapsed_formatter_
.begin(),
201 time_elapsed_formatter_
.end());
203 friend struct base::DefaultLazyInstanceTraits
<TimeFormatter
>;
205 std::vector
<icu::PluralFormat
*> short_formatter_
;
206 std::vector
<icu::PluralFormat
*> time_left_formatter_
;
207 std::vector
<icu::PluralFormat
*> time_left_long_formatter_
;
208 std::vector
<icu::PluralFormat
*> time_elapsed_formatter_
;
209 static void BuildFormats(FormatType format_type
,
210 std::vector
<icu::PluralFormat
*>* time_formats
);
211 static icu::PluralFormat
* createFallbackFormat(
212 const icu::PluralRules
& rules
, int index
, FormatType format_type
);
214 DISALLOW_COPY_AND_ASSIGN(TimeFormatter
);
217 static base::LazyInstance
<TimeFormatter
> g_time_formatter
=
218 LAZY_INSTANCE_INITIALIZER
;
220 void TimeFormatter::BuildFormats(
221 FormatType format_type
, std::vector
<icu::PluralFormat
*>* time_formats
) {
222 const icu::UnicodeString kKeywords
[] = {
223 UNICODE_STRING_SIMPLE("other"), UNICODE_STRING_SIMPLE("one"),
224 UNICODE_STRING_SIMPLE("zero"), UNICODE_STRING_SIMPLE("two"),
225 UNICODE_STRING_SIMPLE("few"), UNICODE_STRING_SIMPLE("many")
227 UErrorCode err
= U_ZERO_ERROR
;
228 scoped_ptr
<icu::PluralRules
> rules(
229 icu::PluralRules::forLocale(icu::Locale::getDefault(), err
));
230 if (U_FAILURE(err
)) {
232 icu::UnicodeString
fallback_rules("one: n is 1", -1, US_INV
);
233 rules
.reset(icu::PluralRules::createRules(fallback_rules
, err
));
234 DCHECK(U_SUCCESS(err
));
237 const MessageIDs
& message_ids
= GetMessageIDs(format_type
);
239 for (int i
= 0; i
< 4; ++i
) {
240 icu::UnicodeString pattern
;
241 for (size_t j
= 0; j
< arraysize(kKeywords
); ++j
) {
242 int msg_id
= message_ids
.ids
[i
][j
];
243 std::string sub_pattern
= l10n_util::GetStringUTF8(msg_id
);
244 // NA means this keyword is not used in the current locale.
245 // Even if a translator translated for this keyword, we do not
246 // use it unless it's 'other' (j=0) or it's defined in the rules
247 // for the current locale. Special-casing of 'other' will be removed
248 // once ICU's isKeyword is fixed to return true for isKeyword('other').
249 if (sub_pattern
.compare("NA") != 0 &&
250 (j
== 0 || rules
->isKeyword(kKeywords
[j
]))) {
251 pattern
+= kKeywords
[j
];
252 pattern
+= UNICODE_STRING_SIMPLE("{");
253 pattern
+= icu::UnicodeString(sub_pattern
.c_str(), "UTF-8");
254 pattern
+= UNICODE_STRING_SIMPLE("}");
257 icu::PluralFormat
* format
= new icu::PluralFormat(*rules
, pattern
, err
);
258 if (U_SUCCESS(err
)) {
259 time_formats
->push_back(format
);
262 time_formats
->push_back(createFallbackFormat(*rules
, i
, format_type
));
263 // Reset it so that next ICU call can proceed.
269 // Create a hard-coded fallback plural format. This will never be called
270 // unless translators make a mistake.
271 icu::PluralFormat
* TimeFormatter::createFallbackFormat(
272 const icu::PluralRules
& rules
, int index
, FormatType format_type
) {
273 const icu::UnicodeString kUnits
[4][2] = {
274 { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") },
275 { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") },
276 { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") },
277 { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") }
279 icu::UnicodeString
suffix(GetFallbackFormatSuffix(format_type
), -1, US_INV
);
280 icu::UnicodeString pattern
;
281 if (rules
.isKeyword(UNICODE_STRING_SIMPLE("one"))) {
282 pattern
+= UNICODE_STRING_SIMPLE("one{# ") + kUnits
[index
][0] + suffix
;
284 pattern
+= UNICODE_STRING_SIMPLE(" other{# ") + kUnits
[index
][1] + suffix
;
285 UErrorCode err
= U_ZERO_ERROR
;
286 icu::PluralFormat
* format
= new icu::PluralFormat(rules
, pattern
, err
);
287 DCHECK(U_SUCCESS(err
));
291 static string16
FormatTimeImpl(const TimeDelta
& delta
, FormatType format_type
) {
292 if (delta
.ToInternalValue() < 0) {
293 NOTREACHED() << "Negative duration";
299 const std::vector
<icu::PluralFormat
*>& formatters
=
300 g_time_formatter
.Get().formatter(format_type
);
302 UErrorCode error
= U_ZERO_ERROR
;
303 icu::UnicodeString time_string
;
304 // Less than a minute gets "X seconds left"
305 if (delta
.ToInternalValue() < Time::kMicrosecondsPerMinute
) {
306 number
= static_cast<int>(delta
.ToInternalValue() /
307 Time::kMicrosecondsPerSecond
);
308 time_string
= formatters
[0]->format(number
, error
);
310 // Less than 1 hour gets "X minutes left".
311 } else if (delta
.ToInternalValue() < Time::kMicrosecondsPerHour
) {
312 number
= static_cast<int>(delta
.ToInternalValue() /
313 Time::kMicrosecondsPerMinute
);
314 time_string
= formatters
[1]->format(number
, error
);
316 // Less than 1 day remaining gets "X hours left"
317 } else if (delta
.ToInternalValue() < Time::kMicrosecondsPerDay
) {
318 number
= static_cast<int>(delta
.ToInternalValue() /
319 Time::kMicrosecondsPerHour
);
320 time_string
= formatters
[2]->format(number
, error
);
322 // Anything bigger gets "X days left"
324 number
= static_cast<int>(delta
.ToInternalValue() /
325 Time::kMicrosecondsPerDay
);
326 time_string
= formatters
[3]->format(number
, error
);
329 // With the fallback added, this should never fail.
330 DCHECK(U_SUCCESS(error
));
331 int capacity
= time_string
.length() + 1;
332 DCHECK_GT(capacity
, 1);
334 time_string
.extract(static_cast<UChar
*>(WriteInto(&result
, capacity
)),
336 DCHECK(U_SUCCESS(error
));
341 string16
TimeFormat::TimeElapsed(const TimeDelta
& delta
) {
342 return FormatTimeImpl(delta
, FORMAT_ELAPSED
);
346 string16
TimeFormat::TimeRemaining(const TimeDelta
& delta
) {
347 return FormatTimeImpl(delta
, FORMAT_REMAINING
);
351 string16
TimeFormat::TimeRemainingLong(const TimeDelta
& delta
) {
352 return FormatTimeImpl(delta
, FORMAT_REMAINING_LONG
);
356 string16
TimeFormat::TimeRemainingShort(const TimeDelta
& delta
) {
357 return FormatTimeImpl(delta
, FORMAT_SHORT
);
361 string16
TimeFormat::RelativeDate(
363 const Time
* optional_midnight_today
) {
364 Time midnight_today
= optional_midnight_today
? *optional_midnight_today
:
365 Time::Now().LocalMidnight();
366 TimeDelta day
= TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay
);
367 Time tomorrow
= midnight_today
+ day
;
368 Time yesterday
= midnight_today
- day
;
369 if (time
>= tomorrow
)
371 else if (time
>= midnight_today
)
372 return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY
);
373 else if (time
>= yesterday
)
374 return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY
);