|
|
@@ -0,0 +1,1707 @@
|
|
|
+using System;
|
|
|
+using System.IO;
|
|
|
+using System.Runtime.InteropServices;
|
|
|
+using System.Text;
|
|
|
+using Ryujinx.Common;
|
|
|
+using Ryujinx.HLE.Utilities;
|
|
|
+using static Ryujinx.HLE.HOS.Services.Time.TimeZoneRule;
|
|
|
+
|
|
|
+namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
|
|
+{
|
|
|
+ public class TimeZone
|
|
|
+ {
|
|
|
+ private const int TimeTypeSize = 8;
|
|
|
+ private const int EpochYear = 1970;
|
|
|
+ private const int YearBase = 1900;
|
|
|
+ private const int EpochWeekDay = 4;
|
|
|
+ private const int SecondsPerMinute = 60;
|
|
|
+ private const int MinutesPerHour = 60;
|
|
|
+ private const int HoursPerDays = 24;
|
|
|
+ private const int DaysPerWekk = 7;
|
|
|
+ private const int DaysPerNYear = 365;
|
|
|
+ private const int DaysPerLYear = 366;
|
|
|
+ private const int MonthsPerYear = 12;
|
|
|
+ private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour;
|
|
|
+ private const int SecondsPerDay = SecondsPerHour * HoursPerDays;
|
|
|
+
|
|
|
+ private const int YearsPerRepeat = 400;
|
|
|
+ private const long AverageSecondsPerYear = 31556952;
|
|
|
+ private const long SecondsPerRepeat = YearsPerRepeat * AverageSecondsPerYear;
|
|
|
+
|
|
|
+ private static readonly int[] YearLengths = { DaysPerNYear, DaysPerLYear };
|
|
|
+ private static readonly int[][] MonthsLengths = new int[][]
|
|
|
+ {
|
|
|
+ new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
|
|
+ new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
|
|
+ };
|
|
|
+
|
|
|
+ private const string TimeZoneDefaultRule = ",M4.1.0,M10.5.0";
|
|
|
+
|
|
|
+ [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)]
|
|
|
+ private struct CalendarTimeInternal
|
|
|
+ {
|
|
|
+ // 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.
|
|
|
+ public long Year;
|
|
|
+ public sbyte Month;
|
|
|
+ public sbyte Day;
|
|
|
+ public sbyte Hour;
|
|
|
+ public sbyte Minute;
|
|
|
+ public sbyte Second;
|
|
|
+
|
|
|
+ public int CompareTo(CalendarTimeInternal other)
|
|
|
+ {
|
|
|
+ if (Year != other.Year)
|
|
|
+ {
|
|
|
+ if (Year < other.Year)
|
|
|
+ {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Month != other.Month)
|
|
|
+ {
|
|
|
+ return Month - other.Month;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Day != other.Day)
|
|
|
+ {
|
|
|
+ return Day - other.Day;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Hour != other.Hour)
|
|
|
+ {
|
|
|
+ return Hour - other.Hour;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Minute != other.Minute)
|
|
|
+ {
|
|
|
+ return Minute - other.Minute;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Second != other.Second)
|
|
|
+ {
|
|
|
+ return Second - other.Second;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum RuleType
|
|
|
+ {
|
|
|
+ JulianDay,
|
|
|
+ DayOfYear,
|
|
|
+ MonthNthDayOfWeek
|
|
|
+ }
|
|
|
+
|
|
|
+ private struct Rule
|
|
|
+ {
|
|
|
+ public RuleType Type;
|
|
|
+ public int Day;
|
|
|
+ public int Week;
|
|
|
+ public int Month;
|
|
|
+ public int TransitionTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int Detzcode32(byte[] bytes)
|
|
|
+ {
|
|
|
+ if (BitConverter.IsLittleEndian)
|
|
|
+ {
|
|
|
+ Array.Reverse(bytes, 0, bytes.Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ return BitConverter.ToInt32(bytes, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static unsafe int Detzcode32(int* data)
|
|
|
+ {
|
|
|
+ int result = *data;
|
|
|
+ if (BitConverter.IsLittleEndian)
|
|
|
+ {
|
|
|
+ byte[] bytes = BitConverter.GetBytes(result);
|
|
|
+ Array.Reverse(bytes, 0, bytes.Length);
|
|
|
+ result = BitConverter.ToInt32(bytes, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static unsafe long Detzcode64(long* data)
|
|
|
+ {
|
|
|
+ long result = *data;
|
|
|
+ if (BitConverter.IsLittleEndian)
|
|
|
+ {
|
|
|
+ byte[] bytes = BitConverter.GetBytes(result);
|
|
|
+ Array.Reverse(bytes, 0, bytes.Length);
|
|
|
+ result = BitConverter.ToInt64(bytes, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool DifferByRepeat(long t1, long t0)
|
|
|
+ {
|
|
|
+ return (t1 - t0) == SecondsPerRepeat;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static unsafe bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex)
|
|
|
+ {
|
|
|
+ if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ TimeTypeInfo a = outRules.Ttis[aIndex];
|
|
|
+ TimeTypeInfo b = outRules.Ttis[bIndex];
|
|
|
+
|
|
|
+ fixed (char* chars = outRules.Chars)
|
|
|
+ {
|
|
|
+ return a.GmtOffset == b.GmtOffset &&
|
|
|
+ a.IsDaySavingTime == b.IsDaySavingTime &&
|
|
|
+ a.IsStandardTimeDaylight == b.IsStandardTimeDaylight &&
|
|
|
+ a.IsGMT == b.IsGMT &&
|
|
|
+ StringUtils.CompareCStr(chars + a.AbbreviationListIndex, chars + b.AbbreviationListIndex) == 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int GetQZName(char[] name, int namePosition, char delimiter)
|
|
|
+ {
|
|
|
+ int i = namePosition;
|
|
|
+
|
|
|
+ while (name[i] != '\0' && name[i] != delimiter)
|
|
|
+ {
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int GetTZName(char[] name, int namePosition)
|
|
|
+ {
|
|
|
+ int i = namePosition;
|
|
|
+
|
|
|
+ char c = name[i];
|
|
|
+
|
|
|
+ while (c != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
|
|
|
+ {
|
|
|
+ c = name[i];
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max)
|
|
|
+ {
|
|
|
+ num = 0;
|
|
|
+
|
|
|
+ char c = name[namePosition];
|
|
|
+
|
|
|
+ if (!char.IsDigit(c))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ do
|
|
|
+ {
|
|
|
+ num = num * 10 + (c - '0');
|
|
|
+ if (num > max)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ c = name[++namePosition];
|
|
|
+ }
|
|
|
+ while (char.IsDigit(c));
|
|
|
+
|
|
|
+ if (num < min)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool GetSeconds(char[] name, ref int namePosition, out int seconds)
|
|
|
+ {
|
|
|
+ seconds = 0;
|
|
|
+
|
|
|
+ int num;
|
|
|
+
|
|
|
+ bool isValid = GetNum(name, ref namePosition, out num, 0, HoursPerDays * DaysPerWekk - 1);
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ seconds = num * SecondsPerHour;
|
|
|
+ if (name[namePosition] == ':')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+ isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1);
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ seconds += num * SecondsPerMinute;
|
|
|
+ if (name[namePosition] == ':')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+ isValid = GetNum(name, ref namePosition, out num, 0, SecondsPerMinute);
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ seconds += num;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool GetOffset(char[] name, ref int namePosition, ref int offset)
|
|
|
+ {
|
|
|
+ bool isNegative = false;
|
|
|
+
|
|
|
+ if (name[namePosition] == '-')
|
|
|
+ {
|
|
|
+ isNegative = true;
|
|
|
+ namePosition++;
|
|
|
+ }
|
|
|
+ else if (name[namePosition] == '+')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isValid = GetSeconds(name, ref namePosition, out offset);
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isNegative)
|
|
|
+ {
|
|
|
+ offset = -offset;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool GetRule(char[] name, ref int namePosition, out Rule rule)
|
|
|
+ {
|
|
|
+ rule = new Rule();
|
|
|
+
|
|
|
+ bool isValid = false;
|
|
|
+
|
|
|
+ if (name[namePosition] == 'J')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+
|
|
|
+ rule.Type = RuleType.JulianDay;
|
|
|
+ isValid = GetNum(name, ref namePosition, out rule.Day, 1, DaysPerNYear);
|
|
|
+ }
|
|
|
+ else if (name[namePosition] == 'M')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+
|
|
|
+ rule.Type = RuleType.MonthNthDayOfWeek;
|
|
|
+ isValid = GetNum(name, ref namePosition, out rule.Month, 1, MonthsPerYear);
|
|
|
+
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition++] != '.')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ isValid = GetNum(name, ref namePosition, out rule.Week, 1, 5);
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition++] != '.')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1);
|
|
|
+ }
|
|
|
+ else if (char.IsDigit(name[namePosition]))
|
|
|
+ {
|
|
|
+ rule.Type = RuleType.DayOfYear;
|
|
|
+ isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] == '/')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+ return GetOffset(name, ref namePosition, ref rule.TransitionTime);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ rule.TransitionTime = 2 * SecondsPerHour;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int IsLeap(int year)
|
|
|
+ {
|
|
|
+ if (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0))
|
|
|
+ {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool ParsePosixName(Span<char> name, out TimeZoneRule outRules, bool lastDitch)
|
|
|
+ {
|
|
|
+ outRules = new TimeZoneRule
|
|
|
+ {
|
|
|
+ Ats = new long[TzMaxTimes],
|
|
|
+ Types = new byte[TzMaxTimes],
|
|
|
+ Ttis = new TimeTypeInfo[TzMaxTypes],
|
|
|
+ Chars = new char[TzCharsArraySize]
|
|
|
+ };
|
|
|
+
|
|
|
+ int stdLen;
|
|
|
+ Span<char> stdName = name;
|
|
|
+ int namePosition = 0;
|
|
|
+ int stdOffset = 0;
|
|
|
+
|
|
|
+ if (lastDitch)
|
|
|
+ {
|
|
|
+ stdLen = 3;
|
|
|
+ namePosition += stdLen;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (name[namePosition] == '<')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+
|
|
|
+ stdName = name.Slice(namePosition);
|
|
|
+
|
|
|
+ int stdNamePosition = namePosition;
|
|
|
+
|
|
|
+ namePosition = GetQZName(name.ToArray(), namePosition, '>');
|
|
|
+ if (name[namePosition] != '>')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ stdLen = namePosition - stdNamePosition;
|
|
|
+ namePosition++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ namePosition = GetTZName(name.ToArray(), namePosition);
|
|
|
+ stdLen = namePosition;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stdLen == 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isValid = GetOffset(name.ToArray(), ref namePosition, ref stdOffset);
|
|
|
+
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int charCount = stdLen + 1;
|
|
|
+ int destLen = 0;
|
|
|
+ int dstOffset = 0;
|
|
|
+
|
|
|
+ Span<char> destName = name.Slice(namePosition);
|
|
|
+
|
|
|
+ if (TzCharsArraySize < charCount)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] != '\0')
|
|
|
+ {
|
|
|
+ if (name[namePosition] == '<')
|
|
|
+ {
|
|
|
+ destName = name.Slice(++namePosition);
|
|
|
+ int destNamePosition = namePosition;
|
|
|
+
|
|
|
+ namePosition = GetQZName(name.ToArray(), namePosition, '>');
|
|
|
+
|
|
|
+ if (name[namePosition] != '>')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ destLen = namePosition - destNamePosition;
|
|
|
+ namePosition++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ destName = name.Slice(namePosition);
|
|
|
+ namePosition = GetTZName(name.ToArray(), namePosition);
|
|
|
+ destLen = namePosition;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (destLen == 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ charCount += destLen + 1;
|
|
|
+ if (TzCharsArraySize < charCount)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] != '\0' && name[namePosition] != ',' && name[namePosition] != ';')
|
|
|
+ {
|
|
|
+ bool isValid = GetOffset(name.ToArray(), ref namePosition, ref dstOffset);
|
|
|
+
|
|
|
+ if (!isValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ dstOffset = stdOffset - SecondsPerHour;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] == '\0')
|
|
|
+ {
|
|
|
+ name = TimeZoneDefaultRule.ToCharArray();
|
|
|
+ namePosition = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] == ',' || name[namePosition] == ';')
|
|
|
+ {
|
|
|
+ namePosition++;
|
|
|
+
|
|
|
+ bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start);
|
|
|
+ if (!IsRuleValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition++] != ',')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end);
|
|
|
+ if (!IsRuleValid)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name[namePosition] != '\0')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.TypeCount = 2;
|
|
|
+
|
|
|
+ outRules.Ttis[0] = new TimeTypeInfo
|
|
|
+ {
|
|
|
+ GmtOffset = -dstOffset,
|
|
|
+ IsDaySavingTime = true,
|
|
|
+ AbbreviationListIndex = stdLen + 1
|
|
|
+ };
|
|
|
+
|
|
|
+ outRules.Ttis[1] = new TimeTypeInfo
|
|
|
+ {
|
|
|
+ GmtOffset = -stdOffset,
|
|
|
+ IsDaySavingTime = false,
|
|
|
+ AbbreviationListIndex = 0
|
|
|
+ };
|
|
|
+
|
|
|
+ outRules.DefaultType = 0;
|
|
|
+
|
|
|
+ int timeCount = 0;
|
|
|
+ long janFirst = 0;
|
|
|
+ int janOffset = 0;
|
|
|
+ int yearBegining = EpochYear;
|
|
|
+
|
|
|
+ do
|
|
|
+ {
|
|
|
+ int yearSeconds = YearLengths[IsLeap(yearBegining - 1)] * SecondsPerDay;
|
|
|
+ yearBegining--;
|
|
|
+ if (IncrementOverflow64(ref janFirst, -yearSeconds))
|
|
|
+ {
|
|
|
+ janOffset = -yearSeconds;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ while (EpochYear - YearsPerRepeat / 2 < yearBegining);
|
|
|
+
|
|
|
+ int yearLimit = yearBegining + YearsPerRepeat + 1;
|
|
|
+ int year;
|
|
|
+ for (year = yearBegining; year < yearLimit; year++)
|
|
|
+ {
|
|
|
+ int startTime = TransitionTime(year, start, stdOffset);
|
|
|
+ int endTime = TransitionTime(year, end, dstOffset);
|
|
|
+
|
|
|
+ int yearSeconds = YearLengths[IsLeap(year)] * SecondsPerDay;
|
|
|
+
|
|
|
+ bool isReversed = endTime < startTime;
|
|
|
+ if (isReversed)
|
|
|
+ {
|
|
|
+ int swap = startTime;
|
|
|
+
|
|
|
+ startTime = endTime;
|
|
|
+ endTime = swap;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isReversed || (startTime < endTime && (endTime - startTime < (yearSeconds + (stdOffset - dstOffset)))))
|
|
|
+ {
|
|
|
+ if (TzMaxTimes - 2 < timeCount)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ats[timeCount] = janFirst;
|
|
|
+ if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + startTime))
|
|
|
+ {
|
|
|
+ outRules.Types[timeCount++] = isReversed ? (byte)1 : (byte)0;
|
|
|
+ }
|
|
|
+ else if (janOffset != 0)
|
|
|
+ {
|
|
|
+ outRules.DefaultType = isReversed ? 1 : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ats[timeCount] = janFirst;
|
|
|
+ if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + endTime))
|
|
|
+ {
|
|
|
+ outRules.Types[timeCount++] = isReversed ? (byte)0 : (byte)1;
|
|
|
+ yearLimit = year + YearsPerRepeat + 1;
|
|
|
+ }
|
|
|
+ else if (janOffset != 0)
|
|
|
+ {
|
|
|
+ outRules.DefaultType = isReversed ? 0 : 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref janFirst, janOffset + yearSeconds))
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ janOffset = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.TimeCount = timeCount;
|
|
|
+
|
|
|
+ // There is no time variation, this is then a perpetual DST rule
|
|
|
+ if (timeCount == 0)
|
|
|
+ {
|
|
|
+ outRules.TypeCount = 1;
|
|
|
+ }
|
|
|
+ else if (YearsPerRepeat < year - yearBegining)
|
|
|
+ {
|
|
|
+ outRules.GoBack = true;
|
|
|
+ outRules.GoAhead = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (name[namePosition] == '\0')
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ long theirStdOffset = 0;
|
|
|
+ for (int i = 0; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ int j = outRules.Types[i];
|
|
|
+ if (outRules.Ttis[j].IsStandardTimeDaylight)
|
|
|
+ {
|
|
|
+ theirStdOffset = -outRules.Ttis[j].GmtOffset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ long theirDstOffset = 0;
|
|
|
+ for (int i = 0; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ int j = outRules.Types[i];
|
|
|
+ if (outRules.Ttis[j].IsDaySavingTime)
|
|
|
+ {
|
|
|
+ theirDstOffset = -outRules.Ttis[j].GmtOffset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isDaySavingTime = false;
|
|
|
+ long theirOffset = theirStdOffset;
|
|
|
+ for (int i = 0; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ int j = outRules.Types[i];
|
|
|
+ outRules.Types[i] = outRules.Ttis[j].IsDaySavingTime ? (byte)1 : (byte)0;
|
|
|
+ if (!outRules.Ttis[j].IsGMT)
|
|
|
+ {
|
|
|
+ if (isDaySavingTime && !outRules.Ttis[j].IsStandardTimeDaylight)
|
|
|
+ {
|
|
|
+ outRules.Ats[i] += dstOffset - theirStdOffset;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ outRules.Ats[i] += stdOffset - theirStdOffset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ theirOffset = -outRules.Ttis[j].GmtOffset;
|
|
|
+ if (outRules.Ttis[j].IsDaySavingTime)
|
|
|
+ {
|
|
|
+ theirDstOffset = theirOffset;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ theirStdOffset = theirOffset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ttis[0] = new TimeTypeInfo
|
|
|
+ {
|
|
|
+ GmtOffset = -stdOffset,
|
|
|
+ IsDaySavingTime = false,
|
|
|
+ AbbreviationListIndex = 0
|
|
|
+ };
|
|
|
+
|
|
|
+ outRules.Ttis[1] = new TimeTypeInfo
|
|
|
+ {
|
|
|
+ GmtOffset = -dstOffset,
|
|
|
+ IsDaySavingTime = true,
|
|
|
+ AbbreviationListIndex = stdLen + 1
|
|
|
+ };
|
|
|
+
|
|
|
+ outRules.TypeCount = 2;
|
|
|
+ outRules.DefaultType = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // default is perpetual standard time
|
|
|
+ outRules.TypeCount = 1;
|
|
|
+ outRules.TimeCount = 0;
|
|
|
+ outRules.DefaultType = 0;
|
|
|
+ outRules.Ttis[0] = new TimeTypeInfo
|
|
|
+ {
|
|
|
+ GmtOffset = -stdOffset,
|
|
|
+ IsDaySavingTime = false,
|
|
|
+ AbbreviationListIndex = 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.CharCount = charCount;
|
|
|
+
|
|
|
+ int charsPosition = 0;
|
|
|
+
|
|
|
+ for (int i = 0; i < stdLen; i++)
|
|
|
+ {
|
|
|
+ outRules.Chars[i] = stdName[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ charsPosition += stdLen;
|
|
|
+ outRules.Chars[charsPosition++] = '\0';
|
|
|
+
|
|
|
+ if (destLen != 0)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < destLen; i++)
|
|
|
+ {
|
|
|
+ outRules.Chars[charsPosition + i] = destName[i];
|
|
|
+ }
|
|
|
+ outRules.Chars[charsPosition + destLen] = '\0';
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int TransitionTime(int year, Rule rule, int offset)
|
|
|
+ {
|
|
|
+ int leapYear = IsLeap(year);
|
|
|
+
|
|
|
+ int value;
|
|
|
+ switch (rule.Type)
|
|
|
+ {
|
|
|
+ case RuleType.JulianDay:
|
|
|
+ value = (rule.Day - 1) * SecondsPerDay;
|
|
|
+ if (leapYear == 1 && rule.Day >= 60)
|
|
|
+ {
|
|
|
+ value += SecondsPerDay;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case RuleType.DayOfYear:
|
|
|
+ value = rule.Day * SecondsPerDay;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case RuleType.MonthNthDayOfWeek:
|
|
|
+ // Here we use Zeller's Congruence to get the day of week of the first month.
|
|
|
+
|
|
|
+ int m1 = (rule.Month + 9) % 12 + 1;
|
|
|
+ int yy0 = (rule.Month <= 2) ? (year - 1) : year;
|
|
|
+ int yy1 = yy0 / 100;
|
|
|
+ int yy2 = yy0 % 100;
|
|
|
+
|
|
|
+ int dayOfWeek = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
|
|
|
+
|
|
|
+ if (dayOfWeek < 0)
|
|
|
+ {
|
|
|
+ dayOfWeek += DaysPerWekk;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the zero origin
|
|
|
+ int d = rule.Day - dayOfWeek;
|
|
|
+
|
|
|
+ if (d < 0)
|
|
|
+ {
|
|
|
+ d += DaysPerWekk;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 1; i < rule.Week; i++)
|
|
|
+ {
|
|
|
+ if (d + DaysPerWekk >= MonthsLengths[leapYear][rule.Month - 1])
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ d += DaysPerWekk;
|
|
|
+ }
|
|
|
+
|
|
|
+ value = d * SecondsPerDay;
|
|
|
+ for (int i = 0; i < rule.Month - 1; i++)
|
|
|
+ {
|
|
|
+ value += MonthsLengths[leapYear][i] * SecondsPerDay;
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new NotImplementedException("Unknown time transition!");
|
|
|
+ }
|
|
|
+
|
|
|
+ return value + rule.TransitionTime + offset;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool NormalizeOverflow32(ref int ip, ref int unit, int baseValue)
|
|
|
+ {
|
|
|
+ int delta;
|
|
|
+
|
|
|
+ if (unit >= 0)
|
|
|
+ {
|
|
|
+ delta = unit / baseValue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ delta = -1 - (-1 - unit) / baseValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ unit -= delta * baseValue;
|
|
|
+
|
|
|
+ return IncrementOverflow32(ref ip, delta);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool NormalizeOverflow64(ref long ip, ref long unit, long baseValue)
|
|
|
+ {
|
|
|
+ long delta;
|
|
|
+
|
|
|
+ if (unit >= 0)
|
|
|
+ {
|
|
|
+ delta = unit / baseValue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ delta = -1 - (-1 - unit) / baseValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ unit -= delta * baseValue;
|
|
|
+
|
|
|
+ return IncrementOverflow64(ref ip, delta);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IncrementOverflow32(ref int time, int j)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ time = checked(time + j);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ catch (OverflowException)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IncrementOverflow64(ref long time, long j)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ time = checked(time + j);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ catch (OverflowException)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal static bool ParsePosixName(string name, out TimeZoneRule outRules)
|
|
|
+ {
|
|
|
+ return ParsePosixName(name.ToCharArray(), out outRules, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ internal static unsafe bool LoadTimeZoneRules(out TimeZoneRule outRules, Stream inputData)
|
|
|
+ {
|
|
|
+ outRules = new TimeZoneRule
|
|
|
+ {
|
|
|
+ Ats = new long[TzMaxTimes],
|
|
|
+ Types = new byte[TzMaxTimes],
|
|
|
+ Ttis = new TimeTypeInfo[TzMaxTypes],
|
|
|
+ Chars = new char[TzCharsArraySize]
|
|
|
+ };
|
|
|
+
|
|
|
+ BinaryReader reader = new BinaryReader(inputData);
|
|
|
+
|
|
|
+ long streamLength = reader.BaseStream.Length;
|
|
|
+
|
|
|
+ if (streamLength < Marshal.SizeOf<TzifHeader>())
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ TzifHeader header = reader.ReadStruct<TzifHeader>();
|
|
|
+
|
|
|
+ streamLength -= Marshal.SizeOf<TzifHeader>();
|
|
|
+
|
|
|
+ int ttisGMTCount = Detzcode32(header.TtisGMTCount);
|
|
|
+ int ttisSTDCount = Detzcode32(header.TtisSTDCount);
|
|
|
+ int leapCount = Detzcode32(header.LeapCount);
|
|
|
+ int timeCount = Detzcode32(header.TimeCount);
|
|
|
+ int typeCount = Detzcode32(header.TypeCount);
|
|
|
+ int charCount = Detzcode32(header.CharCount);
|
|
|
+
|
|
|
+ if (!(0 <= leapCount
|
|
|
+ && leapCount < TzMaxLeaps
|
|
|
+ && 0 < typeCount
|
|
|
+ && typeCount < TzMaxTypes
|
|
|
+ && 0 <= timeCount
|
|
|
+ && timeCount < TzMaxTimes
|
|
|
+ && 0 <= charCount
|
|
|
+ && charCount < TzMaxChars
|
|
|
+ && (ttisSTDCount == typeCount || ttisSTDCount == 0)
|
|
|
+ && (ttisGMTCount == typeCount || ttisGMTCount == 0)))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (streamLength < (timeCount * TimeTypeSize
|
|
|
+ + timeCount
|
|
|
+ + typeCount * 6
|
|
|
+ + charCount
|
|
|
+ + leapCount * (TimeTypeSize + 4)
|
|
|
+ + ttisSTDCount
|
|
|
+ + ttisGMTCount))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.TimeCount = timeCount;
|
|
|
+ outRules.TypeCount = typeCount;
|
|
|
+ outRules.CharCount = charCount;
|
|
|
+
|
|
|
+ byte[] workBuffer = StreamUtils.StreamToBytes(inputData);
|
|
|
+
|
|
|
+ timeCount = 0;
|
|
|
+
|
|
|
+ fixed (byte* workBufferPtrStart = workBuffer)
|
|
|
+ {
|
|
|
+ byte* p = workBufferPtrStart;
|
|
|
+ for (int i = 0; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ long at = Detzcode64((long*)p);
|
|
|
+ outRules.Types[i] = 1;
|
|
|
+
|
|
|
+ if (timeCount != 0 && at <= outRules.Ats[timeCount - 1])
|
|
|
+ {
|
|
|
+ if (at < outRules.Ats[timeCount - 1])
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Types[i - 1] = 0;
|
|
|
+ timeCount--;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ats[timeCount++] = at;
|
|
|
+
|
|
|
+ p += TimeTypeSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ timeCount = 0;
|
|
|
+ for (int i = 0; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ byte type = *p++;
|
|
|
+ if (outRules.TypeCount <= type)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (outRules.Types[i] != 0)
|
|
|
+ {
|
|
|
+ outRules.Types[timeCount++] = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.TimeCount = timeCount;
|
|
|
+
|
|
|
+ for (int i = 0; i < outRules.TypeCount; i++)
|
|
|
+ {
|
|
|
+ TimeTypeInfo ttis = outRules.Ttis[i];
|
|
|
+ ttis.GmtOffset = Detzcode32((int*)p);
|
|
|
+ p += 4;
|
|
|
+
|
|
|
+ if (*p >= 2)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ttis.IsDaySavingTime = *p != 0;
|
|
|
+ p++;
|
|
|
+
|
|
|
+ int abbreviationListIndex = *p++;
|
|
|
+ if (abbreviationListIndex >= outRules.CharCount)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ttis.AbbreviationListIndex = abbreviationListIndex;
|
|
|
+
|
|
|
+ outRules.Ttis[i] = ttis;
|
|
|
+ }
|
|
|
+
|
|
|
+ fixed (char* chars = outRules.Chars)
|
|
|
+ {
|
|
|
+ Encoding.ASCII.GetChars(p, outRules.CharCount, chars, outRules.CharCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ p += outRules.CharCount;
|
|
|
+ outRules.Chars[outRules.CharCount] = '\0';
|
|
|
+
|
|
|
+ for (int i = 0; i < outRules.TypeCount; i++)
|
|
|
+ {
|
|
|
+ if (ttisSTDCount == 0)
|
|
|
+ {
|
|
|
+ outRules.Ttis[i].IsStandardTimeDaylight = false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (*p >= 2)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ttis[i].IsStandardTimeDaylight = *p++ != 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < outRules.TypeCount; i++)
|
|
|
+ {
|
|
|
+ if (ttisSTDCount == 0)
|
|
|
+ {
|
|
|
+ outRules.Ttis[i].IsGMT = false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (*p >= 2)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.Ttis[i].IsGMT = *p++ != 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ long position = (p - workBufferPtrStart);
|
|
|
+ long nRead = streamLength - position;
|
|
|
+
|
|
|
+ if (nRead < 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Nintendo abort in case of a TzIf file with a POSIX TZ Name too long to fit inside a TimeZoneRule.
|
|
|
+ // As it's impossible in normal usage to achive this, we also force a crash.
|
|
|
+ if (nRead > (TzNameMax + 1))
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException();
|
|
|
+ }
|
|
|
+
|
|
|
+ char[] tempName = new char[TzNameMax + 1];
|
|
|
+ Array.Copy(workBuffer, position, tempName, 0, nRead);
|
|
|
+
|
|
|
+ if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes)
|
|
|
+ {
|
|
|
+ tempName[nRead - 1] = '\0';
|
|
|
+
|
|
|
+ char[] name = new char[TzNameMax];
|
|
|
+ Array.Copy(tempName, 1, name, 0, nRead - 1);
|
|
|
+
|
|
|
+ if (ParsePosixName(name, out TimeZoneRule tempRules, false))
|
|
|
+ {
|
|
|
+ int abbreviationCount = 0;
|
|
|
+ charCount = outRules.CharCount;
|
|
|
+
|
|
|
+ fixed (char* chars = outRules.Chars)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < tempRules.TypeCount; i++)
|
|
|
+ {
|
|
|
+ fixed (char* tempChars = tempRules.Chars)
|
|
|
+ {
|
|
|
+ char* tempAbbreviation = tempChars + tempRules.Ttis[i].AbbreviationListIndex;
|
|
|
+ int j;
|
|
|
+
|
|
|
+ for (j = 0; j < charCount; j++)
|
|
|
+ {
|
|
|
+ if (StringUtils.CompareCStr(chars + j, tempAbbreviation) == 0)
|
|
|
+ {
|
|
|
+ tempRules.Ttis[i].AbbreviationListIndex = j;
|
|
|
+ abbreviationCount++;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (j >= charCount)
|
|
|
+ {
|
|
|
+ int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation);
|
|
|
+ if (j + abbreviationLength < TzMaxChars)
|
|
|
+ {
|
|
|
+ for (int x = 0; x < abbreviationLength; x++)
|
|
|
+ {
|
|
|
+ chars[j + x] = tempAbbreviation[x];
|
|
|
+ }
|
|
|
+
|
|
|
+ charCount = j + abbreviationLength + 1;
|
|
|
+
|
|
|
+ tempRules.Ttis[i].AbbreviationListIndex = j;
|
|
|
+ abbreviationCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (abbreviationCount == tempRules.TypeCount)
|
|
|
+ {
|
|
|
+ outRules.CharCount = charCount;
|
|
|
+
|
|
|
+ // Remove trailing
|
|
|
+ while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2]))
|
|
|
+ {
|
|
|
+ outRules.TimeCount--;
|
|
|
+ }
|
|
|
+
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < tempRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i])
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes)
|
|
|
+ {
|
|
|
+ outRules.Ats[outRules.TimeCount] = tempRules.Ats[i];
|
|
|
+ outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]);
|
|
|
+
|
|
|
+ outRules.TimeCount++;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < tempRules.TypeCount; i++)
|
|
|
+ {
|
|
|
+ outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (outRules.TypeCount == 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (outRules.TimeCount > 1)
|
|
|
+ {
|
|
|
+ for (int i = 1; i < outRules.TimeCount; i++)
|
|
|
+ {
|
|
|
+ if (TimeTypeEquals(outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
|
|
|
+ {
|
|
|
+ outRules.GoBack = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = outRules.TimeCount - 2; i >= 0; i--)
|
|
|
+ {
|
|
|
+ if (TimeTypeEquals(outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
|
|
|
+ {
|
|
|
+ outRules.GoAhead = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int defaultType;
|
|
|
+
|
|
|
+ for (defaultType = 0; defaultType < outRules.TimeCount; defaultType++)
|
|
|
+ {
|
|
|
+ if (outRules.Types[defaultType] == 0)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ defaultType = defaultType < outRules.TimeCount ? -1 : 0;
|
|
|
+
|
|
|
+ if (defaultType < 0 && outRules.TimeCount > 0 && outRules.Ttis[outRules.Types[0]].IsDaySavingTime)
|
|
|
+ {
|
|
|
+ defaultType = outRules.Types[0];
|
|
|
+ while (--defaultType >= 0)
|
|
|
+ {
|
|
|
+ if (!outRules.Ttis[defaultType].IsDaySavingTime)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (defaultType < 0)
|
|
|
+ {
|
|
|
+ defaultType = 0;
|
|
|
+ while (outRules.Ttis[defaultType].IsDaySavingTime)
|
|
|
+ {
|
|
|
+ if (++defaultType >= outRules.TypeCount)
|
|
|
+ {
|
|
|
+ defaultType = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ outRules.DefaultType = defaultType;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static long GetLeapDaysNotNeg(long year)
|
|
|
+ {
|
|
|
+ return year / 4 - year / 100 + year / 400;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static long GetLeapDays(long year)
|
|
|
+ {
|
|
|
+ if (year < 0)
|
|
|
+ {
|
|
|
+ return -1 - GetLeapDaysNotNeg(-1 - year);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return GetLeapDaysNotNeg(year);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int CreateCalendarTime(long time, int gmtOffset, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
|
|
|
+ {
|
|
|
+ long year = EpochYear;
|
|
|
+ long timeDays = time / SecondsPerDay;
|
|
|
+ long remainingSeconds = time % SecondsPerDay;
|
|
|
+
|
|
|
+ calendarTime = new CalendarTimeInternal();
|
|
|
+ calendarAdditionalInfo = new CalendarAdditionalInfo()
|
|
|
+ {
|
|
|
+ TimezoneName = new char[8]
|
|
|
+ };
|
|
|
+
|
|
|
+ while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
|
|
|
+ {
|
|
|
+ long timeDelta = timeDays / DaysPerLYear;
|
|
|
+ long delta = timeDelta;
|
|
|
+
|
|
|
+ if (delta == 0)
|
|
|
+ {
|
|
|
+ delta = timeDays < 0 ? -1 : 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ long newYear = year;
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref newYear, delta))
|
|
|
+ {
|
|
|
+ return TimeError.OutOfRange;
|
|
|
+ }
|
|
|
+
|
|
|
+ long leapDays = GetLeapDays(newYear - 1) - GetLeapDays(year - 1);
|
|
|
+ timeDays -= (newYear - year) * DaysPerNYear;
|
|
|
+ timeDays -= leapDays;
|
|
|
+ year = newYear;
|
|
|
+ }
|
|
|
+
|
|
|
+ long dayOfYear = timeDays;
|
|
|
+ remainingSeconds += gmtOffset;
|
|
|
+ while (remainingSeconds < 0)
|
|
|
+ {
|
|
|
+ remainingSeconds += SecondsPerDay;
|
|
|
+ dayOfYear -= 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (remainingSeconds >= SecondsPerDay)
|
|
|
+ {
|
|
|
+ remainingSeconds -= SecondsPerDay;
|
|
|
+ dayOfYear += 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (dayOfYear < 0)
|
|
|
+ {
|
|
|
+ if (IncrementOverflow64(ref year, -1))
|
|
|
+ {
|
|
|
+ return TimeError.OutOfRange;
|
|
|
+ }
|
|
|
+
|
|
|
+ dayOfYear += YearLengths[IsLeap((int)year)];
|
|
|
+ }
|
|
|
+
|
|
|
+ while (dayOfYear >= YearLengths[IsLeap((int)year)])
|
|
|
+ {
|
|
|
+ dayOfYear -= YearLengths[IsLeap((int)year)];
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref year, 1))
|
|
|
+ {
|
|
|
+ return TimeError.OutOfRange;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Year = year;
|
|
|
+ calendarAdditionalInfo.DayOfYear = (uint)dayOfYear;
|
|
|
+
|
|
|
+ long dayOfWeek = (EpochWeekDay + ((year - EpochYear) % DaysPerWekk) * (DaysPerNYear % DaysPerWekk) + GetLeapDays(year - 1) - GetLeapDays(EpochYear - 1) + dayOfYear) % DaysPerWekk;
|
|
|
+ if (dayOfWeek < 0)
|
|
|
+ {
|
|
|
+ dayOfWeek += DaysPerWekk;
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarAdditionalInfo.DayOfWeek = (uint)dayOfWeek;
|
|
|
+
|
|
|
+ calendarTime.Hour = (sbyte)((remainingSeconds / SecondsPerHour) % SecondsPerHour);
|
|
|
+ remainingSeconds %= SecondsPerHour;
|
|
|
+
|
|
|
+ calendarTime.Minute = (sbyte)(remainingSeconds / SecondsPerMinute);
|
|
|
+ calendarTime.Second = (sbyte)(remainingSeconds % SecondsPerMinute);
|
|
|
+
|
|
|
+ int[] ip = MonthsLengths[IsLeap((int)year)];
|
|
|
+
|
|
|
+ while (dayOfYear >= ip[calendarTime.Month])
|
|
|
+ {
|
|
|
+ calendarTime.Month += 1;
|
|
|
+
|
|
|
+ dayOfYear -= ip[calendarTime.Month];
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Day = (sbyte)(dayOfYear + 1);
|
|
|
+
|
|
|
+ calendarAdditionalInfo.IsDaySavingTime = false;
|
|
|
+ calendarAdditionalInfo.GmtOffset = gmtOffset;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
|
|
|
+ {
|
|
|
+ calendarTime = new CalendarTimeInternal();
|
|
|
+ calendarAdditionalInfo = new CalendarAdditionalInfo()
|
|
|
+ {
|
|
|
+ TimezoneName = new char[8]
|
|
|
+ };
|
|
|
+
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if ((rules.GoAhead && time < rules.Ats[0]) || (rules.GoBack && time > rules.Ats[rules.TimeCount - 1]))
|
|
|
+ {
|
|
|
+ long newTime = time;
|
|
|
+
|
|
|
+ long seconds;
|
|
|
+ long years;
|
|
|
+
|
|
|
+ if (time < rules.Ats[0])
|
|
|
+ {
|
|
|
+ seconds = rules.Ats[0] - time;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ seconds = time - rules.Ats[rules.TimeCount - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ seconds -= 1;
|
|
|
+
|
|
|
+ years = (seconds / SecondsPerRepeat + 1) * YearsPerRepeat;
|
|
|
+ seconds = years * AverageSecondsPerYear;
|
|
|
+
|
|
|
+ if (time < rules.Ats[0])
|
|
|
+ {
|
|
|
+ newTime += seconds;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ newTime -= seconds;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newTime < rules.Ats[0] && newTime > rules.Ats[rules.TimeCount - 1])
|
|
|
+ {
|
|
|
+ return TimeError.TimeNotFound;
|
|
|
+ }
|
|
|
+
|
|
|
+ result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo);
|
|
|
+ if (result != 0)
|
|
|
+ {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (time < rules.Ats[0])
|
|
|
+ {
|
|
|
+ calendarTime.Year -= years;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ calendarTime.Year += years;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ int ttiIndex;
|
|
|
+
|
|
|
+ if (rules.TimeCount == 0 || time < rules.Ats[0])
|
|
|
+ {
|
|
|
+ ttiIndex = rules.DefaultType;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int low = 1;
|
|
|
+ int high = rules.TimeCount;
|
|
|
+
|
|
|
+ while (low < high)
|
|
|
+ {
|
|
|
+ int mid = (low + high) >> 1;
|
|
|
+
|
|
|
+ if (time < rules.Ats[mid])
|
|
|
+ {
|
|
|
+ high = mid;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ low = mid + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ttiIndex = rules.Types[low - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ result = CreateCalendarTime(time, rules.Ttis[ttiIndex].GmtOffset, out calendarTime, out calendarAdditionalInfo);
|
|
|
+
|
|
|
+ if (result == 0)
|
|
|
+ {
|
|
|
+ calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
|
|
|
+
|
|
|
+ unsafe
|
|
|
+ {
|
|
|
+ fixed (char* timeZoneAbbreviation = &rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex])
|
|
|
+ {
|
|
|
+ int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
|
|
|
+ for (int i = 0; i < timeZoneSize; i++)
|
|
|
+ {
|
|
|
+ calendarAdditionalInfo.TimezoneName[i] = timeZoneAbbreviation[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int ToPosixTimeInternal(TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
|
|
|
+ {
|
|
|
+ posixTime = 0;
|
|
|
+
|
|
|
+ int hour = calendarTime.Hour;
|
|
|
+ int minute = calendarTime.Minute;
|
|
|
+
|
|
|
+ if (NormalizeOverflow32(ref hour, ref minute, MinutesPerHour))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Minute = (sbyte)minute;
|
|
|
+
|
|
|
+ int day = calendarTime.Day;
|
|
|
+ if (NormalizeOverflow32(ref day, ref hour, HoursPerDays))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Day = (sbyte)day;
|
|
|
+ calendarTime.Hour = (sbyte)hour;
|
|
|
+
|
|
|
+ long year = calendarTime.Year;
|
|
|
+ long month = calendarTime.Month;
|
|
|
+
|
|
|
+ if (NormalizeOverflow64(ref year, ref month, MonthsPerYear))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Month = (sbyte)month;
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref year, YearBase))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (day <= 0)
|
|
|
+ {
|
|
|
+ if (IncrementOverflow64(ref year, -1))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ long li = year;
|
|
|
+
|
|
|
+ if (1 < calendarTime.Month)
|
|
|
+ {
|
|
|
+ li++;
|
|
|
+ }
|
|
|
+
|
|
|
+ day += YearLengths[IsLeap((int)li)];
|
|
|
+ }
|
|
|
+
|
|
|
+ while (day > DaysPerLYear)
|
|
|
+ {
|
|
|
+ long li = year;
|
|
|
+
|
|
|
+ if (1 < calendarTime.Month)
|
|
|
+ {
|
|
|
+ li++;
|
|
|
+ }
|
|
|
+
|
|
|
+ day -= YearLengths[IsLeap((int)li)];
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref year, 1))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ int i = MonthsLengths[IsLeap((int)year)][calendarTime.Month];
|
|
|
+
|
|
|
+ if (day <= i)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ day -= i;
|
|
|
+ calendarTime.Month += 1;
|
|
|
+
|
|
|
+ if (calendarTime.Month >= MonthsPerYear)
|
|
|
+ {
|
|
|
+ calendarTime.Month = 0;
|
|
|
+ if (IncrementOverflow64(ref year, 1))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Day = (sbyte)day;
|
|
|
+
|
|
|
+ if (IncrementOverflow64(ref year, -YearBase))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ calendarTime.Year = year;
|
|
|
+
|
|
|
+ int savedSeconds;
|
|
|
+
|
|
|
+ if (calendarTime.Second >= 0 && calendarTime.Second < SecondsPerMinute)
|
|
|
+ {
|
|
|
+ savedSeconds = 0;
|
|
|
+ }
|
|
|
+ else if (year + YearBase < EpochYear)
|
|
|
+ {
|
|
|
+ int second = calendarTime.Second;
|
|
|
+ if (IncrementOverflow32(ref second, 1 - SecondsPerMinute))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ savedSeconds = second;
|
|
|
+ calendarTime.Second = 1 - SecondsPerMinute;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ savedSeconds = calendarTime.Second;
|
|
|
+ calendarTime.Second = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ long low = long.MinValue;
|
|
|
+ long high = long.MaxValue;
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ long pivot = low / 2 + high / 2;
|
|
|
+
|
|
|
+ if (pivot < low)
|
|
|
+ {
|
|
|
+ pivot = low;
|
|
|
+ }
|
|
|
+ else if (pivot > high)
|
|
|
+ {
|
|
|
+ pivot = high;
|
|
|
+ }
|
|
|
+
|
|
|
+ int direction;
|
|
|
+
|
|
|
+ int result = ToCalendarTimeInternal(rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
|
|
|
+ if (result != 0)
|
|
|
+ {
|
|
|
+ if (pivot > 0)
|
|
|
+ {
|
|
|
+ direction = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ direction = -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ direction = candidateCalendarTime.CompareTo(calendarTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (direction == 0)
|
|
|
+ {
|
|
|
+ long timeResult = pivot + savedSeconds;
|
|
|
+
|
|
|
+ if ((timeResult < pivot) != (savedSeconds < 0))
|
|
|
+ {
|
|
|
+ return TimeError.Overflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ posixTime = timeResult;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (pivot == low)
|
|
|
+ {
|
|
|
+ if (pivot == long.MaxValue)
|
|
|
+ {
|
|
|
+ return TimeError.TimeNotFound;
|
|
|
+ }
|
|
|
+
|
|
|
+ pivot += 1;
|
|
|
+ low += 1;
|
|
|
+ }
|
|
|
+ else if (pivot == high)
|
|
|
+ {
|
|
|
+ if (pivot == long.MinValue)
|
|
|
+ {
|
|
|
+ return TimeError.TimeNotFound;
|
|
|
+ }
|
|
|
+
|
|
|
+ pivot -= 1;
|
|
|
+ high -= 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (low > high)
|
|
|
+ {
|
|
|
+ return TimeError.TimeNotFound;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (direction > 0)
|
|
|
+ {
|
|
|
+ high = pivot;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ low = pivot;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal static int ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
|
|
|
+ {
|
|
|
+ int result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
|
|
|
+
|
|
|
+ calendar = new CalendarInfo()
|
|
|
+ {
|
|
|
+ Time = new CalendarTime()
|
|
|
+ {
|
|
|
+ Year = (short)calendarTime.Year,
|
|
|
+ Month = calendarTime.Month,
|
|
|
+ Day = calendarTime.Day,
|
|
|
+ Hour = calendarTime.Hour,
|
|
|
+ Minute = calendarTime.Minute,
|
|
|
+ Second = calendarTime.Second
|
|
|
+ },
|
|
|
+ AdditionalInfo = calendarAdditionalInfo
|
|
|
+ };
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal static int ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
|
|
|
+ {
|
|
|
+ CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
|
|
|
+ {
|
|
|
+ Year = calendarTime.Year,
|
|
|
+ Month = calendarTime.Month,
|
|
|
+ Day = calendarTime.Day,
|
|
|
+ Hour = calendarTime.Hour,
|
|
|
+ Minute = calendarTime.Minute,
|
|
|
+ Second = calendarTime.Second
|
|
|
+ };
|
|
|
+
|
|
|
+ return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|