2023-11-21 23:05:03 +08:00

1252 lines
44 KiB
C#

using System;
using System.Runtime.CompilerServices;
namespace CPF.Windows.Json.Deserialize
{
internal class DateTimeResolve : DefaultJsonResolve
{
[FuncLable(FuncType.SameType)]
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal unsafe static DateTime ReadDateTime(JsonReader reader, JsonDeserializeHandler handler)
{
reader.ReadQuotes();
char* ip = reader.Pointer;
char c = ip[0];
if (c >= '0' && c <= '9')
{
//https://tools.ietf.org/html/rfc3339
//http://en.wikipedia.org/wiki/ISO_8601
//\"2019-02-11T18:42:04.0068385Z\"
//\"2019-02-11T18:42:04.0068385+08:00\"
return ReadISO8601Date(reader);
}
else if (c == '\\')
{
// "\/Date(628318530718)\/"
return ReadMicrosoftDate(reader, handler);
}
else
{
//Mon, 11 Feb 2019 18:39:32 GMT
return ReadRFC1123DateTime(reader);
}
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe DateTime ReadMicrosoftDate(JsonReader reader, JsonDeserializeHandler handler)
{
if (!reader.StrCompair("\\/Date("))
goto Throw;
long l = PrimitiveResolve.ReadLong(reader, handler);
DateTime dt = new DateTime(l * 10000L + 621355968000000000L);
if (reader.StrCompair(")\\/\""))
return dt;
Throw:
throw new JsonDeserializationTypeResolutionException( reader, typeof(DateTime), "Unresolvable Date");
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe DateTime ReadISO8601Date(JsonReader reader)
{
// ISO8601 / RFC3339 (the internet "profile"* of ISO8601) is a plague
// See: http://en.wikipedia.org/wiki/ISO_8601 &
// http://tools.ietf.org/html/rfc3339
// *is bullshit
// Here are the possible formats for dates
// YYYY-MM-DD
// YYYY-MM
// YYYY-DDD (ordinal date)
// YYYY-Www (week date, the W is a literal)
// YYYY-Www-D
// YYYYMMDD
// YYYYWww
// YYYYWwwD
// YYYYDDD
// Here are the possible formats for times
// hh
// hh:mm
// hhmm
// hh:mm:ss
// hhmmss
// hh,fff*
// hh:mm,fff*
// hhmm,fff*
// hh:mm:ss,fff*
// hhmmss,fff*
// hh.fff*
// hh:mm.fff*
// hhmm.fff*
// hh:mm:ss.fff*
// hhmmss.fff*
// * arbitrarily many (technically an "agreed upon" number, I'm agreeing on 7 because that's out to a Tick)
// Here are the possible formats for timezones
// Z
// +hh
// +hh:mm
// +hhmm
// -hh
// -hh:mm
// -hhmm
// they are concatenated to form a full instant, with T as a separator between date & time
// i.e. <date>T<time><timezone>
// the longest possible string:
// 9999-12-31T01:23:45.6789012+34:56
//
// Maximum date size is 33 characters
int leng = -1;
int? tPos = null;
int? zPlusOrMinus = null;
char c;
char* ip = reader.Pointer;
for (int i = 0; i < reader.Remaining; i++)
{
c = ip[i];
if (c == '"')
{
leng = i - 1;
break;
}
// RFC3339 allows lowercase t and spaces as alternatives to ISO8601's T
if (c == 'T' || c == 't' || c == ' ')
{
if (tPos.HasValue)
throw new JsonDeserializationTypeResolutionException( reader, typeof(DateTime), "Unexpected second T in ISO8601 date");
tPos = i - 1;
}
if (tPos.HasValue)
{
// RFC3339 allows lowercase z as alternatives to ISO8601's Z
if (c == 'Z' || c == 'z' || c == '+' || c == '-')
{
if (zPlusOrMinus.HasValue)
throw new JsonDeserializationTypeResolutionException( reader, typeof(DateTime), "Unexpected second Z, +, or - in ISO8601 date");
zPlusOrMinus = i - 1;
}
}
}
if (leng < 1)
throw new JsonWrongCharacterException( reader);
if (leng > 32)
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date is too long");
reader.Pointer += leng + 2;//1 => idx ,1 => " ==2
reader.Remaining -= leng + 2;
var date = ParseISO8601Date(ip, 0, tPos ?? leng); // this is in *LOCAL TIME* because that's what the spec says
if (!tPos.HasValue)
{
return date;
}
var time = ParseISO8601Time(ip, tPos.Value + 2, zPlusOrMinus ?? leng);
if (!zPlusOrMinus.HasValue)
{
try
{
return date + time;
}
catch
{
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date with time could not be represented as a DateTime");
}
}
// only +1 here because the separator is significant (oy vey)
var timezoneOffset = ParseISO8601TimeZoneOffset(ip, zPlusOrMinus.Value + 1, leng, out bool unknownLocalOffset);
try
{
if (unknownLocalOffset)
{
return DateTime.SpecifyKind(date, DateTimeKind.Unspecified) + time;
}
return DateTime.SpecifyKind(date, DateTimeKind.Utc) + time - timezoneOffset;
}
catch
{
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date with time and timezone offset could not be represented as a DateTime");
}
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe DateTime ParseISO8601Date(char* buffer, int start, int stop)
{
// Here are the possible formats for dates
// YYYY-MM-DD
// YYYY-MM
// YYYY-DDD (ordinal date)
// YYYY-Www (week date, the W is a literal)
// YYYY-Www-D
// YYYYMMDD
// YYYYWww
// YYYYWwwD
// YYYYDDD
bool? hasSeparators = null;
var len = stop - start + 1;
if (len < 4)
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date must begin with a 4 character year");
var year = 0;
var month = 0;
var day = 0;
int c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
year += c - '0';
year *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
year += c - '0';
year *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
year += c - '0';
year *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c); ;
year += c - '0';
if (year == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 year 0000 cannot be converted to a DateTime");
// we've reached the end
if (start == stop)
{
hasSeparators = null;
// year is [1,9999] for sure, no need to handle errors
return new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Local);
}
start++;
hasSeparators = buffer[start] == '-';
var isWeekDate = buffer[start] == 'W';
if (hasSeparators.Value && start != stop)
{
isWeekDate = buffer[start + 1] == 'W';
if (isWeekDate)
{
start++;
}
}
if (isWeekDate)
{
start++; // skip the W
var week = 0;
if (hasSeparators.Value)
{
// Could still be
// YYYY-Www length: 8
// YYYY-Www-D length: 10
switch (len)
{
case 8:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
week *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
if (week == 0 || week > 53) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected week to be between 01 and 53");
return ConvertWeekDateToDateTime(year, week, 1);
case 10:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
week *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
if (week == 0 || week > 53) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected week to be between 01 and 53");
start++;
c = buffer[start];
if (c != '-') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
start++;
c = buffer[start];
if (c < '1' || c > '7') throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected day to be a digit between 1 and 7");
day = c - '0';
return ConvertWeekDateToDateTime(year, week, day);
default:
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected date string length");
}
}
else
{
// Could still be
// YYYYWww length: 7
// YYYYWwwD length: 8
switch (len)
{
case 7:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
week *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
if (week == 0 || week > 53) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), " Expected week to be between 01 and 53");
return ConvertWeekDateToDateTime(year, week, 1);
case 8:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
week *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
week += c - '0';
if (week == 0 || week > 53) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected week to be between 01 and 53");
start++;
c = buffer[start];
if (c < '1' || c > '7') throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected day to be a digit between 1 and 7");
day = c - '0';
return ConvertWeekDateToDateTime(year, week, day);
default:
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected date string length");
}
}
}
if (hasSeparators.Value)
{
start++;
// Could still be:
// YYYY-MM length: 7
// YYYY-DDD length: 8
// YYYY-MM-DD length: 10
switch (len)
{
case 7:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
month *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
if (month == 0 || month > 12) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected month to be between 01 and 12");
// year is [1,9999] and month is [1,12] for sure, no need to handle errors
return new DateTime(year, month, 1, 0, 0, 0, DateTimeKind.Local);
case 8:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
if (day == 0 || day > 366) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected ordinal day to be between 001 and 366");
if (day == 366)
{
var isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
if (!isLeapYear) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Ordinal day can only be 366 in a leap year");
}
// year is [1,9999] and day is [1,366], no need to handle errors
return new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Local).AddDays(day - 1);
case 10:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
month *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
if (month == 0 || month > 12) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected month to be between 01 and 12");
start++;
if (buffer[start] != '-') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
if (day == 0 || day > 31) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected day to be between 01 and 31");
start++;
try
{
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Local);
}
catch
{
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date could not be mapped to DateTime");
}
default:
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected date string length");
}
}
// Could still be
// YYYYDDD length: 7
// YYYYMMDD length: 8
switch (len)
{
case 7:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
if (day == 0 || day > 366) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected ordinal day to be between 001 and 366");
start++;
if (day == 366)
{
var isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
if (!isLeapYear) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Ordinal day can only be 366 in a leap year");
}
// year is [1,9999] and day is [1,366], no need to handle errors
return new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Local).AddDays(day - 1);
case 8:
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
month *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
month += c - '0';
if (month == 0 || month > 12) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected month to be between 01 and 12");
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
day *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
day += c - '0';
if (day == 0 || day > 31) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected day to be between 01 and 31");
start++;
try
{
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Local);
}
catch
{
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 date could not be mapped to DateTime");
}
default:
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected date string length");
}
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe TimeSpan ParseISO8601Time(char* buffer, int start, int stop)
{
const long hoursToTicks = 36000000000;
const long minutesToTicks = 600000000;
const long secondsToTicks = 10000000;
// Here are the possible formats for times
// hh
// hh,fff
// hh.fff
//
// hhmmss
// hhmm
// hhmm,fff
// hhmm.fff
// hhmmss.fff
// hhmmss,fff
// hh:mm
// hh:mm:ss
// hh:mm,fff
// hh:mm:ss,fff
// hh:mm.fff
// hh:mm:ss.fff
bool? hasSeparators = null;
var len = stop - start + 1;
if (len < 2) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "ISO8601 time must begin with a 2 character hour");
var hour = 0;
int c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
hour += c - '0';
hour *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
hour += c - '0';
if (hour > 24) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected hour to be between 00 and 24");
// just an hour part
if (start == stop)
{
return TimeSpan.FromHours(hour);
}
start++;
c = buffer[start];
// hour with a fractional part
if (c == ',' || c == '.')
{
start++;
var frac = 0;
var fracLength = 0;
while (start <= stop)
{
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
// Max precision of TimeSpan.FromTicks
if (fracLength < 9)
{
frac *= 10;
frac += c - '0';
fracLength++;
}
start++;
}
if (fracLength == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected fractional part of ISO8601 time");
long hoursAsTicks = hour * hoursToTicks;
hoursAsTicks += frac * 36 * TimeSpanResolve.Pow10(9 - fracLength);
return TimeSpan.FromTicks(hoursAsTicks);
}
if (c == ':')
{
if (hasSeparators.HasValue && !hasSeparators.Value) throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
hasSeparators = true;
start++;
}
else
{
if (hasSeparators.HasValue && hasSeparators.Value) throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
hasSeparators = false;
}
if (hasSeparators.Value)
{
// Could still be
// hh:mm
// hh:mm:ss
// hh:mm,fff
// hh:mm:ss,fff
// hh:mm.fff
// hh:mm:ss.fff
if (len < 4) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected minute part of ISO8601 time");
var min = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
min += c - '0';
min *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
min += c - '0';
if (min > 59) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected minute to be between 00 and 59");
// just HOUR and MINUTE part
if (start == stop)
{
return new TimeSpan(hour, min, 0);
}
start++;
c = buffer[start];
// HOUR, MINUTE, and FRACTION
if (c == ',' || c == '.')
{
start++;
var frac = 0;
var fracLength = 0;
while (start <= stop)
{
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
// Max precision of TimeSpan.FromTicks
if (fracLength < 8)
{
frac *= 10;
frac += c - '0';
fracLength++;
}
start++;
}
if (fracLength == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected fractional part of ISO8601 time");
long hoursAsTicks = hour * hoursToTicks;
long minsAsTicks = min * minutesToTicks;
minsAsTicks += frac * 6 * TimeSpanResolve.Pow10(8 - fracLength);
return TimeSpan.FromTicks(hoursAsTicks + minsAsTicks);
}
if (c != ':') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
start++;
var secs = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
secs += c - '0';
secs *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
secs += c - '0';
// HOUR, MINUTE, and SECONDS
if (start == stop)
{
return new TimeSpan(hour, min, secs);
}
start++;
c = buffer[start];
if (c == ',' || c == '.')
{
start++;
var frac = 0;
var fracLength = 0;
while (start <= stop)
{
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
// Max precision of TimeSpan.FromTicks
if (fracLength < 7)
{
frac *= 10;
frac += c - '0';
fracLength++;
}
start++;
}
if (fracLength == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected fractional part of ISO8601 time");
long hoursAsTicks = hour * hoursToTicks;
long minsAsTicks = min * minutesToTicks;
long secsAsTicks = secs * secondsToTicks;
secsAsTicks += frac * TimeSpanResolve.Pow10(7 - fracLength);
return TimeSpan.FromTicks(hoursAsTicks + minsAsTicks + secsAsTicks);
}
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected ,, or .");
}
else
{
// Could still be
// hhmmss
// hhmm
// hhmm,fff
// hhmm.fff
// hhmmss.fff
// hhmmss,fff
if (len < 4) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected minute part of ISO8601 time");
var min = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
min += c - '0';
min *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
min += c - '0';
if (min > 59) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected minute to be between 00 and 59");
// just HOUR and MINUTE part
if (start == stop)
{
return new TimeSpan(hour, min, 0);
}
start++;
c = buffer[start];
// HOUR, MINUTE, and FRACTION
if (c == ',' || c == '.')
{
start++;
var frac = 0;
var fracLength = 0;
while (start <= stop)
{
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
// Max precision of TimeSpan.FromTicks
if (fracLength < 8)
{
frac *= 10;
frac += c - '0';
fracLength++;
}
start++;
}
if (fracLength == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected fractional part of ISO8601 time");
long hoursAsTicks = hour * hoursToTicks;
long minsAsTicks = min * minutesToTicks;
minsAsTicks += frac * 6 * TimeSpanResolve.Pow10(8 - fracLength);
return TimeSpan.FromTicks(hoursAsTicks + minsAsTicks);
}
if (c == ':') throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected separator in ISO8601 time");
var secs = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
secs += c - '0';
secs *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
secs += c - '0';
// HOUR, MINUTE, and SECONDS
if (start == stop)
{
return new TimeSpan(hour, min, secs);
}
start++;
c = buffer[start];
if (c == ',' || c == '.')
{
start++;
var frac = 0;
var fracLength = 0;
while (start <= stop)
{
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
// Max precision of TimeSpan.FromTicks
if (fracLength < 7)
{
frac *= 10;
frac += c - '0';
fracLength++;
}
start++;
}
if (fracLength == 0) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected fractional part of ISO8601 time");
long hoursAsTicks = hour * hoursToTicks;
long minsAsTicks = min * minutesToTicks;
long secsAsTicks = secs * secondsToTicks;
secsAsTicks += frac * TimeSpanResolve.Pow10(7 - fracLength);
return TimeSpan.FromTicks(hoursAsTicks + minsAsTicks + secsAsTicks);
}
throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected ,, or .");
}
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe TimeSpan ParseISO8601TimeZoneOffset(char* buffer, int start, int stop, out bool unknownLocalOffset)
{
// Here are the possible formats for timezones
// Z
// +hh
// +hh:mm
// +hhmm
// -hh
// -hh:mm
// -hhmm
bool? hasSeparators = null;
int c = buffer[start];
// no need to validate, the caller has done that
if (c == 'Z' || c == 'z')
{
unknownLocalOffset = false;
return TimeSpan.Zero;
}
var isNegative = c == '-';
start++;
var len = stop - start + 1;
if (len < 2) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected hour part of ISO8601 timezone offset");
var hour = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
hour += c - '0';
hour *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
hour += c - '0';
if (hour > 24) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected hour offset to be between 00 and 24");
// just an HOUR offset
if (start == stop)
{
unknownLocalOffset = false;
if (isNegative)
{
return new TimeSpan(-hour, 0, 0);
}
return new TimeSpan(hour, 0, 0);
}
start++;
c = buffer[start];
if (c == ':')
{
if (hasSeparators.HasValue && !hasSeparators.Value) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Unexpected separator in ISO8601 timezone offset");
hasSeparators = true;
start++;
}
else
{
if (hasSeparators.HasValue && hasSeparators.Value) throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
hasSeparators = false;
}
if (stop - start + 1 < 2) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Not enough character for ISO8601 timezone offset");
var mins = 0;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
mins += c - '0';
mins *= 10;
start++;
c = buffer[start];
CheckCharFromZeroToNineInDateTime(c);
mins += c - '0';
if (mins > 59) throw new JsonDeserializationTypeResolutionException(typeof(DateTime), "Expected minute offset to be between 00 and 59");
if (isNegative)
{
// per Section 4.3 of of RFC3339 (http://tools.ietf.org/html/rfc3339)
// a timezone of "-00:00" is used to indicate an "Unknown Local Offset"
unknownLocalOffset = hour == 0 && mins == 0;
return new TimeSpan(-hour, -mins, 0);
}
unknownLocalOffset = false;
return new TimeSpan(hour, mins, 0);
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static DateTime ConvertWeekDateToDateTime(int year, int week, int day)
{
// January 4th will always be in week 1
var ret = new DateTime(year, 1, 4, 0, 0, 0, DateTimeKind.Utc);
if (week != 1)
{
ret += TimeSpan.FromDays(7 * (week - 1));
}
int currentDay;
switch (ret.DayOfWeek)
{
case DayOfWeek.Sunday: currentDay = 7; break;
case DayOfWeek.Monday: currentDay = 1; break;
case DayOfWeek.Tuesday: currentDay = 2; break;
case DayOfWeek.Wednesday: currentDay = 3; break;
case DayOfWeek.Thursday: currentDay = 4; break;
case DayOfWeek.Friday: currentDay = 5; break;
case DayOfWeek.Saturday: currentDay = 6; break;
default: throw new Exception("Unexpected DayOfWeek");
}
var offset = day - currentDay;
ret += TimeSpan.FromDays(offset);
return ret;
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static DateTime ReadRFC1123DateTime(JsonReader reader)
{
DayOfWeek dayOfWeek = ReadRFC1123DayOfWeek(reader);
if (!reader.StrCompair(", "))
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
int day = 0;
char c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
day += c - '0';
c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
day *= 10;
day += c - '0';
c = reader.GetChar();
if (c != ' ')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
byte month = ReadRFC1123Month(reader);
c = reader.GetChar();
if (c != ' ')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
var year = 0;
c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
year += c - '0';
c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
year *= 10;
year += c - '0';
c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
year *= 10;
year += c - '0';
c = reader.GetChar();
if (c < '0' || c > '9')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
year *= 10;
year += c - '0';
c = reader.GetChar();
if (c != ' ')
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
var hour = 0;
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
hour += c - '0';
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
hour *= 10;
hour += c - '0';
c = reader.GetChar();
if (c != ':') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
var min = 0;
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
min += c - '0';
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
min *= 10;
min += c - '0';
c = reader.GetChar();
if (c != ':') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
var sec = 0;
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
sec += c - '0';
c = reader.GetChar();
CheckCharFromZeroToNineInDateTime(c);
sec *= 10;
sec += c - '0';
if (!reader.StrCompair(" GMT\""))
throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
var ret = new DateTime(year, month, day, hour, min, sec, DateTimeKind.Utc);
return ret;
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private static DayOfWeek ReadRFC1123DayOfWeek(JsonReader reader)
{
char c = reader.GetChar();
// Mon
if (c == 'M')
{
if (reader.StrCompair("on"))
return DayOfWeek.Monday;
}
// Tue | Thu
if (c == 'T')
{
if (reader.StrCompair("ue"))
return DayOfWeek.Tuesday;
if (reader.StrCompair("hu"))
return DayOfWeek.Thursday;
}
// Wed
if (c == 'W')
{
if (reader.StrCompair("ed"))
return DayOfWeek.Wednesday;
}
// Fri
if (c == 'F')
{
if (reader.StrCompair("ri"))
return DayOfWeek.Friday;
}
// Sat | Sun
if (c == 'S')
{
if (reader.StrCompair("at"))
return DayOfWeek.Saturday;
if (reader.StrCompair("un"))
return DayOfWeek.Sunday;
}
throw new JsonWrongCharacterException(reader, "CPF.Windows.Json is not defined by the Datetime format");
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private static byte ReadRFC1123Month(JsonReader reader)
{
var c = reader.GetChar();
if (c == -1)
goto Throw;
// Jan | Jun | Jul
if (c == 'J')
{
c = reader.GetChar();
if (c == 'a')
{
c = reader.GetChar();
if (c != 'n') goto Throw;
return 1;
}
if (c != 'u') goto Throw;
c = reader.GetChar();
if (c == 'n') return 6;
if (c == 'l') return 7;
goto Throw;
}
// Feb
if (c == 'F')
{
c = reader.GetChar();
if (c != 'e') goto Throw;
c = reader.GetChar();
if (c != 'b') goto Throw;
return 2;
}
// Mar | May
if (c == 'M')
{
c = reader.GetChar();
if (c != 'a') goto Throw;
c = reader.GetChar();
if (c == 'r') return 3;
if (c == 'y') return 5;
goto Throw;
}
// Apr | Aug
if (c == 'A')
{
c = reader.GetChar();
if (c == 'p')
{
c = reader.GetChar();
if (c != 'r') goto Throw;
return 4;
}
if (c == 'u')
{
c = reader.GetChar();
if (c != 'g') goto Throw;
return 8;
}
goto Throw;
}
// Sep
if (c == 'S')
{
c = reader.GetChar();
if (c != 'e') goto Throw;
c = reader.GetChar();
if (c != 'p') goto Throw;
return 9;
}
// Oct
if (c == 'O')
{
c = reader.GetChar();
if (c != 'c') goto Throw;
c = reader.GetChar();
if (c != 't') goto Throw;
return 10;
}
// Nov
if (c == 'N')
{
c = reader.GetChar();
if (c != 'o') goto Throw;
c = reader.GetChar();
if (c != 'v') goto Throw;
return 11;
}
// Dec
if (c == 'D')
{
c = reader.GetChar();
if (c != 'e') goto Throw;
c = reader.GetChar();
if (c != 'c') goto Throw;
return 12;
}
Throw:
throw new JsonDeserializationTypeResolutionException(reader, typeof(DateTime));
}
#if !Net4
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static void CheckCharFromZeroToNineInDateTime(int c)
{
if (c < '0' || c > '9') throw new JsonDeserializationTypeResolutionException(typeof(DateTime));
}
}
}