blob: 2e51714bc35dfb5963e528fc4840ed126ad057cb [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/l10n/time_format.h"
#include <limits>
#include "base/check_op.h"
#include "base/component_export.h"
#include "base/lazy_instance.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "ui/base/l10n/formatter.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/strings/grit/ui_strings.h"
using base::TimeDelta;
using ui::TimeFormat;
namespace ui {
COMPONENT_EXPORT(UI_BASE)
base::LazyInstance<FormatterContainer>::Leaky g_container =
LAZY_INSTANCE_INITIALIZER;
// static
base::string16 TimeFormat::Simple(TimeFormat::Format format,
TimeFormat::Length length,
const base::TimeDelta& delta) {
return Detailed(format, length, 0, delta);
}
base::string16 TimeFormat::SimpleWithMonthAndYear(TimeFormat::Format format,
TimeFormat::Length length,
const base::TimeDelta& delta,
bool with_month_and_year) {
return DetailedWithMonthAndYear(format, length, 0, delta,
with_month_and_year);
}
// static
base::string16 TimeFormat::Detailed(TimeFormat::Format format,
TimeFormat::Length length,
int cutoff,
const base::TimeDelta& delta) {
return DetailedWithMonthAndYear(format, length, cutoff, delta, false);
}
base::string16 TimeFormat::DetailedWithMonthAndYear(
TimeFormat::Format format,
TimeFormat::Length length,
int cutoff,
const base::TimeDelta& delta,
bool with_month_and_year) {
DCHECK_GE(delta, TimeDelta());
// Negative cutoff: always use two-value format.
if (cutoff < 0)
cutoff = std::numeric_limits<int>::max();
constexpr TimeDelta kMinute = TimeDelta::FromMinutes(1);
constexpr TimeDelta kHour = TimeDelta::FromHours(1);
constexpr TimeDelta kDay = TimeDelta::FromDays(1);
// Simplify one year to be 365 days.
constexpr TimeDelta kYear = 365 * kDay;
// An average month is a twelfth of a year.
constexpr TimeDelta kMonth = kYear / 12;
constexpr TimeDelta kHalfSecond = TimeDelta::FromSeconds(1) / 2;
constexpr TimeDelta kHalfMinute = kMinute / 2;
constexpr TimeDelta kHalfHour = kHour / 2;
constexpr TimeDelta kHalfDay = kDay / 2;
// Rationale: Start by determining major (first) unit, then add minor (second)
// unit if mandated by |cutoff|.
icu::UnicodeString time_string;
const Formatter* formatter = g_container.Get().Get(format, length);
if (delta < kMinute - kHalfSecond) {
// Anything up to 59.500 seconds is formatted as seconds.
const int seconds = base::ClampRound(delta.InSecondsF());
formatter->Format(Formatter::UNIT_SEC, seconds, &time_string);
} else if (delta < kHour - (cutoff < base::Time::kMinutesPerHour
? kHalfMinute
: kHalfSecond)) {
// Anything up to 59.5 minutes (respectively 59:59.500 when |cutoff| permits
// two-value output) is formatted as minutes (respectively minutes and
// seconds).
if (delta >= cutoff * kMinute - kHalfSecond) {
const int minutes = (delta + kHalfMinute).InMinutes();
formatter->Format(Formatter::UNIT_MIN, minutes, &time_string);
} else {
const int minutes = (delta + kHalfSecond).InMinutes();
const int seconds =
base::ClampRound(delta.InSecondsF()) % base::Time::kSecondsPerMinute;
formatter->Format(Formatter::TWO_UNITS_MIN_SEC,
minutes, seconds, &time_string);
}
} else if (delta < kDay - (cutoff < base::Time::kHoursPerDay ? kHalfHour
: kHalfMinute)) {
// Anything up to 23.5 hours (respectively 23:59:30.000 when |cutoff|
// permits two-value output) is formatted as hours (respectively hours and
// minutes).
if (delta >= cutoff * kHour - kHalfMinute) {
const int hours = (delta + kHalfHour).InHours();
formatter->Format(Formatter::UNIT_HOUR, hours, &time_string);
} else {
const int hours = (delta + kHalfMinute).InHours();
const int minutes =
(delta + kHalfMinute).InMinutes() % base::Time::kMinutesPerHour;
formatter->Format(Formatter::TWO_UNITS_HOUR_MIN,
hours, minutes, &time_string);
}
} else if (!with_month_and_year || delta < kMonth) {
// Anything bigger is formatted as days (respectively days and hours).
if (delta >= cutoff * kDay - kHalfHour) {
const int days = (delta + kHalfDay).InDays();
formatter->Format(Formatter::UNIT_DAY, days, &time_string);
} else {
const int days = (delta + kHalfHour).InDays();
const int hours =
(delta + kHalfHour).InHours() % base::Time::kHoursPerDay;
formatter->Format(Formatter::TWO_UNITS_DAY_HOUR, days, hours,
&time_string);
}
} else if (delta < kYear) {
DCHECK(with_month_and_year);
const int month = base::ClampFloor(delta / kMonth);
DCHECK_GE(month, 1);
DCHECK_LE(month, 12);
formatter->Format(Formatter::UNIT_MONTH, month, &time_string);
} else {
DCHECK(with_month_and_year);
const int year = base::ClampFloor(delta / kYear);
formatter->Format(Formatter::UNIT_YEAR, year, &time_string);
}
const int capacity = time_string.length() + 1;
DCHECK_GT(capacity, 1);
base::string16 result;
UErrorCode error = U_ZERO_ERROR;
time_string.extract(static_cast<UChar*>(base::WriteInto(&result, capacity)),
capacity, error);
DCHECK(U_SUCCESS(error));
return result;
}
// static
base::string16 TimeFormat::RelativeDate(
const base::Time& time,
const base::Time* optional_midnight_today) {
const base::Time midnight_today = optional_midnight_today
? *optional_midnight_today
: base::Time::Now().LocalMidnight();
constexpr TimeDelta kDay = TimeDelta::FromDays(1);
const base::Time tomorrow = midnight_today + kDay;
const base::Time yesterday = midnight_today - kDay;
if (time >= tomorrow)
return base::string16();
if (time >= midnight_today)
return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
return (time >= yesterday)
? l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY)
: base::string16();
}
} // namespace ui