TimeZone.cs 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702
  1. using Ryujinx.Common;
  2. using Ryujinx.Common.Memory;
  3. using Ryujinx.Common.Utilities;
  4. using Ryujinx.HLE.Utilities;
  5. using System;
  6. using System.Buffers.Binary;
  7. using System.IO;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
  11. namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
  12. {
  13. public class TimeZone
  14. {
  15. private const int TimeTypeSize = 8;
  16. private const int EpochYear = 1970;
  17. private const int YearBase = 1900;
  18. private const int EpochWeekDay = 4;
  19. private const int SecondsPerMinute = 60;
  20. private const int MinutesPerHour = 60;
  21. private const int HoursPerDays = 24;
  22. private const int DaysPerWekk = 7;
  23. private const int DaysPerNYear = 365;
  24. private const int DaysPerLYear = 366;
  25. private const int MonthsPerYear = 12;
  26. private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour;
  27. private const int SecondsPerDay = SecondsPerHour * HoursPerDays;
  28. private const int YearsPerRepeat = 400;
  29. private const long AverageSecondsPerYear = 31556952;
  30. private const long SecondsPerRepeat = YearsPerRepeat * AverageSecondsPerYear;
  31. private static readonly int[] YearLengths = { DaysPerNYear, DaysPerLYear };
  32. private static readonly int[][] MonthsLengths = new int[][]
  33. {
  34. new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
  35. new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
  36. };
  37. private static readonly byte[] TimeZoneDefaultRule = Encoding.ASCII.GetBytes(",M4.1.0,M10.5.0");
  38. [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)]
  39. private struct CalendarTimeInternal
  40. {
  41. // NOTE: On the IPC side this is supposed to be a 16 bits value but internally this need to be a 64 bits value for ToPosixTime.
  42. public long Year;
  43. public sbyte Month;
  44. public sbyte Day;
  45. public sbyte Hour;
  46. public sbyte Minute;
  47. public sbyte Second;
  48. public int CompareTo(CalendarTimeInternal other)
  49. {
  50. if (Year != other.Year)
  51. {
  52. if (Year < other.Year)
  53. {
  54. return -1;
  55. }
  56. return 1;
  57. }
  58. if (Month != other.Month)
  59. {
  60. return Month - other.Month;
  61. }
  62. if (Day != other.Day)
  63. {
  64. return Day - other.Day;
  65. }
  66. if (Hour != other.Hour)
  67. {
  68. return Hour - other.Hour;
  69. }
  70. if (Minute != other.Minute)
  71. {
  72. return Minute - other.Minute;
  73. }
  74. if (Second != other.Second)
  75. {
  76. return Second - other.Second;
  77. }
  78. return 0;
  79. }
  80. }
  81. private enum RuleType
  82. {
  83. JulianDay,
  84. DayOfYear,
  85. MonthNthDayOfWeek
  86. }
  87. private struct Rule
  88. {
  89. public RuleType Type;
  90. public int Day;
  91. public int Week;
  92. public int Month;
  93. public int TransitionTime;
  94. }
  95. private static int Detzcode32(ReadOnlySpan<byte> bytes)
  96. {
  97. return BinaryPrimitives.ReadInt32BigEndian(bytes);
  98. }
  99. private static int Detzcode32(int value)
  100. {
  101. if (BitConverter.IsLittleEndian)
  102. {
  103. return BinaryPrimitives.ReverseEndianness(value);
  104. }
  105. return value;
  106. }
  107. private static long Detzcode64(ReadOnlySpan<byte> bytes)
  108. {
  109. return BinaryPrimitives.ReadInt64BigEndian(bytes);
  110. }
  111. private static bool DifferByRepeat(long t1, long t0)
  112. {
  113. return (t1 - t0) == SecondsPerRepeat;
  114. }
  115. private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex)
  116. {
  117. if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
  118. {
  119. return false;
  120. }
  121. TimeTypeInfo a = outRules.Ttis[aIndex];
  122. TimeTypeInfo b = outRules.Ttis[bIndex];
  123. return a.GmtOffset == b.GmtOffset &&
  124. a.IsDaySavingTime == b.IsDaySavingTime &&
  125. a.IsStandardTimeDaylight == b.IsStandardTimeDaylight &&
  126. a.IsGMT == b.IsGMT &&
  127. StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
  128. }
  129. private static int GetQZName(ReadOnlySpan<byte> name, int namePosition, char delimiter)
  130. {
  131. int i = namePosition;
  132. while (name[i] != '\0' && name[i] != delimiter)
  133. {
  134. i++;
  135. }
  136. return i;
  137. }
  138. private static int GetTZName(ReadOnlySpan<byte> name, int namePosition)
  139. {
  140. int i = namePosition;
  141. char c;
  142. while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
  143. {
  144. i++;
  145. }
  146. return i;
  147. }
  148. private static bool GetNum(ReadOnlySpan<byte> name, ref int namePosition, out int num, int min, int max)
  149. {
  150. num = 0;
  151. if (namePosition >= name.Length)
  152. {
  153. return false;
  154. }
  155. char c = (char)name[namePosition];
  156. if (!char.IsDigit(c))
  157. {
  158. return false;
  159. }
  160. do
  161. {
  162. num = num * 10 + (c - '0');
  163. if (num > max)
  164. {
  165. return false;
  166. }
  167. if (++namePosition >= name.Length)
  168. {
  169. return false;
  170. }
  171. c = (char)name[namePosition];
  172. }
  173. while (char.IsDigit(c));
  174. if (num < min)
  175. {
  176. return false;
  177. }
  178. return true;
  179. }
  180. private static bool GetSeconds(ReadOnlySpan<byte> name, ref int namePosition, out int seconds)
  181. {
  182. seconds = 0;
  183. bool isValid = GetNum(name, ref namePosition, out int num, 0, HoursPerDays * DaysPerWekk - 1);
  184. if (!isValid)
  185. {
  186. return false;
  187. }
  188. seconds = num * SecondsPerHour;
  189. if (namePosition >= name.Length)
  190. {
  191. return false;
  192. }
  193. if (name[namePosition] == ':')
  194. {
  195. namePosition++;
  196. isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1);
  197. if (!isValid)
  198. {
  199. return false;
  200. }
  201. seconds += num * SecondsPerMinute;
  202. if (namePosition >= name.Length)
  203. {
  204. return false;
  205. }
  206. if (name[namePosition] == ':')
  207. {
  208. namePosition++;
  209. isValid = GetNum(name, ref namePosition, out num, 0, SecondsPerMinute);
  210. if (!isValid)
  211. {
  212. return false;
  213. }
  214. seconds += num;
  215. }
  216. }
  217. return true;
  218. }
  219. private static bool GetOffset(ReadOnlySpan<byte> name, ref int namePosition, ref int offset)
  220. {
  221. bool isNegative = false;
  222. if (namePosition >= name.Length)
  223. {
  224. return false;
  225. }
  226. if (name[namePosition] == '-')
  227. {
  228. isNegative = true;
  229. namePosition++;
  230. }
  231. else if (name[namePosition] == '+')
  232. {
  233. namePosition++;
  234. }
  235. if (namePosition >= name.Length)
  236. {
  237. return false;
  238. }
  239. bool isValid = GetSeconds(name, ref namePosition, out offset);
  240. if (!isValid)
  241. {
  242. return false;
  243. }
  244. if (isNegative)
  245. {
  246. offset = -offset;
  247. }
  248. return true;
  249. }
  250. private static bool GetRule(ReadOnlySpan<byte> name, ref int namePosition, out Rule rule)
  251. {
  252. rule = new Rule();
  253. bool isValid = false;
  254. if (name[namePosition] == 'J')
  255. {
  256. namePosition++;
  257. rule.Type = RuleType.JulianDay;
  258. isValid = GetNum(name, ref namePosition, out rule.Day, 1, DaysPerNYear);
  259. }
  260. else if (name[namePosition] == 'M')
  261. {
  262. namePosition++;
  263. rule.Type = RuleType.MonthNthDayOfWeek;
  264. isValid = GetNum(name, ref namePosition, out rule.Month, 1, MonthsPerYear);
  265. if (!isValid)
  266. {
  267. return false;
  268. }
  269. if (name[namePosition++] != '.')
  270. {
  271. return false;
  272. }
  273. isValid = GetNum(name, ref namePosition, out rule.Week, 1, 5);
  274. if (!isValid)
  275. {
  276. return false;
  277. }
  278. if (name[namePosition++] != '.')
  279. {
  280. return false;
  281. }
  282. isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1);
  283. }
  284. else if (char.IsDigit((char)name[namePosition]))
  285. {
  286. rule.Type = RuleType.DayOfYear;
  287. isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
  288. }
  289. else
  290. {
  291. return false;
  292. }
  293. if (!isValid)
  294. {
  295. return false;
  296. }
  297. if (name[namePosition] == '/')
  298. {
  299. namePosition++;
  300. return GetOffset(name, ref namePosition, ref rule.TransitionTime);
  301. }
  302. else
  303. {
  304. rule.TransitionTime = 2 * SecondsPerHour;
  305. }
  306. return true;
  307. }
  308. private static int IsLeap(int year)
  309. {
  310. if (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0))
  311. {
  312. return 1;
  313. }
  314. return 0;
  315. }
  316. private static bool ParsePosixName(ReadOnlySpan<byte> name, ref TimeZoneRule outRules, bool lastDitch)
  317. {
  318. outRules = new TimeZoneRule();
  319. int stdLen;
  320. ReadOnlySpan<byte> stdName = name;
  321. int namePosition = 0;
  322. int stdOffset = 0;
  323. if (lastDitch)
  324. {
  325. stdLen = 3;
  326. namePosition += stdLen;
  327. }
  328. else
  329. {
  330. if (name[namePosition] == '<')
  331. {
  332. namePosition++;
  333. stdName = name.Slice(namePosition);
  334. int stdNamePosition = namePosition;
  335. namePosition = GetQZName(name, namePosition, '>');
  336. if (name[namePosition] != '>')
  337. {
  338. return false;
  339. }
  340. stdLen = namePosition - stdNamePosition;
  341. namePosition++;
  342. }
  343. else
  344. {
  345. namePosition = GetTZName(name, namePosition);
  346. stdLen = namePosition;
  347. }
  348. if (stdLen == 0)
  349. {
  350. return false;
  351. }
  352. bool isValid = GetOffset(name.ToArray(), ref namePosition, ref stdOffset);
  353. if (!isValid)
  354. {
  355. return false;
  356. }
  357. }
  358. int charCount = stdLen + 1;
  359. int destLen = 0;
  360. int dstOffset = 0;
  361. ReadOnlySpan<byte> destName = name.Slice(namePosition);
  362. if (TzCharsArraySize < charCount)
  363. {
  364. return false;
  365. }
  366. if (name[namePosition] != '\0')
  367. {
  368. if (name[namePosition] == '<')
  369. {
  370. destName = name.Slice(++namePosition);
  371. int destNamePosition = namePosition;
  372. namePosition = GetQZName(name.ToArray(), namePosition, '>');
  373. if (name[namePosition] != '>')
  374. {
  375. return false;
  376. }
  377. destLen = namePosition - destNamePosition;
  378. namePosition++;
  379. }
  380. else
  381. {
  382. destName = name.Slice(namePosition);
  383. namePosition = GetTZName(name, namePosition);
  384. destLen = namePosition;
  385. }
  386. if (destLen == 0)
  387. {
  388. return false;
  389. }
  390. charCount += destLen + 1;
  391. if (TzCharsArraySize < charCount)
  392. {
  393. return false;
  394. }
  395. if (name[namePosition] != '\0' && name[namePosition] != ',' && name[namePosition] != ';')
  396. {
  397. bool isValid = GetOffset(name.ToArray(), ref namePosition, ref dstOffset);
  398. if (!isValid)
  399. {
  400. return false;
  401. }
  402. }
  403. else
  404. {
  405. dstOffset = stdOffset - SecondsPerHour;
  406. }
  407. if (name[namePosition] == '\0')
  408. {
  409. name = TimeZoneDefaultRule;
  410. namePosition = 0;
  411. }
  412. if (name[namePosition] == ',' || name[namePosition] == ';')
  413. {
  414. namePosition++;
  415. bool IsRuleValid = GetRule(name, ref namePosition, out Rule start);
  416. if (!IsRuleValid)
  417. {
  418. return false;
  419. }
  420. if (name[namePosition++] != ',')
  421. {
  422. return false;
  423. }
  424. IsRuleValid = GetRule(name, ref namePosition, out Rule end);
  425. if (!IsRuleValid)
  426. {
  427. return false;
  428. }
  429. if (name[namePosition] != '\0')
  430. {
  431. return false;
  432. }
  433. outRules.TypeCount = 2;
  434. outRules.Ttis[0] = new TimeTypeInfo
  435. {
  436. GmtOffset = -dstOffset,
  437. IsDaySavingTime = true,
  438. AbbreviationListIndex = stdLen + 1
  439. };
  440. outRules.Ttis[1] = new TimeTypeInfo
  441. {
  442. GmtOffset = -stdOffset,
  443. IsDaySavingTime = false,
  444. AbbreviationListIndex = 0
  445. };
  446. outRules.DefaultType = 0;
  447. int timeCount = 0;
  448. long janFirst = 0;
  449. int janOffset = 0;
  450. int yearBegining = EpochYear;
  451. do
  452. {
  453. int yearSeconds = YearLengths[IsLeap(yearBegining - 1)] * SecondsPerDay;
  454. yearBegining--;
  455. if (IncrementOverflow64(ref janFirst, -yearSeconds))
  456. {
  457. janOffset = -yearSeconds;
  458. break;
  459. }
  460. }
  461. while (EpochYear - YearsPerRepeat / 2 < yearBegining);
  462. int yearLimit = yearBegining + YearsPerRepeat + 1;
  463. int year;
  464. for (year = yearBegining; year < yearLimit; year++)
  465. {
  466. int startTime = TransitionTime(year, start, stdOffset);
  467. int endTime = TransitionTime(year, end, dstOffset);
  468. int yearSeconds = YearLengths[IsLeap(year)] * SecondsPerDay;
  469. bool isReversed = endTime < startTime;
  470. if (isReversed)
  471. {
  472. int swap = startTime;
  473. startTime = endTime;
  474. endTime = swap;
  475. }
  476. if (isReversed || (startTime < endTime && (endTime - startTime < (yearSeconds + (stdOffset - dstOffset)))))
  477. {
  478. if (TzMaxTimes - 2 < timeCount)
  479. {
  480. break;
  481. }
  482. outRules.Ats[timeCount] = janFirst;
  483. if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + startTime))
  484. {
  485. outRules.Types[timeCount++] = isReversed ? (byte)1 : (byte)0;
  486. }
  487. else if (janOffset != 0)
  488. {
  489. outRules.DefaultType = isReversed ? 1 : 0;
  490. }
  491. outRules.Ats[timeCount] = janFirst;
  492. if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + endTime))
  493. {
  494. outRules.Types[timeCount++] = isReversed ? (byte)0 : (byte)1;
  495. yearLimit = year + YearsPerRepeat + 1;
  496. }
  497. else if (janOffset != 0)
  498. {
  499. outRules.DefaultType = isReversed ? 0 : 1;
  500. }
  501. }
  502. if (IncrementOverflow64(ref janFirst, janOffset + yearSeconds))
  503. {
  504. break;
  505. }
  506. janOffset = 0;
  507. }
  508. outRules.TimeCount = timeCount;
  509. // There is no time variation, this is then a perpetual DST rule
  510. if (timeCount == 0)
  511. {
  512. outRules.TypeCount = 1;
  513. }
  514. else if (YearsPerRepeat < year - yearBegining)
  515. {
  516. outRules.GoBack = true;
  517. outRules.GoAhead = true;
  518. }
  519. }
  520. else
  521. {
  522. if (name[namePosition] == '\0')
  523. {
  524. return false;
  525. }
  526. long theirStdOffset = 0;
  527. for (int i = 0; i < outRules.TimeCount; i++)
  528. {
  529. int j = outRules.Types[i];
  530. if (outRules.Ttis[j].IsStandardTimeDaylight)
  531. {
  532. theirStdOffset = -outRules.Ttis[j].GmtOffset;
  533. }
  534. }
  535. long theirDstOffset = 0;
  536. for (int i = 0; i < outRules.TimeCount; i++)
  537. {
  538. int j = outRules.Types[i];
  539. if (outRules.Ttis[j].IsDaySavingTime)
  540. {
  541. theirDstOffset = -outRules.Ttis[j].GmtOffset;
  542. }
  543. }
  544. bool isDaySavingTime = false;
  545. long theirOffset = theirStdOffset;
  546. for (int i = 0; i < outRules.TimeCount; i++)
  547. {
  548. int j = outRules.Types[i];
  549. outRules.Types[i] = outRules.Ttis[j].IsDaySavingTime ? (byte)1 : (byte)0;
  550. if (!outRules.Ttis[j].IsGMT)
  551. {
  552. if (isDaySavingTime && !outRules.Ttis[j].IsStandardTimeDaylight)
  553. {
  554. outRules.Ats[i] += dstOffset - theirStdOffset;
  555. }
  556. else
  557. {
  558. outRules.Ats[i] += stdOffset - theirStdOffset;
  559. }
  560. }
  561. theirOffset = -outRules.Ttis[j].GmtOffset;
  562. if (outRules.Ttis[j].IsDaySavingTime)
  563. {
  564. theirDstOffset = theirOffset;
  565. }
  566. else
  567. {
  568. theirStdOffset = theirOffset;
  569. }
  570. }
  571. outRules.Ttis[0] = new TimeTypeInfo
  572. {
  573. GmtOffset = -stdOffset,
  574. IsDaySavingTime = false,
  575. AbbreviationListIndex = 0
  576. };
  577. outRules.Ttis[1] = new TimeTypeInfo
  578. {
  579. GmtOffset = -dstOffset,
  580. IsDaySavingTime = true,
  581. AbbreviationListIndex = stdLen + 1
  582. };
  583. outRules.TypeCount = 2;
  584. outRules.DefaultType = 0;
  585. }
  586. }
  587. else
  588. {
  589. // default is perpetual standard time
  590. outRules.TypeCount = 1;
  591. outRules.TimeCount = 0;
  592. outRules.DefaultType = 0;
  593. outRules.Ttis[0] = new TimeTypeInfo
  594. {
  595. GmtOffset = -stdOffset,
  596. IsDaySavingTime = false,
  597. AbbreviationListIndex = 0
  598. };
  599. }
  600. outRules.CharCount = charCount;
  601. int charsPosition = 0;
  602. for (int i = 0; i < stdLen; i++)
  603. {
  604. outRules.Chars[i] = stdName[i];
  605. }
  606. charsPosition += stdLen;
  607. outRules.Chars[charsPosition++] = 0;
  608. if (destLen != 0)
  609. {
  610. for (int i = 0; i < destLen; i++)
  611. {
  612. outRules.Chars[charsPosition + i] = destName[i];
  613. }
  614. outRules.Chars[charsPosition + destLen] = 0;
  615. }
  616. return true;
  617. }
  618. private static int TransitionTime(int year, Rule rule, int offset)
  619. {
  620. int leapYear = IsLeap(year);
  621. int value;
  622. switch (rule.Type)
  623. {
  624. case RuleType.JulianDay:
  625. value = (rule.Day - 1) * SecondsPerDay;
  626. if (leapYear == 1 && rule.Day >= 60)
  627. {
  628. value += SecondsPerDay;
  629. }
  630. break;
  631. case RuleType.DayOfYear:
  632. value = rule.Day * SecondsPerDay;
  633. break;
  634. case RuleType.MonthNthDayOfWeek:
  635. // Here we use Zeller's Congruence to get the day of week of the first month.
  636. int m1 = (rule.Month + 9) % 12 + 1;
  637. int yy0 = (rule.Month <= 2) ? (year - 1) : year;
  638. int yy1 = yy0 / 100;
  639. int yy2 = yy0 % 100;
  640. int dayOfWeek = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
  641. if (dayOfWeek < 0)
  642. {
  643. dayOfWeek += DaysPerWekk;
  644. }
  645. // Get the zero origin
  646. int d = rule.Day - dayOfWeek;
  647. if (d < 0)
  648. {
  649. d += DaysPerWekk;
  650. }
  651. for (int i = 1; i < rule.Week; i++)
  652. {
  653. if (d + DaysPerWekk >= MonthsLengths[leapYear][rule.Month - 1])
  654. {
  655. break;
  656. }
  657. d += DaysPerWekk;
  658. }
  659. value = d * SecondsPerDay;
  660. for (int i = 0; i < rule.Month - 1; i++)
  661. {
  662. value += MonthsLengths[leapYear][i] * SecondsPerDay;
  663. }
  664. break;
  665. default:
  666. throw new NotImplementedException("Unknown time transition!");
  667. }
  668. return value + rule.TransitionTime + offset;
  669. }
  670. private static bool NormalizeOverflow32(ref int ip, ref int unit, int baseValue)
  671. {
  672. int delta;
  673. if (unit >= 0)
  674. {
  675. delta = unit / baseValue;
  676. }
  677. else
  678. {
  679. delta = -1 - (-1 - unit) / baseValue;
  680. }
  681. unit -= delta * baseValue;
  682. return IncrementOverflow32(ref ip, delta);
  683. }
  684. private static bool NormalizeOverflow64(ref long ip, ref long unit, long baseValue)
  685. {
  686. long delta;
  687. if (unit >= 0)
  688. {
  689. delta = unit / baseValue;
  690. }
  691. else
  692. {
  693. delta = -1 - (-1 - unit) / baseValue;
  694. }
  695. unit -= delta * baseValue;
  696. return IncrementOverflow64(ref ip, delta);
  697. }
  698. private static bool IncrementOverflow32(ref int time, int j)
  699. {
  700. try
  701. {
  702. time = checked(time + j);
  703. return false;
  704. }
  705. catch (OverflowException)
  706. {
  707. return true;
  708. }
  709. }
  710. private static bool IncrementOverflow64(ref long time, long j)
  711. {
  712. try
  713. {
  714. time = checked(time + j);
  715. return false;
  716. }
  717. catch (OverflowException)
  718. {
  719. return true;
  720. }
  721. }
  722. internal static bool ParsePosixName(string name, ref TimeZoneRule outRules)
  723. {
  724. return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false);
  725. }
  726. internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData)
  727. {
  728. outRules = new TimeZoneRule();
  729. BinaryReader reader = new BinaryReader(inputData);
  730. long streamLength = reader.BaseStream.Length;
  731. if (streamLength < Marshal.SizeOf<TzifHeader>())
  732. {
  733. return false;
  734. }
  735. TzifHeader header = reader.ReadStruct<TzifHeader>();
  736. streamLength -= Marshal.SizeOf<TzifHeader>();
  737. int ttisGMTCount = Detzcode32(header.TtisGMTCount);
  738. int ttisSTDCount = Detzcode32(header.TtisSTDCount);
  739. int leapCount = Detzcode32(header.LeapCount);
  740. int timeCount = Detzcode32(header.TimeCount);
  741. int typeCount = Detzcode32(header.TypeCount);
  742. int charCount = Detzcode32(header.CharCount);
  743. if (!(0 <= leapCount
  744. && leapCount < TzMaxLeaps
  745. && 0 < typeCount
  746. && typeCount < TzMaxTypes
  747. && 0 <= timeCount
  748. && timeCount < TzMaxTimes
  749. && 0 <= charCount
  750. && charCount < TzMaxChars
  751. && (ttisSTDCount == typeCount || ttisSTDCount == 0)
  752. && (ttisGMTCount == typeCount || ttisGMTCount == 0)))
  753. {
  754. return false;
  755. }
  756. if (streamLength < (timeCount * TimeTypeSize
  757. + timeCount
  758. + typeCount * 6
  759. + charCount
  760. + leapCount * (TimeTypeSize + 4)
  761. + ttisSTDCount
  762. + ttisGMTCount))
  763. {
  764. return false;
  765. }
  766. outRules.TimeCount = timeCount;
  767. outRules.TypeCount = typeCount;
  768. outRules.CharCount = charCount;
  769. byte[] workBuffer = StreamUtils.StreamToBytes(inputData);
  770. timeCount = 0;
  771. {
  772. Span<byte> p = workBuffer;
  773. for (int i = 0; i < outRules.TimeCount; i++)
  774. {
  775. long at = Detzcode64(p);
  776. outRules.Types[i] = 1;
  777. if (timeCount != 0 && at <= outRules.Ats[timeCount - 1])
  778. {
  779. if (at < outRules.Ats[timeCount - 1])
  780. {
  781. return false;
  782. }
  783. outRules.Types[i - 1] = 0;
  784. timeCount--;
  785. }
  786. outRules.Ats[timeCount++] = at;
  787. p = p[TimeTypeSize..];
  788. }
  789. timeCount = 0;
  790. for (int i = 0; i < outRules.TimeCount; i++)
  791. {
  792. byte type = p[0];
  793. p = p[1..];
  794. if (outRules.TypeCount <= type)
  795. {
  796. return false;
  797. }
  798. if (outRules.Types[i] != 0)
  799. {
  800. outRules.Types[timeCount++] = type;
  801. }
  802. }
  803. outRules.TimeCount = timeCount;
  804. for (int i = 0; i < outRules.TypeCount; i++)
  805. {
  806. TimeTypeInfo ttis = outRules.Ttis[i];
  807. ttis.GmtOffset = Detzcode32(p);
  808. p = p[sizeof(int)..];
  809. if (p[0] >= 2)
  810. {
  811. return false;
  812. }
  813. ttis.IsDaySavingTime = p[0] != 0;
  814. p = p[1..];
  815. int abbreviationListIndex = p[0];
  816. p = p[1..];
  817. if (abbreviationListIndex >= outRules.CharCount)
  818. {
  819. return false;
  820. }
  821. ttis.AbbreviationListIndex = abbreviationListIndex;
  822. outRules.Ttis[i] = ttis;
  823. }
  824. p[..outRules.CharCount].CopyTo(outRules.Chars);
  825. p = p[outRules.CharCount..];
  826. outRules.Chars[outRules.CharCount] = 0;
  827. for (int i = 0; i < outRules.TypeCount; i++)
  828. {
  829. if (ttisSTDCount == 0)
  830. {
  831. outRules.Ttis[i].IsStandardTimeDaylight = false;
  832. }
  833. else
  834. {
  835. if (p[0] >= 2)
  836. {
  837. return false;
  838. }
  839. outRules.Ttis[i].IsStandardTimeDaylight = p[0] != 0;
  840. p = p[1..];
  841. }
  842. }
  843. for (int i = 0; i < outRules.TypeCount; i++)
  844. {
  845. if (ttisSTDCount == 0)
  846. {
  847. outRules.Ttis[i].IsGMT = false;
  848. }
  849. else
  850. {
  851. if (p[0] >= 2)
  852. {
  853. return false;
  854. }
  855. outRules.Ttis[i].IsGMT = p[0] != 0;
  856. p = p[1..];
  857. }
  858. }
  859. long position = (workBuffer.Length - p.Length);
  860. long nRead = streamLength - position;
  861. if (nRead < 0)
  862. {
  863. return false;
  864. }
  865. // Nintendo abort in case of a TzIf file with a POSIX TZ Name too long to fit inside a TimeZoneRule.
  866. // As it's impossible in normal usage to achive this, we also force a crash.
  867. if (nRead > (TzNameMax + 1))
  868. {
  869. throw new InvalidOperationException();
  870. }
  871. byte[] tempName = new byte[TzNameMax + 1];
  872. Array.Copy(workBuffer, position, tempName, 0, nRead);
  873. if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes)
  874. {
  875. tempName[nRead - 1] = 0;
  876. byte[] name = new byte[TzNameMax];
  877. Array.Copy(tempName, 1, name, 0, nRead - 1);
  878. Box<TimeZoneRule> tempRulesBox = new Box<TimeZoneRule>();
  879. ref TimeZoneRule tempRules = ref tempRulesBox.Data;
  880. if (ParsePosixName(name, ref tempRulesBox.Data, false))
  881. {
  882. int abbreviationCount = 0;
  883. charCount = outRules.CharCount;
  884. Span<byte> chars = outRules.Chars;
  885. for (int i = 0; i < tempRules.TypeCount; i++)
  886. {
  887. ReadOnlySpan<byte> tempChars = tempRules.Chars;
  888. ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
  889. int j;
  890. for (j = 0; j < charCount; j++)
  891. {
  892. if (StringUtils.CompareCStr(chars[j..], tempAbbreviation) == 0)
  893. {
  894. tempRules.Ttis[i].AbbreviationListIndex = j;
  895. abbreviationCount++;
  896. break;
  897. }
  898. }
  899. if (j >= charCount)
  900. {
  901. int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation);
  902. if (j + abbreviationLength < TzMaxChars)
  903. {
  904. for (int x = 0; x < abbreviationLength; x++)
  905. {
  906. chars[j + x] = tempAbbreviation[x];
  907. }
  908. charCount = j + abbreviationLength + 1;
  909. tempRules.Ttis[i].AbbreviationListIndex = j;
  910. abbreviationCount++;
  911. }
  912. }
  913. }
  914. if (abbreviationCount == tempRules.TypeCount)
  915. {
  916. outRules.CharCount = charCount;
  917. // Remove trailing
  918. while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2]))
  919. {
  920. outRules.TimeCount--;
  921. }
  922. int i;
  923. for (i = 0; i < tempRules.TimeCount; i++)
  924. {
  925. if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i])
  926. {
  927. break;
  928. }
  929. }
  930. while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes)
  931. {
  932. outRules.Ats[outRules.TimeCount] = tempRules.Ats[i];
  933. outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]);
  934. outRules.TimeCount++;
  935. i++;
  936. }
  937. for (i = 0; i < tempRules.TypeCount; i++)
  938. {
  939. outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i];
  940. }
  941. }
  942. }
  943. }
  944. if (outRules.TypeCount == 0)
  945. {
  946. return false;
  947. }
  948. if (outRules.TimeCount > 1)
  949. {
  950. for (int i = 1; i < outRules.TimeCount; i++)
  951. {
  952. if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
  953. {
  954. outRules.GoBack = true;
  955. break;
  956. }
  957. }
  958. for (int i = outRules.TimeCount - 2; i >= 0; i--)
  959. {
  960. if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
  961. {
  962. outRules.GoAhead = true;
  963. break;
  964. }
  965. }
  966. }
  967. int defaultType;
  968. for (defaultType = 0; defaultType < outRules.TimeCount; defaultType++)
  969. {
  970. if (outRules.Types[defaultType] == 0)
  971. {
  972. break;
  973. }
  974. }
  975. defaultType = defaultType < outRules.TimeCount ? -1 : 0;
  976. if (defaultType < 0 && outRules.TimeCount > 0 && outRules.Ttis[outRules.Types[0]].IsDaySavingTime)
  977. {
  978. defaultType = outRules.Types[0];
  979. while (--defaultType >= 0)
  980. {
  981. if (!outRules.Ttis[defaultType].IsDaySavingTime)
  982. {
  983. break;
  984. }
  985. }
  986. }
  987. if (defaultType < 0)
  988. {
  989. defaultType = 0;
  990. while (outRules.Ttis[defaultType].IsDaySavingTime)
  991. {
  992. if (++defaultType >= outRules.TypeCount)
  993. {
  994. defaultType = 0;
  995. break;
  996. }
  997. }
  998. }
  999. outRules.DefaultType = defaultType;
  1000. }
  1001. return true;
  1002. }
  1003. private static long GetLeapDaysNotNeg(long year)
  1004. {
  1005. return year / 4 - year / 100 + year / 400;
  1006. }
  1007. private static long GetLeapDays(long year)
  1008. {
  1009. if (year < 0)
  1010. {
  1011. return -1 - GetLeapDaysNotNeg(-1 - year);
  1012. }
  1013. else
  1014. {
  1015. return GetLeapDaysNotNeg(year);
  1016. }
  1017. }
  1018. private static ResultCode CreateCalendarTime(long time, int gmtOffset, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
  1019. {
  1020. long year = EpochYear;
  1021. long timeDays = time / SecondsPerDay;
  1022. long remainingSeconds = time % SecondsPerDay;
  1023. calendarTime = new CalendarTimeInternal();
  1024. calendarAdditionalInfo = new CalendarAdditionalInfo();
  1025. while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
  1026. {
  1027. long timeDelta = timeDays / DaysPerLYear;
  1028. long delta = timeDelta;
  1029. if (delta == 0)
  1030. {
  1031. delta = timeDays < 0 ? -1 : 1;
  1032. }
  1033. long newYear = year;
  1034. if (IncrementOverflow64(ref newYear, delta))
  1035. {
  1036. return ResultCode.OutOfRange;
  1037. }
  1038. long leapDays = GetLeapDays(newYear - 1) - GetLeapDays(year - 1);
  1039. timeDays -= (newYear - year) * DaysPerNYear;
  1040. timeDays -= leapDays;
  1041. year = newYear;
  1042. }
  1043. long dayOfYear = timeDays;
  1044. remainingSeconds += gmtOffset;
  1045. while (remainingSeconds < 0)
  1046. {
  1047. remainingSeconds += SecondsPerDay;
  1048. dayOfYear -= 1;
  1049. }
  1050. while (remainingSeconds >= SecondsPerDay)
  1051. {
  1052. remainingSeconds -= SecondsPerDay;
  1053. dayOfYear += 1;
  1054. }
  1055. while (dayOfYear < 0)
  1056. {
  1057. if (IncrementOverflow64(ref year, -1))
  1058. {
  1059. return ResultCode.OutOfRange;
  1060. }
  1061. dayOfYear += YearLengths[IsLeap((int)year)];
  1062. }
  1063. while (dayOfYear >= YearLengths[IsLeap((int)year)])
  1064. {
  1065. dayOfYear -= YearLengths[IsLeap((int)year)];
  1066. if (IncrementOverflow64(ref year, 1))
  1067. {
  1068. return ResultCode.OutOfRange;
  1069. }
  1070. }
  1071. calendarTime.Year = year;
  1072. calendarAdditionalInfo.DayOfYear = (uint)dayOfYear;
  1073. long dayOfWeek = (EpochWeekDay + ((year - EpochYear) % DaysPerWekk) * (DaysPerNYear % DaysPerWekk) + GetLeapDays(year - 1) - GetLeapDays(EpochYear - 1) + dayOfYear) % DaysPerWekk;
  1074. if (dayOfWeek < 0)
  1075. {
  1076. dayOfWeek += DaysPerWekk;
  1077. }
  1078. calendarAdditionalInfo.DayOfWeek = (uint)dayOfWeek;
  1079. calendarTime.Hour = (sbyte)((remainingSeconds / SecondsPerHour) % SecondsPerHour);
  1080. remainingSeconds %= SecondsPerHour;
  1081. calendarTime.Minute = (sbyte)(remainingSeconds / SecondsPerMinute);
  1082. calendarTime.Second = (sbyte)(remainingSeconds % SecondsPerMinute);
  1083. int[] ip = MonthsLengths[IsLeap((int)year)];
  1084. for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month)
  1085. {
  1086. dayOfYear -= ip[calendarTime.Month];
  1087. }
  1088. calendarTime.Day = (sbyte)(dayOfYear + 1);
  1089. calendarAdditionalInfo.IsDaySavingTime = false;
  1090. calendarAdditionalInfo.GmtOffset = gmtOffset;
  1091. return 0;
  1092. }
  1093. private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
  1094. {
  1095. calendarTime = new CalendarTimeInternal();
  1096. calendarAdditionalInfo = new CalendarAdditionalInfo();
  1097. ResultCode result;
  1098. if ((rules.GoAhead && time < rules.Ats[0]) || (rules.GoBack && time > rules.Ats[rules.TimeCount - 1]))
  1099. {
  1100. long newTime = time;
  1101. long seconds;
  1102. long years;
  1103. if (time < rules.Ats[0])
  1104. {
  1105. seconds = rules.Ats[0] - time;
  1106. }
  1107. else
  1108. {
  1109. seconds = time - rules.Ats[rules.TimeCount - 1];
  1110. }
  1111. seconds -= 1;
  1112. years = (seconds / SecondsPerRepeat + 1) * YearsPerRepeat;
  1113. seconds = years * AverageSecondsPerYear;
  1114. if (time < rules.Ats[0])
  1115. {
  1116. newTime += seconds;
  1117. }
  1118. else
  1119. {
  1120. newTime -= seconds;
  1121. }
  1122. if (newTime < rules.Ats[0] && newTime > rules.Ats[rules.TimeCount - 1])
  1123. {
  1124. return ResultCode.TimeNotFound;
  1125. }
  1126. result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo);
  1127. if (result != 0)
  1128. {
  1129. return result;
  1130. }
  1131. if (time < rules.Ats[0])
  1132. {
  1133. calendarTime.Year -= years;
  1134. }
  1135. else
  1136. {
  1137. calendarTime.Year += years;
  1138. }
  1139. return ResultCode.Success;
  1140. }
  1141. int ttiIndex;
  1142. if (rules.TimeCount == 0 || time < rules.Ats[0])
  1143. {
  1144. ttiIndex = rules.DefaultType;
  1145. }
  1146. else
  1147. {
  1148. int low = 1;
  1149. int high = rules.TimeCount;
  1150. while (low < high)
  1151. {
  1152. int mid = (low + high) >> 1;
  1153. if (time < rules.Ats[mid])
  1154. {
  1155. high = mid;
  1156. }
  1157. else
  1158. {
  1159. low = mid + 1;
  1160. }
  1161. }
  1162. ttiIndex = rules.Types[low - 1];
  1163. }
  1164. result = CreateCalendarTime(time, rules.Ttis[ttiIndex].GmtOffset, out calendarTime, out calendarAdditionalInfo);
  1165. if (result == 0)
  1166. {
  1167. calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
  1168. ReadOnlySpan<byte> timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..];
  1169. int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
  1170. timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.ToSpan());
  1171. }
  1172. return result;
  1173. }
  1174. private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
  1175. {
  1176. posixTime = 0;
  1177. int hour = calendarTime.Hour;
  1178. int minute = calendarTime.Minute;
  1179. if (NormalizeOverflow32(ref hour, ref minute, MinutesPerHour))
  1180. {
  1181. return ResultCode.Overflow;
  1182. }
  1183. calendarTime.Minute = (sbyte)minute;
  1184. int day = calendarTime.Day;
  1185. if (NormalizeOverflow32(ref day, ref hour, HoursPerDays))
  1186. {
  1187. return ResultCode.Overflow;
  1188. }
  1189. calendarTime.Day = (sbyte)day;
  1190. calendarTime.Hour = (sbyte)hour;
  1191. long year = calendarTime.Year;
  1192. long month = calendarTime.Month;
  1193. if (NormalizeOverflow64(ref year, ref month, MonthsPerYear))
  1194. {
  1195. return ResultCode.Overflow;
  1196. }
  1197. calendarTime.Month = (sbyte)month;
  1198. if (IncrementOverflow64(ref year, YearBase))
  1199. {
  1200. return ResultCode.Overflow;
  1201. }
  1202. while (day <= 0)
  1203. {
  1204. if (IncrementOverflow64(ref year, -1))
  1205. {
  1206. return ResultCode.Overflow;
  1207. }
  1208. long li = year;
  1209. if (1 < calendarTime.Month)
  1210. {
  1211. li++;
  1212. }
  1213. day += YearLengths[IsLeap((int)li)];
  1214. }
  1215. while (day > DaysPerLYear)
  1216. {
  1217. long li = year;
  1218. if (1 < calendarTime.Month)
  1219. {
  1220. li++;
  1221. }
  1222. day -= YearLengths[IsLeap((int)li)];
  1223. if (IncrementOverflow64(ref year, 1))
  1224. {
  1225. return ResultCode.Overflow;
  1226. }
  1227. }
  1228. while (true)
  1229. {
  1230. int i = MonthsLengths[IsLeap((int)year)][calendarTime.Month];
  1231. if (day <= i)
  1232. {
  1233. break;
  1234. }
  1235. day -= i;
  1236. calendarTime.Month += 1;
  1237. if (calendarTime.Month >= MonthsPerYear)
  1238. {
  1239. calendarTime.Month = 0;
  1240. if (IncrementOverflow64(ref year, 1))
  1241. {
  1242. return ResultCode.Overflow;
  1243. }
  1244. }
  1245. }
  1246. calendarTime.Day = (sbyte)day;
  1247. if (IncrementOverflow64(ref year, -YearBase))
  1248. {
  1249. return ResultCode.Overflow;
  1250. }
  1251. calendarTime.Year = year;
  1252. int savedSeconds;
  1253. if (calendarTime.Second >= 0 && calendarTime.Second < SecondsPerMinute)
  1254. {
  1255. savedSeconds = 0;
  1256. }
  1257. else if (year + YearBase < EpochYear)
  1258. {
  1259. int second = calendarTime.Second;
  1260. if (IncrementOverflow32(ref second, 1 - SecondsPerMinute))
  1261. {
  1262. return ResultCode.Overflow;
  1263. }
  1264. savedSeconds = second;
  1265. calendarTime.Second = 1 - SecondsPerMinute;
  1266. }
  1267. else
  1268. {
  1269. savedSeconds = calendarTime.Second;
  1270. calendarTime.Second = 0;
  1271. }
  1272. long low = long.MinValue;
  1273. long high = long.MaxValue;
  1274. while (true)
  1275. {
  1276. long pivot = low / 2 + high / 2;
  1277. if (pivot < low)
  1278. {
  1279. pivot = low;
  1280. }
  1281. else if (pivot > high)
  1282. {
  1283. pivot = high;
  1284. }
  1285. int direction;
  1286. ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
  1287. if (result != 0)
  1288. {
  1289. if (pivot > 0)
  1290. {
  1291. direction = 1;
  1292. }
  1293. else
  1294. {
  1295. direction = -1;
  1296. }
  1297. }
  1298. else
  1299. {
  1300. direction = candidateCalendarTime.CompareTo(calendarTime);
  1301. }
  1302. if (direction == 0)
  1303. {
  1304. long timeResult = pivot + savedSeconds;
  1305. if ((timeResult < pivot) != (savedSeconds < 0))
  1306. {
  1307. return ResultCode.Overflow;
  1308. }
  1309. posixTime = timeResult;
  1310. break;
  1311. }
  1312. else
  1313. {
  1314. if (pivot == low)
  1315. {
  1316. if (pivot == long.MaxValue)
  1317. {
  1318. return ResultCode.TimeNotFound;
  1319. }
  1320. pivot += 1;
  1321. low += 1;
  1322. }
  1323. else if (pivot == high)
  1324. {
  1325. if (pivot == long.MinValue)
  1326. {
  1327. return ResultCode.TimeNotFound;
  1328. }
  1329. pivot -= 1;
  1330. high -= 1;
  1331. }
  1332. if (low > high)
  1333. {
  1334. return ResultCode.TimeNotFound;
  1335. }
  1336. if (direction > 0)
  1337. {
  1338. high = pivot;
  1339. }
  1340. else
  1341. {
  1342. low = pivot;
  1343. }
  1344. }
  1345. }
  1346. return ResultCode.Success;
  1347. }
  1348. internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
  1349. {
  1350. ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
  1351. calendar = new CalendarInfo()
  1352. {
  1353. Time = new CalendarTime()
  1354. {
  1355. Year = (short)calendarTime.Year,
  1356. // NOTE: Nintendo's month range is 1-12, internal range is 0-11.
  1357. Month = (sbyte)(calendarTime.Month + 1),
  1358. Day = calendarTime.Day,
  1359. Hour = calendarTime.Hour,
  1360. Minute = calendarTime.Minute,
  1361. Second = calendarTime.Second
  1362. },
  1363. AdditionalInfo = calendarAdditionalInfo
  1364. };
  1365. return result;
  1366. }
  1367. internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
  1368. {
  1369. CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
  1370. {
  1371. Year = calendarTime.Year,
  1372. // NOTE: Nintendo's month range is 1-12, internal range is 0-11.
  1373. Month = (sbyte)(calendarTime.Month - 1),
  1374. Day = calendarTime.Day,
  1375. Hour = calendarTime.Hour,
  1376. Minute = calendarTime.Minute,
  1377. Second = calendarTime.Second
  1378. };
  1379. return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime);
  1380. }
  1381. }
  1382. }