Extract SIGPIPE ignoring code to a common place.
[chromium-blink-merge.git] / chrome / common / time_format.cc
blob13554d3d735862cd9bba6c87a4c25d909cfc50b8
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"
7 #include <vector>
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"
24 using base::Time;
25 using base::TimeDelta;
27 namespace {
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.
34 struct MessageIDs {
35 // There are 4 different time units and 6 different pluralities.
36 int ids[4][6];
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
57 } };
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
80 } };
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
103 } };
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
126 } };
128 // Different format types.
129 enum FormatType {
130 FORMAT_SHORT,
131 FORMAT_REMAINING,
132 FORMAT_REMAINING_LONG,
133 FORMAT_ELAPSED,
136 } // namespace
138 class TimeFormatter {
139 public:
140 const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
141 switch (format_type) {
142 case FORMAT_SHORT:
143 return short_formatter_;
144 case FORMAT_REMAINING:
145 return time_left_formatter_;
146 case FORMAT_REMAINING_LONG:
147 return time_left_long_formatter_;
148 case FORMAT_ELAPSED:
149 return time_elapsed_formatter_;
150 default:
151 NOTREACHED();
152 return short_formatter_;
155 private:
156 static const MessageIDs& GetMessageIDs(FormatType format_type) {
157 switch (format_type) {
158 case FORMAT_SHORT:
159 return kTimeShortMessageIDs;
160 case FORMAT_REMAINING:
161 return kTimeRemainingMessageIDs;
162 case FORMAT_REMAINING_LONG:
163 return kTimeRemainingLongMessageIDs;
164 case FORMAT_ELAPSED:
165 return kTimeElapsedMessageIDs;
166 default:
167 NOTREACHED();
168 return kTimeShortMessageIDs;
172 static const char* GetFallbackFormatSuffix(FormatType format_type) {
173 switch (format_type) {
174 case FORMAT_SHORT:
175 return kFallbackFormatSuffixShort;
176 case FORMAT_REMAINING:
177 case FORMAT_REMAINING_LONG:
178 return kFallbackFormatSuffixLeft;
179 case FORMAT_ELAPSED:
180 return kFallbackFormatSuffixAgo;
181 default:
182 NOTREACHED();
183 return kFallbackFormatSuffixShort;
187 TimeFormatter() {
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_);
193 ~TimeFormatter() {
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)) {
231 err = U_ZERO_ERROR;
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);
260 } else {
261 delete format;
262 time_formats->push_back(createFallbackFormat(*rules, i, format_type));
263 // Reset it so that next ICU call can proceed.
264 err = U_ZERO_ERROR;
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));
288 return format;
291 static string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
292 if (delta.ToInternalValue() < 0) {
293 NOTREACHED() << "Negative duration";
294 return string16();
297 int number;
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"
323 } else {
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);
333 string16 result;
334 time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
335 capacity, error);
336 DCHECK(U_SUCCESS(error));
337 return result;
340 // static
341 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
342 return FormatTimeImpl(delta, FORMAT_ELAPSED);
345 // static
346 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
347 return FormatTimeImpl(delta, FORMAT_REMAINING);
350 // static
351 string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
352 return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
355 // static
356 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
357 return FormatTimeImpl(delta, FORMAT_SHORT);
360 // static
361 string16 TimeFormat::RelativeDate(
362 const Time& time,
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)
370 return string16();
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);
375 return string16();