chromium/ash/system/time/calendar_utils.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/time/calendar_utils.h"

#include <optional>
#include <string>

#include "ash/calendar/calendar_client.h"
#include "ash/calendar/calendar_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/color_util.h"
#include "ash/system/time/date_helper.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "components/user_manager/user_type.h"
#include "ui/views/layout/table_layout.h"

namespace ash::calendar_utils {

bool IsMultiCalendarEnabled() {
  return features::IsMultiCalendarSupportEnabled();
}

bool IsToday(const base::Time selected_date) {
  return IsTheSameDay(selected_date, base::Time::Now());
}

bool IsTheSameDay(std::optional<base::Time> date_a,
                  std::optional<base::Time> date_b) {
  if (!date_a.has_value() || !date_b.has_value()) {
    return false;
  }

  return calendar_utils::GetMonthDayYear(date_a.value()) ==
         calendar_utils::GetMonthDayYear(date_b.value());
}

ASH_EXPORT std::set<base::Time> GetSurroundingMonthsUTC(
    const base::Time& selected_date,
    int num_months_out) {
  std::set<base::Time> months;

  // First month is the one that contains |selected_date|.
  base::Time selected_date_start =
      calendar_utils::GetStartOfMonthUTC(selected_date);
  months.emplace(selected_date_start);

  // Add |num_months_out| before and after.
  base::Time current_forward = selected_date_start;
  base::Time current_backward = selected_date_start;
  for (int i = 0; i < num_months_out; ++i) {
    current_forward = calendar_utils::GetStartOfNextMonthUTC(current_forward);
    months.emplace(current_forward);
    current_backward =
        calendar_utils::GetStartOfPreviousMonthUTC(current_backward);
    months.emplace(current_backward);
  }

  return months;
}

base::Time::Exploded GetExplodedUTC(const base::Time& date) {
  base::Time::Exploded exploded;
  date.UTCExplode(&exploded);
  return exploded;
}

std::u16string FormatDate(const icu::SimpleDateFormat& formatter,
                          const base::Time date) {
  return DateHelper::GetInstance()->GetFormattedTime(&formatter, date);
}

std::u16string FormatInterval(const icu::DateIntervalFormat* formatter,
                              const base::Time& start_time,
                              const base::Time& end_time) {
  return DateHelper::GetInstance()->GetFormattedInterval(formatter, start_time,
                                                         end_time);
}

std::u16string GetMonthDayYear(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->month_day_year_formatter(), date);
}

std::u16string GetMonthDayYearWeek(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->month_day_year_week_formatter(), date);
}

std::u16string GetMonthName(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->month_name_formatter(), date);
}

std::u16string GetDayOfMonth(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->day_of_month_formatter(), date);
}

std::u16string GetDayIntOfMonth(const base::Time local_date) {
  return base::UTF8ToUTF16(base::NumberToString(
      calendar_utils::GetExplodedUTC(local_date).day_of_month));
}

std::u16string GetMonthNameAndDayOfMonth(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->month_day_formatter(), date);
}

std::u16string GetTwelveHourClockTime(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->twelve_hour_clock_formatter(), date);
}

std::u16string GetTwentyFourHourClockTime(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->twenty_four_hour_clock_formatter(), date);
}

std::u16string GetTimeZone(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->time_zone_formatter(), date);
}

std::u16string GetDayOfWeek(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->day_of_week_formatter(), date);
}

std::u16string GetYear(const base::Time date) {
  return calendar_utils::FormatDate(DateHelper::GetInstance()->year_formatter(),
                                    date);
}

std::u16string GetMonthNameAndYear(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->month_name_year_formatter(), date);
}

std::u16string GetTwelveHourClockHours(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->twelve_hour_clock_hours_formatter(), date);
}

std::u16string GetTwentyFourHourClockHours(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->twenty_four_hour_clock_hours_formatter(),
      date);
}

std::u16string GetMinutes(const base::Time date) {
  return calendar_utils::FormatDate(
      DateHelper::GetInstance()->minutes_formatter(), date);
}

std::u16string FormatTwelveHourClockTimeInterval(const base::Time& start_time,
                                                 const base::Time& end_time) {
  return calendar_utils::FormatInterval(
      DateHelper::GetInstance()->twelve_hour_clock_interval_formatter(),
      start_time, end_time);
}

std::u16string FormatTwentyFourHourClockTimeInterval(
    const base::Time& start_time,
    const base::Time& end_time) {
  return calendar_utils::FormatInterval(
      DateHelper::GetInstance()->twenty_four_hour_clock_interval_formatter(),
      start_time, end_time);
}

void SetUpWeekColumns(views::TableLayout* layout) {
  for (int i = 0; i < calendar_utils::kDateInOneWeek; ++i) {
    layout->AddColumn(views::LayoutAlignment::kStretch,
                      views::LayoutAlignment::kStretch, 1.0f,
                      views::TableLayout::ColumnSize::kFixed, 0, 0);
  }
}

int GetMonthsBetween(const base::Time& start_date, const base::Time& end_date) {
  base::Time::Exploded start_exp = calendar_utils::GetExplodedUTC(start_date);
  base::Time::Exploded end_exp = calendar_utils::GetExplodedUTC(end_date);
  return (end_exp.year - start_exp.year) * 12 +
         (end_exp.month - start_exp.month) % 12;
}

base::Time GetMaxTime(const base::Time d1, const base::Time d2) {
  return (d1 > d2) ? d1 : d2;
}

base::Time GetMinTime(const base::Time d1, const base::Time d2) {
  return (d1 < d2) ? d1 : d2;
}

SkColor GetPrimaryTextColor() {
  const ash::AshColorProvider* color_provider = ash::AshColorProvider::Get();
  return color_provider->GetContentLayerColor(
      AshColorProvider::ContentLayerType::kTextColorPrimary);
}

SkColor GetSecondaryTextColor() {
  const ash::AshColorProvider* color_provider = ash::AshColorProvider::Get();
  return color_provider->GetContentLayerColor(
      AshColorProvider::ContentLayerType::kTextColorSecondary);
}

SkColor GetDisabledTextColor() {
  const ash::AshColorProvider* color_provider = ash::AshColorProvider::Get();
  const SkColor primary_color = color_provider->GetContentLayerColor(
      AshColorProvider::ContentLayerType::kTextColorPrimary);
  return ColorUtil::GetDisabledColor(primary_color);
}

base::Time GetFirstDayOfMonth(const base::Time& date) {
  return date -
         base::Days(calendar_utils::GetExplodedUTC(date).day_of_month - 1);
}

base::Time GetStartOfPreviousMonthLocal(base::Time date) {
  return GetFirstDayOfMonth(GetFirstDayOfMonth(date) - base::Days(1));
}

base::Time GetStartOfNextMonthLocal(base::Time date) {
  // Adds over 31 days to make sure it goes to the next month.
  return GetFirstDayOfMonth(GetFirstDayOfMonth(date) + base::Days(33));
}

ASH_EXPORT base::Time GetStartOfMonthUTC(const base::Time& date) {
  return (date -
          base::Days(calendar_utils::GetExplodedUTC(date).day_of_month - 1))
      .UTCMidnight();
}

base::Time GetNextDayMidnight(base::Time date) {
  return (date.UTCMidnight() + base::Days(1) + kDurationForAdjustingDST)
      .UTCMidnight();
}

ASH_EXPORT base::Time GetStartOfPreviousMonthUTC(base::Time date) {
  return GetStartOfMonthUTC(GetStartOfMonthUTC(date) - base::Days(1));
}

ASH_EXPORT base::Time GetStartOfNextMonthUTC(base::Time date) {
  // Adds over 31 days to make sure it goes to the next month.
  return GetStartOfMonthUTC(GetStartOfMonthUTC(date) + base::Days(33));
}

ASH_EXPORT bool ShouldFetchCalendarData() {
  return IsActiveUser() && !IsDisabledByAdmin();
}

ASH_EXPORT bool IsActiveUser() {
  std::optional<user_manager::UserType> user_type =
      Shell::Get()->session_controller()->GetUserType();
  return (user_type && (*user_type == user_manager::UserType::kRegular ||
                        *user_type == user_manager::UserType::kChild)) &&
         !Shell::Get()->session_controller()->IsUserSessionBlocked();
}

ASH_EXPORT bool IsDisabledByAdmin() {
  const auto* const client = Shell::Get()->calendar_controller()->GetClient();
  return !client || client->IsDisabledByAdmin();
}

base::TimeDelta GetTimeDifference(base::Time date) {
  return DateHelper::GetInstance()->GetTimeDifference(date);
}

base::Time GetFirstDayOfWeekLocalMidnight(base::Time date) {
  int day_of_week = calendar_utils::GetDayOfWeekInt(date);
  base::Time first_day_of_week =
      DateHelper::GetInstance()->GetLocalMidnight(date) -
      base::Days(day_of_week - 1) + kDurationForAdjustingDST;
  return DateHelper::GetInstance()->GetLocalMidnight(first_day_of_week);
}

ASH_EXPORT const std::pair<base::Time, base::Time> GetFetchStartEndTimes(
    base::Time start_of_month_local_midnight) {
  base::Time start = start_of_month_local_midnight -
                     DateHelper::GetInstance()->GetTimeDifference(
                         start_of_month_local_midnight);
  base::Time end =
      GetStartOfMonthUTC(start_of_month_local_midnight + base::Days(33));
  end -= DateHelper::GetInstance()->GetTimeDifference(end);
  return std::make_pair(start, end);
}

int GetDayOfWeekInt(const base::Time date) {
  int day_int;
  if (base::StringToInt(GetDayOfWeek(date), &day_int)) {
    return day_int;
  }

  // For a few special locales the day of week is not in a number. In these
  // cases, use the default day of week from time exploded. For example:
  // 'pa-PK', it returns '۰۳' for the fourth day of week.
  base::Time date_local = date + GetTimeDifference(date);
  base::Time::Exploded local_date_exploded = GetExplodedUTC(date_local);
  // Time exploded uses 0-based day of week (0 = Sunday, etc.)
  return local_date_exploded.day_of_week + 1;
}

bool IsMultiDayEvent(const google_apis::calendar::CalendarEvent* event) {
  DCHECK(event);
  return (GetStartTimeMidnightAdjusted(event) <
          GetEndTimeMidnightAdjusted(event));
}

base::Time GetStartTimeAdjusted(
    const google_apis::calendar::CalendarEvent* event) {
  base::Time start_time = event->start_time().date_time();
  return start_time + GetTimeDifference(start_time);
}

base::Time GetEndTimeAdjusted(
    const google_apis::calendar::CalendarEvent* event) {
  base::Time end_time = event->end_time().date_time();
  return end_time + GetTimeDifference(end_time);
}

ASH_EXPORT base::Time GetStartTimeMidnightAdjusted(
    const google_apis::calendar::CalendarEvent* event) {
  return GetStartTimeAdjusted(event).UTCMidnight();
}

ASH_EXPORT base::Time GetEndTimeMidnightAdjusted(
    const google_apis::calendar::CalendarEvent* event) {
  return GetEndTimeAdjusted(event).UTCMidnight();
}

ASH_EXPORT const std::tuple<base::Time, base::Time> GetStartAndEndTime(
    const google_apis::calendar::CalendarEvent* event,
    const base::Time& selected_date,
    const base::Time& selected_date_midnight,
    const base::Time& selected_date_midnight_utc) {
  const base::Time selected_last_minute =
      calendar_utils::GetNextDayMidnight(selected_date_midnight) -
      base::Minutes(1);
  const base::TimeDelta time_difference =
      calendar_utils::GetTimeDifference(selected_date);
  const base::Time selected_last_minute_utc =
      selected_last_minute - time_difference;

  // If it's an "all day" event, then we want to display 00:00 - 23:59 for the
  // event. The formatter we use will apply timezone changes to the given
  // `base::Time` which are set to UTC midnight in the response, so we need to
  // negate the timezone, so when the formatter formats, it will make the dates
  // midnight in the local timezone.
  if (event->all_day_event()) {
    return std::make_tuple(selected_date_midnight_utc,
                           selected_last_minute_utc);
  }

  base::Time start_time = calendar_utils::GetMaxTime(
      event->start_time().date_time(), selected_date_midnight_utc);
  base::Time end_time = calendar_utils::GetMinTime(
      event->end_time().date_time(), selected_last_minute_utc);

  return std::make_tuple(start_time, end_time);
}

const std::tuple<base::Time, base::Time> GetMidnight(const base::Time time) {
  const auto time_difference = GetTimeDifference(time);
  const auto utc_midnight = (time + time_difference).UTCMidnight();
  const auto local_midnight = utc_midnight - time_difference;

  return std::make_tuple(utc_midnight, local_midnight);
}

}  // namespace ash::calendar_utils