1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151

ash / system / time / event_date_formatter_util.cc [blame]

// 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