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