Ryujinx/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
Ac_K 5d08e9b495
hos: Cleanup the project (#2634)
* hos: Cleanup the project

Since a lot of changes has been done on the HOS project, there are some leftover here and there, or class just used in one service, things at wrong places, and more.
This PR fixes that, additionnally to that, I've realigned some vars because I though it make the code more readable.

* Address gdkchan feedback

* addresses Thog feedback

* Revert ElfSymbol
2021-09-15 01:24:49 +02:00

1739 lines
54 KiB
C#

using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Utilities;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.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;
while ((c = name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
{
i++;
}
return i;
}
private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max)
{
num = 0;
if (namePosition >= name.Length)
{
return false;
}
char c = name[namePosition];
if (!char.IsDigit(c))
{
return false;
}
do
{
num = num * 10 + (c - '0');
if (num > max)
{
return false;
}
if (++namePosition >= name.Length)
{
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;
bool isValid = GetNum(name, ref namePosition, out int num, 0, HoursPerDays * DaysPerWekk - 1);
if (!isValid)
{
return false;
}
seconds = num * SecondsPerHour;
if (namePosition >= name.Length)
{
return false;
}
if (name[namePosition] == ':')
{
namePosition++;
isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1);
if (!isValid)
{
return false;
}
seconds += num * SecondsPerMinute;
if (namePosition >= name.Length)
{
return false;
}
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 (namePosition >= name.Length)
{
return false;
}
if (name[namePosition] == '-')
{
isNegative = true;
namePosition++;
}
else if (name[namePosition] == '+')
{
namePosition++;
}
if (namePosition >= name.Length)
{
return false;
}
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 ParseTimeZoneBinary(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 ResultCode 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 ResultCode.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 ResultCode.OutOfRange;
}
dayOfYear += YearLengths[IsLeap((int)year)];
}
while (dayOfYear >= YearLengths[IsLeap((int)year)])
{
dayOfYear -= YearLengths[IsLeap((int)year)];
if (IncrementOverflow64(ref year, 1))
{
return ResultCode.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)];
for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month)
{
dayOfYear -= ip[calendarTime.Month];
}
calendarTime.Day = (sbyte)(dayOfYear + 1);
calendarAdditionalInfo.IsDaySavingTime = false;
calendarAdditionalInfo.GmtOffset = gmtOffset;
return 0;
}
private static ResultCode ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
{
calendarTime = new CalendarTimeInternal();
calendarAdditionalInfo = new CalendarAdditionalInfo()
{
TimezoneName = new char[8]
};
ResultCode 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 ResultCode.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 ResultCode.Success;
}
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 ResultCode 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 ResultCode.Overflow;
}
calendarTime.Minute = (sbyte)minute;
int day = calendarTime.Day;
if (NormalizeOverflow32(ref day, ref hour, HoursPerDays))
{
return ResultCode.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 ResultCode.Overflow;
}
calendarTime.Month = (sbyte)month;
if (IncrementOverflow64(ref year, YearBase))
{
return ResultCode.Overflow;
}
while (day <= 0)
{
if (IncrementOverflow64(ref year, -1))
{
return ResultCode.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 ResultCode.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 ResultCode.Overflow;
}
}
}
calendarTime.Day = (sbyte)day;
if (IncrementOverflow64(ref year, -YearBase))
{
return ResultCode.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 ResultCode.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;
ResultCode 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 ResultCode.Overflow;
}
posixTime = timeResult;
break;
}
else
{
if (pivot == low)
{
if (pivot == long.MaxValue)
{
return ResultCode.TimeNotFound;
}
pivot += 1;
low += 1;
}
else if (pivot == high)
{
if (pivot == long.MinValue)
{
return ResultCode.TimeNotFound;
}
pivot -= 1;
high -= 1;
}
if (low > high)
{
return ResultCode.TimeNotFound;
}
if (direction > 0)
{
high = pivot;
}
else
{
low = pivot;
}
}
}
return ResultCode.Success;
}
internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
{
ResultCode result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
calendar = new CalendarInfo()
{
Time = new CalendarTime()
{
Year = (short)calendarTime.Year,
// NOTE: Nintendo's month range is 1-12, internal range is 0-11.
Month = (sbyte)(calendarTime.Month + 1),
Day = calendarTime.Day,
Hour = calendarTime.Hour,
Minute = calendarTime.Minute,
Second = calendarTime.Second
},
AdditionalInfo = calendarAdditionalInfo
};
return result;
}
internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
{
CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
{
Year = calendarTime.Year,
// NOTE: Nintendo's month range is 1-12, internal range is 0-11.
Month = (sbyte)(calendarTime.Month - 1),
Day = calendarTime.Day,
Hour = calendarTime.Hour,
Minute = calendarTime.Minute,
Second = calendarTime.Second
};
return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime);
}
}
}