1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/history/visit_filter.h" 6 7 #include <math.h> 8 9 #include <algorithm> 10 11 #include "base/logging.h" 12 #include "base/time/time.h" 13 #include "chrome/browser/history/history_types.h" 14 15 namespace history { 16 17 const double kLn2 = 0.6931471805599453; 18 19 VisitFilter::VisitFilter() 20 : day_(DAY_UNDEFINED), 21 max_results_(0), 22 sorting_order_(ORDER_BY_RECENCY) { 23 } 24 25 VisitFilter::~VisitFilter() { 26 } 27 28 void VisitFilter::SetFilterTime(const base::Time& filter_time) { 29 filter_time_ = filter_time; 30 UpdateTimeVector(); 31 } 32 33 void VisitFilter::SetFilterWidth(const base::TimeDelta& filter_width) { 34 filter_width_ = filter_width; 35 UpdateTimeVector(); 36 } 37 38 void VisitFilter::SetDayOfTheWeekFilter(int day) { 39 day_ = day; 40 UpdateTimeVector(); 41 } 42 43 void VisitFilter::SetDayTypeFilter(bool workday) { 44 day_ = workday ? WORKDAY : HOLIDAY; 45 UpdateTimeVector(); 46 } 47 48 void VisitFilter::ClearFilters() { 49 filter_time_ = base::Time(); 50 filter_width_ = base::TimeDelta::FromHours(0); 51 day_ = DAY_UNDEFINED; 52 UpdateTimeVector(); 53 } 54 55 bool VisitFilter::UpdateTimeVector() { 56 57 TimeVector days_of_the_week; 58 if (day_ >= 0 && day_ <= 6) { 59 GetTimesOnTheDayOfTheWeek(day_, filter_time_, max_results_, 60 &days_of_the_week); 61 } else if (day_ == WORKDAY || day_ == HOLIDAY) { 62 GetTimesOnTheSameDayType( 63 (day_ == WORKDAY), filter_time_, max_results_, &days_of_the_week); 64 } 65 66 TimeVector times_of_the_day; 67 if (filter_width_ != base::TimeDelta::FromSeconds(0)) { 68 if (sorting_order_ == ORDER_BY_TIME_GAUSSIAN) { 69 // Limit queries to 5 standard deviations. 70 GetTimesInRange(filter_time_ - 5 * filter_width_, 71 filter_time_ + 5 * filter_width_, 72 max_results_, ×_of_the_day); 73 } else { 74 GetTimesInRange(filter_time_ - filter_width_, 75 filter_time_ + filter_width_, 76 max_results_, ×_of_the_day); 77 } 78 } 79 80 if (times_of_the_day.empty()) { 81 if (days_of_the_week.empty()) 82 times_.clear(); 83 else 84 times_.swap(days_of_the_week); 85 } else { 86 if (days_of_the_week.empty()) 87 times_.swap(times_of_the_day); 88 else 89 IntersectTimeVectors(times_of_the_day, days_of_the_week, ×_); 90 } 91 92 return !times_.empty(); 93 } 94 95 // static 96 void VisitFilter::GetTimesInRange(base::Time begin_time_of_the_day, 97 base::Time end_time_of_the_day, 98 size_t max_results, 99 TimeVector* times) { 100 DCHECK(times); 101 times->clear(); 102 times->reserve(max_results); 103 const size_t kMaxReturnedResults = 62; // 2 months (<= 62 days). 104 105 if (!max_results) 106 max_results = kMaxReturnedResults; 107 108 // If range is more than 24 hours, return a contiguous interval covering 109 // |max_results| days. 110 base::TimeDelta one_day = base::TimeDelta::FromDays(1); 111 if (end_time_of_the_day - begin_time_of_the_day >= one_day) { 112 times->push_back( 113 std::make_pair(begin_time_of_the_day - one_day * (max_results - 1), 114 end_time_of_the_day)); 115 return; 116 } 117 118 for (size_t i = 0; i < max_results; ++i) { 119 times->push_back( 120 std::make_pair(begin_time_of_the_day - base::TimeDelta::FromDays(i), 121 end_time_of_the_day - base::TimeDelta::FromDays(i))); 122 } 123 } 124 125 double VisitFilter::GetVisitScore(const VisitRow& visit) const { 126 // Decay score by half each week. 127 base::TimeDelta time_passed = filter_time_ - visit.visit_time; 128 // Clamp to 0 in case time jumps backwards (e.g. due to DST). 129 double decay_exponent = std::max(0.0, kLn2 * static_cast<double>( 130 time_passed.InMicroseconds()) / base::Time::kMicrosecondsPerWeek); 131 double staleness = 1.0 / exp(decay_exponent); 132 133 double score = 0; 134 switch (sorting_order()) { 135 case ORDER_BY_RECENCY: 136 score = 1.0; // Let the staleness factor take care of it. 137 break; 138 case ORDER_BY_VISIT_COUNT: 139 score = 1.0; // Every visit counts the same. 140 staleness = 1.0; // No decay on this one. 141 break; 142 case ORDER_BY_TIME_GAUSSIAN: { 143 double offset = 144 GetTimeOfDayDifference(filter_time_, 145 visit.visit_time).InMicroseconds(); 146 double sd = filter_width_.InMicroseconds(); 147 148 // Calculate score using the normal distribution density function. 149 score = exp(-(offset * offset) / (2 * sd * sd)); 150 break; 151 } 152 case ORDER_BY_TIME_LINEAR: { 153 base::TimeDelta offset = GetTimeOfDayDifference(filter_time_, 154 visit.visit_time); 155 if (offset > filter_width_) { 156 score = 0; 157 } else { 158 score = 1 - offset.InMicroseconds() / static_cast<double>( 159 filter_width_.InMicroseconds()); 160 } 161 break; 162 } 163 case ORDER_BY_DURATION_SPENT: 164 default: 165 NOTREACHED() << "Not implemented!"; 166 } 167 return staleness * score; 168 } 169 170 base::TimeDelta 171 VisitFilter::GetTimeOfDayDifference(base::Time t1, base::Time t2) { 172 base::TimeDelta time_of_day1 = t1 - t1.LocalMidnight(); 173 base::TimeDelta time_of_day2 = t2 - t2.LocalMidnight(); 174 175 base::TimeDelta difference; 176 if (time_of_day1 < time_of_day2) 177 difference = time_of_day2 - time_of_day1; 178 else 179 difference = time_of_day1 - time_of_day2; 180 181 // If the difference is more than 12 hours, we'll get closer by 'wrapping' 182 // around the day barrier. 183 if (difference > base::TimeDelta::FromHours(12)) 184 difference = base::TimeDelta::FromHours(24) - difference; 185 186 return difference; 187 } 188 189 // static 190 void VisitFilter::GetTimesOnTheDayOfTheWeek(int day, 191 base::Time week, 192 size_t max_results, 193 TimeVector* times) { 194 DCHECK(times); 195 196 base::Time::Exploded exploded_time; 197 if (week.is_null()) 198 week = base::Time::Now(); 199 week.LocalExplode(&exploded_time); 200 base::TimeDelta shift = base::TimeDelta::FromDays( 201 exploded_time.day_of_week - day); 202 203 base::Time day_base = week.LocalMidnight(); 204 day_base -= shift; 205 206 times->clear(); 207 times->reserve(max_results); 208 209 base::TimeDelta one_day = base::TimeDelta::FromDays(1); 210 211 const size_t kMaxReturnedResults = 9; // 2 months (<= 9 weeks). 212 213 if (!max_results) 214 max_results = kMaxReturnedResults; 215 216 for (size_t i = 0; i < max_results; ++i) { 217 times->push_back( 218 std::make_pair(day_base - base::TimeDelta::FromDays(i * 7), 219 day_base + one_day - base::TimeDelta::FromDays(i * 7))); 220 } 221 } 222 223 // static 224 void VisitFilter::GetTimesOnTheSameDayType(bool workday, 225 base::Time week, 226 size_t max_results, 227 TimeVector* times) { 228 DCHECK(times); 229 if (week.is_null()) 230 week = base::Time::Now(); 231 // TODO(georgey): internationalize workdays/weekends/holidays. 232 if (!workday) { 233 TimeVector sunday; 234 TimeVector saturday; 235 base::Time::Exploded exploded_time; 236 week.LocalExplode(&exploded_time); 237 238 GetTimesOnTheDayOfTheWeek(exploded_time.day_of_week ? 7 : 0, week, 239 max_results, &sunday); 240 GetTimesOnTheDayOfTheWeek(exploded_time.day_of_week ? 6 : -1, week, 241 max_results, &saturday); 242 UniteTimeVectors(sunday, saturday, times); 243 if (max_results && times->size() > max_results) 244 times->resize(max_results); 245 } else { 246 TimeVector vectors[3]; 247 GetTimesOnTheDayOfTheWeek(1, week, max_results, &vectors[0]); 248 for (size_t i = 2; i <= 5; ++i) { 249 GetTimesOnTheDayOfTheWeek(i, week, max_results, &vectors[(i - 1) % 3]); 250 UniteTimeVectors(vectors[(i - 2) % 3], vectors[(i - 1) % 3], 251 &vectors[i % 3]); 252 if (max_results && vectors[i % 3].size() > max_results) 253 vectors[i % 3].resize(max_results); 254 vectors[i % 3].swap(vectors[(i - 1) % 3]); 255 } 256 // 1 == 5 - 1 % 3 257 times->swap(vectors[1]); 258 } 259 } 260 261 // static 262 bool VisitFilter::UniteTimeVectors(const TimeVector& vector1, 263 const TimeVector& vector2, 264 TimeVector* result) { 265 // The vectors are sorted going back in time, but each pair has |first| as the 266 // beginning of time period and |second| as the end, for example: 267 // { 19:20, 20:00 } { 17:00, 18:10 } { 11:33, 11:35 }... 268 // The pairs in one vector are guaranteed not to intersect. 269 DCHECK(result); 270 result->clear(); 271 result->reserve(vector1.size() + vector2.size()); 272 273 size_t vi[2]; 274 const TimeVector* vectors[2] = { &vector1, &vector2 }; 275 for (vi[0] = 0, vi[1] = 0; 276 vi[0] < vectors[0]->size() && vi[1] < vectors[1]->size();) { 277 std::pair<base::Time, base::Time> united_timeslot; 278 // Check which element occurs later (for the following diagrams time is 279 // increasing to the right, 'f' means first, 's' means second). 280 // after the folowing 2 statements: 281 // vectors[iterator_index][vi[iterator_index]] f---s 282 // vectors[1 - iterator_index][vi[1 - iterator_index]] f---s 283 // united_timeslot f---s 284 // or 285 // vectors[iterator_index][vi[iterator_index]] f---s 286 // vectors[1 - iterator_index][vi[1 - iterator_index]] f-s 287 // united_timeslot f---s 288 size_t iterator_index = 289 ((*vectors[0])[vi[0]].second >= (*vectors[1])[vi[1]].second) ? 0 : 1; 290 united_timeslot = (*vectors[iterator_index])[vi[iterator_index]]; 291 ++vi[iterator_index]; 292 bool added_timeslot; 293 // Merge all timeslots intersecting with |united_timeslot|. 294 do { 295 added_timeslot = false; 296 for (size_t i = 0; i <= 1; ++i) { 297 if (vi[i] < vectors[i]->size() && 298 (*vectors[i])[vi[i]].second >= united_timeslot.first) { 299 // vectors[i][vi[i]] f---s 300 // united_timeslot f---s 301 // or 302 // united_timeslot f------s 303 added_timeslot = true; 304 if ((*vectors[i])[vi[i]].first < united_timeslot.first) { 305 // vectors[i][vi[i]] f---s 306 // united_timeslot f---s 307 // results in: 308 // united_timeslot f-----s 309 united_timeslot.first = (*vectors[i])[vi[i]].first; 310 } 311 ++vi[i]; 312 } 313 } 314 } while (added_timeslot); 315 result->push_back(united_timeslot); 316 } 317 for (size_t i = 0; i <= 1; ++i) { 318 for (; vi[i] < vectors[i]->size(); ++vi[i]) 319 result->push_back((*vectors[i])[vi[i]]); 320 } 321 return !result->empty(); 322 } 323 324 // static 325 bool VisitFilter::IntersectTimeVectors(const TimeVector& vector1, 326 const TimeVector& vector2, 327 TimeVector* result) { 328 DCHECK(result); 329 result->clear(); 330 result->reserve(std::max(vector1.size(), vector2.size())); 331 332 TimeVector::const_iterator vi[2]; 333 for (vi[0] = vector1.begin(), vi[1] = vector2.begin(); 334 vi[0] != vector1.end() && vi[1] != vector2.end();) { 335 size_t it_index = (vi[0]->second >= vi[1]->second) ? 0 : 1; 336 if (vi[it_index]->first >= vi[1 - it_index]->second) { 337 // vector 1 ++++ 338 // vector 2 ++ 339 ++vi[it_index]; 340 } else if (vi[it_index]->first >= vi[1 - it_index]->first) { 341 // vector 1 ++++ 342 // vector 2 +++++ 343 result->push_back(std::make_pair(vi[it_index]->first, 344 vi[1 - it_index]->second)); 345 ++vi[it_index]; 346 } else { 347 // vector 1 ++++ 348 // vector 2 ++ 349 result->push_back(std::make_pair(vi[1 - it_index]->first, 350 vi[1 - it_index]->second)); 351 ++vi[1 - it_index]; 352 } 353 } 354 355 return !result->empty(); 356 } 357 358 } // namespace history 359