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 base::string16 GetCurrentTimezoneID() OVERRIDE; 258 virtual void SetTimezoneFromID(const base::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 base::string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() { 325 return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone()); 326 } 327 328 void TimezoneSettingsBaseImpl::SetTimezoneFromID( 329 const base::string16& timezone_id) { 330 scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone( 331 icu::UnicodeString(timezone_id.c_str(), timezone_id.size()))); 332 SetTimezone(*timezone); 333 } 334 335 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) { 336 observers_.AddObserver(observer); 337 } 338 339 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) { 340 observers_.RemoveObserver(observer); 341 } 342 343 const std::vector<icu::TimeZone*>& 344 TimezoneSettingsBaseImpl::GetTimezoneList() const { 345 return timezones_; 346 } 347 348 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() { 349 for (size_t i = 0; i < arraysize(kTimeZones); ++i) { 350 timezones_.push_back(icu::TimeZone::createTimeZone( 351 icu::UnicodeString(kTimeZones[i], -1, US_INV))); 352 } 353 } 354 355 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull( 356 const icu::TimeZone& timezone) const { 357 const icu::TimeZone* known_timezone = NULL; 358 for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin(); 359 iter != timezones_.end(); ++iter) { 360 const icu::TimeZone* entry = *iter; 361 if (*entry == timezone) 362 return entry; 363 if (entry->hasSameRules(timezone)) 364 known_timezone = entry; 365 } 366 367 // May return NULL if we did not find a matching timezone in our list. 368 return known_timezone; 369 } 370 371 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) { 372 // Replace |timezone| by a known timezone with the same rules. If none exists 373 // go on with |timezone|. 374 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 375 if (!known_timezone) 376 known_timezone = &timezone; 377 378 timezone_.reset(known_timezone->clone()); 379 std::string id = base::UTF16ToUTF8(GetTimezoneID(*known_timezone)); 380 VLOG(1) << "Setting timezone to " << id; 381 // It's safe to change the timezone config files in the background as the 382 // following operations don't depend on the completion of the config change. 383 base::WorkerPool::GetTaskRunner(true /* task is slow */)-> 384 PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id)); 385 icu::TimeZone::setDefault(*known_timezone); 386 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 387 } 388 389 // static 390 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() { 391 return Singleton<TimezoneSettingsImpl, 392 DefaultSingletonTraits<TimezoneSettingsImpl> >::get(); 393 } 394 395 TimezoneSettingsImpl::TimezoneSettingsImpl() { 396 std::string id = GetTimezoneIDAsString(); 397 if (id.empty()) { 398 id = kFallbackTimeZoneId; 399 LOG(ERROR) << "Got an empty string for timezone, default to '" << id; 400 } 401 402 timezone_.reset(icu::TimeZone::createTimeZone( 403 icu::UnicodeString::fromUTF8(id))); 404 405 // Store a known timezone equivalent to id in |timezone_|. 406 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 407 if (known_timezone != NULL && *known_timezone != *timezone_) 408 // Not necessary to update the filesystem because |known_timezone| has the 409 // same rules. 410 timezone_.reset(known_timezone->clone()); 411 412 icu::TimeZone::setDefault(*timezone_); 413 VLOG(1) << "Timezone initially set to " << id; 414 } 415 416 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) { 417 // Replace |timezone| by a known timezone with the same rules. If none exists 418 // go on with |timezone|. 419 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone); 420 if (!known_timezone) 421 known_timezone = &timezone; 422 423 timezone_.reset(known_timezone->clone()); 424 icu::TimeZone::setDefault(*known_timezone); 425 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone)); 426 } 427 428 // static 429 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() { 430 return Singleton<TimezoneSettingsStubImpl, 431 DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get(); 432 } 433 434 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() { 435 timezone_.reset(icu::TimeZone::createDefault()); 436 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_); 437 if (known_timezone != NULL && *known_timezone != *timezone_) 438 timezone_.reset(known_timezone->clone()); 439 } 440 441 } // namespace 442 443 namespace chromeos { 444 namespace system { 445 446 TimezoneSettings::Observer::~Observer() {} 447 448 // static 449 TimezoneSettings* TimezoneSettings::GetInstance() { 450 if (base::SysInfo::IsRunningOnChromeOS()) { 451 return TimezoneSettingsImpl::GetInstance(); 452 } else { 453 return TimezoneSettingsStubImpl::GetInstance(); 454 } 455 } 456 457 // static 458 base::string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) { 459 icu::UnicodeString id; 460 timezone.getID(id); 461 return base::string16(id.getBuffer(), id.length()); 462 } 463 464 } // namespace system 465 } // namespace chromeos 466