1252 lines
44 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|