Home | History | Annotate | Download | only in l10n
      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 base::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 
    335 namespace ui {
    336 
    337 // static
    338 base::string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
    339   return FormatTimeImpl(delta, FORMAT_ELAPSED);
    340 }
    341 
    342 // static
    343 base::string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
    344   return FormatTimeImpl(delta, FORMAT_REMAINING);
    345 }
    346 
    347 // static
    348 base::string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
    349   return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
    350 }
    351 
    352 // static
    353 base::string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
    354   return FormatTimeImpl(delta, FORMAT_SHORT);
    355 }
    356 
    357 // static
    358 base::string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) {
    359   return FormatTimeImpl(delta, FORMAT_DURATION_LONG);
    360 }
    361 
    362 // static
    363 base::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