15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2011 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch#include "ui/base/l10n/time_format.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include <limits>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/lazy_instance.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/strings/string_util.h"
12eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/time/time.h"
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "third_party/icu/source/common/unicode/unistr.h"
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/base/l10n/formatter.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/base/ui_base_export.h"
171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "ui/strings/grit/ui_strings.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using base::Time;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using base::TimeDelta;
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)using ui::TimeFormat;
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace ui {
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)UI_BASE_EXPORT base::LazyInstance<FormatterContainer> g_container =
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    LAZY_INSTANCE_INITIALIZER;
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// static
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)base::string16 TimeFormat::Simple(TimeFormat::Format format,
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                  TimeFormat::Length length,
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                  const base::TimeDelta& delta) {
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return Detailed(format, length, 0, delta);
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// static
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)base::string16 TimeFormat::Detailed(TimeFormat::Format format,
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    TimeFormat::Length length,
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    int cutoff,
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    const base::TimeDelta& delta) {
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (delta < TimeDelta::FromSeconds(0)) {
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NOTREACHED() << "Negative duration";
42a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return base::string16();
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Negative cutoff: always use two-value format.
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (cutoff < 0)
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    cutoff = std::numeric_limits<int>::max();
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta one_minute(TimeDelta::FromMinutes(1));
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta one_hour(TimeDelta::FromHours(1));
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta one_day(TimeDelta::FromDays(1));
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta half_second(TimeDelta::FromMilliseconds(500));
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta half_minute(TimeDelta::FromSeconds(30));
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta half_hour(TimeDelta::FromMinutes(30));
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const TimeDelta half_day(TimeDelta::FromHours(12));
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Rationale: Start by determining major (first) unit, then add minor (second)
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // unit if mandated by |cutoff|.
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  icu::UnicodeString time_string;
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  const Formatter* formatter = g_container.Get().Get(format, length);
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (delta < one_minute - half_second) {
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Anything up to 59.500 seconds is formatted as seconds.
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const int seconds = static_cast<int>((delta + half_second).InSeconds());
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    formatter->Format(Formatter::UNIT_SEC, seconds, time_string);
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  } else if (delta < one_hour - (cutoff < 60 ? half_minute : half_second)) {
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Anything up to 59.5 minutes (respectively 59:59.500 when |cutoff| permits
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // two-value output) is formatted as minutes (respectively minutes and
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // seconds).
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (delta >= cutoff * one_minute - half_second) {
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int minutes = (delta + half_minute).InMinutes();
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::UNIT_MIN, minutes, time_string);
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    } else {
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int minutes = (delta + half_second).InMinutes();
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int seconds = static_cast<int>(
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          (delta + half_second).InSeconds() % 60);
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::TWO_UNITS_MIN_SEC,
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                        minutes, seconds, time_string);
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  } else if (delta < one_day - (cutoff < 24 ? half_hour : half_minute)) {
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Anything up to 23.5 hours (respectively 23:59:30.000 when |cutoff|
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // permits two-value output) is formatted as hours (respectively hours and
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // minutes).
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (delta >= cutoff * one_hour - half_minute) {
86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int hours = (delta + half_hour).InHours();
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::UNIT_HOUR, hours, time_string);
88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    } else {
89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int hours = (delta + half_minute).InHours();
90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int minutes = (delta + half_minute).InMinutes() % 60;
91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::TWO_UNITS_HOUR_MIN,
92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                        hours, minutes, time_string);
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Anything bigger is formatted as days (respectively days and hours).
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (delta >= cutoff * one_day - half_hour) {
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int days = (delta + half_day).InDays();
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::UNIT_DAY, days, time_string);
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    } else {
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int days = (delta + half_hour).InDays();
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      const int hours = (delta + half_hour).InHours() % 24;
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      formatter->Format(Formatter::TWO_UNITS_DAY_HOUR,
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                        days, hours, time_string);
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  const int capacity = time_string.length() + 1;
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK_GT(capacity, 1);
110a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  base::string16 result;
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  UErrorCode error = U_ZERO_ERROR;
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      capacity, error);
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(U_SUCCESS(error));
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return result;
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// static
119a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)base::string16 TimeFormat::RelativeDate(
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const Time& time,
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const Time* optional_midnight_today) {
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Time midnight_today = optional_midnight_today ? *optional_midnight_today :
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Time::Now().LocalMidnight();
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TimeDelta day = TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay);
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Time tomorrow = midnight_today + day;
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Time yesterday = midnight_today - day;
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (time >= tomorrow)
128a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return base::string16();
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (time >= midnight_today)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (time >= yesterday)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);
133a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  return base::string16();
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
135bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch
136bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch}  // namespace ui
137