TimeZoneInfo.Unix.cs 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Globalization;
  7. using System.IO;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Security;
  11. using Internal.IO;
  12. namespace System
  13. {
  14. public sealed partial class TimeZoneInfo
  15. {
  16. private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
  17. private const string ZoneTabFileName = "zone.tab";
  18. private const string TimeZoneEnvironmentVariable = "TZ";
  19. private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
  20. private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
  21. {
  22. TZifHead t;
  23. DateTime[] dts;
  24. byte[] typeOfLocalTime;
  25. TZifType[] transitionType;
  26. string zoneAbbreviations;
  27. bool[] StandardTime;
  28. bool[] GmtTime;
  29. string futureTransitionsPosixFormat;
  30. // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
  31. TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
  32. _id = id;
  33. _displayName = LocalId;
  34. _baseUtcOffset = TimeSpan.Zero;
  35. // find the best matching baseUtcOffset and display strings based on the current utcNow value.
  36. // NOTE: read the display strings from the tzfile now in case they can't be loaded later
  37. // from the globalization data.
  38. DateTime utcNow = DateTime.UtcNow;
  39. for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++)
  40. {
  41. int type = typeOfLocalTime[i];
  42. if (!transitionType[type].IsDst)
  43. {
  44. _baseUtcOffset = transitionType[type].UtcOffset;
  45. _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
  46. }
  47. else
  48. {
  49. _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
  50. }
  51. }
  52. if (dts.Length == 0)
  53. {
  54. // time zones like Africa/Bujumbura and Etc/GMT* have no transition times but still contain
  55. // TZifType entries that may contain a baseUtcOffset and display strings
  56. for (int i = 0; i < transitionType.Length; i++)
  57. {
  58. if (!transitionType[i].IsDst)
  59. {
  60. _baseUtcOffset = transitionType[i].UtcOffset;
  61. _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
  62. }
  63. else
  64. {
  65. _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
  66. }
  67. }
  68. }
  69. _displayName = _standardDisplayName;
  70. GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Generic, ref _displayName);
  71. GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
  72. GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
  73. if (_standardDisplayName == _displayName)
  74. {
  75. if (_baseUtcOffset >= TimeSpan.Zero)
  76. _displayName = $"(UTC+{_baseUtcOffset:hh\\:mm}) {_standardDisplayName}";
  77. else
  78. _displayName = $"(UTC-{_baseUtcOffset:hh\\:mm}) {_standardDisplayName}";
  79. }
  80. // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
  81. // with DateTimeOffset, SQL Server, and the W3C XML Specification
  82. if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
  83. {
  84. _baseUtcOffset = new TimeSpan(_baseUtcOffset.Hours, _baseUtcOffset.Minutes, 0);
  85. }
  86. if (!dstDisabled)
  87. {
  88. // only create the adjustment rule if DST is enabled
  89. TZif_GenerateAdjustmentRules(out _adjustmentRules, _baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
  90. }
  91. ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
  92. }
  93. private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string displayName)
  94. {
  95. if (GlobalizationMode.Invariant)
  96. {
  97. displayName = _standardDisplayName;
  98. return;
  99. }
  100. string timeZoneDisplayName;
  101. bool result = Interop.CallStringMethod(
  102. (buffer, locale, id, type) =>
  103. {
  104. fixed (char* bufferPtr = buffer)
  105. {
  106. return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length);
  107. }
  108. },
  109. CultureInfo.CurrentUICulture.Name,
  110. _id,
  111. nameType,
  112. out timeZoneDisplayName);
  113. // If there is an unknown error, don't set the displayName field.
  114. // It will be set to the abbreviation that was read out of the tzfile.
  115. if (result)
  116. {
  117. displayName = timeZoneDisplayName;
  118. }
  119. }
  120. /// <summary>
  121. /// Returns a cloned array of AdjustmentRule objects
  122. /// </summary>
  123. public AdjustmentRule[] GetAdjustmentRules()
  124. {
  125. if (_adjustmentRules == null)
  126. {
  127. return Array.Empty<AdjustmentRule>();
  128. }
  129. // The rules we use in Unix care mostly about the start and end dates but don't fill the transition start and end info.
  130. // as the rules now is public, we should fill it properly so the caller doesn't have to know how we use it internally
  131. // and can use it as it is used in Windows
  132. AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length];
  133. for (int i = 0; i < _adjustmentRules.Length; i++)
  134. {
  135. var rule = _adjustmentRules[i];
  136. var start = rule.DateStart.Kind == DateTimeKind.Utc ?
  137. // At the daylight start we didn't start the daylight saving yet then we convert to Local time
  138. // by adding the _baseUtcOffset to the UTC time
  139. new DateTime(rule.DateStart.Ticks + _baseUtcOffset.Ticks, DateTimeKind.Unspecified) :
  140. rule.DateStart;
  141. var end = rule.DateEnd.Kind == DateTimeKind.Utc ?
  142. // At the daylight saving end, the UTC time is mapped to local time which is already shifted by the daylight delta
  143. // we calculate the local time by adding _baseUtcOffset + DaylightDelta to the UTC time
  144. new DateTime(rule.DateEnd.Ticks + _baseUtcOffset.Ticks + rule.DaylightDelta.Ticks, DateTimeKind.Unspecified) :
  145. rule.DateEnd;
  146. var startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day);
  147. var endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day);
  148. rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition);
  149. }
  150. return rules;
  151. }
  152. private static void PopulateAllSystemTimeZones(CachedData cachedData)
  153. {
  154. Debug.Assert(Monitor.IsEntered(cachedData));
  155. string timeZoneDirectory = GetTimeZoneDirectory();
  156. foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory))
  157. {
  158. TimeZoneInfo value;
  159. Exception ex;
  160. TryGetTimeZone(timeZoneId, false, out value, out ex, cachedData, alwaysFallbackToLocalMachine: true); // populate the cache
  161. }
  162. }
  163. /// <summary>
  164. /// Helper function for retrieving the local system time zone.
  165. /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
  166. /// Assumes cachedData lock is taken.
  167. /// </summary>
  168. /// <returns>A new TimeZoneInfo instance.</returns>
  169. private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
  170. {
  171. Debug.Assert(Monitor.IsEntered(cachedData));
  172. // Without Registry support, create the TimeZoneInfo from a TZ file
  173. return GetLocalTimeZoneFromTzFile();
  174. }
  175. private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
  176. {
  177. value = null;
  178. e = null;
  179. string timeZoneDirectory = GetTimeZoneDirectory();
  180. string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
  181. byte[] rawData;
  182. try
  183. {
  184. rawData = File.ReadAllBytes(timeZoneFilePath);
  185. }
  186. catch (UnauthorizedAccessException ex)
  187. {
  188. e = ex;
  189. return TimeZoneInfoResult.SecurityException;
  190. }
  191. catch (FileNotFoundException ex)
  192. {
  193. e = ex;
  194. return TimeZoneInfoResult.TimeZoneNotFoundException;
  195. }
  196. catch (DirectoryNotFoundException ex)
  197. {
  198. e = ex;
  199. return TimeZoneInfoResult.TimeZoneNotFoundException;
  200. }
  201. catch (IOException ex)
  202. {
  203. e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath), ex);
  204. return TimeZoneInfoResult.InvalidTimeZoneException;
  205. }
  206. value = GetTimeZoneFromTzData(rawData, id);
  207. if (value == null)
  208. {
  209. e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath));
  210. return TimeZoneInfoResult.InvalidTimeZoneException;
  211. }
  212. return TimeZoneInfoResult.Success;
  213. }
  214. /// <summary>
  215. /// Returns a collection of TimeZone Id values from the zone.tab file in the timeZoneDirectory.
  216. /// </summary>
  217. /// <remarks>
  218. /// Lines that start with # are comments and are skipped.
  219. /// </remarks>
  220. private static List<string> GetTimeZoneIds(string timeZoneDirectory)
  221. {
  222. List<string> timeZoneIds = new List<string>();
  223. try
  224. {
  225. using (StreamReader sr = new StreamReader(Path.Combine(timeZoneDirectory, ZoneTabFileName), Encoding.UTF8))
  226. {
  227. string zoneTabFileLine;
  228. while ((zoneTabFileLine = sr.ReadLine()) != null)
  229. {
  230. if (!string.IsNullOrEmpty(zoneTabFileLine) && zoneTabFileLine[0] != '#')
  231. {
  232. // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
  233. int firstTabIndex = zoneTabFileLine.IndexOf('\t');
  234. if (firstTabIndex != -1)
  235. {
  236. int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
  237. if (secondTabIndex != -1)
  238. {
  239. string timeZoneId;
  240. int startIndex = secondTabIndex + 1;
  241. int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
  242. if (thirdTabIndex != -1)
  243. {
  244. int length = thirdTabIndex - startIndex;
  245. timeZoneId = zoneTabFileLine.Substring(startIndex, length);
  246. }
  247. else
  248. {
  249. timeZoneId = zoneTabFileLine.Substring(startIndex);
  250. }
  251. if (!string.IsNullOrEmpty(timeZoneId))
  252. {
  253. timeZoneIds.Add(timeZoneId);
  254. }
  255. }
  256. }
  257. }
  258. }
  259. }
  260. }
  261. catch (IOException) { }
  262. catch (UnauthorizedAccessException) { }
  263. return timeZoneIds;
  264. }
  265. /// <summary>
  266. /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
  267. /// 1. Read the TZ environment variable. If it is set, use it.
  268. /// 2. Look for the data in /etc/localtime.
  269. /// 3. Look for the data in GetTimeZoneDirectory()/localtime.
  270. /// 4. Use UTC if all else fails.
  271. /// </summary>
  272. private static bool TryGetLocalTzFile(out byte[] rawData, out string id)
  273. {
  274. rawData = null;
  275. id = null;
  276. string tzVariable = GetTzEnvironmentVariable();
  277. // If the env var is null, use the localtime file
  278. if (tzVariable == null)
  279. {
  280. return
  281. TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
  282. TryLoadTzFile(Path.Combine(GetTimeZoneDirectory(), "localtime"), ref rawData, ref id);
  283. }
  284. // If it's empty, use UTC (TryGetLocalTzFile() should return false).
  285. if (tzVariable.Length == 0)
  286. {
  287. return false;
  288. }
  289. // Otherwise, use the path from the env var. If it's not absolute, make it relative
  290. // to the system timezone directory
  291. string tzFilePath;
  292. if (tzVariable[0] != '/')
  293. {
  294. id = tzVariable;
  295. tzFilePath = Path.Combine(GetTimeZoneDirectory(), tzVariable);
  296. }
  297. else
  298. {
  299. tzFilePath = tzVariable;
  300. }
  301. return TryLoadTzFile(tzFilePath, ref rawData, ref id);
  302. }
  303. private static string GetTzEnvironmentVariable()
  304. {
  305. string result = Environment.GetEnvironmentVariable(TimeZoneEnvironmentVariable);
  306. if (!string.IsNullOrEmpty(result))
  307. {
  308. if (result[0] == ':')
  309. {
  310. // strip off the ':' prefix
  311. result = result.Substring(1);
  312. }
  313. }
  314. return result;
  315. }
  316. private static bool TryLoadTzFile(string tzFilePath, ref byte[] rawData, ref string id)
  317. {
  318. if (File.Exists(tzFilePath))
  319. {
  320. try
  321. {
  322. rawData = File.ReadAllBytes(tzFilePath);
  323. if (string.IsNullOrEmpty(id))
  324. {
  325. id = FindTimeZoneIdUsingReadLink(tzFilePath);
  326. if (string.IsNullOrEmpty(id))
  327. {
  328. id = FindTimeZoneId(rawData);
  329. }
  330. }
  331. return true;
  332. }
  333. catch (IOException) { }
  334. catch (SecurityException) { }
  335. catch (UnauthorizedAccessException) { }
  336. }
  337. return false;
  338. }
  339. /// <summary>
  340. /// Finds the time zone id by using 'readlink' on the path to see if tzFilePath is
  341. /// a symlink to a file.
  342. /// </summary>
  343. private static string FindTimeZoneIdUsingReadLink(string tzFilePath)
  344. {
  345. string id = null;
  346. string symlinkPath = Interop.Sys.ReadLink(tzFilePath);
  347. if (symlinkPath != null)
  348. {
  349. // symlinkPath can be relative path, use Path to get the full absolute path.
  350. symlinkPath = Path.GetFullPath(symlinkPath, Path.GetDirectoryName(tzFilePath));
  351. string timeZoneDirectory = GetTimeZoneDirectory();
  352. if (symlinkPath.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
  353. {
  354. id = symlinkPath.Substring(timeZoneDirectory.Length);
  355. }
  356. }
  357. return id;
  358. }
  359. /// <summary>
  360. /// Enumerate files
  361. /// </summary>
  362. private static IEnumerable<string> EnumerateFilesRecursively(string path)
  363. {
  364. List<string> toExplore = null; // List used as a stack
  365. string currentPath = path;
  366. for(;;)
  367. {
  368. using (Microsoft.Win32.SafeHandles.SafeDirectoryHandle dirHandle = Interop.Sys.OpenDir(currentPath))
  369. {
  370. if (dirHandle.IsInvalid)
  371. {
  372. throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), currentPath, isDirectory: true);
  373. }
  374. // Read each entry from the enumerator
  375. Interop.Sys.DirectoryEntry dirent;
  376. while (Interop.Sys.ReadDir(dirHandle, out dirent) == 0)
  377. {
  378. if (dirent.InodeName == "." || dirent.InodeName == "..")
  379. continue;
  380. string fullPath = Path.Combine(currentPath, dirent.InodeName);
  381. // Get from the dir entry whether the entry is a file or directory.
  382. // We classify everything as a file unless we know it to be a directory.
  383. bool isDir;
  384. if (dirent.InodeType == Interop.Sys.NodeType.DT_DIR)
  385. {
  386. // We know it's a directory.
  387. isDir = true;
  388. }
  389. else if (dirent.InodeType == Interop.Sys.NodeType.DT_LNK || dirent.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
  390. {
  391. // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
  392. // If we can't (e.g. symlink to a file, broken symlink, etc.), we'll just treat it as a file.
  393. Interop.Sys.FileStatus fileinfo;
  394. if (Interop.Sys.Stat(fullPath, out fileinfo) >= 0)
  395. {
  396. isDir = (fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
  397. }
  398. else
  399. {
  400. isDir = false;
  401. }
  402. }
  403. else
  404. {
  405. // Otherwise, treat it as a file. This includes regular files, FIFOs, etc.
  406. isDir = false;
  407. }
  408. // Yield the result if the user has asked for it. In the case of directories,
  409. // always explore it by pushing it onto the stack, regardless of whether
  410. // we're returning directories.
  411. if (isDir)
  412. {
  413. if (toExplore == null)
  414. {
  415. toExplore = new List<string>();
  416. }
  417. toExplore.Add(fullPath);
  418. }
  419. else
  420. {
  421. yield return fullPath;
  422. }
  423. }
  424. }
  425. if (toExplore == null || toExplore.Count == 0)
  426. break;
  427. currentPath = toExplore[toExplore.Count - 1];
  428. toExplore.RemoveAt(toExplore.Count - 1);
  429. }
  430. }
  431. /// <summary>
  432. /// Find the time zone id by searching all the tzfiles for the one that matches rawData
  433. /// and return its file name.
  434. /// </summary>
  435. private static string FindTimeZoneId(byte[] rawData)
  436. {
  437. // default to "Local" if we can't find the right tzfile
  438. string id = LocalId;
  439. string timeZoneDirectory = GetTimeZoneDirectory();
  440. string localtimeFilePath = Path.Combine(timeZoneDirectory, "localtime");
  441. string posixrulesFilePath = Path.Combine(timeZoneDirectory, "posixrules");
  442. byte[] buffer = new byte[rawData.Length];
  443. try
  444. {
  445. foreach (string filePath in EnumerateFilesRecursively(timeZoneDirectory))
  446. {
  447. // skip the localtime and posixrules file, since they won't give us the correct id
  448. if (!string.Equals(filePath, localtimeFilePath, StringComparison.OrdinalIgnoreCase)
  449. && !string.Equals(filePath, posixrulesFilePath, StringComparison.OrdinalIgnoreCase))
  450. {
  451. if (CompareTimeZoneFile(filePath, buffer, rawData))
  452. {
  453. // if all bytes are the same, this must be the right tz file
  454. id = filePath;
  455. // strip off the root time zone directory
  456. if (id.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
  457. {
  458. id = id.Substring(timeZoneDirectory.Length);
  459. }
  460. break;
  461. }
  462. }
  463. }
  464. }
  465. catch (IOException) { }
  466. catch (SecurityException) { }
  467. catch (UnauthorizedAccessException) { }
  468. return id;
  469. }
  470. private static bool CompareTimeZoneFile(string filePath, byte[] buffer, byte[] rawData)
  471. {
  472. try
  473. {
  474. // bufferSize == 1 used to avoid unnecessary buffer in FileStream
  475. using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
  476. {
  477. if (stream.Length == rawData.Length)
  478. {
  479. int index = 0;
  480. int count = rawData.Length;
  481. while (count > 0)
  482. {
  483. int n = stream.Read(buffer, index, count);
  484. if (n == 0)
  485. throw Error.GetEndOfFile();
  486. int end = index + n;
  487. for (; index < end; index++)
  488. {
  489. if (buffer[index] != rawData[index])
  490. {
  491. return false;
  492. }
  493. }
  494. count -= n;
  495. }
  496. return true;
  497. }
  498. }
  499. }
  500. catch (IOException) { }
  501. catch (SecurityException) { }
  502. catch (UnauthorizedAccessException) { }
  503. return false;
  504. }
  505. /// <summary>
  506. /// Helper function used by 'GetLocalTimeZone()' - this function wraps the call
  507. /// for loading time zone data from computers without Registry support.
  508. ///
  509. /// The TryGetLocalTzFile() call returns a Byte[] containing the compiled tzfile.
  510. /// </summary>
  511. private static TimeZoneInfo GetLocalTimeZoneFromTzFile()
  512. {
  513. byte[] rawData;
  514. string id;
  515. if (TryGetLocalTzFile(out rawData, out id))
  516. {
  517. TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id);
  518. if (result != null)
  519. {
  520. return result;
  521. }
  522. }
  523. // if we can't find a local time zone, return UTC
  524. return Utc;
  525. }
  526. private static TimeZoneInfo GetTimeZoneFromTzData(byte[] rawData, string id)
  527. {
  528. if (rawData != null)
  529. {
  530. try
  531. {
  532. return new TimeZoneInfo(rawData, id, dstDisabled: false); // create a TimeZoneInfo instance from the TZif data w/ DST support
  533. }
  534. catch (ArgumentException) { }
  535. catch (InvalidTimeZoneException) { }
  536. try
  537. {
  538. return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
  539. }
  540. catch (ArgumentException) { }
  541. catch (InvalidTimeZoneException) { }
  542. }
  543. return null;
  544. }
  545. private static string GetTimeZoneDirectory()
  546. {
  547. string tzDirectory = Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable);
  548. if (tzDirectory == null)
  549. {
  550. tzDirectory = DefaultTimeZoneDirectory;
  551. }
  552. else if (!tzDirectory.EndsWith(Path.DirectorySeparatorChar))
  553. {
  554. tzDirectory += Path.DirectorySeparatorChar;
  555. }
  556. return tzDirectory;
  557. }
  558. /// <summary>
  559. /// Helper function for retrieving a TimeZoneInfo object by time_zone_name.
  560. /// This function wraps the logic necessary to keep the private
  561. /// SystemTimeZones cache in working order
  562. ///
  563. /// This function will either return a valid TimeZoneInfo instance or
  564. /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'.
  565. /// </summary>
  566. public static TimeZoneInfo FindSystemTimeZoneById(string id)
  567. {
  568. // Special case for Utc as it will not exist in the dictionary with the rest
  569. // of the system time zones. There is no need to do this check for Local.Id
  570. // since Local is a real time zone that exists in the dictionary cache
  571. if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase))
  572. {
  573. return Utc;
  574. }
  575. if (id == null)
  576. {
  577. throw new ArgumentNullException(nameof(id));
  578. }
  579. else if (id.Length == 0 || id.Contains('\0'))
  580. {
  581. throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id));
  582. }
  583. TimeZoneInfo value;
  584. Exception e;
  585. TimeZoneInfoResult result;
  586. CachedData cachedData = s_cachedData;
  587. lock (cachedData)
  588. {
  589. result = TryGetTimeZone(id, false, out value, out e, cachedData, alwaysFallbackToLocalMachine: true);
  590. }
  591. if (result == TimeZoneInfoResult.Success)
  592. {
  593. return value;
  594. }
  595. else if (result == TimeZoneInfoResult.InvalidTimeZoneException)
  596. {
  597. Debug.Assert(e is InvalidTimeZoneException,
  598. "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException");
  599. throw e;
  600. }
  601. else if (result == TimeZoneInfoResult.SecurityException)
  602. {
  603. throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e);
  604. }
  605. else
  606. {
  607. throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e);
  608. }
  609. }
  610. // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone
  611. internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst)
  612. {
  613. bool isDaylightSavings;
  614. // Use the standard code path for Unix since there isn't a faster way of handling current-year-only time zones
  615. return GetUtcOffsetFromUtc(time, Local, out isDaylightSavings, out isAmbiguousLocalDst);
  616. }
  617. // TZFILE(5) BSD File Formats Manual TZFILE(5)
  618. //
  619. // NAME
  620. // tzfile -- timezone information
  621. //
  622. // SYNOPSIS
  623. // #include "/usr/src/lib/libc/stdtime/tzfile.h"
  624. //
  625. // DESCRIPTION
  626. // The time zone information files used by tzset(3) begin with the magic
  627. // characters ``TZif'' to identify them as time zone information files, fol-
  628. // lowed by sixteen bytes reserved for future use, followed by four four-
  629. // byte values written in a ``standard'' byte order (the high-order byte of
  630. // the value is written first). These values are, in order:
  631. //
  632. // tzh_ttisgmtcnt The number of UTC/local indicators stored in the file.
  633. // tzh_ttisstdcnt The number of standard/wall indicators stored in the
  634. // file.
  635. // tzh_leapcnt The number of leap seconds for which data is stored in
  636. // the file.
  637. // tzh_timecnt The number of ``transition times'' for which data is
  638. // stored in the file.
  639. // tzh_typecnt The number of ``local time types'' for which data is
  640. // stored in the file (must not be zero).
  641. // tzh_charcnt The number of characters of ``time zone abbreviation
  642. // strings'' stored in the file.
  643. //
  644. // The above header is followed by tzh_timecnt four-byte values of type
  645. // long, sorted in ascending order. These values are written in ``stan-
  646. // dard'' byte order. Each is used as a transition time (as returned by
  647. // time(3)) at which the rules for computing local time change. Next come
  648. // tzh_timecnt one-byte values of type unsigned char; each one tells which
  649. // of the different types of ``local time'' types described in the file is
  650. // associated with the same-indexed transition time. These values serve as
  651. // indices into an array of ttinfo structures that appears next in the file;
  652. // these structures are defined as follows:
  653. //
  654. // struct ttinfo {
  655. // long tt_gmtoff;
  656. // int tt_isdst;
  657. // unsigned int tt_abbrind;
  658. // };
  659. //
  660. // Each structure is written as a four-byte value for tt_gmtoff of type
  661. // long, in a standard byte order, followed by a one-byte value for tt_isdst
  662. // and a one-byte value for tt_abbrind. In each structure, tt_gmtoff gives
  663. // the number of seconds to be added to UTC, tt_isdst tells whether tm_isdst
  664. // should be set by localtime(3) and tt_abbrind serves as an index into the
  665. // array of time zone abbreviation characters that follow the ttinfo struc-
  666. // ture(s) in the file.
  667. //
  668. // Then there are tzh_leapcnt pairs of four-byte values, written in standard
  669. // byte order; the first value of each pair gives the time (as returned by
  670. // time(3)) at which a leap second occurs; the second gives the total number
  671. // of leap seconds to be applied after the given time. The pairs of values
  672. // are sorted in ascending order by time.b
  673. //
  674. // Then there are tzh_ttisstdcnt standard/wall indicators, each stored as a
  675. // one-byte value; they tell whether the transition times associated with
  676. // local time types were specified as standard time or wall clock time, and
  677. // are used when a time zone file is used in handling POSIX-style time zone
  678. // environment variables.
  679. //
  680. // Finally there are tzh_ttisgmtcnt UTC/local indicators, each stored as a
  681. // one-byte value; they tell whether the transition times associated with
  682. // local time types were specified as UTC or local time, and are used when a
  683. // time zone file is used in handling POSIX-style time zone environment
  684. // variables.
  685. //
  686. // localtime uses the first standard-time ttinfo structure in the file (or
  687. // simply the first ttinfo structure in the absence of a standard-time
  688. // structure) if either tzh_timecnt is zero or the time argument is less
  689. // than the first transition time recorded in the file.
  690. //
  691. // SEE ALSO
  692. // ctime(3), time2posix(3), zic(8)
  693. //
  694. // BSD September 13, 1994 BSD
  695. //
  696. //
  697. //
  698. // TIME(3) BSD Library Functions Manual TIME(3)
  699. //
  700. // NAME
  701. // time -- get time of day
  702. //
  703. // LIBRARY
  704. // Standard C Library (libc, -lc)
  705. //
  706. // SYNOPSIS
  707. // #include <time.h>
  708. //
  709. // time_t
  710. // time(time_t *tloc);
  711. //
  712. // DESCRIPTION
  713. // The time() function returns the value of time in seconds since 0 hours, 0
  714. // minutes, 0 seconds, January 1, 1970, Coordinated Universal Time, without
  715. // including leap seconds. If an error occurs, time() returns the value
  716. // (time_t)-1.
  717. //
  718. // The return value is also stored in *tloc, provided that tloc is non-null.
  719. //
  720. // ERRORS
  721. // The time() function may fail for any of the reasons described in
  722. // gettimeofday(2).
  723. //
  724. // SEE ALSO
  725. // gettimeofday(2), ctime(3)
  726. //
  727. // STANDARDS
  728. // The time function conforms to IEEE Std 1003.1-2001 (``POSIX.1'').
  729. //
  730. // BUGS
  731. // Neither ISO/IEC 9899:1999 (``ISO C99'') nor IEEE Std 1003.1-2001
  732. // (``POSIX.1'') requires time() to set errno on failure; thus, it is impos-
  733. // sible for an application to distinguish the valid time value -1 (repre-
  734. // senting the last UTC second of 1969) from the error return value.
  735. //
  736. // Systems conforming to earlier versions of the C and POSIX standards
  737. // (including older versions of FreeBSD) did not set *tloc in the error
  738. // case.
  739. //
  740. // HISTORY
  741. // A time() function appeared in Version 6 AT&T UNIX.
  742. //
  743. // BSD July 18, 2003 BSD
  744. //
  745. //
  746. private static void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, byte[] typeOfLocalTime,
  747. TZifType[] transitionType, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
  748. {
  749. rules = null;
  750. if (dts.Length > 0)
  751. {
  752. int index = 0;
  753. List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
  754. while (index <= dts.Length)
  755. {
  756. TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
  757. }
  758. rules = rulesList.ToArray();
  759. if (rules != null && rules.Length == 0)
  760. {
  761. rules = null;
  762. }
  763. }
  764. }
  765. private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
  766. byte[] typeOfLocalTime, TZifType[] transitionTypes, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
  767. {
  768. // To generate AdjustmentRules, use the following approach:
  769. // The first AdjustmentRule will go from DateTime.MinValue to the first transition time greater than DateTime.MinValue.
  770. // Each middle AdjustmentRule wil go from dts[index-1] to dts[index].
  771. // The last AdjustmentRule will go from dts[dts.Length-1] to Datetime.MaxValue.
  772. // 0. Skip any DateTime.MinValue transition times. In newer versions of the tzfile, there
  773. // is a "big bang" transition time, which is before the year 0001. Since any times before year 0001
  774. // cannot be represented by DateTime, there is no reason to make AdjustmentRules for these unrepresentable time periods.
  775. // 1. If there are no DateTime.MinValue times, the first AdjustmentRule goes from DateTime.MinValue
  776. // to the first transition and uses the first standard transitionType (or the first transitionType if none of them are standard)
  777. // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
  778. // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
  779. // all daylight savings, or all stanard time.
  780. // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
  781. // a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
  782. // b. continue the last transition offset until DateTime.Max
  783. while (index < dts.Length && dts[index] == DateTime.MinValue)
  784. {
  785. index++;
  786. }
  787. if (rulesList.Count == 0 && index < dts.Length)
  788. {
  789. TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
  790. DateTime endTransitionDate = dts[index];
  791. TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
  792. TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
  793. TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
  794. AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
  795. DateTime.MinValue,
  796. endTransitionDate.AddTicks(-1),
  797. daylightDelta,
  798. default(TransitionTime),
  799. default(TransitionTime),
  800. baseUtcDelta,
  801. noDaylightTransitions: true);
  802. if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
  803. {
  804. NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
  805. }
  806. rulesList.Add(r);
  807. }
  808. else if (index < dts.Length)
  809. {
  810. DateTime startTransitionDate = dts[index - 1];
  811. TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
  812. DateTime endTransitionDate = dts[index];
  813. TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
  814. TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
  815. TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
  816. TransitionTime dstStart;
  817. if (startTransitionType.IsDst)
  818. {
  819. // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
  820. // However, there are some cases in the past where DST = true, and the daylight savings offset
  821. // now equals what the current BaseUtcOffset is. In that case, the AdjustmentRule.DaylightOffset
  822. // is going to be TimeSpan.Zero. But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
  823. // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
  824. // in HasDaylightSaving return true.
  825. dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
  826. }
  827. else
  828. {
  829. dstStart = default(TransitionTime);
  830. }
  831. AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
  832. startTransitionDate,
  833. endTransitionDate.AddTicks(-1),
  834. daylightDelta,
  835. dstStart,
  836. default(TransitionTime),
  837. baseUtcDelta,
  838. noDaylightTransitions: true);
  839. if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
  840. {
  841. NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
  842. }
  843. rulesList.Add(r);
  844. }
  845. else
  846. {
  847. // create the AdjustmentRule that will be used for all DateTimes after the last transition
  848. // NOTE: index == dts.Length
  849. DateTime startTransitionDate = dts[index - 1];
  850. if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
  851. {
  852. AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
  853. if (r != null)
  854. {
  855. if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
  856. {
  857. NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
  858. }
  859. rulesList.Add(r);
  860. }
  861. }
  862. else
  863. {
  864. // just use the last transition as the rule which will be used until the end of time
  865. TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
  866. TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
  867. TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
  868. TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
  869. AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
  870. startTransitionDate,
  871. DateTime.MaxValue,
  872. daylightDelta,
  873. default(TransitionTime),
  874. default(TransitionTime),
  875. baseUtcDelta,
  876. noDaylightTransitions: true);
  877. if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
  878. {
  879. NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
  880. }
  881. rulesList.Add(r);
  882. }
  883. }
  884. index++;
  885. }
  886. private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
  887. {
  888. TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
  889. // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
  890. // with DateTimeOffset, SQL Server, and the W3C XML Specification
  891. if (result.Ticks % TimeSpan.TicksPerMinute != 0)
  892. {
  893. result = new TimeSpan(result.Hours, result.Minutes, 0);
  894. }
  895. return result;
  896. }
  897. /// <summary>
  898. /// Gets the first standard-time transition type, or simply the first transition type
  899. /// if there are no standard transition types.
  900. /// </summary>>
  901. /// <remarks>
  902. /// from 'man tzfile':
  903. /// localtime(3) uses the first standard-time ttinfo structure in the file
  904. /// (or simply the first ttinfo structure in the absence of a standard-time
  905. /// structure) if either tzh_timecnt is zero or the time argument is less
  906. /// than the first transition time recorded in the file.
  907. /// </remarks>
  908. private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
  909. {
  910. foreach (TZifType transitionType in transitionTypes)
  911. {
  912. if (!transitionType.IsDst)
  913. {
  914. return transitionType;
  915. }
  916. }
  917. if (transitionTypes.Length > 0)
  918. {
  919. return transitionTypes[0];
  920. }
  921. throw new InvalidTimeZoneException(SR.InvalidTimeZone_NoTTInfoStructures);
  922. }
  923. /// <summary>
  924. /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
  925. /// </summary>
  926. /// <remarks>
  927. /// See http://man7.org/linux/man-pages/man3/tzset.3.html for the format and semantics of this POSX string.
  928. /// </remarks>
  929. private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset)
  930. {
  931. if (TZif_ParsePosixFormat(posixFormat,
  932. out ReadOnlySpan<char> standardName,
  933. out ReadOnlySpan<char> standardOffset,
  934. out ReadOnlySpan<char> daylightSavingsName,
  935. out ReadOnlySpan<char> daylightSavingsOffset,
  936. out ReadOnlySpan<char> start,
  937. out ReadOnlySpan<char> startTime,
  938. out ReadOnlySpan<char> end,
  939. out ReadOnlySpan<char> endTime))
  940. {
  941. // a valid posixFormat has at least standardName and standardOffset
  942. TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
  943. if (parsedBaseOffset.HasValue)
  944. {
  945. TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
  946. baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
  947. // having a daylightSavingsName means there is a DST rule
  948. if (!daylightSavingsName.IsEmpty)
  949. {
  950. TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
  951. TimeSpan daylightSavingsTimeSpan;
  952. if (!parsedDaylightSavings.HasValue)
  953. {
  954. // default DST to 1 hour if it isn't specified
  955. daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
  956. }
  957. else
  958. {
  959. daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
  960. daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
  961. daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
  962. }
  963. TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
  964. TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
  965. return AdjustmentRule.CreateAdjustmentRule(
  966. startTransitionDate,
  967. DateTime.MaxValue,
  968. daylightSavingsTimeSpan,
  969. dstStart,
  970. dstEnd,
  971. baseOffset,
  972. noDaylightTransitions: false);
  973. }
  974. else
  975. {
  976. // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
  977. return AdjustmentRule.CreateAdjustmentRule(
  978. startTransitionDate,
  979. DateTime.MaxValue,
  980. TimeSpan.Zero,
  981. default(TransitionTime),
  982. default(TransitionTime),
  983. baseOffset,
  984. noDaylightTransitions: true);
  985. }
  986. }
  987. }
  988. return null;
  989. }
  990. private static TimeSpan? TZif_ParseOffsetString(ReadOnlySpan<char> offset)
  991. {
  992. TimeSpan? result = null;
  993. if (offset.Length > 0)
  994. {
  995. bool negative = offset[0] == '-';
  996. if (negative || offset[0] == '+')
  997. {
  998. offset = offset.Slice(1);
  999. }
  1000. // Try parsing just hours first.
  1001. // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
  1002. // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
  1003. int hours;
  1004. if (int.TryParse(offset, out hours))
  1005. {
  1006. result = new TimeSpan(hours, 0, 0);
  1007. }
  1008. else
  1009. {
  1010. TimeSpan parsedTimeSpan;
  1011. if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
  1012. {
  1013. result = parsedTimeSpan;
  1014. }
  1015. }
  1016. if (result.HasValue && negative)
  1017. {
  1018. result = result.Value.Negate();
  1019. }
  1020. }
  1021. return result;
  1022. }
  1023. private static DateTime ParseTimeOfDay(ReadOnlySpan<char> time)
  1024. {
  1025. DateTime timeOfDay;
  1026. TimeSpan? timeOffset = TZif_ParseOffsetString(time);
  1027. if (timeOffset.HasValue)
  1028. {
  1029. // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
  1030. // Some time zones use time values like, "26", "144", or "-2".
  1031. // This allows the week to sometimes be week 4 and sometimes week 5 in the month.
  1032. // For now, strip off any 'days' in the offset, and just get the time of day correct
  1033. timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
  1034. if (timeOffset.Value < TimeSpan.Zero)
  1035. {
  1036. timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
  1037. }
  1038. else
  1039. {
  1040. timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
  1041. }
  1042. timeOfDay += timeOffset.Value;
  1043. }
  1044. else
  1045. {
  1046. // default to 2AM.
  1047. timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
  1048. }
  1049. return timeOfDay;
  1050. }
  1051. private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(ReadOnlySpan<char> date, ReadOnlySpan<char> time)
  1052. {
  1053. if (date.IsEmpty)
  1054. {
  1055. return default(TransitionTime);
  1056. }
  1057. if (date[0] == 'M')
  1058. {
  1059. // Mm.w.d
  1060. // This specifies day d of week w of month m. The day d must be between 0(Sunday) and 6.The week w must be between 1 and 5;
  1061. // week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12.
  1062. int month;
  1063. int week;
  1064. DayOfWeek day;
  1065. if (!TZif_ParseMDateRule(date, out month, out week, out day))
  1066. {
  1067. throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date.ToString()));
  1068. }
  1069. return TransitionTime.CreateFloatingDateRule(ParseTimeOfDay(time), month, week, day);
  1070. }
  1071. else
  1072. {
  1073. if (date[0] != 'J')
  1074. {
  1075. // should be n Julian day format which we don't support.
  1076. //
  1077. // This specifies the Julian day, with n between 0 and 365. February 29 is counted in leap years.
  1078. //
  1079. // n would be a relative number from the begining of the year. which should handle if the
  1080. // the year is a leap year or not.
  1081. //
  1082. // In leap year, n would be counted as:
  1083. //
  1084. // 0 30 31 59 60 90 335 365
  1085. // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
  1086. //
  1087. // while in non leap year we'll have
  1088. //
  1089. // 0 30 31 58 59 89 334 364
  1090. // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
  1091. //
  1092. //
  1093. // For example if n is specified as 60, this means in leap year the rule will start at Mar 1,
  1094. // while in non leap year the rule will start at Mar 2.
  1095. //
  1096. // If we need to support n format, we'll have to have a floating adjustment rule support this case.
  1097. throw new InvalidTimeZoneException(SR.InvalidTimeZone_NJulianDayNotSupported);
  1098. }
  1099. // Julian day
  1100. TZif_ParseJulianDay(date, out int month, out int day);
  1101. return TransitionTime.CreateFixedDateRule(ParseTimeOfDay(time), month, day);
  1102. }
  1103. }
  1104. /// <summary>
  1105. /// Parses a string like Jn or n into month and day values.
  1106. /// </summary>
  1107. /// <returns>
  1108. /// true if the parsing succeeded; otherwise, false.
  1109. /// </returns>
  1110. private static void TZif_ParseJulianDay(ReadOnlySpan<char> date, out int month, out int day)
  1111. {
  1112. // Jn
  1113. // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
  1114. Debug.Assert(!date.IsEmpty);
  1115. Debug.Assert(date[0] == 'J');
  1116. month = day = 0;
  1117. int index = 1;
  1118. if (index >= date.Length || ((uint)(date[index] - '0') > '9'-'0'))
  1119. {
  1120. throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
  1121. }
  1122. int julianDay = 0;
  1123. do
  1124. {
  1125. julianDay = julianDay * 10 + (int) (date[index] - '0');
  1126. index++;
  1127. } while (index < date.Length && ((uint)(date[index] - '0') <= '9'-'0'));
  1128. int[] days = GregorianCalendarHelper.DaysToMonth365;
  1129. if (julianDay == 0 || julianDay > days[days.Length - 1])
  1130. {
  1131. throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
  1132. }
  1133. int i = 1;
  1134. while (i < days.Length && julianDay > days[i])
  1135. {
  1136. i++;
  1137. }
  1138. Debug.Assert(i > 0 && i < days.Length);
  1139. month = i;
  1140. day = julianDay - days[i - 1];
  1141. }
  1142. /// <summary>
  1143. /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
  1144. /// </summary>
  1145. /// <returns>
  1146. /// true if the parsing succeeded; otherwise, false.
  1147. /// </returns>
  1148. private static bool TZif_ParseMDateRule(ReadOnlySpan<char> dateRule, out int month, out int week, out DayOfWeek dayOfWeek)
  1149. {
  1150. if (dateRule[0] == 'M')
  1151. {
  1152. int monthWeekDotIndex = dateRule.IndexOf('.');
  1153. if (monthWeekDotIndex > 0)
  1154. {
  1155. ReadOnlySpan<char> weekDaySpan = dateRule.Slice(monthWeekDotIndex + 1);
  1156. int weekDayDotIndex = weekDaySpan.IndexOf('.');
  1157. if (weekDayDotIndex > 0)
  1158. {
  1159. if (int.TryParse(dateRule.Slice(1, monthWeekDotIndex - 1), out month) &&
  1160. int.TryParse(weekDaySpan.Slice(0, weekDayDotIndex), out week) &&
  1161. int.TryParse(weekDaySpan.Slice(weekDayDotIndex + 1), out int day))
  1162. {
  1163. dayOfWeek = (DayOfWeek)day;
  1164. return true;
  1165. }
  1166. }
  1167. }
  1168. }
  1169. month = 0;
  1170. week = 0;
  1171. dayOfWeek = default(DayOfWeek);
  1172. return false;
  1173. }
  1174. private static bool TZif_ParsePosixFormat(
  1175. ReadOnlySpan<char> posixFormat,
  1176. out ReadOnlySpan<char> standardName,
  1177. out ReadOnlySpan<char> standardOffset,
  1178. out ReadOnlySpan<char> daylightSavingsName,
  1179. out ReadOnlySpan<char> daylightSavingsOffset,
  1180. out ReadOnlySpan<char> start,
  1181. out ReadOnlySpan<char> startTime,
  1182. out ReadOnlySpan<char> end,
  1183. out ReadOnlySpan<char> endTime)
  1184. {
  1185. standardName = null;
  1186. standardOffset = null;
  1187. daylightSavingsName = null;
  1188. daylightSavingsOffset = null;
  1189. start = null;
  1190. startTime = null;
  1191. end = null;
  1192. endTime = null;
  1193. int index = 0;
  1194. standardName = TZif_ParsePosixName(posixFormat, ref index);
  1195. standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
  1196. daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
  1197. if (!daylightSavingsName.IsEmpty)
  1198. {
  1199. daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
  1200. if (index < posixFormat.Length && posixFormat[index] == ',')
  1201. {
  1202. index++;
  1203. TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
  1204. if (index < posixFormat.Length && posixFormat[index] == ',')
  1205. {
  1206. index++;
  1207. TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
  1208. }
  1209. }
  1210. }
  1211. return !standardName.IsEmpty && !standardOffset.IsEmpty;
  1212. }
  1213. private static ReadOnlySpan<char> TZif_ParsePosixName(ReadOnlySpan<char> posixFormat, ref int index)
  1214. {
  1215. bool isBracketEnclosed = index < posixFormat.Length && posixFormat[index] == '<';
  1216. if (isBracketEnclosed)
  1217. {
  1218. // move past the opening bracket
  1219. index++;
  1220. ReadOnlySpan<char> result = TZif_ParsePosixString(posixFormat, ref index, c => c == '>');
  1221. // move past the closing bracket
  1222. if (index < posixFormat.Length && posixFormat[index] == '>')
  1223. {
  1224. index++;
  1225. }
  1226. return result;
  1227. }
  1228. else
  1229. {
  1230. return TZif_ParsePosixString(
  1231. posixFormat,
  1232. ref index,
  1233. c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
  1234. }
  1235. }
  1236. private static ReadOnlySpan<char> TZif_ParsePosixOffset(ReadOnlySpan<char> posixFormat, ref int index) =>
  1237. TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
  1238. private static void TZif_ParsePosixDateTime(ReadOnlySpan<char> posixFormat, ref int index, out ReadOnlySpan<char> date, out ReadOnlySpan<char> time)
  1239. {
  1240. time = null;
  1241. date = TZif_ParsePosixDate(posixFormat, ref index);
  1242. if (index < posixFormat.Length && posixFormat[index] == '/')
  1243. {
  1244. index++;
  1245. time = TZif_ParsePosixTime(posixFormat, ref index);
  1246. }
  1247. }
  1248. private static ReadOnlySpan<char> TZif_ParsePosixDate(ReadOnlySpan<char> posixFormat, ref int index) =>
  1249. TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
  1250. private static ReadOnlySpan<char> TZif_ParsePosixTime(ReadOnlySpan<char> posixFormat, ref int index) =>
  1251. TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
  1252. private static ReadOnlySpan<char> TZif_ParsePosixString(ReadOnlySpan<char> posixFormat, ref int index, Func<char, bool> breakCondition)
  1253. {
  1254. int startIndex = index;
  1255. for (; index < posixFormat.Length; index++)
  1256. {
  1257. char current = posixFormat[index];
  1258. if (breakCondition(current))
  1259. {
  1260. break;
  1261. }
  1262. }
  1263. return posixFormat.Slice(startIndex, index - startIndex);
  1264. }
  1265. // Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
  1266. // zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
  1267. private static string TZif_GetZoneAbbreviation(string zoneAbbreviations, int index)
  1268. {
  1269. int lastIndex = zoneAbbreviations.IndexOf('\0', index);
  1270. return lastIndex > 0 ?
  1271. zoneAbbreviations.Substring(index, lastIndex - index) :
  1272. zoneAbbreviations.Substring(index);
  1273. }
  1274. // Converts an array of bytes into an int - always using standard byte order (Big Endian)
  1275. // per TZif file standard
  1276. private static unsafe int TZif_ToInt32(byte[] value, int startIndex)
  1277. {
  1278. fixed (byte* pbyte = &value[startIndex])
  1279. {
  1280. return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
  1281. }
  1282. }
  1283. // Converts an array of bytes into a long - always using standard byte order (Big Endian)
  1284. // per TZif file standard
  1285. private static unsafe long TZif_ToInt64(byte[] value, int startIndex)
  1286. {
  1287. fixed (byte* pbyte = &value[startIndex])
  1288. {
  1289. int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
  1290. int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
  1291. return (uint)i2 | ((long)i1 << 32);
  1292. }
  1293. }
  1294. private static long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version) =>
  1295. version != TZVersion.V1 ?
  1296. TZif_ToInt64(value, startIndex) :
  1297. TZif_ToInt32(value, startIndex);
  1298. private static DateTime TZif_UnixTimeToDateTime(long unixTime) =>
  1299. unixTime < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
  1300. unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
  1301. DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
  1302. private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
  1303. out string zoneAbbreviations, out bool[] StandardTime, out bool[] GmtTime, out string futureTransitionsPosixFormat)
  1304. {
  1305. // initialize the out parameters in case the TZifHead ctor throws
  1306. dts = null;
  1307. typeOfLocalTime = null;
  1308. transitionType = null;
  1309. zoneAbbreviations = string.Empty;
  1310. StandardTime = null;
  1311. GmtTime = null;
  1312. futureTransitionsPosixFormat = null;
  1313. // read in the 44-byte TZ header containing the count/length fields
  1314. //
  1315. int index = 0;
  1316. t = new TZifHead(data, index);
  1317. index += TZifHead.Length;
  1318. int timeValuesLength = 4; // the first version uses 4-bytes to specify times
  1319. if (t.Version != TZVersion.V1)
  1320. {
  1321. // move index past the V1 information to read the V2 information
  1322. index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
  1323. // read the V2 header
  1324. t = new TZifHead(data, index);
  1325. index += TZifHead.Length;
  1326. timeValuesLength = 8; // the second version uses 8-bytes
  1327. }
  1328. // initialize the containers for the rest of the TZ data
  1329. dts = new DateTime[t.TimeCount];
  1330. typeOfLocalTime = new byte[t.TimeCount];
  1331. transitionType = new TZifType[t.TypeCount];
  1332. zoneAbbreviations = string.Empty;
  1333. StandardTime = new bool[t.TypeCount];
  1334. GmtTime = new bool[t.TypeCount];
  1335. // read in the UTC transition points and convert them to Windows
  1336. //
  1337. for (int i = 0; i < t.TimeCount; i++)
  1338. {
  1339. long unixTime = TZif_ToUnixTime(data, index, t.Version);
  1340. dts[i] = TZif_UnixTimeToDateTime(unixTime);
  1341. index += timeValuesLength;
  1342. }
  1343. // read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
  1344. // these indices directly map to the array index in the transitionType array below
  1345. //
  1346. for (int i = 0; i < t.TimeCount; i++)
  1347. {
  1348. typeOfLocalTime[i] = data[index];
  1349. index += 1;
  1350. }
  1351. // read in the Type table. Each 6-byte entry represents
  1352. // {UtcOffset, IsDst, AbbreviationIndex}
  1353. //
  1354. // each AbbreviationIndex is a character index into the zoneAbbreviations string below
  1355. //
  1356. for (int i = 0; i < t.TypeCount; i++)
  1357. {
  1358. transitionType[i] = new TZifType(data, index);
  1359. index += 6;
  1360. }
  1361. // read in the Abbreviation ASCII string. This string will be in the form:
  1362. // "PST\0PDT\0PWT\0\PPT"
  1363. //
  1364. Encoding enc = Encoding.UTF8;
  1365. zoneAbbreviations = enc.GetString(data, index, (int)t.CharCount);
  1366. index += (int)t.CharCount;
  1367. // skip ahead of the Leap-Seconds Adjustment data. In a future release, consider adding
  1368. // support for Leap-Seconds
  1369. //
  1370. index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
  1371. // read in the Standard Time table. There should be a 1:1 mapping between Type-Index and Standard
  1372. // Time table entries.
  1373. //
  1374. // TRUE = transition time is standard time
  1375. // FALSE = transition time is wall clock time
  1376. // ABSENT = transition time is wall clock time
  1377. //
  1378. for (int i = 0; i < t.IsStdCount && i < t.TypeCount && index < data.Length; i++)
  1379. {
  1380. StandardTime[i] = (data[index++] != 0);
  1381. }
  1382. // read in the GMT Time table. There should be a 1:1 mapping between Type-Index and GMT Time table
  1383. // entries.
  1384. //
  1385. // TRUE = transition time is UTC
  1386. // FALSE = transition time is local time
  1387. // ABSENT = transition time is local time
  1388. //
  1389. for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++)
  1390. {
  1391. GmtTime[i] = (data[index++] != 0);
  1392. }
  1393. if (t.Version != TZVersion.V1)
  1394. {
  1395. // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
  1396. if (data[index++] == '\n' && data[data.Length - 1] == '\n')
  1397. {
  1398. futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
  1399. }
  1400. }
  1401. }
  1402. private struct TZifType
  1403. {
  1404. public const int Length = 6;
  1405. public readonly TimeSpan UtcOffset;
  1406. public readonly bool IsDst;
  1407. public readonly byte AbbreviationIndex;
  1408. public TZifType(byte[] data, int index)
  1409. {
  1410. if (data == null || data.Length < index + Length)
  1411. {
  1412. throw new ArgumentException(SR.Argument_TimeZoneInfoInvalidTZif, nameof(data));
  1413. }
  1414. UtcOffset = new TimeSpan(0, 0, TZif_ToInt32(data, index + 00));
  1415. IsDst = (data[index + 4] != 0);
  1416. AbbreviationIndex = data[index + 5];
  1417. }
  1418. }
  1419. private struct TZifHead
  1420. {
  1421. public const int Length = 44;
  1422. public readonly uint Magic; // TZ_MAGIC "TZif"
  1423. public readonly TZVersion Version; // 1 byte for a \0 or 2 or 3
  1424. // public byte[15] Reserved; // reserved for future use
  1425. public readonly uint IsGmtCount; // number of transition time flags
  1426. public readonly uint IsStdCount; // number of transition time flags
  1427. public readonly uint LeapCount; // number of leap seconds
  1428. public readonly uint TimeCount; // number of transition times
  1429. public readonly uint TypeCount; // number of local time types
  1430. public readonly uint CharCount; // number of abbreviated characters
  1431. public TZifHead(byte[] data, int index)
  1432. {
  1433. if (data == null || data.Length < Length)
  1434. {
  1435. throw new ArgumentException("bad data", nameof(data));
  1436. }
  1437. Magic = (uint)TZif_ToInt32(data, index + 00);
  1438. if (Magic != 0x545A6966)
  1439. {
  1440. // 0x545A6966 = {0x54, 0x5A, 0x69, 0x66} = "TZif"
  1441. throw new ArgumentException(SR.Argument_TimeZoneInfoBadTZif, nameof(data));
  1442. }
  1443. byte version = data[index + 04];
  1444. Version =
  1445. version == '2' ? TZVersion.V2 :
  1446. version == '3' ? TZVersion.V3 :
  1447. TZVersion.V1; // default/fallback to V1 to guard against future, unsupported version numbers
  1448. // skip the 15 byte reserved field
  1449. // don't use the BitConverter class which parses data
  1450. // based on the Endianess of the machine architecture.
  1451. // this data is expected to always be in "standard byte order",
  1452. // regardless of the machine it is being processed on.
  1453. IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
  1454. IsStdCount = (uint)TZif_ToInt32(data, index + 24);
  1455. LeapCount = (uint)TZif_ToInt32(data, index + 28);
  1456. TimeCount = (uint)TZif_ToInt32(data, index + 32);
  1457. TypeCount = (uint)TZif_ToInt32(data, index + 36);
  1458. CharCount = (uint)TZif_ToInt32(data, index + 40);
  1459. }
  1460. }
  1461. private enum TZVersion : byte
  1462. {
  1463. V1 = 0,
  1464. V2,
  1465. V3,
  1466. // when adding more versions, ensure all the logic using TZVersion is still correct
  1467. }
  1468. }
  1469. }