- add sources.
[platform/framework/web/crosswalk.git] / src / ui / base / l10n / time_format.cc
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 "ui/base/l10n/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/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"
25
26 using base::Time;
27 using base::TimeDelta;
28
29 namespace {
30
31 static const char kFallbackFormatSuffixShort[] = "}";
32 static const char kFallbackFormatSuffixLeft[] = " left}";
33 static const char kFallbackFormatSuffixAgo[] = " ago}";
34
35 // Contains message IDs for various time units and pluralities.
36 struct MessageIDs {
37   // There are 4 different time units and 6 different pluralities.
38   int ids[4][6];
39 };
40
41 // Message IDs for different time formats.
42 static const MessageIDs kTimeShortMessageIDs = { {
43   {
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
46   },
47   {
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
50   },
51   {
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
54   },
55   {
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
58   }
59 } };
60
61 static const MessageIDs kTimeRemainingMessageIDs = { {
62   {
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
66   },
67   {
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
71   },
72   {
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
76   },
77   {
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
81   }
82 } };
83
84 static const MessageIDs kTimeRemainingLongMessageIDs = { {
85   {
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
89   },
90   {
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
94   },
95   {
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
99   },
100   {
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
104   }
105 } };
106
107 static const MessageIDs kTimeDurationLongMessageIDs = { {
108   {
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
112   },
113   {
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
117   },
118   {
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
122   },
123   {
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
127   }
128 } };
129
130 static const MessageIDs kTimeElapsedMessageIDs = { {
131   {
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
135   },
136   {
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
140   },
141   {
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
145   },
146   {
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
150   }
151 } };
152
153 // Different format types.
154 enum FormatType {
155   FORMAT_SHORT,
156   FORMAT_REMAINING,
157   FORMAT_REMAINING_LONG,
158   FORMAT_DURATION_LONG,
159   FORMAT_ELAPSED,
160 };
161
162 class TimeFormatter {
163   public:
164     const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
165       switch (format_type) {
166         case FORMAT_SHORT:
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();
174         case FORMAT_ELAPSED:
175           return time_elapsed_formatter_.get();
176         default:
177           NOTREACHED();
178           return short_formatter_.get();
179       }
180     }
181   private:
182     static const MessageIDs& GetMessageIDs(FormatType format_type) {
183       switch (format_type) {
184         case FORMAT_SHORT:
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;
192         case FORMAT_ELAPSED:
193           return kTimeElapsedMessageIDs;
194         default:
195           NOTREACHED();
196           return kTimeShortMessageIDs;
197       }
198     }
199
200     static const char* GetFallbackFormatSuffix(FormatType format_type) {
201       switch (format_type) {
202         case FORMAT_SHORT:
203           return kFallbackFormatSuffixShort;
204         case FORMAT_REMAINING:
205         case FORMAT_REMAINING_LONG:
206           return kFallbackFormatSuffixLeft;
207         case FORMAT_ELAPSED:
208           return kFallbackFormatSuffixAgo;
209         default:
210           NOTREACHED();
211           return kFallbackFormatSuffixShort;
212       }
213     }
214
215     TimeFormatter() {
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_);
221     }
222     ~TimeFormatter() {
223     }
224     friend struct base::DefaultLazyInstanceTraits<TimeFormatter>;
225
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);
235
236     DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
237 };
238
239 static base::LazyInstance<TimeFormatter> g_time_formatter =
240     LAZY_INSTANCE_INITIALIZER;
241
242 void TimeFormatter::BuildFormats(
243     FormatType format_type, ScopedVector<icu::PluralFormat>* time_formats) {
244   const MessageIDs& message_ids = GetMessageIDs(format_type);
245
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]);
251     }
252     scoped_ptr<icu::PluralFormat> format = l10n_util::BuildPluralFormat(ids);
253     if (format) {
254       time_formats->push_back(format.release());
255     } else {
256       scoped_ptr<icu::PluralRules> rules(l10n_util::BuildPluralRules());
257       time_formats->push_back(createFallbackFormat(*rules, i, format_type));
258     }
259   }
260 }
261
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") }
271   };
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;
276   }
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));
281   return format;
282 }
283
284 string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
285   if (delta.ToInternalValue() < 0) {
286     NOTREACHED() << "Negative duration";
287     return string16();
288   }
289
290   int number;
291
292   const std::vector<icu::PluralFormat*>& formatters =
293     g_time_formatter.Get().formatter(format_type);
294
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);
302
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);
308
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);
314
315   // Anything bigger gets "X days left"
316   } else {
317     number = static_cast<int>(delta.ToInternalValue() /
318                               Time::kMicrosecondsPerDay);
319     time_string = formatters[3]->format(number, error);
320   }
321
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);
326   string16 result;
327   time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
328                       capacity, error);
329   DCHECK(U_SUCCESS(error));
330   return result;
331 }
332
333 }  // namespace
334
335 namespace ui {
336
337 // static
338 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
339   return FormatTimeImpl(delta, FORMAT_ELAPSED);
340 }
341
342 // static
343 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
344   return FormatTimeImpl(delta, FORMAT_REMAINING);
345 }
346
347 // static
348 string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
349   return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
350 }
351
352 // static
353 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
354   return FormatTimeImpl(delta, FORMAT_SHORT);
355 }
356
357 // static
358 string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) {
359   return FormatTimeImpl(delta, FORMAT_DURATION_LONG);
360 }
361
362 // static
363 string16 TimeFormat::RelativeDate(
364     const Time& time,
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)
372     return string16();
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);
377   return string16();
378 }
379
380 }  // namespace ui