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  time_t t;
246  struct tm *result = (struct tm *)malloc(sizeof(*result));
247 
248  if (result != nullptr)
249  {
250  t = time(nullptr);
251  localtime_r(&t, result);
252  if (result != nullptr)
253  {
254  if (strftime(name, sizeof(name), "%Z", result) > 0)
255  zone_id = name;
256  free(result);
257  }
258  }
259  }
260  }
261 
262 #endif
263  return zone_id;
264 }
265 
270 QString getTimeZoneID(void)
271 {
272  QString zone_id("UNDEF");
273 #ifndef _WIN32
274  // First, try the TZ environment variable to check for environment-specific
275  // overrides
276  QString tz = getenv("TZ");
277  if (tz.isEmpty())
278  {
279  // No TZ, so attempt to determine the system-configured time zone ID
280  tz = getSystemTimeZoneID();
281  }
282 
283  if (!tz.isEmpty())
284  {
285  zone_id = tz;
286  if (zone_id.startsWith("\"") || zone_id.startsWith("'"))
287  zone_id.remove(0, 1);
288  if (zone_id.endsWith("\"") || zone_id.endsWith("'"))
289  zone_id.chop(1);
290  if (zone_id.startsWith(":"))
291  zone_id.remove(0, 1);
292  // the "posix/" subdirectory typically contains the same files as the
293  // "zoneinfo/" parent directory, but are not typically what are in use
294  if (zone_id.startsWith("posix/"))
295  zone_id.remove(0, 6);
296  }
297 
298 #endif
299  return zone_id;
300 }
301 
306 bool checkTimeZone(void)
307 {
308  return true;
309 }
310 
313 bool checkTimeZone(const QStringList &/*master_settings*/)
314 {
315  return true;
316 }
317 
318 }; // 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)
unsigned char t
Definition: ParseText.cpp:329
const char * name
Definition: ParseText.cpp:328
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)