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 
     25 using base::Time;
     26 using base::TimeDelta;
     27 
     28 namespace {
     29 
     30 static const char kFallbackFormatSuffixShort[] = "}";
     31 static const char kFallbackFormatSuffixLeft[] = " left}";
     32 static const char kFallbackFormatSuffixAgo[] = " ago}";
     33 
     34 // Contains message IDs for various time units and pluralities.
     35 struct MessageIDs {
     36   // There are 4 different time units and 6 different pluralities.
     37   int ids[4][6];
     38 };
     39 
     40 // Message IDs for different time formats.
     41 static const MessageIDs kTimeShortMessageIDs = { {
     42   {
     43     IDS_TIME_SECS_DEFAULT, IDS_TIME_SEC_SINGULAR, IDS_TIME_SECS_ZERO,
     44     IDS_TIME_SECS_TWO, IDS_TIME_SECS_FEW, IDS_TIME_SECS_MANY
     45   },
     46   {
     47     IDS_TIME_MINS_DEFAULT, IDS_TIME_MIN_SINGULAR, IDS_TIME_MINS_ZERO,
     48     IDS_TIME_MINS_TWO, IDS_TIME_MINS_FEW, IDS_TIME_MINS_MANY
     49   },
     50   {
     51     IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR, IDS_TIME_HOURS_ZERO,
     52     IDS_TIME_HOURS_TWO, IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY
     53   },
     54   {
     55     IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR, IDS_TIME_DAYS_ZERO,
     56     IDS_TIME_DAYS_TWO, IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY
     57   }
     58 } };
     59 
     60 static const MessageIDs kTimeRemainingMessageIDs = { {
     61   {
     62     IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
     63     IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
     64     IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
     65   },
     66   {
     67     IDS_TIME_REMAINING_MINS_DEFAULT, IDS_TIME_REMAINING_MIN_SINGULAR,
     68     IDS_TIME_REMAINING_MINS_ZERO, IDS_TIME_REMAINING_MINS_TWO,
     69     IDS_TIME_REMAINING_MINS_FEW, IDS_TIME_REMAINING_MINS_MANY
     70   },
     71   {
     72     IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
     73     IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
     74     IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
     75   },
     76   {
     77     IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
     78     IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
     79     IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
     80   }
     81 } };
     82 
     83 static const MessageIDs kTimeRemainingLongMessageIDs = { {
     84   {
     85     IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
     86     IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
     87     IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
     88   },
     89   {
     90     IDS_TIME_REMAINING_LONG_MINS_DEFAULT, IDS_TIME_REMAINING_LONG_MIN_SINGULAR,
     91     IDS_TIME_REMAINING_LONG_MINS_ZERO, IDS_TIME_REMAINING_LONG_MINS_TWO,
     92     IDS_TIME_REMAINING_LONG_MINS_FEW, IDS_TIME_REMAINING_LONG_MINS_MANY
     93   },
     94   {
     95     IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
     96     IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
     97     IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
     98   },
     99   {
    100     IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
    101     IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
    102     IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
    103   }
    104 } };
    105 
    106 static const MessageIDs kTimeDurationLongMessageIDs = { {
    107   {
    108     IDS_TIME_DURATION_LONG_SECS_DEFAULT, IDS_TIME_DURATION_LONG_SEC_SINGULAR,
    109     IDS_TIME_DURATION_LONG_SECS_ZERO, IDS_TIME_DURATION_LONG_SECS_TWO,
    110     IDS_TIME_DURATION_LONG_SECS_FEW, IDS_TIME_DURATION_LONG_SECS_MANY
    111   },
    112   {
    113     IDS_TIME_DURATION_LONG_MINS_DEFAULT, IDS_TIME_DURATION_LONG_MIN_SINGULAR,
    114     IDS_TIME_DURATION_LONG_MINS_ZERO, IDS_TIME_DURATION_LONG_MINS_TWO,
    115     IDS_TIME_DURATION_LONG_MINS_FEW, IDS_TIME_DURATION_LONG_MINS_MANY
    116   },
    117   {
    118     IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR,
    119     IDS_TIME_HOURS_ZERO, IDS_TIME_HOURS_TWO,
    120     IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY
    121   },
    122   {
    123     IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR,
    124     IDS_TIME_DAYS_ZERO, IDS_TIME_DAYS_TWO,
    125     IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY
    126   }
    127 } };
    128 
    129 static const MessageIDs kTimeElapsedMessageIDs = { {
    130   {
    131     IDS_TIME_ELAPSED_SECS_DEFAULT, IDS_TIME_ELAPSED_SEC_SINGULAR,
    132     IDS_TIME_ELAPSED_SECS_ZERO, IDS_TIME_ELAPSED_SECS_TWO,
    133     IDS_TIME_ELAPSED_SECS_FEW, IDS_TIME_ELAPSED_SECS_MANY
    134   },
    135   {
    136     IDS_TIME_ELAPSED_MINS_DEFAULT, IDS_TIME_ELAPSED_MIN_SINGULAR,
    137     IDS_TIME_ELAPSED_MINS_ZERO, IDS_TIME_ELAPSED_MINS_TWO,
    138     IDS_TIME_ELAPSED_MINS_FEW, IDS_TIME_ELAPSED_MINS_MANY
    139   },
    140   {
    141     IDS_TIME_ELAPSED_HOURS_DEFAULT, IDS_TIME_ELAPSED_HOUR_SINGULAR,
    142     IDS_TIME_ELAPSED_HOURS_ZERO, IDS_TIME_ELAPSED_HOURS_TWO,
    143     IDS_TIME_ELAPSED_HOURS_FEW, IDS_TIME_ELAPSED_HOURS_MANY
    144   },
    145   {
    146     IDS_TIME_ELAPSED_DAYS_DEFAULT, IDS_TIME_ELAPSED_DAY_SINGULAR,
    147     IDS_TIME_ELAPSED_DAYS_ZERO, IDS_TIME_ELAPSED_DAYS_TWO,
    148     IDS_TIME_ELAPSED_DAYS_FEW, IDS_TIME_ELAPSED_DAYS_MANY
    149   }
    150 } };
    151 
    152 // Different format types.
    153 enum FormatType {
    154   FORMAT_SHORT,
    155   FORMAT_REMAINING,
    156   FORMAT_REMAINING_LONG,
    157   FORMAT_DURATION_LONG,
    158   FORMAT_ELAPSED,
    159 };
    160 
    161 class TimeFormatter {
    162   public:
    163     const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
    164       switch (format_type) {
    165         case FORMAT_SHORT:
    166           return short_formatter_.get();
    167         case FORMAT_REMAINING:
    168           return time_left_formatter_.get();
    169         case FORMAT_REMAINING_LONG:
    170           return time_left_long_formatter_.get();
    171         case FORMAT_DURATION_LONG:
    172           return time_duration_long_formatter_.get();
    173         case FORMAT_ELAPSED:
    174           return time_elapsed_formatter_.get();
    175         default:
    176           NOTREACHED();
    177           return short_formatter_.get();
    178       }
    179     }
    180   private:
    181     static const MessageIDs& GetMessageIDs(FormatType format_type) {
    182       switch (format_type) {
    183         case FORMAT_SHORT:
    184           return kTimeShortMessageIDs;
    185         case FORMAT_REMAINING:
    186           return kTimeRemainingMessageIDs;
    187         case FORMAT_REMAINING_LONG:
    188           return kTimeRemainingLongMessageIDs;
    189         case FORMAT_DURATION_LONG:
    190           return kTimeDurationLongMessageIDs;
    191         case FORMAT_ELAPSED:
    192           return kTimeElapsedMessageIDs;
    193         default:
    194           NOTREACHED();
    195           return kTimeShortMessageIDs;
    196       }
    197     }
    198 
    199     static const char* GetFallbackFormatSuffix(FormatType format_type) {
    200       switch (format_type) {
    201         case FORMAT_SHORT:
    202           return kFallbackFormatSuffixShort;
    203         case FORMAT_REMAINING:
    204         case FORMAT_REMAINING_LONG:
    205           return kFallbackFormatSuffixLeft;
    206         case FORMAT_ELAPSED:
    207           return kFallbackFormatSuffixAgo;
    208         default:
    209           NOTREACHED();
    210           return kFallbackFormatSuffixShort;
    211       }
    212     }
    213 
    214     TimeFormatter() {
    215       BuildFormats(FORMAT_SHORT, &short_formatter_);
    216       BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
    217       BuildFormats(FORMAT_REMAINING_LONG, &time_left_long_formatter_);
    218       BuildFormats(FORMAT_DURATION_LONG, &time_duration_long_formatter_);
    219       BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
    220     }
    221     ~TimeFormatter() {
    222     }
    223     friend struct base::DefaultLazyInstanceTraits<TimeFormatter>;
    224 
    225     ScopedVector<icu::PluralFormat> short_formatter_;
    226     ScopedVector<icu::PluralFormat> time_left_formatter_;
    227     ScopedVector<icu::PluralFormat> time_left_long_formatter_;
    228     ScopedVector<icu::PluralFormat> time_duration_long_formatter_;
    229     ScopedVector<icu::PluralFormat> time_elapsed_formatter_;
    230     static void BuildFormats(FormatType format_type,
    231                              ScopedVector<icu::PluralFormat>* time_formats);
    232     static icu::PluralFormat* createFallbackFormat(
    233         const icu::PluralRules& rules, int index, FormatType format_type);
    234 
    235     DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
    236 };
    237 
    238 static base::LazyInstance<TimeFormatter> g_time_formatter =
    239     LAZY_INSTANCE_INITIALIZER;
    240 
    241 void TimeFormatter::BuildFormats(
    242     FormatType format_type, ScopedVector<icu::PluralFormat>* time_formats) {
    243   const icu::UnicodeString kKeywords[] = {
    244     UNICODE_STRING_SIMPLE("other"), UNICODE_STRING_SIMPLE("one"),
    245     UNICODE_STRING_SIMPLE("zero"), UNICODE_STRING_SIMPLE("two"),
    246     UNICODE_STRING_SIMPLE("few"), UNICODE_STRING_SIMPLE("many")
    247   };
    248   UErrorCode err = U_ZERO_ERROR;
    249   scoped_ptr<icu::PluralRules> rules(
    250       icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
    251   if (U_FAILURE(err)) {
    252     err = U_ZERO_ERROR;
    253     icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
    254     rules.reset(icu::PluralRules::createRules(fallback_rules, err));
    255     DCHECK(U_SUCCESS(err));
    256   }
    257 
    258   const MessageIDs& message_ids = GetMessageIDs(format_type);
    259 
    260   for (int i = 0; i < 4; ++i) {
    261     icu::UnicodeString pattern;
    262     for (size_t j = 0; j < arraysize(kKeywords); ++j) {
    263       int msg_id = message_ids.ids[i][j];
    264       std::string sub_pattern = l10n_util::GetStringUTF8(msg_id);
    265       // NA means this keyword is not used in the current locale.
    266       // Even if a translator translated for this keyword, we do not
    267       // use it unless it's 'other' (j=0) or it's defined in the rules
    268       // for the current locale. Special-casing of 'other' will be removed
    269       // once ICU's isKeyword is fixed to return true for isKeyword('other').
    270       if (sub_pattern.compare("NA") != 0 &&
    271           (j == 0 || rules->isKeyword(kKeywords[j]))) {
    272           pattern += kKeywords[j];
    273           pattern += UNICODE_STRING_SIMPLE("{");
    274           pattern += icu::UnicodeString(sub_pattern.c_str(), "UTF-8");
    275           pattern += UNICODE_STRING_SIMPLE("}");
    276       }
    277     }
    278     icu::PluralFormat* format = new icu::PluralFormat(*rules, pattern, err);
    279     if (U_SUCCESS(err)) {
    280       time_formats->push_back(format);
    281     } else {
    282       delete format;
    283       time_formats->push_back(createFallbackFormat(*rules, i, format_type));
    284       // Reset it so that next ICU call can proceed.
    285       err = U_ZERO_ERROR;
    286     }
    287   }
    288 }
    289 
    290 // Create a hard-coded fallback plural format. This will never be called
    291 // unless translators make a mistake.
    292 icu::PluralFormat* TimeFormatter::createFallbackFormat(
    293     const icu::PluralRules& rules, int index, FormatType format_type) {
    294   const icu::UnicodeString kUnits[4][2] = {
    295     { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") },
    296     { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") },
    297     { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") },
    298     { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") }
    299   };
    300   icu::UnicodeString suffix(GetFallbackFormatSuffix(format_type), -1, US_INV);
    301   icu::UnicodeString pattern;
    302   if (rules.isKeyword(UNICODE_STRING_SIMPLE("one"))) {
    303     pattern += UNICODE_STRING_SIMPLE("one{# ") + kUnits[index][0] + suffix;
    304   }
    305   pattern += UNICODE_STRING_SIMPLE(" other{# ") + kUnits[index][1] + suffix;
    306   UErrorCode err = U_ZERO_ERROR;
    307   icu::PluralFormat* format = new icu::PluralFormat(rules, pattern, err);
    308   DCHECK(U_SUCCESS(err));
    309   return format;
    310 }
    311 
    312 string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
    313   if (delta.ToInternalValue() < 0) {
    314     NOTREACHED() << "Negative duration";
    315     return string16();
    316   }
    317 
    318   int number;
    319 
    320   const std::vector<icu::PluralFormat*>& formatters =
    321     g_time_formatter.Get().formatter(format_type);
    322 
    323   UErrorCode error = U_ZERO_ERROR;
    324   icu::UnicodeString time_string;
    325   // Less than a minute gets "X seconds left"
    326   if (delta.ToInternalValue() < Time::kMicrosecondsPerMinute) {
    327     number = static_cast<int>(delta.ToInternalValue() /
    328                               Time::kMicrosecondsPerSecond);
    329     time_string = formatters[0]->format(number, error);
    330 
    331   // Less than 1 hour gets "X minutes left".
    332   } else if (delta.ToInternalValue() < Time::kMicrosecondsPerHour) {
    333     number = static_cast<int>(delta.ToInternalValue() /
    334                               Time::kMicrosecondsPerMinute);
    335     time_string = formatters[1]->format(number, error);
    336 
    337   // Less than 1 day remaining gets "X hours left"
    338   } else if (delta.ToInternalValue() < Time::kMicrosecondsPerDay) {
    339     number = static_cast<int>(delta.ToInternalValue() /
    340                               Time::kMicrosecondsPerHour);
    341     time_string = formatters[2]->format(number, error);
    342 
    343   // Anything bigger gets "X days left"
    344   } else {
    345     number = static_cast<int>(delta.ToInternalValue() /
    346                               Time::kMicrosecondsPerDay);
    347     time_string = formatters[3]->format(number, error);
    348   }
    349 
    350   // With the fallback added, this should never fail.
    351   DCHECK(U_SUCCESS(error));
    352   int capacity = time_string.length() + 1;
    353   DCHECK_GT(capacity, 1);
    354   string16 result;
    355   time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
    356                       capacity, error);
    357   DCHECK(U_SUCCESS(error));
    358   return result;
    359 }
    360 
    361 }  // namespace
    362 
    363 namespace ui {
    364 
    365 // static
    366 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
    367   return FormatTimeImpl(delta, FORMAT_ELAPSED);
    368 }
    369 
    370 // static
    371 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
    372   return FormatTimeImpl(delta, FORMAT_REMAINING);
    373 }
    374 
    375 // static
    376 string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
    377   return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
    378 }
    379 
    380 // static
    381 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
    382   return FormatTimeImpl(delta, FORMAT_SHORT);
    383 }
    384 
    385 // static
    386 string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) {
    387   return FormatTimeImpl(delta, FORMAT_DURATION_LONG);
    388 }
    389 
    390 // static
    391 string16 TimeFormat::RelativeDate(
    392     const Time& time,
    393     const Time* optional_midnight_today) {
    394   Time midnight_today = optional_midnight_today ? *optional_midnight_today :
    395       Time::Now().LocalMidnight();
    396   TimeDelta day = TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay);
    397   Time tomorrow = midnight_today + day;
    398   Time yesterday = midnight_today - day;
    399   if (time >= tomorrow)
    400     return string16();
    401   else if (time >= midnight_today)
    402     return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
    403   else if (time >= yesterday)
    404     return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);
    405   return string16();
    406 }
    407 
    408 }  // namespace ui
    409