• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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