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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/report/utils/time_utils.h"
#include <memory>
#include <string_view>
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chromeos/ash/components/policy/weekly_time/time_utils.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
namespace ash::report::utils {
namespace {
// Record histogram for whether ActivateDate is read and parsed correctly.
void RecordIsActivateDateSet(bool is_set) {
base::UmaHistogramBoolean("Ash.Report.IsActivateDateSet", is_set);
}
} // namespace
base::Time ConvertGmtToPt(base::Clock* clock) {
base::Time gmt_ts = clock->Now();
DCHECK(gmt_ts != base::Time::UnixEpoch() && gmt_ts != base::Time())
<< "Invalid timestamp ts = " << gmt_ts;
int pt_offset;
bool offset_success = policy::weekly_time_utils::GetOffsetFromTimezoneToGmt(
"America/Los_Angeles", clock, &pt_offset);
if (!offset_success) {
LOG(ERROR) << "Failed to get offset for Pacific Time. "
<< "Returning UTC-8 timezone as default.";
return gmt_ts - base::Hours(8);
}
return gmt_ts - base::Milliseconds(pt_offset);
}
std::optional<base::Time> GetPreviousMonth(base::Time ts) {
if (ts == base::Time()) {
LOG(ERROR) << "Timestamp not set = " << ts;
return std::nullopt;
}
base::Time::Exploded exploded;
ts.UTCExplode(&exploded);
// Set new time to the first midnight of the previous month.
// "+ 11) % 12) + 1" wraps the month around if it goes outside 1..12.
exploded.month = (((exploded.month - 1) + 11) % 12) + 1;
exploded.year -= (exploded.month == 12);
exploded.day_of_month = 1;
exploded.hour = exploded.minute = exploded.second = exploded.millisecond = 0;
base::Time new_month_ts;
bool success = base::Time::FromUTCExploded(exploded, &new_month_ts);
if (!success) {
LOG(ERROR) << "Failed to get previous month of ts = " << ts;
return std::nullopt;
}
return new_month_ts;
}
std::optional<base::Time> GetNextMonth(base::Time ts) {
if (ts == base::Time()) {
LOG(ERROR) << "Timestamp not set = " << ts;
return std::nullopt;
}
base::Time::Exploded exploded;
ts.UTCExplode(&exploded);
// Set new time to the first midnight of the next month.
// "+ 11) % 12) + 1" wraps the month around if it goes outside 1..12.
exploded.month = (((exploded.month + 1) + 11) % 12) + 1;
exploded.year += (exploded.month == 1);
exploded.day_of_month = 1;
exploded.hour = exploded.minute = exploded.second = exploded.millisecond = 0;
base::Time new_month_ts;
bool success = base::Time::FromUTCExploded(exploded, &new_month_ts);
if (!success) {
LOG(ERROR) << "Failed to get next month of ts = " << ts;
return std::nullopt;
}
return new_month_ts;
}
std::optional<base::Time> GetPreviousYear(base::Time ts) {
if (ts == base::Time()) {
LOG(ERROR) << "Timestamp not set = " << ts;
return std::nullopt;
}
base::Time::Exploded exploded;
ts.UTCExplode(&exploded);
// Set new time to the first midnight of the previous year.
exploded.year -= 1;
exploded.day_of_month = 1;
exploded.hour = 0;
exploded.minute = 0;
exploded.second = 0;
exploded.millisecond = 0;
base::Time new_year_ts;
bool success = base::Time::FromUTCExploded(exploded, &new_year_ts);
if (!success) {
LOG(ERROR) << "Failed to get previous year of ts = " << ts;
return std::nullopt;
}
return new_year_ts;
}
bool IsSameYearAndMonth(base::Time ts1, base::Time ts2) {
base::Time::Exploded ts1_exploded;
ts1.UTCExplode(&ts1_exploded);
base::Time::Exploded ts2_exploded;
ts2.UTCExplode(&ts2_exploded);
return (ts1_exploded.year == ts2_exploded.year) &&
(ts1_exploded.month == ts2_exploded.month);
}
bool IsFirstActiveUnderNDaysAgo(base::Time active_ts,
base::Time first_active_week,
int num_days) {
// Checks for the starting point which is num of days before active_ts.
base::Time starting_point = active_ts - base::Days(num_days);
// Check if first_active_week is after the starting point
return first_active_week >= starting_point;
}
std::string FormatTimestampToMidnightGMTString(base::Time ts) {
return base::UnlocalizedTimeFormatWithPattern(ts, "yyyy-MM-dd 00:00:00.000 z",
icu::TimeZone::getGMT());
}
std::string TimeToYYYYMMDDString(base::Time ts) {
return base::UnlocalizedTimeFormatWithPattern(ts, "yyyyMMdd",
icu::TimeZone::getGMT());
}
std::string TimeToYYYYMMString(base::Time ts) {
return base::UnlocalizedTimeFormatWithPattern(ts, "yyyyMM",
icu::TimeZone::getGMT());
}
std::optional<base::Time> GetFirstActiveWeek() {
std::optional<std::string_view> first_active_week_val =
system::StatisticsProvider::GetInstance()->GetMachineStatistic(
system::kActivateDateKey);
std::string first_active_week_str =
std::string(first_active_week_val.value_or(kActivateDateKeyNotFound));
if (first_active_week_str == kActivateDateKeyNotFound) {
LOG(ERROR)
<< "Failed to retrieve ActivateDate VPD field from machine statistics. "
<< "Leaving |first_active_week_| unset.";
RecordIsActivateDateSet(false);
return std::nullopt;
}
// Activate date is formatted: "YYYY-WW"
int delimiter_index = first_active_week_str.find('-');
const int expected_first_active_week_size = 7;
const int expected_delimiter_index = 4;
if (first_active_week_str.size() != expected_first_active_week_size ||
delimiter_index != expected_delimiter_index) {
LOG(ERROR) << "ActivateDate was retrieved but is not formatted as YYYY-WW. "
<< "Received string : " << first_active_week_str;
RecordIsActivateDateSet(false);
return std::nullopt;
}
const int expected_year_size = 4;
const int expected_weeks_size = 2;
std::string parsed_year = first_active_week_str.substr(0, expected_year_size);
std::string parsed_weeks = first_active_week_str.substr(
expected_delimiter_index + 1, expected_weeks_size);
if (parsed_year.empty() || parsed_weeks.empty()) {
LOG(ERROR) << "Failed to parse and convert the first active weeks string "
<< "year and weeks.";
RecordIsActivateDateSet(false);
return std::nullopt;
}
// Convert parsed year and weeks to int.
int activate_year, activate_week_of_year;
bool success_year = base::StringToInt(parsed_year, &activate_year);
bool success_week = base::StringToInt(parsed_weeks, &activate_week_of_year);
if (!success_year || !success_week) {
LOG(ERROR) << "Failed to convert parsed_year or parsed_weeks: "
<< parsed_year << " and " << parsed_weeks;
RecordIsActivateDateSet(false);
return std::nullopt;
}
auto iso8601_ts =
utils::Iso8601DateWeekAsTime(activate_year, activate_week_of_year);
if (!iso8601_ts.has_value()) {
LOG(ERROR) << "Failed to ISO8601 year and week of year as a timestamp.";
RecordIsActivateDateSet(false);
return std::nullopt;
}
RecordIsActivateDateSet(true);
return iso8601_ts.value();
}
std::optional<base::Time> FirstMondayOfISONewYear(int iso_year) {
// 1. Get week of first Thursday in iso_year.
// 2. Subtract days to get the first Monday.
// ISO calendar new year may start 1-3 days before the
// Gregorian new year or 1-3 days later.
// Get week of the first Thursday in ISO year.
base::Time first_thursday_ts;
base::Time::Exploded first_thursday_exploded = {iso_year, 1, 0, 1,
0, 0, 0, 0};
bool success =
base::Time::FromUTCExploded(first_thursday_exploded, &first_thursday_ts);
if (!success) {
LOG(ERROR) << "Failed to explode first day of iso Year = " << iso_year;
return std::nullopt;
}
// Re-create exploded object from first thursday timestamp.
// This allows us to get an accurate day of week, so that we can
// determine the first Thursday in iso_year.
first_thursday_ts.UTCExplode(&first_thursday_exploded);
// Adjust number of days to get to the first Thursday of year.
while (first_thursday_exploded.day_of_week != kThursdayDayOfWeekIndex) {
first_thursday_ts += base::Days(1);
// Recalculate exploded object.
first_thursday_ts.UTCExplode(&first_thursday_exploded);
}
base::Time first_monday_ts =
first_thursday_ts -
base::Days(kThursdayDayOfWeekIndex - kMondayDayOfWeekIndex);
return first_monday_ts;
}
// The ActivateDate is formatted: YYYY-WW and is generated based on UTC date.
// Returns the first day of the ISO8601 week.
std::optional<base::Time> Iso8601DateWeekAsTime(int activate_year,
int activate_week_of_year) {
if (activate_year < 0 || activate_week_of_year <= 0 ||
activate_week_of_year > 53) {
LOG(ERROR) << "Invalid year or week of year"
<< ". Variable activate_year = " << activate_year
<< ". Variable activate_week_of_year = "
<< activate_week_of_year;
return std::nullopt;
}
std::optional<base::Time> first_monday_iso_year =
FirstMondayOfISONewYear(activate_year);
if (!first_monday_iso_year.has_value()) {
return std::nullopt;
}
// Get the number of days to the start of a ISO 8601 week standard period
// for that year from the years first monday. This is equal to
// (activate_week_of_year-1) * 7 days.
int days_in_iso_period = 0;
days_in_iso_period = (activate_week_of_year - 1) * 7;
// Add the above two steps to get the start of a ISO 8601 week time.
return first_monday_iso_year.value() + base::Days(days_in_iso_period);
}
std::string ConvertTimeToISO8601String(base::Time ts) {
if (ts.is_null()) {
LOG(ERROR) << "Timestamp ts is not defined correctly. ts = " << ts;
return std::string();
}
// Calculate the year of the given time
base::Time::Exploded exploded;
ts.UTCExplode(&exploded);
int activate_year = exploded.year;
std::optional<base::Time> first_monday_iso_year =
FirstMondayOfISONewYear(activate_year);
if (!first_monday_iso_year.has_value()) {
return std::string();
}
// Assign to the last ISO week of previous year. We use W52 for simplicity.
// Some years have 53 weeks, but we simply use 52 to avoid complexity here.
// This captures ts for up to the 3 days after the new year that are apart of
// the previous year last ISO week.
if (ts < first_monday_iso_year.value()) {
return base::NumberToString(activate_year - 1) + "-52";
}
std::optional<base::Time> first_monday_iso_year_next =
FirstMondayOfISONewYear(activate_year + 1);
if (!first_monday_iso_year_next.has_value()) {
return std::string();
}
// Assign to first ISO week of next year.
// This captures ts for up to the 3 days before the new year that are apart of
// the current year first ISO week.
if (ts >= first_monday_iso_year_next.value()) {
return base::NumberToString(activate_year + 1) + "-01";
}
// Calculate the number of days between the given time and the first
// Monday of the year
base::TimeDelta delta = ts - first_monday_iso_year.value();
int days_difference = delta.InDays();
// Calculate the ISO 8601 week number
int activate_week_of_year = days_difference / 7 + 1;
return base::NumberToString(activate_year) + "-" +
(activate_week_of_year < 10 ? "0" : "") +
base::NumberToString(activate_week_of_year);
}
} // namespace ash::report::utils
|