577 lines
18 KiB
C#
577 lines
18 KiB
C#
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace CPF.Windows.Json.Deserialize
|
|
{
|
|
internal class TimeSpanResolve : DefaultJsonResolve
|
|
{
|
|
[FuncLable(FuncType.SameType)]
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
internal static unsafe TimeSpan ReadTimeSpan( JsonReader reader, JsonDeserializeHandler handler)
|
|
{
|
|
/*
|
|
\"P10675199DT2H48M5.4775807S\" => ISO
|
|
\"10675199.02:48:05.4775807\" => Microsoft
|
|
*/
|
|
reader.ReadQuotes();
|
|
char* ip = reader.Pointer;
|
|
char c = ip[0];
|
|
if (c == '-')
|
|
c = ip[1];
|
|
|
|
if (c == 'P')
|
|
return _ReadISO8601TimeSpan(reader);
|
|
else
|
|
return ReadMicrosoftTimeSpan(reader);
|
|
}
|
|
|
|
static readonly ulong MinTicks = (ulong)-TimeSpan.MinValue.Ticks;
|
|
static readonly ulong MaxTicks = (ulong)TimeSpan.MaxValue.Ticks;
|
|
|
|
static readonly double[] DivideFractionBy =
|
|
{
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000
|
|
};
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static unsafe TimeSpan ReadMicrosoftTimeSpan(JsonReader reader)
|
|
{
|
|
char* ip = reader.Pointer;
|
|
int start = reader.Remaining;
|
|
int strLen = -1;
|
|
for (int z = 0; z < reader.Remaining; z++)
|
|
{
|
|
if (ip[z] == '"')
|
|
{
|
|
strLen = z;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strLen < 1)
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan));
|
|
|
|
reader.Pointer += strLen + 1;// "
|
|
reader.Remaining -= strLen + 1;// "
|
|
|
|
int days, hours, minutes, seconds, fraction;
|
|
days = hours = minutes = seconds = fraction = 0;
|
|
|
|
bool isNegative, pastDays, pastHours, pastMinutes, pastSeconds;
|
|
isNegative = pastDays = pastHours = pastMinutes = pastSeconds = false;
|
|
|
|
var ixOfLastPeriod = -1;
|
|
var part = 0;
|
|
|
|
int i;
|
|
|
|
if (ip[0] == '-')
|
|
{
|
|
isNegative = true;
|
|
i = 1;
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
}
|
|
|
|
for (; i < strLen; i++)
|
|
{
|
|
var c = ip[i];
|
|
if (c == '.')
|
|
{
|
|
ixOfLastPeriod = i;
|
|
|
|
if (!pastDays)
|
|
{
|
|
days = part;
|
|
part = 0;
|
|
pastDays = true;
|
|
continue;
|
|
}
|
|
|
|
if (!pastSeconds)
|
|
{
|
|
seconds = part;
|
|
part = 0;
|
|
pastSeconds = true;
|
|
continue;
|
|
}
|
|
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan));
|
|
}
|
|
|
|
if (c == ':')
|
|
{
|
|
if (!pastHours)
|
|
{
|
|
hours = part;
|
|
part = 0;
|
|
pastHours = true;
|
|
continue;
|
|
}
|
|
|
|
if (!pastMinutes)
|
|
{
|
|
minutes = part;
|
|
part = 0;
|
|
pastMinutes = true;
|
|
continue;
|
|
}
|
|
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan));
|
|
}
|
|
|
|
if (c < '0' || c > '9')
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan));
|
|
}
|
|
|
|
part *= 10;
|
|
part += c - '0';
|
|
}
|
|
|
|
if (!pastSeconds)
|
|
{
|
|
seconds = part;
|
|
pastSeconds = true;
|
|
}
|
|
else
|
|
{
|
|
fraction = part;
|
|
}
|
|
|
|
if (!pastHours || !pastMinutes || !pastSeconds)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Missing required portion of TimeSpan");
|
|
}
|
|
|
|
var msInt = 0;
|
|
if (fraction != 0)
|
|
{
|
|
var sizeOfFraction = strLen - (ixOfLastPeriod + 1);
|
|
|
|
if (sizeOfFraction > 7)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Fractional part of TimeSpan too large");
|
|
}
|
|
|
|
var fracOfSecond = part / DivideFractionBy[sizeOfFraction - 1];
|
|
var ms = fracOfSecond * 1000.0;
|
|
msInt = (int)ms;
|
|
if (ms > msInt)
|
|
return TimeSpan.Parse(reader.SubString(reader.Length - start, strLen));
|
|
}
|
|
|
|
var ret = new TimeSpan(days, hours, minutes, seconds, msInt);
|
|
if (isNegative)
|
|
{
|
|
ret = ret.Negate();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static unsafe TimeSpan _ReadISO8601TimeSpan(JsonReader reader)
|
|
{
|
|
const ulong ticksPerDay = 864000000000;
|
|
|
|
const ulong ticksPerWeek = ticksPerDay * 7;
|
|
const ulong ticksPerMonth = ticksPerDay * 30;
|
|
const ulong ticksPerYear = ticksPerDay * 365;
|
|
|
|
// Format goes like so:
|
|
// - (-)P(([n]Y)([n]M)([n]D))(T([n]H)([n]M)([n]S))
|
|
// - P[n]W
|
|
char* ip = reader.Pointer;
|
|
int len = -1;
|
|
for (int i = 0; i < reader.Remaining; i++)
|
|
{
|
|
if (ip[i] == '"')
|
|
{
|
|
len = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (len < 1)
|
|
throw new JsonWrongCharacterException(reader);
|
|
|
|
reader.Pointer += len + 1;
|
|
reader.Remaining -= len + 1;
|
|
|
|
var ix = 0;
|
|
var isNegative = false;
|
|
|
|
var c = ip[ix];
|
|
if (c == '-')
|
|
{
|
|
isNegative = true;
|
|
ix++;
|
|
}
|
|
|
|
if (ix >= len)
|
|
{
|
|
throw new JsonWrongCharacterException("Expected P, instead TimeSpan string ended");
|
|
}
|
|
|
|
c = ip[ix];
|
|
if (c != 'P')
|
|
{
|
|
throw new JsonWrongCharacterException("Expected P, found " + c.ToString());
|
|
}
|
|
|
|
ix++; // skip 'P'
|
|
|
|
var hasTimePart = ISO8601TimeSpan_ReadDatePart(ip, len, ref ix, out long year, out long month, out long week, out long day);
|
|
|
|
if (week != -1 && (year != -1 || month != -1 || day != -1))
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Week part of TimeSpan defined along with one or more of year, month, or day");
|
|
}
|
|
|
|
if (week != -1 && hasTimePart)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "TimeSpans with a week defined cannot also have a time defined");
|
|
}
|
|
|
|
if (year == -1) year = 0;
|
|
if (month == -1) month = 0;
|
|
if (week == -1) week = 0;
|
|
if (day == -1) day = 0;
|
|
|
|
ulong timeTicks;
|
|
|
|
if (hasTimePart)
|
|
{
|
|
ix++; // skip 'T'
|
|
ISO8601TimeSpan_ReadTimePart(ip, len, ref ix, out timeTicks);
|
|
}
|
|
else
|
|
{
|
|
timeTicks = 0;
|
|
}
|
|
|
|
ulong ticks = 0;
|
|
if (year != 0)
|
|
{
|
|
ticks += ((ulong)year) * ticksPerYear;
|
|
}
|
|
|
|
if (month != 0)
|
|
{
|
|
// .NET (via XmlConvert) converts months to years
|
|
// This isn't inkeeping with the spec, but of the bad choices... I choose this one
|
|
var yearsFromMonths = ((ulong)month) / 12;
|
|
var monthsAfterYears = ((ulong)month) % 12;
|
|
ticks += yearsFromMonths * ticksPerYear + monthsAfterYears * ticksPerMonth;
|
|
}
|
|
|
|
if (week != 0)
|
|
{
|
|
// ISO8601 defines weeks as 7 days, so don't convert weeks to months or years (even if that may seem more sensible)
|
|
ticks += ((ulong)week) * ticksPerWeek;
|
|
}
|
|
|
|
ticks += ((ulong)day) * ticksPerDay + timeTicks;
|
|
|
|
if (ticks >= MaxTicks && !isNegative)
|
|
{
|
|
return TimeSpan.MaxValue;
|
|
}
|
|
|
|
if (ticks >= MinTicks && isNegative)
|
|
{
|
|
return TimeSpan.MinValue;
|
|
}
|
|
|
|
var ret = new TimeSpan((long)ticks);
|
|
if (isNegative)
|
|
{
|
|
ret = ret.Negate();
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
}
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static unsafe bool ISO8601TimeSpan_ReadDatePart(char* ip, int strLen, ref int ix, out long year, out long month, out long week, out long day)
|
|
{
|
|
year = month = week = day = -1;
|
|
|
|
bool yearSeen, monthSeen, weekSeen, daySeen;
|
|
yearSeen = monthSeen = weekSeen = daySeen = false;
|
|
|
|
while (ix != strLen)
|
|
{
|
|
if (ip[ix] == 'T')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var part = ISO8601TimeSpan_ReadPart(ip, strLen, ref ix, out int whole, out int fraction, out int fracLen);
|
|
|
|
if (fracLen != 0)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Fractional values are not supported in the year, month, day, or week parts of an ISO8601 TimeSpan");
|
|
}
|
|
|
|
if (part == 'Y')
|
|
{
|
|
if (yearSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Year part of TimeSpan seen twice");
|
|
}
|
|
|
|
if (monthSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Year part of TimeSpan seen after month already parsed");
|
|
}
|
|
|
|
if (daySeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Year part of TimeSpan seen after day already parsed");
|
|
}
|
|
|
|
year = whole;
|
|
yearSeen = true;
|
|
continue;
|
|
}
|
|
|
|
if (part == 'M')
|
|
{
|
|
if (monthSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Month part of TimeSpan seen twice");
|
|
}
|
|
|
|
if (daySeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Month part of TimeSpan seen after day already parsed");
|
|
}
|
|
|
|
month = whole;
|
|
monthSeen = true;
|
|
continue;
|
|
}
|
|
|
|
if (part == 'W')
|
|
{
|
|
if (weekSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Week part of TimeSpan seen twice");
|
|
}
|
|
|
|
week = whole;
|
|
weekSeen = true;
|
|
continue;
|
|
}
|
|
|
|
if (part == 'D')
|
|
{
|
|
if (daySeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Day part of TimeSpan seen twice");
|
|
}
|
|
|
|
day = whole;
|
|
daySeen = true;
|
|
continue;
|
|
}
|
|
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Expected Y, M, W, or D but found: " + part);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static unsafe char ISO8601TimeSpan_ReadPart(char* ip, int strLen, ref int ix, out int whole, out int fraction, out int fracLen)
|
|
{
|
|
var part = 0;
|
|
while (true)
|
|
{
|
|
var c = ip[ix];
|
|
|
|
if (c == '.' || c == ',')
|
|
{
|
|
whole = part;
|
|
break;
|
|
}
|
|
|
|
ix++;
|
|
if (c < '0' || c > '9' || ix == strLen)
|
|
{
|
|
whole = part;
|
|
fraction = 0;
|
|
fracLen = 0;
|
|
return c;
|
|
}
|
|
|
|
part *= 10;
|
|
part += c - '0';
|
|
}
|
|
|
|
var ixOfPeriod = ix;
|
|
|
|
ix++; // skip the '.' or ','
|
|
part = 0;
|
|
while (true)
|
|
{
|
|
var c = ip[ix];
|
|
|
|
ix++;
|
|
if (c < '0' || c > '9' || ix == strLen)
|
|
{
|
|
fraction = part;
|
|
fracLen = ix - 1 - (ixOfPeriod + 1);
|
|
return c;
|
|
}
|
|
|
|
part *= 10;
|
|
part += c - '0';
|
|
}
|
|
}
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static unsafe void ISO8601TimeSpan_ReadTimePart(char* str, int strLen, ref int ix, out ulong ticks)
|
|
{
|
|
const ulong ticksPerHour = 36000000000;
|
|
const ulong ticksPerMinute = 600000000;
|
|
const ulong ticksPerSecond = 10000000;
|
|
|
|
ticks = 0;
|
|
|
|
bool hourSeen, minutesSeen, secondsSeen;
|
|
hourSeen = minutesSeen = secondsSeen = false;
|
|
|
|
var fracSeen = false;
|
|
|
|
while (ix != strLen)
|
|
{
|
|
if (fracSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Expected Time part of TimeSpan to end");
|
|
}
|
|
|
|
char part = ISO8601TimeSpan_ReadPart(str, strLen, ref ix, out int whole, out int fraction, out int fracLen);
|
|
|
|
if (fracLen != 0)
|
|
{
|
|
fracSeen = true;
|
|
}
|
|
|
|
if (part == 'H')
|
|
{
|
|
if (hourSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Hour part of TimeSpan seen twice");
|
|
}
|
|
|
|
if (minutesSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Hour part of TimeSpan seen after minutes already parsed");
|
|
}
|
|
|
|
if (secondsSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Hour part of TimeSpan seen after seconds already parsed");
|
|
}
|
|
|
|
ticks += (ulong)whole * ticksPerHour + ISO8601TimeSpan_FractionToTicks(9, fraction * 36, fracLen);
|
|
hourSeen = true;
|
|
continue;
|
|
}
|
|
|
|
if (part == 'M')
|
|
{
|
|
if (minutesSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Minute part of TimeSpan seen twice");
|
|
}
|
|
|
|
if (secondsSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Minute part of TimeSpan seen after seconds already parsed");
|
|
}
|
|
|
|
ticks += (ulong)whole * ticksPerMinute + ISO8601TimeSpan_FractionToTicks(8, fraction * 6, fracLen);
|
|
minutesSeen = true;
|
|
continue;
|
|
}
|
|
|
|
if (part == 'S')
|
|
{
|
|
if (secondsSeen)
|
|
{
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Seconds part of TimeSpan seen twice");
|
|
}
|
|
|
|
ticks += (ulong)whole * ticksPerSecond + ISO8601TimeSpan_FractionToTicks(7, fraction, fracLen);
|
|
secondsSeen = true;
|
|
continue;
|
|
}
|
|
|
|
throw new JsonDeserializationTypeResolutionException(typeof(TimeSpan), "Expected H, M, or S but found: " + part);
|
|
}
|
|
}
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
static ulong ISO8601TimeSpan_FractionToTicks(int maxLen, int fraction, int fracLen)
|
|
{
|
|
if (fracLen == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (fracLen > maxLen)
|
|
{
|
|
fraction /= (int)Pow10(fracLen - maxLen);
|
|
fracLen = maxLen;
|
|
}
|
|
|
|
return (ulong)(fraction * Pow10(maxLen - fracLen));
|
|
}
|
|
static readonly long[] PowersOf10 = {
|
|
1L,
|
|
10L,
|
|
100L,
|
|
1000L,
|
|
10000L,
|
|
100000L,
|
|
1000000L,
|
|
10000000L,
|
|
100000000L
|
|
};
|
|
|
|
#if !Net4
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
internal static long Pow10(int power)
|
|
{
|
|
if (power < PowersOf10.Length)
|
|
return PowersOf10[power];
|
|
return (long)Math.Pow(10, power);
|
|
}
|
|
}
|
|
}
|