MythTV  master
mythtimezone.cpp
Go to the documentation of this file.
1 #include "mythtimezone.h"
2 
3 #include <cstdlib> // for malloc, getenv
4 #include <ctime>
5 
6 #include <QDataStream>
7 #include <QTextStream>
8 #include <QDateTime>
9 #include <QFileInfo>
10 #include <QDir>
11 
12 #include "mythcorecontext.h"
13 #include "mythlogging.h"
14 #include "mythdate.h"
15 
16 namespace MythTZ
17 {
18 
19 int calc_utc_offset(void)
20 {
21  QDateTime loc = QDateTime::currentDateTime();
22  QDateTime utc = loc.toUTC();
23  loc = QDateTime(loc.date(), loc.time(), Qt::UTC);
24  return utc.secsTo(loc);
25 }
26 
27 static bool compare_zone_files(const QFileInfo& first_file_info,
28  const QFileInfo& second_file_info)
29 {
30  if (!first_file_info.isFile() || !second_file_info.isFile() ||
31  !first_file_info.isReadable() || !second_file_info.isReadable())
32  return false;
33 
34  qint64 first_file_size = first_file_info.size();
35  // sanity check - zoneinfo files should typically be less than
36  // about 4kB, but leave room for growth
37  if ((first_file_size > 200 * 1024) ||
38  (second_file_info.size() != first_file_size))
39  return false;
40 
41  QFile first_file(first_file_info.absoluteFilePath());
42  QByteArray first_file_data;
43  first_file_data.resize(first_file_size);
44  QFile second_file(second_file_info.absoluteFilePath());
45  QByteArray second_file_data;
46  second_file_data.resize(first_file_size);
47  if (first_file.open(QIODevice::ReadOnly))
48  {
49  QDataStream in(&first_file);
50  if (in.readRawData(first_file_data.data(),
51  first_file_size) != first_file_size)
52  {
53  first_file.close();
54  return false;
55  }
56  first_file.close();
57  }
58  if (second_file.open(QIODevice::ReadOnly))
59  {
60  QDataStream in(&second_file);
61  if (in.readRawData(second_file_data.data(),
62  first_file_size) != first_file_size)
63  {
64  second_file.close();
65  return false;
66  }
67  second_file.close();
68  }
69  return first_file_data == second_file_data;
70 }
71 
72 #ifndef _WIN32
73 /* Helper function for getSystemTimeZoneID() that compares the
74  zoneinfo_file_path (regular) file with files in the zoneinfo_dir_path until
75  it finds a match. The matching file's name is used to determine the time
76  zone ID. */
77 static QString findZoneinfoFile(const QString& zoneinfo_file_path,
78  const QString& zoneinfo_dir_path)
79 {
80  QString zone_id("UNDEF");
81  QDir zoneinfo_dir(zoneinfo_dir_path);
82  QFileInfoList dirlist = zoneinfo_dir.entryInfoList();
83  QFileInfo info;
84  QString basename;
85  QFileInfo zoneinfo_file_info(zoneinfo_file_path);
86 
87  for (QFileInfoList::const_iterator it = dirlist.begin();
88  it != dirlist.end(); ++it)
89  {
90  info = *it;
91  // Skip '.' and '..' and other files starting with "." and
92  // skip localtime (which is often a link to zoneinfo_file_path)
93  basename = info.baseName();
94  if (basename.isEmpty() || (basename == "localtime")) {
95  continue;
96  }
97  if (info.isDir())
98  {
99  zone_id = findZoneinfoFile(zoneinfo_file_path,
100  info.absoluteFilePath());
101  if (zone_id != "UNDEF")
102  return zone_id;
103  }
104  else if (compare_zone_files(zoneinfo_file_info, info))
105  {
106  zone_id = info.absoluteFilePath();
107  break;
108  }
109  }
110  return zone_id;
111 }
112 #endif
113 
114 /* helper fuction to find the zone ID in a configuration string
115  allows NIS-format /etc/timezone , which contains extra information:
116  <time zone ID> <host or NIS domain name> # optional comments
117 */
118 static bool parse_zone_id_config_string(QString& zone_id)
119 {
120  bool found = false;
121  QString zoneinfo_dir_path("/usr/share/zoneinfo/");
122  QRegExp sep("\\s+");
123  QFileInfo file_info;
124 
125  while (!found)
126  {
127  QString temp_zone_id = zone_id;
128  temp_zone_id.replace(' ', '_');
129  file_info.setFile(zoneinfo_dir_path + temp_zone_id);
130  if (file_info.exists())
131  {
132  found = true;
133  }
134  else
135  {
136  zone_id = zone_id.section(sep, 0, -2);
137  if (zone_id.isEmpty())
138  break;
139  }
140  }
141  return found;
142 }
143 
144 /* helper fuction to read time zone id from a file
145  Debian's /etc/timezone or Red Hat's /etc/sysconfig/clock */
146 static bool read_time_zone_id(const QString& filename, QString& zone_id)
147 {
148  bool found = false;
149  QFile file(filename);
150  QFileInfo info(file);
151  if (info.exists() && info.isFile() && info.isReadable())
152  {
153  if (file.open(QIODevice::ReadOnly | QIODevice::Text))
154  {
155  QString line;
156  QTextStream in(&file);
157  // Handle whitespace and quotes
158  QRegExp re("^(?:ZONE\\s*=)?\\s*(['\"]?)([\\w\\s/-\\+]+)\\1\\s*(?:#.*)?$");
159  re.setPatternSyntax(QRegExp::RegExp2);
160  while (!in.atEnd())
161  {
162  line = in.readLine();
163  if (re.indexIn(line) != -1)
164  {
165  zone_id = re.cap(2);
166  if (parse_zone_id_config_string(zone_id))
167  found = true;
168  break;
169  }
170  }
171  file.close();
172  }
173  }
174  return found;
175 }
176 
177 /* Helper function for getTimeZoneID() that provides an unprocessed time zone
178  id obtained using system-dependent means of identifying the system's time
179  zone. */
180 static QString getSystemTimeZoneID(void)
181 {
182  QString zone_id("UNDEF");
183 #ifdef _WIN32
184  // typedef struct _TIME_ZONE_INFORMATION { ...
185  // GetTimeZoneInformation();
186  // ...
187  // Sadly, Windows zone names are different to the (probably Unix)
188  // backend's names - "AUS Eastern Standard Time" vs "Australia/Sydney".
189  // Translation is not worthwhile. Leave it as UNDEF to check the offset.
190 #else
191  // Try to determine the time zone information by inspecting the system
192  // configuration
193  QString time_zone_file_path("/etc/timezone");
194  QString clock_file_path("/etc/sysconfig/clock");
195  QString zoneinfo_file_path("/etc/localtime");
196  QString zoneinfo_dir_path("/usr/share/zoneinfo");
197 
198  // First, check time_zone_file_path (used by Debian-based systems)
199  if (read_time_zone_id(time_zone_file_path, zone_id))
200  return zone_id;
201 
202  // Next, look for the ZONE entry in clock_file_path (used by Red Hat-based
203  // systems)
204  if (read_time_zone_id(clock_file_path, zone_id))
205  return zone_id;
206 
207  // Next check zoneinfo_file_path
208  QFile zoneinfo_file(zoneinfo_file_path);
209  QFileInfo info(zoneinfo_file);
210 
211  if (info.exists() && info.isFile())
212  {
213  QString tz;
214  if (info.isSymLink())
215  {
216  // The symlink refers to a file whose name contains the zone ID
217  tz = info.symLinkTarget();
218  }
219  else
220  {
221  // The zoneinfo_file is a copy of the file in the
222  // zoneinfo_dir_path, so search for the same file in
223  // zoneinfo_dir_path
224  tz = findZoneinfoFile(zoneinfo_file_path, zoneinfo_dir_path);
225  }
226  if (tz != "UNDEF")
227  {
228  int pos = 0;
229  // Get the zone ID from the filename
230  // Look for the basename of zoneinfo_dir_path in case it's a
231  // relative link
232  QString zoneinfo_dirname = zoneinfo_dir_path.section('/', -1);
233  if ((pos = tz.indexOf(zoneinfo_dirname)) != -1)
234  {
235  zone_id = tz.right(tz.size() - (pos + 1) -
236  zoneinfo_dirname.size());
237  }
238  }
239  else
240  {
241  // If we still haven't found a time zone, try localtime_r() to at
242  // least get the zone name/abbreviation (as opposed to the
243  // identifier for the set of rules governing the zone)
244  char name[64];
245  auto *result = (struct tm *)malloc(sizeof(struct tm));
246  if (result != nullptr)
247  {
248  time_t t = time(nullptr);
249  localtime_r(&t, result);
250  if (result != nullptr)
251  {
252  if (strftime(name, sizeof(name), "%Z", result) > 0)
253  zone_id = name;
254  free(result);
255  }
256  }
257  }
258  }
259 
260 #endif
261  return zone_id;
262 }
263 
268 QString getTimeZoneID(void)
269 {
270  QString zone_id("UNDEF");
271 #ifndef _WIN32
272  // First, try the TZ environment variable to check for environment-specific
273  // overrides
274  QString tz = getenv("TZ");
275  if (tz.isEmpty())
276  {
277  // No TZ, so attempt to determine the system-configured time zone ID
278  tz = getSystemTimeZoneID();
279  }
280 
281  if (!tz.isEmpty())
282  {
283  zone_id = tz;
284  if (zone_id.startsWith("\"") || zone_id.startsWith("'"))
285  zone_id.remove(0, 1);
286  if (zone_id.endsWith("\"") || zone_id.endsWith("'"))
287  zone_id.chop(1);
288  if (zone_id.startsWith(":"))
289  zone_id.remove(0, 1);
290  // the "posix/" subdirectory typically contains the same files as the
291  // "zoneinfo/" parent directory, but are not typically what are in use
292  if (zone_id.startsWith("posix/"))
293  zone_id.remove(0, 6);
294  }
295 
296 #endif
297  return zone_id;
298 }
299 
304 bool checkTimeZone(void)
305 {
306  return true;
307 }
308 
311 bool checkTimeZone(const QStringList &/*master_settings*/)
312 {
313  return true;
314 }
315 
316 }; // namespace MythTZ
static bool compare_zone_files(const QFileInfo &first_file_info, const QFileInfo &second_file_info)
static bool parse_zone_id_config_string(QString &zone_id)
static __inline struct tm * localtime_r(const time_t *timep, struct tm *result)
Definition: compat.h:286
static bool read_time_zone_id(const QString &filename, QString &zone_id)
int calc_utc_offset(void)
bool checkTimeZone(void)
Verifies the time zone settings on this system agree with those on the master backend.
QString getTimeZoneID(void)
Returns the zoneinfo time zone ID or as much time zone information as possible.
static QString getSystemTimeZoneID(void)