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.
4
5 #include "chrome/common/time_format.h"
6
7 #include <vector>
8
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/stl_util-inl.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"
23
24 using base::Time;
25 using base::TimeDelta;
26
27 namespace {
28
29 static const char kFallbackFormatSuffixShort[] = "}";
30 static const char kFallbackFormatSuffixLeft[] = " left}";
31 static const char kFallbackFormatSuffixAgo[] = " ago}";
32
33 // Contains message IDs for various time units and pluralities.
34 struct MessageIDs {
35 // There are 4 different time units and 6 different pluralities.
36 int ids[4][6];
37 };
38
39 // Message IDs for different time formats.
40 static const MessageIDs kTimeShortMessageIDs = { {
41 {
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
44 },
45 {
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
48 },
49 {
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
52 },
53 {
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
56 }
57 } };
58
59 static const MessageIDs kTimeRemainingMessageIDs = { {
60 {
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
64 },
65 {
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
69 },
70 {
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
74 },
75 {
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
79 }
80 } };
81
82 static const MessageIDs kTimeElapsedMessageIDs = { {
83 {
84 IDS_TIME_ELAPSED_SECS_DEFAULT, IDS_TIME_ELAPSED_SEC_SINGULAR,
85 IDS_TIME_ELAPSED_SECS_ZERO, IDS_TIME_ELAPSED_SECS_TWO,
86 IDS_TIME_ELAPSED_SECS_FEW, IDS_TIME_ELAPSED_SECS_MANY
87 },
88 {
89 IDS_TIME_ELAPSED_MINS_DEFAULT, IDS_TIME_ELAPSED_MIN_SINGULAR,
90 IDS_TIME_ELAPSED_MINS_ZERO, IDS_TIME_ELAPSED_MINS_TWO,
91 IDS_TIME_ELAPSED_MINS_FEW, IDS_TIME_ELAPSED_MINS_MANY
92 },
93 {
94 IDS_TIME_ELAPSED_HOURS_DEFAULT, IDS_TIME_ELAPSED_HOUR_SINGULAR,
95 IDS_TIME_ELAPSED_HOURS_ZERO, IDS_TIME_ELAPSED_HOURS_TWO,
96 IDS_TIME_ELAPSED_HOURS_FEW, IDS_TIME_ELAPSED_HOURS_MANY
97 },
98 {
99 IDS_TIME_ELAPSED_DAYS_DEFAULT, IDS_TIME_ELAPSED_DAY_SINGULAR,
100 IDS_TIME_ELAPSED_DAYS_ZERO, IDS_TIME_ELAPSED_DAYS_TWO,
101 IDS_TIME_ELAPSED_DAYS_FEW, IDS_TIME_ELAPSED_DAYS_MANY
102 }
103 } };
104
105 // Different format types.
106 enum FormatType {
107 FORMAT_SHORT,
108 FORMAT_REMAINING,
109 FORMAT_ELAPSED,
110 };
111
112 } // namespace
113
114 class TimeFormatter {
115 public:
formatter(FormatType format_type)116 const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
117 switch (format_type) {
118 case FORMAT_SHORT:
119 return short_formatter_;
120 case FORMAT_REMAINING:
121 return time_left_formatter_;
122 case FORMAT_ELAPSED:
123 return time_elapsed_formatter_;
124 default:
125 NOTREACHED();
126 return short_formatter_;
127 }
128 }
129 private:
GetMessageIDs(FormatType format_type)130 static const MessageIDs& GetMessageIDs(FormatType format_type) {
131 switch (format_type) {
132 case FORMAT_SHORT:
133 return kTimeShortMessageIDs;
134 case FORMAT_REMAINING:
135 return kTimeRemainingMessageIDs;
136 case FORMAT_ELAPSED:
137 return kTimeElapsedMessageIDs;
138 default:
139 NOTREACHED();
140 return kTimeShortMessageIDs;
141 }
142 }
143
GetFallbackFormatSuffix(FormatType format_type)144 static const char* GetFallbackFormatSuffix(FormatType format_type) {
145 switch (format_type) {
146 case FORMAT_SHORT:
147 return kFallbackFormatSuffixShort;
148 case FORMAT_REMAINING:
149 return kFallbackFormatSuffixLeft;
150 case FORMAT_ELAPSED:
151 return kFallbackFormatSuffixAgo;
152 default:
153 NOTREACHED();
154 return kFallbackFormatSuffixShort;
155 }
156 }
157
TimeFormatter()158 TimeFormatter() {
159 BuildFormats(FORMAT_SHORT, &short_formatter_);
160 BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
161 BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
162 }
~TimeFormatter()163 ~TimeFormatter() {
164 STLDeleteContainerPointers(short_formatter_.begin(),
165 short_formatter_.end());
166 STLDeleteContainerPointers(time_left_formatter_.begin(),
167 time_left_formatter_.end());
168 STLDeleteContainerPointers(time_elapsed_formatter_.begin(),
169 time_elapsed_formatter_.end());
170 }
171 friend struct base::DefaultLazyInstanceTraits<TimeFormatter>;
172
173 std::vector<icu::PluralFormat*> short_formatter_;
174 std::vector<icu::PluralFormat*> time_left_formatter_;
175 std::vector<icu::PluralFormat*> time_elapsed_formatter_;
176 static void BuildFormats(FormatType format_type,
177 std::vector<icu::PluralFormat*>* time_formats);
178 static icu::PluralFormat* createFallbackFormat(
179 const icu::PluralRules& rules, int index, FormatType format_type);
180
181 DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
182 };
183
184 static base::LazyInstance<TimeFormatter> g_time_formatter(
185 base::LINKER_INITIALIZED);
186
BuildFormats(FormatType format_type,std::vector<icu::PluralFormat * > * time_formats)187 void TimeFormatter::BuildFormats(
188 FormatType format_type, std::vector<icu::PluralFormat*>* time_formats) {
189 static const icu::UnicodeString kKeywords[] = {
190 UNICODE_STRING_SIMPLE("other"), UNICODE_STRING_SIMPLE("one"),
191 UNICODE_STRING_SIMPLE("zero"), UNICODE_STRING_SIMPLE("two"),
192 UNICODE_STRING_SIMPLE("few"), UNICODE_STRING_SIMPLE("many")
193 };
194 UErrorCode err = U_ZERO_ERROR;
195 scoped_ptr<icu::PluralRules> rules(
196 icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
197 if (U_FAILURE(err)) {
198 err = U_ZERO_ERROR;
199 icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
200 rules.reset(icu::PluralRules::createRules(fallback_rules, err));
201 DCHECK(U_SUCCESS(err));
202 }
203
204 const MessageIDs& message_ids = GetMessageIDs(format_type);
205
206 for (int i = 0; i < 4; ++i) {
207 icu::UnicodeString pattern;
208 for (size_t j = 0; j < arraysize(kKeywords); ++j) {
209 int msg_id = message_ids.ids[i][j];
210 std::string sub_pattern = l10n_util::GetStringUTF8(msg_id);
211 // NA means this keyword is not used in the current locale.
212 // Even if a translator translated for this keyword, we do not
213 // use it unless it's 'other' (j=0) or it's defined in the rules
214 // for the current locale. Special-casing of 'other' will be removed
215 // once ICU's isKeyword is fixed to return true for isKeyword('other').
216 if (sub_pattern.compare("NA") != 0 &&
217 (j == 0 || rules->isKeyword(kKeywords[j]))) {
218 pattern += kKeywords[j];
219 pattern += UNICODE_STRING_SIMPLE("{");
220 pattern += icu::UnicodeString(sub_pattern.c_str(), "UTF-8");
221 pattern += UNICODE_STRING_SIMPLE("}");
222 }
223 }
224 icu::PluralFormat* format = new icu::PluralFormat(*rules, pattern, err);
225 if (U_SUCCESS(err)) {
226 time_formats->push_back(format);
227 } else {
228 delete format;
229 time_formats->push_back(createFallbackFormat(*rules, i, format_type));
230 // Reset it so that next ICU call can proceed.
231 err = U_ZERO_ERROR;
232 }
233 }
234 }
235
236 // Create a hard-coded fallback plural format. This will never be called
237 // unless translators make a mistake.
createFallbackFormat(const icu::PluralRules & rules,int index,FormatType format_type)238 icu::PluralFormat* TimeFormatter::createFallbackFormat(
239 const icu::PluralRules& rules, int index, FormatType format_type) {
240 static const icu::UnicodeString kUnits[4][2] = {
241 { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") },
242 { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") },
243 { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") },
244 { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") }
245 };
246 icu::UnicodeString suffix(GetFallbackFormatSuffix(format_type), -1, US_INV);
247 icu::UnicodeString pattern;
248 if (rules.isKeyword(UNICODE_STRING_SIMPLE("one"))) {
249 pattern += UNICODE_STRING_SIMPLE("one{# ") + kUnits[index][0] + suffix;
250 }
251 pattern += UNICODE_STRING_SIMPLE(" other{# ") + kUnits[index][1] + suffix;
252 UErrorCode err = U_ZERO_ERROR;
253 icu::PluralFormat* format = new icu::PluralFormat(rules, pattern, err);
254 DCHECK(U_SUCCESS(err));
255 return format;
256 }
257
FormatTimeImpl(const TimeDelta & delta,FormatType format_type)258 static string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
259 if (delta.ToInternalValue() < 0) {
260 NOTREACHED() << "Negative duration";
261 return string16();
262 }
263
264 int number;
265
266 const std::vector<icu::PluralFormat*>& formatters =
267 g_time_formatter.Get().formatter(format_type);
268
269 UErrorCode error = U_ZERO_ERROR;
270 icu::UnicodeString time_string;
271 // Less than a minute gets "X seconds left"
272 if (delta.ToInternalValue() < Time::kMicrosecondsPerMinute) {
273 number = static_cast<int>(delta.ToInternalValue() /
274 Time::kMicrosecondsPerSecond);
275 time_string = formatters[0]->format(number, error);
276
277 // Less than 1 hour gets "X minutes left".
278 } else if (delta.ToInternalValue() < Time::kMicrosecondsPerHour) {
279 number = static_cast<int>(delta.ToInternalValue() /
280 Time::kMicrosecondsPerMinute);
281 time_string = formatters[1]->format(number, error);
282
283 // Less than 1 day remaining gets "X hours left"
284 } else if (delta.ToInternalValue() < Time::kMicrosecondsPerDay) {
285 number = static_cast<int>(delta.ToInternalValue() /
286 Time::kMicrosecondsPerHour);
287 time_string = formatters[2]->format(number, error);
288
289 // Anything bigger gets "X days left"
290 } else {
291 number = static_cast<int>(delta.ToInternalValue() /
292 Time::kMicrosecondsPerDay);
293 time_string = formatters[3]->format(number, error);
294 }
295
296 // With the fallback added, this should never fail.
297 DCHECK(U_SUCCESS(error));
298 int capacity = time_string.length() + 1;
299 string16 result;
300 time_string.extract(static_cast<UChar*>(
301 WriteInto(&result, capacity)),
302 capacity, error);
303 DCHECK(U_SUCCESS(error));
304 return result;
305 }
306
307 // static
TimeElapsed(const TimeDelta & delta)308 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
309 return FormatTimeImpl(delta, FORMAT_ELAPSED);
310 }
311
312 // static
TimeRemaining(const TimeDelta & delta)313 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
314 return FormatTimeImpl(delta, FORMAT_REMAINING);
315 }
316
317 // static
TimeRemainingShort(const TimeDelta & delta)318 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
319 return FormatTimeImpl(delta, FORMAT_SHORT);
320 }
321
322 // static
RelativeDate(const Time & time,const Time * optional_midnight_today)323 string16 TimeFormat::RelativeDate(
324 const Time& time,
325 const Time* optional_midnight_today) {
326 Time midnight_today = optional_midnight_today ? *optional_midnight_today :
327 Time::Now().LocalMidnight();
328
329 // Filter out "today" and "yesterday"
330 if (time >= midnight_today)
331 return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
332 else if (time >= midnight_today -
333 TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay))
334 return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);
335
336 return string16();
337 }
338