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
26using base::Time;
27using base::TimeDelta;
28
29namespace {
30
31static const char kFallbackFormatSuffixShort[] = "}";
32static const char kFallbackFormatSuffixLeft[] = " left}";
33static const char kFallbackFormatSuffixAgo[] = " ago}";
34
35// Contains message IDs for various time units and pluralities.
36struct 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.
42static 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
61static 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
84static 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
107static 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
130static 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.
154enum FormatType {
155  FORMAT_SHORT,
156  FORMAT_REMAINING,
157  FORMAT_REMAINING_LONG,
158  FORMAT_DURATION_LONG,
159  FORMAT_ELAPSED,
160};
161
162class 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
239static base::LazyInstance<TimeFormatter> g_time_formatter =
240    LAZY_INSTANCE_INITIALIZER;
241
242void 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.
264icu::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
284base::string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
285  if (delta.ToInternalValue() < 0) {
286    NOTREACHED() << "Negative duration";
287    return base::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  base::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
335namespace ui {
336
337// static
338base::string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
339  return FormatTimeImpl(delta, FORMAT_ELAPSED);
340}
341
342// static
343base::string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
344  return FormatTimeImpl(delta, FORMAT_REMAINING);
345}
346
347// static
348base::string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
349  return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
350}
351
352// static
353base::string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
354  return FormatTimeImpl(delta, FORMAT_SHORT);
355}
356
357// static
358base::string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) {
359  return FormatTimeImpl(delta, FORMAT_DURATION_LONG);
360}
361
362// static
363base::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 base::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 base::string16();
378}
379
380}  // namespace ui
381