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