51fa1b2cb0
* timezone: Make timezone implementation safe * hle: Do not use TrimEnd to parse ASCII strings This adds an util that handle reading an ASCII string in a safe way. Previously it was possible to read malformed data that could cause various undefined behaviours in multiple services. * hid: Remove an useless unsafe modifier on keyboard update * Address gdkchan's comment * Address gdkchan's comment
1716 lines
No EOL
53 KiB
C#
1716 lines
No EOL
53 KiB
C#
using Ryujinx.Common;
|
|
using Ryujinx.Common.Utilities;
|
|
using Ryujinx.HLE.Utilities;
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
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(ReadOnlySpan<byte> bytes)
|
|
{
|
|
return BinaryPrimitives.ReadInt32BigEndian(bytes);
|
|
}
|
|
|
|
private static int Detzcode32(int value)
|
|
{
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReverseEndianness(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static long Detzcode64(ReadOnlySpan<byte> bytes)
|
|
{
|
|
return BinaryPrimitives.ReadInt64BigEndian(bytes);
|
|
}
|
|
|
|
private static bool DifferByRepeat(long t1, long t0)
|
|
{
|
|
return (t1 - t0) == SecondsPerRepeat;
|
|
}
|
|
|
|
private static 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];
|
|
|
|
return a.GmtOffset == b.GmtOffset &&
|
|
a.IsDaySavingTime == b.IsDaySavingTime &&
|
|
a.IsStandardTimeDaylight == b.IsStandardTimeDaylight &&
|
|
a.IsGMT == b.IsGMT &&
|
|
StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
|
|
}
|
|
|
|
private static int GetQZName(ReadOnlySpan<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(ReadOnlySpan<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;
|
|
|
|
ReadOnlySpan<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, 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;
|
|
|
|
ReadOnlySpan<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 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;
|
|
|
|
{
|
|
Span<byte> p = workBuffer;
|
|
for (int i = 0; i < outRules.TimeCount; i++)
|
|
{
|
|
long at = Detzcode64(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 = p[TimeTypeSize..];
|
|
}
|
|
|
|
timeCount = 0;
|
|
for (int i = 0; i < outRules.TimeCount; i++)
|
|
{
|
|
byte type = p[0];
|
|
p = p[1..];
|
|
|
|
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(p);
|
|
p = p[sizeof(int)..];
|
|
|
|
if (p[0] >= 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ttis.IsDaySavingTime = p[0] != 0;
|
|
p = p[1..];
|
|
|
|
int abbreviationListIndex = p[0];
|
|
p = p[1..];
|
|
|
|
if (abbreviationListIndex >= outRules.CharCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ttis.AbbreviationListIndex = abbreviationListIndex;
|
|
|
|
outRules.Ttis[i] = ttis;
|
|
}
|
|
|
|
Encoding.ASCII.GetChars(p[..outRules.CharCount].ToArray()).CopyTo(outRules.Chars.AsSpan());
|
|
|
|
p = 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[0] >= 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
outRules.Ttis[i].IsStandardTimeDaylight = p[0] != 0;
|
|
p = p[1..];
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < outRules.TypeCount; i++)
|
|
{
|
|
if (ttisSTDCount == 0)
|
|
{
|
|
outRules.Ttis[i].IsGMT = false;
|
|
}
|
|
else
|
|
{
|
|
if (p[0] >= 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
outRules.Ttis[i].IsGMT = p[0] != 0;
|
|
p = p[1..];
|
|
}
|
|
|
|
}
|
|
|
|
long position = (workBuffer.Length - p.Length);
|
|
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;
|
|
|
|
Span<char> chars = outRules.Chars;
|
|
|
|
for (int i = 0; i < tempRules.TypeCount; i++)
|
|
{
|
|
ReadOnlySpan<char> tempChars = tempRules.Chars;
|
|
ReadOnlySpan<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;
|
|
|
|
ReadOnlySpan<char> timeZoneAbbreviation = rules.Chars.AsSpan()[rules.Ttis[ttiIndex].AbbreviationListIndex..];
|
|
|
|
int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
|
|
|
|
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan());
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |