| // Copyright 2022 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 "ash/system/time/date_helper.h" |
| |
| #include "ash/shell.h" |
| #include "ash/system/locale/locale_update_controller_impl.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/time/calendar_utils.h" |
| #include "base/i18n/unicodestring.h" |
| #include "base/time/time.h" |
| #include "third_party/icu/source/common/unicode/dtintrv.h" |
| #include "third_party/icu/source/i18n/unicode/dtitvfmt.h" |
| #include "third_party/icu/source/i18n/unicode/fieldpos.h" |
| #include "third_party/icu/source/i18n/unicode/gregocal.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Milliseconds per minute. |
| constexpr int kMillisecondsPerMinute = 60000; |
| |
| UDate TimeToUDate(const base::Time& time) { |
| return static_cast<UDate>(time.ToDoubleT() * |
| base::Time::kMillisecondsPerSecond); |
| } |
| |
| } // namespace |
| |
| // static |
| DateHelper* DateHelper::GetInstance() { |
| return base::Singleton<DateHelper>::get(); |
| } |
| |
| icu::SimpleDateFormat DateHelper::CreateSimpleDateFormatter( |
| const char* pattern) { |
| // Generate a locale-dependent format pattern. The generator will take |
| // care of locale-dependent formatting issues like which separator to |
| // use (some locales use '.' instead of ':'), and where to put the am/pm |
| // marker. |
| UErrorCode status = U_ZERO_ERROR; |
| DCHECK(U_SUCCESS(status)); |
| std::unique_ptr<icu::DateTimePatternGenerator> generator( |
| icu::DateTimePatternGenerator::createInstance(status)); |
| DCHECK(U_SUCCESS(status)); |
| icu::UnicodeString generated_pattern = |
| generator->getBestPattern(icu::UnicodeString(pattern), status); |
| DCHECK(U_SUCCESS(status)); |
| |
| // Then, format the time using the generated pattern. |
| icu::SimpleDateFormat formatter(generated_pattern, status); |
| DCHECK(U_SUCCESS(status)); |
| |
| return formatter; |
| } |
| |
| std::unique_ptr<icu::DateIntervalFormat> |
| DateHelper::CreateDateIntervalFormatter(const char* pattern) { |
| UErrorCode status = U_ZERO_ERROR; |
| icu::DateIntervalFormat* formatter = |
| icu::DateIntervalFormat::createInstance(pattern, status); |
| DCHECK(U_SUCCESS(status)); |
| return absl::WrapUnique(formatter); |
| } |
| |
| std::u16string DateHelper::GetFormattedTime(const icu::DateFormat* formatter, |
| const base::Time& time) { |
| DCHECK(formatter); |
| icu::UnicodeString date_string; |
| |
| formatter->format(TimeToUDate(time), date_string); |
| return base::i18n::UnicodeStringToString16(date_string); |
| } |
| |
| std::u16string DateHelper::GetFormattedInterval( |
| const icu::DateIntervalFormat* formatter, |
| const base::Time& start_time, |
| const base::Time& end_time) { |
| DCHECK(formatter); |
| UErrorCode status = U_ZERO_ERROR; |
| icu::DateInterval interval(TimeToUDate(start_time), TimeToUDate(end_time)); |
| icu::FieldPosition position = 0; |
| icu::UnicodeString interval_string; |
| formatter->format(&interval, interval_string, position, status); |
| DCHECK(U_SUCCESS(status)); |
| return base::i18n::UnicodeStringToString16(interval_string); |
| } |
| |
| base::TimeDelta DateHelper::GetTimeDifference(base::Time date) const { |
| const icu::TimeZone& time_zone = |
| system::TimezoneSettings::GetInstance()->GetTimezone(); |
| const base::TimeDelta raw_time_diff = |
| base::Minutes(time_zone.getRawOffset() / kMillisecondsPerMinute); |
| |
| // Calculates the time difference adjust by the possible daylight savings |
| // offset. If the status of any step fails, returns the default time |
| // difference without considering daylight savings. |
| if (!gregorian_calendar_) |
| return raw_time_diff; |
| |
| UDate current_date = TimeToUDate(date); |
| UErrorCode status = U_ZERO_ERROR; |
| gregorian_calendar_->setTime(current_date, status); |
| if (U_FAILURE(status)) |
| return raw_time_diff; |
| |
| status = U_ZERO_ERROR; |
| UBool day_light = gregorian_calendar_->inDaylightTime(status); |
| if (U_FAILURE(status)) |
| return raw_time_diff; |
| |
| int gmt_offset = time_zone.getRawOffset(); |
| if (day_light) |
| gmt_offset += time_zone.getDSTSavings(); |
| |
| return base::Minutes(gmt_offset / kMillisecondsPerMinute); |
| } |
| |
| base::Time DateHelper::GetLocalMidnight(base::Time date) { |
| base::TimeDelta time_difference = GetTimeDifference(date); |
| return (date + time_difference).UTCMidnight() - time_difference; |
| } |
| |
| DateHelper::DateHelper() |
| : day_of_month_formatter_(CreateSimpleDateFormatter("d")), |
| month_day_formatter_(CreateSimpleDateFormatter("MMMMd")), |
| month_day_year_formatter_(CreateSimpleDateFormatter("MMMMdyyyy")), |
| month_day_year_week_formatter_( |
| CreateSimpleDateFormatter("MMMMEEEEdyyyy")), |
| month_name_formatter_(CreateSimpleDateFormatter("MMMM")), |
| month_name_year_formatter_(CreateSimpleDateFormatter("MMMM yyyy")), |
| time_zone_formatter_(CreateSimpleDateFormatter("zzzz")), |
| twelve_hour_clock_formatter_(CreateSimpleDateFormatter("h:mm a")), |
| twenty_four_hour_clock_formatter_(CreateSimpleDateFormatter("HH:mm")), |
| day_of_week_formatter_(CreateSimpleDateFormatter("ee")), |
| week_title_formatter_(CreateSimpleDateFormatter("EEEEE")), |
| year_formatter_(CreateSimpleDateFormatter("YYYY")), |
| twelve_hour_clock_interval_formatter_(CreateDateIntervalFormatter("hm")), |
| twenty_four_hour_clock_interval_formatter_( |
| CreateDateIntervalFormatter("Hm")) { |
| const icu::TimeZone& time_zone = |
| system::TimezoneSettings::GetInstance()->GetTimezone(); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| gregorian_calendar_ = |
| std::make_unique<icu::GregorianCalendar>(time_zone, status); |
| DCHECK(U_SUCCESS(status)); |
| CalculateLocalWeekTitles(); |
| time_zone_settings_observer_.Observe(system::TimezoneSettings::GetInstance()); |
| |
| // Not using a scoped observer since the Shell can be destructed before this |
| // `DateHelper` instance gets destructed. |
| Shell::Get()->locale_update_controller()->AddObserver(this); |
| } |
| |
| DateHelper::~DateHelper() { |
| if (Shell::HasInstance()) |
| Shell::Get()->locale_update_controller()->RemoveObserver(this); |
| } |
| |
| void DateHelper::ResetFormatters() { |
| day_of_month_formatter_ = CreateSimpleDateFormatter("d"); |
| month_day_formatter_ = CreateSimpleDateFormatter("MMMMd"); |
| month_day_year_formatter_ = CreateSimpleDateFormatter("MMMMdyyyy"); |
| month_day_year_week_formatter_ = CreateSimpleDateFormatter("MMMMEEEEdyyyy"); |
| month_name_formatter_ = CreateSimpleDateFormatter("MMMM"); |
| month_name_year_formatter_ = CreateSimpleDateFormatter("MMMM yyyy"); |
| time_zone_formatter_ = CreateSimpleDateFormatter("zzzz"); |
| twelve_hour_clock_formatter_ = CreateSimpleDateFormatter("h:mm a"); |
| twenty_four_hour_clock_formatter_ = CreateSimpleDateFormatter("HH:mm"); |
| day_of_week_formatter_ = CreateSimpleDateFormatter("ee"); |
| week_title_formatter_ = CreateSimpleDateFormatter("EEEEE"); |
| year_formatter_ = CreateSimpleDateFormatter("YYYY"); |
| twelve_hour_clock_interval_formatter_ = CreateDateIntervalFormatter("hm"); |
| twenty_four_hour_clock_interval_formatter_ = |
| CreateDateIntervalFormatter("Hm"); |
| } |
| |
| void DateHelper::CalculateLocalWeekTitles() { |
| week_titles_.clear(); |
| |
| // To avoid the DST difference, use a certain date here to calculate the week |
| // titles, since there are no daylight saving starts/ends in June worldwide. |
| // If the `DCHECK` fails, use `Now()`. |
| base::Time start_date = base::Time::Now(); |
| bool result = base::Time::FromString("15 Jun 2021 10:00 GMT", &start_date); |
| DCHECK(result); |
| start_date = GetLocalMidnight(start_date); |
| std::u16string day_of_week = |
| GetFormattedTime(&day_of_week_formatter_, start_date); |
| |
| // For a few special locales the day of week is not in a number. In these |
| // cases, use the default week titles. |
| int day_int; |
| if (!base::StringToInt(day_of_week, &day_int)) { |
| week_titles_ = kDefaultWeekTitle; |
| return; |
| } |
| |
| int safe_index = 0; |
| // Find a first day of a week. |
| while (day_int != 1) { |
| start_date += base::Hours(25); |
| day_of_week = GetFormattedTime(&day_of_week_formatter_, start_date); |
| bool result = base::StringToInt(day_of_week, &day_int); |
| DCHECK(result); |
| ++safe_index; |
| if (safe_index == calendar_utils::kDateInOneWeek) { |
| NOTREACHED() << "Should already find the first day within 7 times, since " |
| "there are only 7 days in a week"; |
| week_titles_ = kDefaultWeekTitle; |
| return; |
| } |
| } |
| |
| int day_index = 0; |
| while (day_index < calendar_utils::kDateInOneWeek) { |
| week_titles_.push_back( |
| GetFormattedTime(&week_title_formatter_, start_date)); |
| start_date += base::Hours(25); |
| ++day_index; |
| } |
| } |
| |
| void DateHelper::TimezoneChanged(const icu::TimeZone& timezone) { |
| ResetFormatters(); |
| gregorian_calendar_->setTimeZone( |
| system::TimezoneSettings::GetInstance()->GetTimezone()); |
| Shell::Get()->system_tray_model()->calendar_model()->RedistributeEvents(); |
| } |
| |
| void DateHelper::OnLocaleChanged() { |
| CalculateLocalWeekTitles(); |
| } |
| |
| } // namespace ash |