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/file_util.h" 11 #include "base/files/file_path.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/Athens", 114 "Europe/Bucharest", 115 "Europe/Chisinau", 116 "Europe/Helsinki", 117 "Europe/Istanbul", 118 "Europe/Kiev", 119 "Europe/Riga", 120 "Europe/Sofia", 121 "Europe/Tallinn", 122 "Europe/Vilnius", 123 "Asia/Amman", 124 "Asia/Beirut", 125 "Asia/Jerusalem", 126 "Africa/Nairobi", 127 "Asia/Baghdad", 128 "Asia/Riyadh", 129 "Asia/Kuwait", 130 "Europe/Minsk", 131 "Asia/Tehran", 132 "Europe/Moscow", 133 "Asia/Dubai", 134 "Asia/Tbilisi", 135 "Indian/Mauritius", 136 "Asia/Baku", 137 "Asia/Yerevan", 138 "Asia/Kabul", 139 "Asia/Karachi", 140 "Asia/Ashgabat", 141 "Asia/Oral", 142 "Asia/Calcutta", 143 "Asia/Colombo", 144 "Asia/Katmandu", 145 "Asia/Yekaterinburg", 146 "Asia/Almaty", 147 "Asia/Dhaka", 148 "Asia/Rangoon", 149 "Asia/Bangkok", 150 "Asia/Jakarta", 151 "Asia/Omsk", 152 "Asia/Novosibirsk", 153 "Asia/Ho_Chi_Minh", 154 "Asia/Phnom_Penh", 155 "Asia/Vientiane", 156 "Asia/Shanghai", 157 "Asia/Hong_Kong", 158 "Asia/Kuala_Lumpur", 159 "Asia/Singapore", 160 "Asia/Manila", 161 "Asia/Taipei", 162 "Asia/Makassar", 163 "Asia/Krasnoyarsk", 164 "Australia/Perth", 165 "Australia/Eucla", 166 "Asia/Irkutsk", 167 "Asia/Seoul", 168 "Asia/Tokyo", 169 "Asia/Jayapura", 170 "Australia/Darwin", 171 "Australia/Adelaide", 172 "Asia/Yakutsk", 173 "Pacific/Guam", 174 "Australia/Brisbane", 175 "Australia/Hobart", 176 "Australia/Sydney", 177 "Pacific/Port_Moresby", 178 "Asia/Vladivostok", 179 "Asia/Sakhalin", 180 "Asia/Magadan", 181 "Pacific/Fiji", 182 "Pacific/Majuro", 183 "Pacific/Auckland", 184 "Pacific/Tongatapu", 185 "Pacific/Apia", 186 "Pacific/Kiritimati", 187 }; 188 189 std::string GetTimezoneIDAsString() { 190 // Compare with chromiumos/src/platform/init/ui.conf which fixes certain 191 // incorrect states of the timezone symlink on startup. Thus errors occuring 192 // here should be rather contrived. 193 194 // Look at kTimezoneSymlink, see which timezone we are symlinked to. 195 char buf[256]; 196 const ssize_t len = readlink(kTimezoneSymlink, buf, 197 sizeof(buf)-1); 198 if (len == -1) { 199 LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink " 200 << kTimezoneSymlink; 201 return std::string(); 202 } 203 204 std::string timezone(buf, len); 205 // Remove kTimezoneFilesDir from the beginning. 206 if (timezone.find(kTimezoneFilesDir) != 0) { 207 LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong " 208 << timezone; 209 return std::string(); 210 } 211 212 return timezone.substr(strlen(kTimezoneFilesDir)); 213 } 214 215 void SetTimezoneIDFromString(const std::string& id) { 216 // Change the kTimezoneSymlink symlink to the path for this timezone. 217 // We want to do this in an atomic way. So we are going to create the symlink 218 // at kTimezoneSymlink2 and then move it to kTimezoneSymlink 219 220 base::FilePath timezone_symlink(kTimezoneSymlink); 221 base::FilePath timezone_symlink2(kTimezoneSymlink2); 222 base::FilePath timezone_file(kTimezoneFilesDir + id); 223 224 // Make sure timezone_file exists. 225 if (!base::PathExists(timezone_file)) { 226 LOG(ERROR) << "SetTimezoneID: Cannot find timezone file " 227 << timezone_file.value(); 228 return; 229 } 230 231 // Delete old symlink2 if it exists. 232 base::DeleteFile(timezone_symlink2, false); 233 234 // Create new symlink2. 235 if (symlink(timezone_file.value().c_str(), 236 timezone_symlink2.value().c_str()) == -1) { 237 LOG(ERROR) << "SetTimezoneID: Unable to create symlink " 238 << timezone_symlink2.value() << " to " << timezone_file.value(); 239 return; 240 } 241 242 // Move symlink2 to symlink. 243 if (!base::ReplaceFile(timezone_symlink2, timezone_symlink, NULL)) { 244 LOG(ERROR) << "SetTimezoneID: Unable to move symlink " 245 << timezone_symlink2.value() << " to " 246 << timezone_symlink.value(); 247 } 248 } 249 250 // Common code of the TimezoneSettings implementations. 251 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings { 252 public: 253 virtual ~TimezoneSettingsBaseImpl(); 254 255 // TimezoneSettings implementation: 256 virtual const icu::TimeZone& GetTimezone() OVERRIDE; 257 virtual string16 GetCurrentTimezoneID() OVERRIDE; 258 virtual void SetTimezoneFromID(const string16& timezone_id) OVERRIDE; 259 virtual void AddObserver(Observer* observer) OVERRIDE; 260 virtual void RemoveObserver(Observer* observer) OVERRIDE; 261 virtual const std::vector<icu::TimeZone*>& GetTimezoneList() const OVERRIDE; 262 263 protected: 264 TimezoneSettingsBaseImpl(); 265 266 // Returns |timezone| if it is an element of |timezones_|. 267 // Otherwise, returns a timezone from |timezones_|, if such exists, that has 268 // the same rule as the given |timezone|. 269 // Otherwise, returns NULL. 270 // Note multiple timezones with the same time zone offset may exist 271 // e.g. 272 // US/Pacific == America/Los_Angeles 273 const icu::TimeZone* GetKnownTimezoneOrNull( 274 const icu::TimeZone& timezone) const; 275 276 ObserverList<Observer> observers_; 277 std::vector<icu::TimeZone*> timezones_; 278 scoped_ptr<icu::TimeZone> timezone_; 279 280 private: 281 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl); 282 }; 283 284 // The TimezoneSettings implementation used in production. 285 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl { 286 public: 287 // TimezoneSettings implementation: 288 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE; 289 290 static TimezoneSettingsImpl* GetInstance(); 291 292 private: 293 friend struct DefaultSingletonTraits<TimezoneSettingsImpl>; 294 295 TimezoneSettingsImpl(); 296 297 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl); 298 }; 299 300 // The stub TimezoneSettings implementation used on Linux desktop. 301 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl { 302 public: 303 // TimezoneSettings implementation: 304 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE; 305 306 static TimezoneSettingsStubImpl* GetInstance(); 307 308 private: 309 friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>; 310 311 TimezoneSettingsStubImpl(); 312 313 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl); 314 }; 315 316 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() { 317 STLDeleteElements(&timezones_); 318 } 319 320 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() { 321 return *timezone_.get(); 322 } 323 324 string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() { 325 return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone()); 326 } 327 328 void TimezoneSettingsBaseImpl::SetTimezoneFromID(const string16& timezone_id) { 329 scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone( 330 icu::UnicodeString(timezone_id.c_str(), timezone_id.size()))); 331 SetTimezone(*timezone); 332 } 333 334 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) { 335 observers_.AddObserver(observer); 336 } 337 338 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) { 339 observers_.RemoveObserver(observer); 340 } 341 342 const std::vector<icu::TimeZone*>& 343 TimezoneSettingsBaseImpl::GetTimezoneList() const { 344 return timezones_; 345 } 346 347 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() { 348 for (size_t i = 0; i < arraysize(kTimeZones); ++i) { 349 timezones_.push_back(icu::TimeZone::createTimeZone( 350 icu::UnicodeString(kTimeZones[i], -1, US_INV))); 351 } 352 } 353 354 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull( 355 const icu::TimeZone& timezone) const { 356 const icu::TimeZone* known_timezone = NULL; 357 for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin(); 358 iter != timezones_.end(); ++iter) { 359 const icu::TimeZone* entry = *iter; 360 if (*entry == timezone) 361 return entry; 362 if (entry->hasSameRules(timezone)) 363 known_timezone = entry; 364 } 365 366 // May return NULL if we did not find a matching timezone in our list. 367 return known_timezone; 368 } 369 370 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) { 371 // Replace |timezone| by a known timezone with the same rules. If none exists 372 // go on with |timezone|. 373 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 374 if (!known_timezone) 375 known_timezone = &timezone; 376 377 timezone_.reset(known_timezone->clone()); 378 std::string id = UTF16ToUTF8(GetTimezoneID(*known_timezone)); 379 VLOG(1) << "Setting timezone to " << id; 380 // It's safe to change the timezone config files in the background as the 381 // following operations don't depend on the completion of the config change. 382 base::WorkerPool::GetTaskRunner(true /* task is slow */)-> 383 PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id)); 384 icu::TimeZone::setDefault(*known_timezone); 385 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 386 } 387 388 // static 389 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() { 390 return Singleton<TimezoneSettingsImpl, 391 DefaultSingletonTraits<TimezoneSettingsImpl> >::get(); 392 } 393 394 TimezoneSettingsImpl::TimezoneSettingsImpl() { 395 std::string id = GetTimezoneIDAsString(); 396 if (id.empty()) { 397 id = kFallbackTimeZoneId; 398 LOG(ERROR) << "Got an empty string for timezone, default to '" << id; 399 } 400 401 timezone_.reset(icu::TimeZone::createTimeZone( 402 icu::UnicodeString::fromUTF8(id))); 403 404 // Store a known timezone equivalent to id in |timezone_|. 405 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 406 if (known_timezone != NULL && *known_timezone != *timezone_) 407 // Not necessary to update the filesystem because |known_timezone| has the 408 // same rules. 409 timezone_.reset(known_timezone->clone()); 410 411 icu::TimeZone::setDefault(*timezone_); 412 VLOG(1) << "Timezone initially set to " << id; 413 } 414 415 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) { 416 // Replace |timezone| by a known timezone with the same rules. If none exists 417 // go on with |timezone|. 418 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 419 if (!known_timezone) 420 known_timezone = &timezone; 421 422 timezone_.reset(known_timezone->clone()); 423 icu::TimeZone::setDefault(*known_timezone); 424 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 425 } 426 427 // static 428 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() { 429 return Singleton<TimezoneSettingsStubImpl, 430 DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get(); 431 } 432 433 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() { 434 timezone_.reset(icu::TimeZone::createDefault()); 435 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 436 if (known_timezone != NULL && *known_timezone != *timezone_) 437 timezone_.reset(known_timezone->clone()); 438 } 439 440 } // namespace 441 442 namespace chromeos { 443 namespace system { 444 445 TimezoneSettings::Observer::~Observer() {} 446 447 // static 448 TimezoneSettings* TimezoneSettings::GetInstance() { 449 if (base::SysInfo::IsRunningOnChromeOS()) { 450 return TimezoneSettingsImpl::GetInstance(); 451 } else { 452 return TimezoneSettingsStubImpl::GetInstance(); 453 } 454 } 455 456 // static 457 string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) { 458 icu::UnicodeString id; 459 timezone.getID(id); 460 return string16(id.getBuffer(), id.length()); 461 } 462 463 } // namespace system 464 } // namespace chromeos 465