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 <limits>
8
9#include "base/lazy_instance.h"
10#include "base/logging.h"
11#include "base/strings/string_util.h"
12#include "base/time/time.h"
13#include "third_party/icu/source/common/unicode/unistr.h"
14#include "ui/base/l10n/formatter.h"
15#include "ui/base/l10n/l10n_util.h"
16#include "ui/base/ui_base_export.h"
17#include "ui/strings/grit/ui_strings.h"
18
19using base::Time;
20using base::TimeDelta;
21using ui::TimeFormat;
22
23namespace ui {
24
25UI_BASE_EXPORT base::LazyInstance<FormatterContainer> g_container =
26    LAZY_INSTANCE_INITIALIZER;
27
28// static
29base::string16 TimeFormat::Simple(TimeFormat::Format format,
30                                  TimeFormat::Length length,
31                                  const base::TimeDelta& delta) {
32  return Detailed(format, length, 0, delta);
33}
34
35// static
36base::string16 TimeFormat::Detailed(TimeFormat::Format format,
37                                    TimeFormat::Length length,
38                                    int cutoff,
39                                    const base::TimeDelta& delta) {
40  if (delta < TimeDelta::FromSeconds(0)) {
41    NOTREACHED() << "Negative duration";
42    return base::string16();
43  }
44
45  // Negative cutoff: always use two-value format.
46  if (cutoff < 0)
47    cutoff = std::numeric_limits<int>::max();
48
49  const TimeDelta one_minute(TimeDelta::FromMinutes(1));
50  const TimeDelta one_hour(TimeDelta::FromHours(1));
51  const TimeDelta one_day(TimeDelta::FromDays(1));
52  const TimeDelta half_second(TimeDelta::FromMilliseconds(500));
53  const TimeDelta half_minute(TimeDelta::FromSeconds(30));
54  const TimeDelta half_hour(TimeDelta::FromMinutes(30));
55  const TimeDelta half_day(TimeDelta::FromHours(12));
56
57  // Rationale: Start by determining major (first) unit, then add minor (second)
58  // unit if mandated by |cutoff|.
59  icu::UnicodeString time_string;
60  const Formatter* formatter = g_container.Get().Get(format, length);
61  if (delta < one_minute - half_second) {
62    // Anything up to 59.500 seconds is formatted as seconds.
63    const int seconds = static_cast<int>((delta + half_second).InSeconds());
64    formatter->Format(Formatter::UNIT_SEC, seconds, time_string);
65
66  } else if (delta < one_hour - (cutoff < 60 ? half_minute : half_second)) {
67    // Anything up to 59.5 minutes (respectively 59:59.500 when |cutoff| permits
68    // two-value output) is formatted as minutes (respectively minutes and
69    // seconds).
70    if (delta >= cutoff * one_minute - half_second) {
71      const int minutes = (delta + half_minute).InMinutes();
72      formatter->Format(Formatter::UNIT_MIN, minutes, time_string);
73    } else {
74      const int minutes = (delta + half_second).InMinutes();
75      const int seconds = static_cast<int>(
76          (delta + half_second).InSeconds() % 60);
77      formatter->Format(Formatter::TWO_UNITS_MIN_SEC,
78                        minutes, seconds, time_string);
79    }
80
81  } else if (delta < one_day - (cutoff < 24 ? half_hour : half_minute)) {
82    // Anything up to 23.5 hours (respectively 23:59:30.000 when |cutoff|
83    // permits two-value output) is formatted as hours (respectively hours and
84    // minutes).
85    if (delta >= cutoff * one_hour - half_minute) {
86      const int hours = (delta + half_hour).InHours();
87      formatter->Format(Formatter::UNIT_HOUR, hours, time_string);
88    } else {
89      const int hours = (delta + half_minute).InHours();
90      const int minutes = (delta + half_minute).InMinutes() % 60;
91      formatter->Format(Formatter::TWO_UNITS_HOUR_MIN,
92                        hours, minutes, time_string);
93    }
94
95  } else {
96    // Anything bigger is formatted as days (respectively days and hours).
97    if (delta >= cutoff * one_day - half_hour) {
98      const int days = (delta + half_day).InDays();
99      formatter->Format(Formatter::UNIT_DAY, days, time_string);
100    } else {
101      const int days = (delta + half_hour).InDays();
102      const int hours = (delta + half_hour).InHours() % 24;
103      formatter->Format(Formatter::TWO_UNITS_DAY_HOUR,
104                        days, hours, time_string);
105    }
106  }
107
108  const int capacity = time_string.length() + 1;
109  DCHECK_GT(capacity, 1);
110  base::string16 result;
111  UErrorCode error = U_ZERO_ERROR;
112  time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
113                      capacity, error);
114  DCHECK(U_SUCCESS(error));
115  return result;
116}
117
118// static
119base::string16 TimeFormat::RelativeDate(
120    const Time& time,
121    const Time* optional_midnight_today) {
122  Time midnight_today = optional_midnight_today ? *optional_midnight_today :
123      Time::Now().LocalMidnight();
124  TimeDelta day = TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay);
125  Time tomorrow = midnight_today + day;
126  Time yesterday = midnight_today - day;
127  if (time >= tomorrow)
128    return base::string16();
129  else if (time >= midnight_today)
130    return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
131  else if (time >= yesterday)
132    return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);
133  return base::string16();
134}
135
136}  // namespace ui
137