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 26using base::Time; 27using base::TimeDelta; 28 29namespace { 30 31static const char kFallbackFormatSuffixShort[] = "}"; 32static const char kFallbackFormatSuffixLeft[] = " left}"; 33static const char kFallbackFormatSuffixAgo[] = " ago}"; 34 35// Contains message IDs for various time units and pluralities. 36struct 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. 42static 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 61static 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 84static 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 107static 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 130static 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. 154enum FormatType { 155 FORMAT_SHORT, 156 FORMAT_REMAINING, 157 FORMAT_REMAINING_LONG, 158 FORMAT_DURATION_LONG, 159 FORMAT_ELAPSED, 160}; 161 162class 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 239static base::LazyInstance<TimeFormatter> g_time_formatter = 240 LAZY_INSTANCE_INITIALIZER; 241 242void 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. 264icu::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 284base::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 335namespace ui { 336 337// static 338base::string16 TimeFormat::TimeElapsed(const TimeDelta& delta) { 339 return FormatTimeImpl(delta, FORMAT_ELAPSED); 340} 341 342// static 343base::string16 TimeFormat::TimeRemaining(const TimeDelta& delta) { 344 return FormatTimeImpl(delta, FORMAT_REMAINING); 345} 346 347// static 348base::string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) { 349 return FormatTimeImpl(delta, FORMAT_REMAINING_LONG); 350} 351 352// static 353base::string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) { 354 return FormatTimeImpl(delta, FORMAT_SHORT); 355} 356 357// static 358base::string16 TimeFormat::TimeDurationLong(const TimeDelta& delta) { 359 return FormatTimeImpl(delta, FORMAT_DURATION_LONG); 360} 361 362// static 363base::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