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