1 // Copyright 2013 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 "chromeos/settings/timezone_settings.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/files/file_path.h" 11 #include "base/files/file_util.h" 12 #include "base/location.h" 13 #include "base/logging.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/memory/singleton.h" 16 #include "base/observer_list.h" 17 #include "base/stl_util.h" 18 #include "base/strings/string_util.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "base/sys_info.h" 21 #include "base/task_runner.h" 22 #include "base/threading/worker_pool.h" 23 #include "third_party/icu/source/i18n/unicode/timezone.h" 24 25 namespace { 26 27 // The filepath to the timezone file that symlinks to the actual timezone file. 28 const char kTimezoneSymlink[] = "/var/lib/timezone/localtime"; 29 const char kTimezoneSymlink2[] = "/var/lib/timezone/localtime2"; 30 31 // The directory that contains all the timezone files. So for timezone 32 // "US/Pacific", the actual timezone file is: "/usr/share/zoneinfo/US/Pacific" 33 const char kTimezoneFilesDir[] = "/usr/share/zoneinfo/"; 34 35 // Fallback time zone ID used in case of an unexpected error. 36 const char kFallbackTimeZoneId[] = "America/Los_Angeles"; 37 38 // TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones. 39 // Even after filtering out duplicate entries with a strict identity check, 40 // we still have 400+ zones. Relaxing the criteria for the timezone 41 // identity is likely to cut down the number to < 100. Until we 42 // come up with a better list, we hard-code the following list. It came from 43 // from Android initially, but more entries have been added. 44 static const char* kTimeZones[] = { 45 "Pacific/Midway", 46 "Pacific/Honolulu", 47 "America/Anchorage", 48 "America/Los_Angeles", 49 "America/Vancouver", 50 "America/Tijuana", 51 "America/Phoenix", 52 "America/Chihuahua", 53 "America/Denver", 54 "America/Edmonton", 55 "America/Mazatlan", 56 "America/Regina", 57 "America/Costa_Rica", 58 "America/Chicago", 59 "America/Mexico_City", 60 "America/Winnipeg", 61 "Pacific/Easter", 62 "America/Bogota", 63 "America/Lima", 64 "America/New_York", 65 "America/Toronto", 66 "America/Caracas", 67 "America/Barbados", 68 "America/Halifax", 69 "America/Manaus", 70 "America/Santiago", 71 "America/St_Johns", 72 "America/Araguaina", 73 "America/Argentina/Buenos_Aires", 74 "America/Argentina/San_Luis", 75 "America/Sao_Paulo", 76 "America/Montevideo", 77 "America/Godthab", 78 "Atlantic/South_Georgia", 79 "Atlantic/Cape_Verde", 80 "Atlantic/Azores", 81 "Atlantic/Reykjavik", 82 "Atlantic/St_Helena", 83 "Africa/Casablanca", 84 "Atlantic/Faroe", 85 "Europe/Dublin", 86 "Europe/Lisbon", 87 "Europe/London", 88 "Europe/Amsterdam", 89 "Europe/Belgrade", 90 "Europe/Berlin", 91 "Europe/Brussels", 92 "Europe/Budapest", 93 "Europe/Copenhagen", 94 "Europe/Ljubljana", 95 "Europe/Madrid", 96 "Europe/Oslo", 97 "Europe/Paris", 98 "Europe/Prague", 99 "Europe/Rome", 100 "Europe/Stockholm", 101 "Europe/Sarajevo", 102 "Europe/Tirane", 103 "Europe/Vienna", 104 "Europe/Warsaw", 105 "Europe/Zurich", 106 "Africa/Windhoek", 107 "Africa/Lagos", 108 "Africa/Brazzaville", 109 "Africa/Cairo", 110 "Africa/Harare", 111 "Africa/Maputo", 112 "Africa/Johannesburg", 113 "Europe/Kaliningrad", 114 "Europe/Athens", 115 "Europe/Bucharest", 116 "Europe/Chisinau", 117 "Europe/Helsinki", 118 "Europe/Istanbul", 119 "Europe/Kiev", 120 "Europe/Riga", 121 "Europe/Sofia", 122 "Europe/Tallinn", 123 "Europe/Vilnius", 124 "Asia/Amman", 125 "Asia/Beirut", 126 "Asia/Jerusalem", 127 "Africa/Nairobi", 128 "Asia/Baghdad", 129 "Asia/Riyadh", 130 "Asia/Kuwait", 131 "Europe/Minsk", 132 "Europe/Moscow", 133 "Asia/Tehran", 134 "Europe/Samara", 135 "Asia/Dubai", 136 "Asia/Tbilisi", 137 "Indian/Mauritius", 138 "Asia/Baku", 139 "Asia/Yerevan", 140 "Asia/Kabul", 141 "Asia/Karachi", 142 "Asia/Ashgabat", 143 "Asia/Oral", 144 "Asia/Yekaterinburg", 145 "Asia/Calcutta", 146 "Asia/Colombo", 147 "Asia/Katmandu", 148 "Asia/Omsk", 149 "Asia/Almaty", 150 "Asia/Dhaka", 151 "Asia/Novosibirsk", 152 "Asia/Rangoon", 153 "Asia/Bangkok", 154 "Asia/Jakarta", 155 "Asia/Krasnoyarsk", 156 "Asia/Novokuznetsk", 157 "Asia/Ho_Chi_Minh", 158 "Asia/Phnom_Penh", 159 "Asia/Vientiane", 160 "Asia/Shanghai", 161 "Asia/Hong_Kong", 162 "Asia/Kuala_Lumpur", 163 "Asia/Singapore", 164 "Asia/Manila", 165 "Asia/Taipei", 166 "Asia/Makassar", 167 "Asia/Irkutsk", 168 "Asia/Yakutsk", 169 "Australia/Perth", 170 "Australia/Eucla", 171 "Asia/Seoul", 172 "Asia/Tokyo", 173 "Asia/Jayapura", 174 "Asia/Sakhalin", 175 "Asia/Vladivostok", 176 "Asia/Magadan", 177 "Australia/Darwin", 178 "Australia/Adelaide", 179 "Pacific/Guam", 180 "Australia/Brisbane", 181 "Australia/Hobart", 182 "Australia/Sydney", 183 "Asia/Anadyr", 184 "Pacific/Port_Moresby", 185 "Asia/Kamchatka", 186 "Pacific/Fiji", 187 "Pacific/Majuro", 188 "Pacific/Auckland", 189 "Pacific/Tongatapu", 190 "Pacific/Apia", 191 "Pacific/Kiritimati", 192 }; 193 194 std::string GetTimezoneIDAsString() { 195 // Compare with chromiumos/src/platform/init/ui.conf which fixes certain 196 // incorrect states of the timezone symlink on startup. Thus errors occuring 197 // here should be rather contrived. 198 199 // Look at kTimezoneSymlink, see which timezone we are symlinked to. 200 char buf[256]; 201 const ssize_t len = readlink(kTimezoneSymlink, buf, 202 sizeof(buf)-1); 203 if (len == -1) { 204 LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink " 205 << kTimezoneSymlink; 206 return std::string(); 207 } 208 209 std::string timezone(buf, len); 210 // Remove kTimezoneFilesDir from the beginning. 211 if (timezone.find(kTimezoneFilesDir) != 0) { 212 LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong " 213 << timezone; 214 return std::string(); 215 } 216 217 return timezone.substr(strlen(kTimezoneFilesDir)); 218 } 219 220 void SetTimezoneIDFromString(const std::string& id) { 221 // Change the kTimezoneSymlink symlink to the path for this timezone. 222 // We want to do this in an atomic way. So we are going to create the symlink 223 // at kTimezoneSymlink2 and then move it to kTimezoneSymlink 224 225 base::FilePath timezone_symlink(kTimezoneSymlink); 226 base::FilePath timezone_symlink2(kTimezoneSymlink2); 227 base::FilePath timezone_file(kTimezoneFilesDir + id); 228 229 // Make sure timezone_file exists. 230 if (!base::PathExists(timezone_file)) { 231 LOG(ERROR) << "SetTimezoneID: Cannot find timezone file " 232 << timezone_file.value(); 233 return; 234 } 235 236 // Delete old symlink2 if it exists. 237 base::DeleteFile(timezone_symlink2, false); 238 239 // Create new symlink2. 240 if (symlink(timezone_file.value().c_str(), 241 timezone_symlink2.value().c_str()) == -1) { 242 LOG(ERROR) << "SetTimezoneID: Unable to create symlink " 243 << timezone_symlink2.value() << " to " << timezone_file.value(); 244 return; 245 } 246 247 // Move symlink2 to symlink. 248 if (!base::ReplaceFile(timezone_symlink2, timezone_symlink, NULL)) { 249 LOG(ERROR) << "SetTimezoneID: Unable to move symlink " 250 << timezone_symlink2.value() << " to " 251 << timezone_symlink.value(); 252 } 253 } 254 255 // Common code of the TimezoneSettings implementations. 256 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings { 257 public: 258 virtual ~TimezoneSettingsBaseImpl(); 259 260 // TimezoneSettings implementation: 261 virtual const icu::TimeZone& GetTimezone() OVERRIDE; 262 virtual base::string16 GetCurrentTimezoneID() OVERRIDE; 263 virtual void SetTimezoneFromID(const base::string16& timezone_id) OVERRIDE; 264 virtual void AddObserver(Observer* observer) OVERRIDE; 265 virtual void RemoveObserver(Observer* observer) OVERRIDE; 266 virtual const std::vector<icu::TimeZone*>& GetTimezoneList() const OVERRIDE; 267 268 protected: 269 TimezoneSettingsBaseImpl(); 270 271 // Returns |timezone| if it is an element of |timezones_|. 272 // Otherwise, returns a timezone from |timezones_|, if such exists, that has 273 // the same rule as the given |timezone|. 274 // Otherwise, returns NULL. 275 // Note multiple timezones with the same time zone offset may exist 276 // e.g. 277 // US/Pacific == America/Los_Angeles 278 const icu::TimeZone* GetKnownTimezoneOrNull( 279 const icu::TimeZone& timezone) const; 280 281 ObserverList<Observer> observers_; 282 std::vector<icu::TimeZone*> timezones_; 283 scoped_ptr<icu::TimeZone> timezone_; 284 285 private: 286 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl); 287 }; 288 289 // The TimezoneSettings implementation used in production. 290 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl { 291 public: 292 // TimezoneSettings implementation: 293 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE; 294 295 static TimezoneSettingsImpl* GetInstance(); 296 297 private: 298 friend struct DefaultSingletonTraits<TimezoneSettingsImpl>; 299 300 TimezoneSettingsImpl(); 301 302 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl); 303 }; 304 305 // The stub TimezoneSettings implementation used on Linux desktop. 306 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl { 307 public: 308 // TimezoneSettings implementation: 309 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE; 310 311 static TimezoneSettingsStubImpl* GetInstance(); 312 313 private: 314 friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>; 315 316 TimezoneSettingsStubImpl(); 317 318 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl); 319 }; 320 321 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() { 322 STLDeleteElements(&timezones_); 323 } 324 325 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() { 326 return *timezone_.get(); 327 } 328 329 base::string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() { 330 return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone()); 331 } 332 333 void TimezoneSettingsBaseImpl::SetTimezoneFromID( 334 const base::string16& timezone_id) { 335 scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone( 336 icu::UnicodeString(timezone_id.c_str(), timezone_id.size()))); 337 SetTimezone(*timezone); 338 } 339 340 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) { 341 observers_.AddObserver(observer); 342 } 343 344 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) { 345 observers_.RemoveObserver(observer); 346 } 347 348 const std::vector<icu::TimeZone*>& 349 TimezoneSettingsBaseImpl::GetTimezoneList() const { 350 return timezones_; 351 } 352 353 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() { 354 for (size_t i = 0; i < arraysize(kTimeZones); ++i) { 355 timezones_.push_back(icu::TimeZone::createTimeZone( 356 icu::UnicodeString(kTimeZones[i], -1, US_INV))); 357 } 358 } 359 360 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull( 361 const icu::TimeZone& timezone) const { 362 const icu::TimeZone* known_timezone = NULL; 363 for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin(); 364 iter != timezones_.end(); ++iter) { 365 const icu::TimeZone* entry = *iter; 366 if (*entry == timezone) 367 return entry; 368 if (entry->hasSameRules(timezone)) 369 known_timezone = entry; 370 } 371 372 // May return NULL if we did not find a matching timezone in our list. 373 return known_timezone; 374 } 375 376 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) { 377 // Replace |timezone| by a known timezone with the same rules. If none exists 378 // go on with |timezone|. 379 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 380 if (!known_timezone) 381 known_timezone = &timezone; 382 383 timezone_.reset(known_timezone->clone()); 384 std::string id = base::UTF16ToUTF8(GetTimezoneID(*known_timezone)); 385 VLOG(1) << "Setting timezone to " << id; 386 // It's safe to change the timezone config files in the background as the 387 // following operations don't depend on the completion of the config change. 388 base::WorkerPool::GetTaskRunner(true /* task is slow */)-> 389 PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id)); 390 icu::TimeZone::setDefault(*known_timezone); 391 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 392 } 393 394 // static 395 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() { 396 return Singleton<TimezoneSettingsImpl, 397 DefaultSingletonTraits<TimezoneSettingsImpl> >::get(); 398 } 399 400 TimezoneSettingsImpl::TimezoneSettingsImpl() { 401 std::string id = GetTimezoneIDAsString(); 402 if (id.empty()) { 403 id = kFallbackTimeZoneId; 404 LOG(ERROR) << "Got an empty string for timezone, default to '" << id; 405 } 406 407 timezone_.reset(icu::TimeZone::createTimeZone( 408 icu::UnicodeString::fromUTF8(id))); 409 410 // Store a known timezone equivalent to id in |timezone_|. 411 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 412 if (known_timezone != NULL && *known_timezone != *timezone_) 413 // Not necessary to update the filesystem because |known_timezone| has the 414 // same rules. 415 timezone_.reset(known_timezone->clone()); 416 417 icu::TimeZone::setDefault(*timezone_); 418 VLOG(1) << "Timezone initially set to " << id; 419 } 420 421 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) { 422 // Replace |timezone| by a known timezone with the same rules. If none exists 423 // go on with |timezone|. 424 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 425 if (!known_timezone) 426 known_timezone = &timezone; 427 428 timezone_.reset(known_timezone->clone()); 429 icu::TimeZone::setDefault(*known_timezone); 430 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 431 } 432 433 // static 434 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() { 435 return Singleton<TimezoneSettingsStubImpl, 436 DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get(); 437 } 438 439 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() { 440 timezone_.reset(icu::TimeZone::createDefault()); 441 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 442 if (known_timezone != NULL && *known_timezone != *timezone_) 443 timezone_.reset(known_timezone->clone()); 444 } 445 446 } // namespace 447 448 namespace chromeos { 449 namespace system { 450 451 TimezoneSettings::Observer::~Observer() {} 452 453 // static 454 TimezoneSettings* TimezoneSettings::GetInstance() { 455 if (base::SysInfo::IsRunningOnChromeOS()) { 456 return TimezoneSettingsImpl::GetInstance(); 457 } else { 458 return TimezoneSettingsStubImpl::GetInstance(); 459 } 460 } 461 462 // static 463 base::string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) { 464 icu::UnicodeString id; 465 timezone.getID(id); 466 return base::string16(id.getBuffer(), id.length()); 467 } 468 469 } // namespace system 470 } // namespace chromeos 471