chromium/ash/system/time/event_date_formatter_util.cc

// Copyright 2022 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/event_date_formatter_util.h"

#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/clock_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_utils.h"
#include "ash/system/time/date_helper.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/time_formatting.h"
#include "base/time/time.h"
#include "google_apis/calendar/calendar_api_response_types.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash::event_date_formatter_util {
namespace {

bool Is12HourClock() {
  return Shell::Get()->system_tray_model()->clock()->hour_clock_type() ==
         base::k12HourClock;
}

// Calculate the number of elapsed days so far.
// We add 1 as if 1 day has passed in the event, then we're on Day 2.
// `selected_date_midnight` will be the selected date at 00:00:00 UTC.
// `selected_date_midnight_utc` will be the selected date adjusted for local
// timezone in UTC.
int GetEventElapsedDayCount(const google_apis::calendar::CalendarEvent* event,
                            const base::Time& selected_date_midnight,
                            const base::Time& selected_date_midnight_utc) {
  // For all day events, we can just take selected midnight UTC minus the
  // event start time, as all day events start at midnight UTC.
  if (event->all_day_event()) {
    return (selected_date_midnight - event->start_time().date_time()).InDays() +
           1;
  }

  // For other events, we take the adjusted to local selected midnight minus the
  // adjusted to local midnight event start time.
  const auto start_time_local_midnight =
      DateHelper::GetInstance()->GetLocalMidnight(
          event->start_time().date_time());
  return (selected_date_midnight_utc - start_time_local_midnight).InDays() + 1;
}

int GetEventTotalDayCount(const google_apis::calendar::CalendarEvent* event) {
  const auto start_time = calendar_utils::GetStartTimeMidnightAdjusted(event);
  const auto end_time = calendar_utils::GetEndTimeMidnightAdjusted(event);

  const int total_day_count = (end_time - start_time).InDays();

  // Events ending at midnight of the following day that the event ends, i.e.
  // all day events or multi-day events that finish at midnight in the local
  // timezone, shouldn't be included in the total day count.
  // `base::Time::InDays()` will be correct for these events, e.g. a 2 day,
  // all day event with start and end times of 20220101 00:00:00 UTC - 20220103
  // 00:00:00 UTC will be calculated as 2 days in time. Technically the event
  // spans a 3 day period, but we want to show this as a 2 day event.
  const auto end_time_adjusted = calendar_utils::GetEndTimeAdjusted(event);
  base::Time::Exploded exploded_end_time;
  end_time_adjusted.UTCExplode(&exploded_end_time);

  auto event_ends_at_midnight =
      (exploded_end_time.hour == 0 && exploded_end_time.minute == 0);
  if (event->all_day_event() || event_ends_at_midnight)
    return total_day_count;

  // For multi-day events not ending at midnight, they'll span multiple days,
  // but the `base::Time::InDays()` function will return 1 less than the total
  // amount of days that an event might span e.g. for a 2 day, multi-day
  // event of 20220101 08:00:00 UTC - 20220102 08:00:00 UTC, the elapsed
  // time is 1 day, but it spans over 2 days.
  return total_day_count + 1;
}

// Calculates the total and elapsed number of days for the event.
// Returns "(Day n / n)".
const std::u16string GetEventDayText(
    const google_apis::calendar::CalendarEvent* event,
    const base::Time& selected_date_midnight,
    const base::Time& selected_date_midnight_utc) {
  const int elapsed_day_count = GetEventElapsedDayCount(
      event, selected_date_midnight, selected_date_midnight_utc);
  const int total_day_count = GetEventTotalDayCount(event);

  return l10n_util::GetStringFUTF16(IDS_ASH_CALENDAR_EVENT_ENTRY_DAYS_ELAPSED,
                                    base::FormatNumber(elapsed_day_count),
                                    base::FormatNumber(total_day_count));
}
}  // namespace

ASH_EXPORT const std::tuple<std::u16string, std::u16string>
GetStartAndEndTimeAccessibleNames(base::Time start_time, base::Time end_time) {
  if (Is12HourClock()) {
    return std::make_tuple(calendar_utils::GetTwelveHourClockTime(start_time),
                           calendar_utils::GetTwelveHourClockTime(end_time));
  }

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

ASH_EXPORT const std::u16string GetFormattedInterval(base::Time start_time,
                                                     base::Time end_time) {
  if (Is12HourClock()) {
    return calendar_utils::FormatTwelveHourClockTimeInterval(start_time,
                                                             end_time);
  }

  return calendar_utils::FormatTwentyFourHourClockTimeInterval(start_time,
                                                               end_time);
}

ASH_EXPORT const std::u16string GetMultiDayText(
    const google_apis::calendar::CalendarEvent* event,
    const base::Time& selected_date_midnight,
    const base::Time& selected_date_midnight_utc) {
  const auto day_text = GetEventDayText(event, selected_date_midnight,
                                        selected_date_midnight_utc);

  // Returns "(Day n / n)".
  if (event->all_day_event())
    return day_text;

  const auto end_time_local_midnight =
      calendar_utils::GetEndTimeMidnightAdjusted(event);
  const auto [start_time, end_time] = GetStartAndEndTimeAccessibleNames(
      event->start_time().date_time(), event->end_time().date_time());

  // Returns "Starts at `start_time` `day_text`.
  if (selected_date_midnight < end_time_local_midnight) {
    return l10n_util::GetStringFUTF16(
        IDS_ASH_CALENDAR_EVENT_ENTRY_STARTS_AT_TIME, start_time, day_text);
  }

  // Returns "Ends at `end_time` `day_text`.
  if (selected_date_midnight == end_time_local_midnight) {
    return l10n_util::GetStringFUTF16(IDS_ASH_CALENDAR_EVENT_ENTRY_ENDS_AT_TIME,
                                      end_time, day_text);
  }

  NOTREACHED()
      << "The `selected_date_midnight` is past the end of the event. Value is: "
      << selected_date_midnight;
}

}  // namespace ash::event_date_formatter_util