Home | History | Annotate | Download | only in common
      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:
    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:
    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 
    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 
    158     TimeFormatter() {
    159       BuildFormats(FORMAT_SHORT, &short_formatter_);
    160       BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
    161       BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
    162     }
    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 
    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.
    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 
    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
    308 string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
    309   return FormatTimeImpl(delta, FORMAT_ELAPSED);
    310 }
    311 
    312 // static
    313 string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
    314   return FormatTimeImpl(delta, FORMAT_REMAINING);
    315 }
    316 
    317 // static
    318 string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
    319   return FormatTimeImpl(delta, FORMAT_SHORT);
    320 }
    321 
    322 // static
    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