1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "util/calendar/calendar-icu.h" 18 19#include <memory> 20 21#include "util/base/macros.h" 22#include "unicode/gregocal.h" 23#include "unicode/timezone.h" 24#include "unicode/ucal.h" 25 26namespace libtextclassifier2 { 27namespace { 28int MapToDayOfWeekOrDefault(int relation_type, int default_value) { 29 switch (relation_type) { 30 case DateParseData::MONDAY: 31 return UCalendarDaysOfWeek::UCAL_MONDAY; 32 case DateParseData::TUESDAY: 33 return UCalendarDaysOfWeek::UCAL_TUESDAY; 34 case DateParseData::WEDNESDAY: 35 return UCalendarDaysOfWeek::UCAL_WEDNESDAY; 36 case DateParseData::THURSDAY: 37 return UCalendarDaysOfWeek::UCAL_THURSDAY; 38 case DateParseData::FRIDAY: 39 return UCalendarDaysOfWeek::UCAL_FRIDAY; 40 case DateParseData::SATURDAY: 41 return UCalendarDaysOfWeek::UCAL_SATURDAY; 42 case DateParseData::SUNDAY: 43 return UCalendarDaysOfWeek::UCAL_SUNDAY; 44 default: 45 return default_value; 46 } 47} 48 49bool DispatchToRecedeOrToLastDayOfWeek(icu::Calendar* date, int relation_type, 50 int distance) { 51 UErrorCode status = U_ZERO_ERROR; 52 switch (relation_type) { 53 case DateParseData::MONDAY: 54 case DateParseData::TUESDAY: 55 case DateParseData::WEDNESDAY: 56 case DateParseData::THURSDAY: 57 case DateParseData::FRIDAY: 58 case DateParseData::SATURDAY: 59 case DateParseData::SUNDAY: 60 for (int i = 0; i < distance; i++) { 61 do { 62 if (U_FAILURE(status)) { 63 TC_LOG(ERROR) << "error day of week"; 64 return false; 65 } 66 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status); 67 if (U_FAILURE(status)) { 68 TC_LOG(ERROR) << "error adding a day"; 69 return false; 70 } 71 } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) != 72 MapToDayOfWeekOrDefault(relation_type, 1)); 73 } 74 return true; 75 case DateParseData::DAY: 76 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1 * distance, status); 77 if (U_FAILURE(status)) { 78 TC_LOG(ERROR) << "error adding a day"; 79 return false; 80 } 81 82 return true; 83 case DateParseData::WEEK: 84 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1); 85 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -7 * (distance - 1), 86 status); 87 if (U_FAILURE(status)) { 88 TC_LOG(ERROR) << "error adding a week"; 89 return false; 90 } 91 92 return true; 93 case DateParseData::MONTH: 94 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1); 95 date->add(UCalendarDateFields::UCAL_MONTH, -1 * (distance - 1), status); 96 if (U_FAILURE(status)) { 97 TC_LOG(ERROR) << "error adding a month"; 98 return false; 99 } 100 return true; 101 case DateParseData::YEAR: 102 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1); 103 date->add(UCalendarDateFields::UCAL_YEAR, -1 * (distance - 1), status); 104 if (U_FAILURE(status)) { 105 TC_LOG(ERROR) << "error adding a year"; 106 107 return true; 108 default: 109 return false; 110 } 111 return false; 112 } 113} 114 115bool DispatchToAdvancerOrToNextOrSameDayOfWeek(icu::Calendar* date, 116 int relation_type) { 117 UErrorCode status = U_ZERO_ERROR; 118 switch (relation_type) { 119 case DateParseData::MONDAY: 120 case DateParseData::TUESDAY: 121 case DateParseData::WEDNESDAY: 122 case DateParseData::THURSDAY: 123 case DateParseData::FRIDAY: 124 case DateParseData::SATURDAY: 125 case DateParseData::SUNDAY: 126 while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) != 127 MapToDayOfWeekOrDefault(relation_type, 1)) { 128 if (U_FAILURE(status)) { 129 TC_LOG(ERROR) << "error day of week"; 130 return false; 131 } 132 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status); 133 if (U_FAILURE(status)) { 134 TC_LOG(ERROR) << "error adding a day"; 135 return false; 136 } 137 } 138 return true; 139 case DateParseData::DAY: 140 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status); 141 if (U_FAILURE(status)) { 142 TC_LOG(ERROR) << "error adding a day"; 143 return false; 144 } 145 146 return true; 147 case DateParseData::WEEK: 148 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1); 149 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7, status); 150 if (U_FAILURE(status)) { 151 TC_LOG(ERROR) << "error adding a week"; 152 return false; 153 } 154 155 return true; 156 case DateParseData::MONTH: 157 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1); 158 date->add(UCalendarDateFields::UCAL_MONTH, 1, status); 159 if (U_FAILURE(status)) { 160 TC_LOG(ERROR) << "error adding a month"; 161 return false; 162 } 163 return true; 164 case DateParseData::YEAR: 165 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1); 166 date->add(UCalendarDateFields::UCAL_YEAR, 1, status); 167 if (U_FAILURE(status)) { 168 TC_LOG(ERROR) << "error adding a year"; 169 170 return true; 171 default: 172 return false; 173 } 174 return false; 175 } 176} 177 178bool DispatchToAdvancerOrToNextDayOfWeek(icu::Calendar* date, int relation_type, 179 int distance) { 180 UErrorCode status = U_ZERO_ERROR; 181 switch (relation_type) { 182 case DateParseData::MONDAY: 183 case DateParseData::TUESDAY: 184 case DateParseData::WEDNESDAY: 185 case DateParseData::THURSDAY: 186 case DateParseData::FRIDAY: 187 case DateParseData::SATURDAY: 188 case DateParseData::SUNDAY: 189 for (int i = 0; i < distance; i++) { 190 do { 191 if (U_FAILURE(status)) { 192 TC_LOG(ERROR) << "error day of week"; 193 return false; 194 } 195 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status); 196 if (U_FAILURE(status)) { 197 TC_LOG(ERROR) << "error adding a day"; 198 return false; 199 } 200 } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) != 201 MapToDayOfWeekOrDefault(relation_type, 1)); 202 } 203 return true; 204 case DateParseData::DAY: 205 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, distance, status); 206 if (U_FAILURE(status)) { 207 TC_LOG(ERROR) << "error adding a day"; 208 return false; 209 } 210 211 return true; 212 case DateParseData::WEEK: 213 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1); 214 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7 * distance, status); 215 if (U_FAILURE(status)) { 216 TC_LOG(ERROR) << "error adding a week"; 217 return false; 218 } 219 220 return true; 221 case DateParseData::MONTH: 222 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1); 223 date->add(UCalendarDateFields::UCAL_MONTH, 1 * distance, status); 224 if (U_FAILURE(status)) { 225 TC_LOG(ERROR) << "error adding a month"; 226 return false; 227 } 228 return true; 229 case DateParseData::YEAR: 230 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1); 231 date->add(UCalendarDateFields::UCAL_YEAR, 1 * distance, status); 232 if (U_FAILURE(status)) { 233 TC_LOG(ERROR) << "error adding a year"; 234 235 return true; 236 default: 237 return false; 238 } 239 return false; 240 } 241} 242 243bool RoundToGranularity(DatetimeGranularity granularity, 244 icu::Calendar* calendar) { 245 // Force recomputation before doing the rounding. 246 UErrorCode status = U_ZERO_ERROR; 247 calendar->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status); 248 if (U_FAILURE(status)) { 249 TC_LOG(ERROR) << "Can't interpret date."; 250 return false; 251 } 252 253 switch (granularity) { 254 case GRANULARITY_YEAR: 255 calendar->set(UCalendarDateFields::UCAL_MONTH, 0); 256 TC_FALLTHROUGH_INTENDED; 257 case GRANULARITY_MONTH: 258 calendar->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1); 259 TC_FALLTHROUGH_INTENDED; 260 case GRANULARITY_DAY: 261 calendar->set(UCalendarDateFields::UCAL_HOUR, 0); 262 TC_FALLTHROUGH_INTENDED; 263 case GRANULARITY_HOUR: 264 calendar->set(UCalendarDateFields::UCAL_MINUTE, 0); 265 TC_FALLTHROUGH_INTENDED; 266 case GRANULARITY_MINUTE: 267 calendar->set(UCalendarDateFields::UCAL_SECOND, 0); 268 break; 269 270 case GRANULARITY_WEEK: 271 calendar->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 272 calendar->getFirstDayOfWeek()); 273 calendar->set(UCalendarDateFields::UCAL_HOUR, 0); 274 calendar->set(UCalendarDateFields::UCAL_MINUTE, 0); 275 calendar->set(UCalendarDateFields::UCAL_SECOND, 0); 276 break; 277 278 case GRANULARITY_UNKNOWN: 279 case GRANULARITY_SECOND: 280 break; 281 } 282 283 return true; 284} 285 286} // namespace 287 288bool CalendarLib::InterpretParseData(const DateParseData& parse_data, 289 int64 reference_time_ms_utc, 290 const std::string& reference_timezone, 291 const std::string& reference_locale, 292 DatetimeGranularity granularity, 293 int64* interpreted_time_ms_utc) const { 294 UErrorCode status = U_ZERO_ERROR; 295 296 std::unique_ptr<icu::Calendar> date(icu::Calendar::createInstance( 297 icu::Locale::createFromName(reference_locale.c_str()), status)); 298 if (U_FAILURE(status)) { 299 TC_LOG(ERROR) << "error getting calendar instance"; 300 return false; 301 } 302 303 date->adoptTimeZone(icu::TimeZone::createTimeZone( 304 icu::UnicodeString::fromUTF8(reference_timezone))); 305 date->setTime(reference_time_ms_utc, status); 306 307 // By default, the parsed time is interpreted to be on the reference day. But 308 // a parsed date, should have time 0:00:00 unless specified. 309 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, 0); 310 date->set(UCalendarDateFields::UCAL_MINUTE, 0); 311 date->set(UCalendarDateFields::UCAL_SECOND, 0); 312 date->set(UCalendarDateFields::UCAL_MILLISECOND, 0); 313 314 static const int64 kMillisInHour = 1000 * 60 * 60; 315 if (parse_data.field_set_mask & DateParseData::Fields::ZONE_OFFSET_FIELD) { 316 date->set(UCalendarDateFields::UCAL_ZONE_OFFSET, 317 parse_data.zone_offset * kMillisInHour); 318 } 319 if (parse_data.field_set_mask & DateParseData::Fields::DST_OFFSET_FIELD) { 320 // convert from hours to milliseconds 321 date->set(UCalendarDateFields::UCAL_DST_OFFSET, 322 parse_data.dst_offset * kMillisInHour); 323 } 324 325 if (parse_data.field_set_mask & DateParseData::Fields::RELATION_FIELD) { 326 switch (parse_data.relation) { 327 case DateParseData::Relation::NEXT: 328 if (parse_data.field_set_mask & 329 DateParseData::Fields::RELATION_TYPE_FIELD) { 330 if (!DispatchToAdvancerOrToNextDayOfWeek( 331 date.get(), parse_data.relation_type, 1)) { 332 return false; 333 } 334 } 335 break; 336 case DateParseData::Relation::NEXT_OR_SAME: 337 if (parse_data.field_set_mask & 338 DateParseData::Fields::RELATION_TYPE_FIELD) { 339 if (!DispatchToAdvancerOrToNextOrSameDayOfWeek( 340 date.get(), parse_data.relation_type)) { 341 return false; 342 } 343 } 344 break; 345 case DateParseData::Relation::LAST: 346 if (parse_data.field_set_mask & 347 DateParseData::Fields::RELATION_TYPE_FIELD) { 348 if (!DispatchToRecedeOrToLastDayOfWeek(date.get(), 349 parse_data.relation_type, 1)) { 350 return false; 351 } 352 } 353 break; 354 case DateParseData::Relation::NOW: 355 // NOOP 356 break; 357 case DateParseData::Relation::TOMORROW: 358 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status); 359 if (U_FAILURE(status)) { 360 TC_LOG(ERROR) << "error adding a day"; 361 return false; 362 } 363 break; 364 case DateParseData::Relation::YESTERDAY: 365 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1, status); 366 if (U_FAILURE(status)) { 367 TC_LOG(ERROR) << "error subtracting a day"; 368 return false; 369 } 370 break; 371 case DateParseData::Relation::PAST: 372 if (parse_data.field_set_mask & 373 DateParseData::Fields::RELATION_TYPE_FIELD) { 374 if (parse_data.field_set_mask & 375 DateParseData::Fields::RELATION_DISTANCE_FIELD) { 376 if (!DispatchToRecedeOrToLastDayOfWeek( 377 date.get(), parse_data.relation_type, 378 parse_data.relation_distance)) { 379 return false; 380 } 381 } 382 } 383 break; 384 case DateParseData::Relation::FUTURE: 385 if (parse_data.field_set_mask & 386 DateParseData::Fields::RELATION_TYPE_FIELD) { 387 if (parse_data.field_set_mask & 388 DateParseData::Fields::RELATION_DISTANCE_FIELD) { 389 if (!DispatchToAdvancerOrToNextDayOfWeek( 390 date.get(), parse_data.relation_type, 391 parse_data.relation_distance)) { 392 return false; 393 } 394 } 395 } 396 break; 397 } 398 } 399 if (parse_data.field_set_mask & DateParseData::Fields::YEAR_FIELD) { 400 date->set(UCalendarDateFields::UCAL_YEAR, parse_data.year); 401 } 402 if (parse_data.field_set_mask & DateParseData::Fields::MONTH_FIELD) { 403 // NOTE: Java and ICU disagree on month formats 404 date->set(UCalendarDateFields::UCAL_MONTH, parse_data.month - 1); 405 } 406 if (parse_data.field_set_mask & DateParseData::Fields::DAY_FIELD) { 407 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, parse_data.day_of_month); 408 } 409 if (parse_data.field_set_mask & DateParseData::Fields::HOUR_FIELD) { 410 if (parse_data.field_set_mask & DateParseData::Fields::AMPM_FIELD && 411 parse_data.ampm == 1 && parse_data.hour < 12) { 412 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour + 12); 413 } else { 414 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour); 415 } 416 } 417 if (parse_data.field_set_mask & DateParseData::Fields::MINUTE_FIELD) { 418 date->set(UCalendarDateFields::UCAL_MINUTE, parse_data.minute); 419 } 420 if (parse_data.field_set_mask & DateParseData::Fields::SECOND_FIELD) { 421 date->set(UCalendarDateFields::UCAL_SECOND, parse_data.second); 422 } 423 424 if (!RoundToGranularity(granularity, date.get())) { 425 return false; 426 } 427 428 *interpreted_time_ms_utc = date->getTime(status); 429 if (U_FAILURE(status)) { 430 TC_LOG(ERROR) << "error getting time from instance"; 431 return false; 432 } 433 434 return true; 435} 436} // namespace libtextclassifier2 437