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
25namespace {
26
27// The filepath to the timezone file that symlinks to the actual timezone file.
28const char kTimezoneSymlink[] = "/var/lib/timezone/localtime";
29const 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"
33const char kTimezoneFilesDir[] = "/usr/share/zoneinfo/";
34
35// Fallback time zone ID used in case of an unexpected error.
36const 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.
44static 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
189std::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
215void 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.
251class 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.
285class 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.
301class 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
316TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
317  STLDeleteElements(&timezones_);
318}
319
320const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
321  return *timezone_.get();
322}
323
324string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
325  return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
326}
327
328void 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
334void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
335  observers_.AddObserver(observer);
336}
337
338void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
339  observers_.RemoveObserver(observer);
340}
341
342const std::vector<icu::TimeZone*>&
343TimezoneSettingsBaseImpl::GetTimezoneList() const {
344  return timezones_;
345}
346
347TimezoneSettingsBaseImpl::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
354const 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
370void 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
389TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
390  return Singleton<TimezoneSettingsImpl,
391                   DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
392}
393
394TimezoneSettingsImpl::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
415void 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
428TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
429  return Singleton<TimezoneSettingsStubImpl,
430      DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
431}
432
433TimezoneSettingsStubImpl::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
442namespace chromeos {
443namespace system {
444
445TimezoneSettings::Observer::~Observer() {}
446
447// static
448TimezoneSettings* TimezoneSettings::GetInstance() {
449  if (base::SysInfo::IsRunningOnChromeOS()) {
450    return TimezoneSettingsImpl::GetInstance();
451  } else {
452    return TimezoneSettingsStubImpl::GetInstance();
453  }
454}
455
456// static
457string16 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