static class · System
TimeFormat
static class TimeFormat
Dates and lengths of time as text, and read back.
- Kind
- static class
- Namespace
- System
- Assembly
- System.Core
- Access
- internal, static, abstract
- Source file
Time.ant
Inheritance
- Base classes
-
AnyTimeFormat
Methods
MM/dd/yyyy HH:mm:ss, written straight into its 19 bytes.
yyyy-MM-ddTHH:mm:ss.fffffff and the kind's suffix (the "o" format), straight into its bytes.
A date by a standard or custom format.
A length of time by a format.
NotADate(str text)
#
A length of time: [-]d, [-][d.]h:mm[:ss[.f…]], [-]d:h:mm:ss[.f…], spaces around.
Whether unit is in text[from..end).
ReadDate(str text, str? format, DateTimeStyles styles) : DateTime?
#
A date, in format (standard or custom) or, with none, in any of the forms Parse reads.
Two digits at at (-1 when they are not).
RoundTripRead(str text, DateTimeStyles styles) : DateTime?
#
yyyy-MM-ddTHH:mm:ss.fffffff and nothing, Z, or +hh:mm.
Made(DateFields found, DateTimeStyles styles) : DateTime?
#
The date the fields make, by the styles.
A date read as ticks on its clock, with a zone or not (utc: Z or GMT; hasOffset: an offset in ticks), by
the styles.
Digits at at (at least least, at most most) as a number; -1 when there are too few.
How many digits follow at (up to most).
Which of names (ignoring case) the text at at starts with, longest first: its index, and its length in
length[0]; -1 when none.
Zone(str text, i32 at, DateFields found) : i32
#
An offset at at: +hh:mm, +hhmm or +hh (- too), into found; how many units it took (0: none).
ReadExact(str text, str format, DateFields found) : bool
#
text in exactly a custom pattern.
ReadAny(str text, DateFields found) : bool
#
text in any of the forms Parse reads.
A year written with digits digits (two: 1950–2049).
Private and internal members (11)
str[] dayNames
#
str[] shortDayNames
#
str[] monthNames
#
str[] shortMonthNames
#
BadFormat()
#
How many times the unit at i repeats from there.
The custom pattern a one-letter standard date format stands for (null: not one).
The offset from UTC a date's z and K write: none for Unspecified (K), 0 for UTC, the zone's for local.
value by a custom date pattern.
Inherited members (5)
GetTypemethod from AnyUndescribedmethod from AnyToStringmethod from AnyHashmethod from Anydestroymethod from Any
Source
TimeFormat as Antril, decompiled from its Larva HIR.
// System.TimeFormat — System.Core (decompiled from Larva HIR)
into namespace System
{
/// Dates and lengths of time as text, and read back.
static class TimeFormat
{
str[] dayNames;
str[] shortDayNames;
str[] monthNames;
str[] shortMonthNames;
[NoInline]
BadFormat()
{
throw new FormatError("Input string was not in a correct format.");
}
Put2(u8[] b, i32 at, i64 v)
{
b[at] = (u8)(48 + v / 10);
b[at + 1] = (u8)(48 + v % 10);
}
Put4(u8[] b, i32 at, i64 v)
{
Put2(b, at, v / 100);
Put2(b, at + 2, v % 100);
}
/// How many times the unit at `i` repeats from there.
Run(str format, i32 i) : i32
{
u8 c = format[i];
i32 j = i + 1;
loop (j < format.Length && format[j] == c)
j++;
return j - i;
}
/// The custom pattern a one-letter standard date format stands for (null: not one).
Standard(u8 letter) : str?
{
switch (letter)
{
100 =>
return "MM/dd/yyyy";
68 =>
return "dddd, dd MMMM yyyy";
102 =>
return "dddd, dd MMMM yyyy HH:mm";
70 =>
return "dddd, dd MMMM yyyy HH:mm:ss";
103 =>
return "MM/dd/yyyy HH:mm";
71 =>
return "MM/dd/yyyy HH:mm:ss";
77 or 109 =>
return "MMMM dd";
79 or 111 =>
return "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK";
82 or 114 =>
return "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
115 =>
return "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
116 =>
return "HH:mm";
84 =>
return "HH:mm:ss";
117 =>
return "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
85 =>
return "dddd, dd MMMM yyyy HH:mm:ss";
89 or 121 =>
return "yyyy MMMM";
_ =>
return null;
}
}
/// The offset from UTC a date's `z` and `K` write: none for Unspecified (K), 0 for UTC, the zone's for local.
ZoneOf(DateTime value) : i64
{
DateTimeKind kind = value.Kind;
if (kind == DateTimeKind.Utc)
return 0;
return LocalZone.OffsetOfLocal(value.Ticks);
}
/// `value` by a custom date pattern.
Custom(DateTime value, str format, TimeText text)
{
i64 ticks = value.Ticks;
u32 parts = Dates.Parts((u64)ticks);
i32 year = (i32)(parts >> 9);
i32 month = (i32)(parts >> 5 & 15);
i32 day = (i32)(parts & 31);
i32 hour = (i32)(ticks / 36000000000 % 24);
i32 i = 0;
loop (i < format.Length)
{
u8 c = format[i];
i32 run = Run(format, i);
if (c == 121)
{
if (run <= 2)
text.Digits((i64)year % 100, run);
else
text.Digits(year, run);
}
else if (c == 77)
{
if (run <= 2)
text.Digits(month, run);
else
text.Text(run == 3 ? shortMonthNames[month - 1] : monthNames[month - 1]);
}
else if (c == 100)
{
if (run <= 2)
text.Digits(day, run);
else
{
i32 weekday = (i32)((ticks / 864000000000 + 1) % 7);
text.Text(run == 3 ? shortDayNames[weekday] : dayNames[weekday]);
}
}
else if (c == 72)
text.Digits(hour, run > 2 ? 2 : run);
else if (c == 104)
text.Digits(hour % 12 == 0 ? 12 : (i64)hour % 12, run > 2 ? 2 : run);
else if (c == 109)
text.Digits(ticks / 600000000 % 60, run > 2 ? 2 : run);
else if (c == 115)
text.Digits(ticks / 10000000 % 60, run > 2 ? 2 : run);
else if (c == 102 || c == 70)
{
if (run > 7)
BadFormat();
i64 fraction = ticks % 10000000;
loop (i32 k = run; k < 7; k++)
fraction = fraction / 10;
if (c == 102)
text.Digits(fraction, run);
else if (fraction == 0)
{
if (text.Last() == 46)
text.Drop();
}
else
{
i32 digits = run;
loop (fraction % 10 == 0)
{
fraction = fraction / 10;
digits--;
}
text.Digits(fraction, digits);
}
}
else if (c == 116)
{
text.Add(hour < 12 ? 65 : 80);
if (run > 1)
text.Add(77);
}
else if (c == 75)
{
DateTimeKind kind = value.Kind;
if (kind == DateTimeKind.Utc)
text.Add(90);
else if (kind == DateTimeKind.Local)
text.Offset(ZoneOf(value), 2, true, true);
}
else if (c == 122)
{
i64 zone = ZoneOf(value);
if (run == 1)
text.Offset(zone, 1, false, false);
else
text.Offset(zone, 2, run >= 3, true);
}
else if (c == 39 || c == 34)
{
run = 1;
i32 j = i + 1;
loop (j < format.Length && format[j] != c)
{
text.Add(format[j]);
j++;
}
if (j >= format.Length)
BadFormat();
i = j;
}
else if (c == 92)
{
if (i + 1 >= format.Length)
BadFormat();
text.Add(format[i + 1]);
run = 2;
}
else if (c == 37)
run = 1;
else
{
loop (i32 k = 0..run)
text.Add(c);
}
i += run;
}
}
public:
/// `MM/dd/yyyy HH:mm:ss`, written straight into its 19 bytes.
General(DateTime value) : str
{
i64 ticks = value.Ticks;
u32 parts = Dates.Parts((u64)ticks);
u8[] b = new u8[19];
Put2(b, 0, (i64)(parts >> 5) & 15);
b[2] = 47;
Put2(b, 3, (i64)parts & 31);
b[5] = 47;
Put4(b, 6, (i64)(parts >> 9));
b[10] = 32;
i64 seconds = ticks / 10000000 % 86400;
Put2(b, 11, seconds / 3600);
b[13] = 58;
Put2(b, 14, seconds / 60 % 60);
b[16] = 58;
Put2(b, 17, seconds % 60);
return new str(b);
}
/// `yyyy-MM-ddTHH:mm:ss.fffffff` and the kind's suffix (the "o" format), straight into its bytes.
RoundTrip(DateTime value) : str
{
i64 ticks = value.Ticks;
DateTimeKind kind = value.Kind;
i64 zone = kind == DateTimeKind.Local ? LocalZone.OffsetOfLocal(ticks) : 0;
u32 parts = Dates.Parts((u64)ticks);
u8[] b = new u8[kind == DateTimeKind.Unspecified ? 27 : kind == DateTimeKind.Utc ? 28 : 33];
Put4(b, 0, (i64)(parts >> 9));
b[4] = 45;
Put2(b, 5, (i64)(parts >> 5) & 15);
b[7] = 45;
Put2(b, 8, (i64)parts & 31);
b[10] = 84;
i64 seconds = ticks / 10000000 % 86400;
Put2(b, 11, seconds / 3600);
b[13] = 58;
Put2(b, 14, seconds / 60 % 60);
b[16] = 58;
Put2(b, 17, seconds % 60);
b[19] = 46;
i64 fraction = ticks % 10000000;
loop (i32 k = 26; k >= 20; k--)
{
b[k] = (u8)(48 + fraction % 10);
fraction = fraction / 10;
}
if (kind == DateTimeKind.Utc)
b[27] = 90;
else if (kind == DateTimeKind.Local)
{
i64 minutes = zone / 600000000;
b[27] = minutes < 0 ? 45 : 43;
if (minutes < 0)
minutes = -minutes;
Put2(b, 28, minutes / 60);
b[30] = 58;
Put2(b, 31, minutes % 60);
}
return new str(b);
}
/// A date by a standard or custom format.
Date(DateTime value, str format) : str
{
if (format.Length == 0)
return General(value);
if (format.Length == 1)
{
u8 letter = format[0];
if (letter == 71)
return General(value);
if (letter == 111 || letter == 79)
return RoundTrip(value);
if (Standard(letter) is str pattern)
{
TimeText made = new TimeText();
Custom(letter == 85 ? value.ToUniversalTime() : value, pattern, made);
return made.Made();
}
BadFormat();
}
TimeText text = new TimeText();
Custom(value, format, text);
return text.Made();
}
/// A length of time by a format.
Span(TimeSpan value, str format) : str
{
i64 ticks = value.Ticks;
bool negative = ticks < 0;
i64 days = ticks / 864000000000;
i64 rest = ticks % 864000000000;
if (negative)
{
days = -days;
rest = -rest;
}
i64 hours = rest / 36000000000;
i64 minutes = rest / 600000000 % 60;
i64 seconds = rest / 10000000 % 60;
i64 fraction = rest % 10000000;
TimeText text = new TimeText();
u8 letter = format.Length == 1 ? format[0] : 0;
if (format.Length == 0 || letter == 99 || letter == 116 || letter == 84)
{
if (negative)
text.Add(45);
if (days != 0)
{
text.Digits(days, 1);
text.Add(46);
}
text.Digits(hours, 2);
text.Add(58);
text.Digits(minutes, 2);
text.Add(58);
text.Digits(seconds, 2);
if (fraction != 0)
{
text.Add(46);
text.Digits(fraction, 7);
}
return text.Made();
}
if (letter == 103 || letter == 71)
{
if (negative)
text.Add(45);
if (days != 0 || letter == 71)
{
text.Digits(days, 1);
text.Add(58);
}
text.Digits(hours, letter == 71 ? 2 : 1);
text.Add(58);
text.Digits(minutes, 2);
text.Add(58);
text.Digits(seconds, 2);
if (letter == 71)
{
text.Add(46);
text.Digits(fraction, 7);
}
else if (fraction != 0)
{
i32 digits = 7;
loop (fraction % 10 == 0)
{
fraction = fraction / 10;
digits--;
}
text.Add(46);
text.Digits(fraction, digits);
}
return text.Made();
}
if (format.Length == 1)
BadFormat();
i32 i = 0;
loop (i < format.Length)
{
u8 c = format[i];
i32 run = Run(format, i);
if (c == 100)
{
if (run > 8)
BadFormat();
text.Digits(days, run);
}
else if (c == 104)
text.Digits(hours, run > 2 ? 2 : run);
else if (c == 109)
text.Digits(minutes, run > 2 ? 2 : run);
else if (c == 115)
text.Digits(seconds, run > 2 ? 2 : run);
else if (c == 102 || c == 70)
{
if (run > 7)
BadFormat();
i64 part = fraction;
loop (i32 k = run; k < 7; k++)
part = part / 10;
if (c == 102)
text.Digits(part, run);
else if (part != 0)
{
i32 digits = run;
loop (part % 10 == 0)
{
part = part / 10;
digits--;
}
text.Digits(part, digits);
}
}
else if (c == 39 || c == 34)
{
run = 1;
i32 j = i + 1;
loop (j < format.Length && format[j] != c)
{
text.Add(format[j]);
j++;
}
if (j >= format.Length)
BadFormat();
i = j;
}
else if (c == 92)
{
if (i + 1 >= format.Length)
BadFormat();
text.Add(format[i + 1]);
run = 2;
}
else if (c == 37)
run = 1;
else
BadFormat();
i += run;
}
return text.Made();
}
[NoInline]
NotADate(str text)
{
throw new FormatError("String '" + text + "' was not recognized as a valid DateTime.");
}
/// A length of time: `[-]d`, `[-][d.]h:mm[:ss[.f…]]`, `[-]d:h:mm:ss[.f…]`, spaces around.
ReadSpan(str text) : TimeSpan?
{
i32 i = 0;
i32 end = text.Length;
loop (i < end && text[i] == 32)
i++;
loop (end > i && text[end - 1] == 32)
end--;
bool negative = i < end && text[i] == 45;
if (negative)
i++;
i64[] numbers = new i64[5];
i32 count = 0;
i64 days = 0;
bool dotDays = false;
i64 fraction = 0;
loop (true)
{
if (i >= end || text[i] < 48 || text[i] > 57)
return null;
i64 n = 0;
i32 digits = 0;
loop (i < end && text[i] >= 48 && text[i] <= 57)
{
n = n * 10 + ((i64)text[i] - 48);
digits++;
if (digits > 10)
return null;
i++;
}
numbers[count] = n;
count++;
if (i == end)
break;
u8 c = text[i];
if (c == 46 && count == 1 && !dotDays && i + 1 < end && Has(text, i + 1, end, 58))
{
dotDays = true;
days = n;
count = 0;
i++;
continue;
}
if (c == 46 && count >= 3)
{
i++;
i32 places = 0;
if (i >= end)
return null;
loop (i < end && text[i] >= 48 && text[i] <= 57)
{
if (places == 7)
return null;
fraction = fraction * 10 + ((i64)text[i] - 48);
places++;
i++;
}
if (places == 0 || i != end)
return null;
loop (places < 7)
{
fraction = fraction * 10;
places++;
}
break;
}
if (c != 58 || count == 4)
return null;
i++;
}
i64 hours = 0;
i64 minutes = 0;
i64 seconds = 0;
if (count == 1 && !dotDays)
days = numbers[0];
else if (count == 2 || count == 3)
{
hours = numbers[0];
minutes = numbers[1];
seconds = count == 3 ? numbers[2] : 0;
}
else if (count == 4 && !dotDays)
{
days = numbers[0];
hours = numbers[1];
minutes = numbers[2];
seconds = numbers[3];
}
else
return null;
if (count == 2 && fraction != 0)
return null;
if (hours > 23 || minutes > 59 || seconds > 59 || days > 10675199)
return null;
i64 total = ((days * 24 + hours) * 60 + minutes) * 600000000 + seconds * 10000000 + fraction;
if (total < 0)
return null;
return new TimeSpan(negative ? -total : total);
}
/// Whether `unit` is in text[from..end).
Has(str text, i32 from, i32 end, u8 unit) : bool
{
loop (i32 i = from..end)
{
if (text[i] == unit)
return true;
}
return false;
}
/// A date, in `format` (standard or custom) or, with none, in any of the forms Parse reads.
ReadDate(str text, str? format, DateTimeStyles styles) : DateTime?
{
if (format is str o && o.Length == 1 && (o[0] == 111 || o[0] == 79))
return RoundTripRead(text, styles);
DateFields found = new DateFields();
if (format is str exact)
{
str pattern = exact;
if (exact.Length == 1)
{
if (Standard(exact[0]) is str standard)
pattern = standard;
else
return null;
}
if (!ReadExact(text, pattern, found))
return null;
}
else if (!ReadAny(text, found))
return null;
return Made(found, styles);
}
/// Two digits at `at` (-1 when they are not).
Two(str text, i32 at) : i32
{
u32 a = (u32)text[at] - 48;
u32 b = (u32)text[at + 1] - 48;
return a > 9 || b > 9 ? -1 : (i32)(a * 10 + b);
}
/// `yyyy-MM-ddTHH:mm:ss.fffffff` and nothing, Z, or `+hh:mm`.
RoundTripRead(str text, DateTimeStyles styles) : DateTime?
{
i32 length = text.Length;
if (length != 27 && length != 28 && length != 33)
return null;
if (text[4] != 45 || text[7] != 45 || text[10] != 84 || text[13] != 58 || text[16] != 58 || text[19] != 46)
return null;
i32 high = Two(text, 0);
i32 low = Two(text, 2);
i32 month = Two(text, 5);
i32 day = Two(text, 8);
i32 hour = Two(text, 11);
i32 minute = Two(text, 14);
i32 second = Two(text, 17);
if ((high | low | month | day | hour | minute | second) < 0)
return null;
i32 year = high * 100 + low;
if (year < 1 || month < 1 || month > 12 || day < 1 || day > 28 && day > Dates.MonthLength(year, month) || hour > 23 || minute > 59 || second > 59)
return null;
i64 fraction = 0;
loop (i32 i = 20..27)
{
u32 digit = (u32)text[i] - 48;
if (digit > 9)
return null;
fraction = fraction * 10 + (i64)digit;
}
i64 ticks = Dates.DaysOf(year, month, day) * 864000000000 + ((i64)hour * 3600 + (i64)minute * 60 + (i64)second) * 10000000 + fraction;
if (length == 27)
return Kinded(ticks, false, false, 0, styles);
if (length == 28)
return text[27] == 90 ? Kinded(ticks, true, false, 0, styles) : null;
u8 sign = text[27];
i32 hours = Two(text, 28);
i32 minutes = Two(text, 31);
if (sign != 43 && sign != 45 || text[30] != 58 || hours < 0 || minutes < 0 || hours > 14 || minutes > 59)
return null;
i64 offset = ((i64)hours * 60 + (i64)minutes) * 600000000;
return Kinded(ticks, false, true, sign == 45 ? -offset : offset, styles);
}
/// The date the fields make, by the styles.
Made(DateFields found, DateTimeStyles styles) : DateTime?
{
if (found.Year < 0 && found.Month < 0 && found.Day < 0)
{
DateTime today = DateTime.Now;
u32 parts = Dates.Parts((u64)today.Ticks);
found.Year = (i32)(parts >> 9);
found.Month = (i32)(parts >> 5 & 15);
found.Day = (i32)(parts & 31);
}
if (found.Year < 0)
found.Year = DateTime.Now.Year;
if (found.Month < 0)
found.Month = 1;
if (found.Day < 0)
found.Day = 1;
if (found.Year < 1 || found.Year > 9999 || found.Month < 1 || found.Month > 12 || found.Day < 1 || found.Day > DateTime.DaysInMonth(found.Year, found.Month))
return null;
i32 hour = found.Hour;
if (found.Half >= 0)
{
if (hour < 1 || hour > 12)
return null;
hour = hour % 12 + found.Half * 12;
}
if (hour > 23 || found.Minute > 59 || found.Second > 59)
return null;
i64 ticks = Dates.DaysOf(found.Year, found.Month, found.Day) * 864000000000 + ((i64)hour * 3600 + (i64)found.Minute * 60 + (i64)found.Second) * 10000000 + found.Fraction;
if (found.Weekday >= 0 && (i32)((ticks / 864000000000 + 1) % 7) != found.Weekday)
return null;
return Kinded(ticks, found.Utc, found.HasOffset, found.Offset, styles);
}
/// A date read as `ticks` on its clock, with a zone or not (`utc`: Z or GMT; `hasOffset`: an offset in ticks), by
/// the styles.
Kinded(i64 ticks, bool utc, bool hasOffset, i64 offset, DateTimeStyles styles) : DateTime?
{
bool toUtc = (styles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal;
if (utc || hasOffset)
{
i64 moment = ticks - (hasOffset ? offset : 0);
if (moment < 0 || moment > 3155378975999999999)
return null;
if (toUtc || utc && !hasOffset && (styles & DateTimeStyles.RoundtripKind) == DateTimeStyles.RoundtripKind)
return new DateTime(moment, DateTimeKind.Utc);
return new DateTime(moment, DateTimeKind.Utc).ToLocalTime();
}
if ((styles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
{
DateTime universal = new DateTime(ticks, DateTimeKind.Utc);
return toUtc ? universal : universal.ToLocalTime();
}
if ((styles & DateTimeStyles.AssumeLocal) == DateTimeStyles.AssumeLocal)
{
DateTime local = new DateTime(ticks, DateTimeKind.Local);
return toUtc ? local.ToUniversalTime() : local;
}
return new DateTime(ticks, DateTimeKind.Unspecified);
}
/// Digits at `at` (at least `least`, at most `most`) as a number; -1 when there are too few.
Number(str text, i32 at, i32 least, i32 most) : i64
{
i64 n = 0;
i32 i = at;
loop (i < text.Length && i - at < most && text[i] >= 48 && text[i] <= 57)
{
n = n * 10 + ((i64)text[i] - 48);
i++;
}
return i - at < least ? -1 : n;
}
/// How many digits follow `at` (up to `most`).
DigitCount(str text, i32 at, i32 most) : i32
{
i32 i = at;
loop (i < text.Length && i - at < most && text[i] >= 48 && text[i] <= 57)
i++;
return i - at;
}
/// Which of `names` (ignoring case) the text at `at` starts with, longest first: its index, and its length in
/// `length[0]`; -1 when none.
Name(str text, i32 at, str[] names, i32[] length) : i32
{
i32 best = -1;
loop (i32 n = 0..names.Length)
{
str name = names[n];
if (at + name.Length > text.Length || best >= 0 && name.Length <= length[0])
continue;
bool same = true;
loop (i32 k = 0..name.Length)
{
u8 a = text[at + k];
u8 b = name[k];
if (a >= 65 && a <= 90)
a = a + 32;
if (b >= 65 && b <= 90)
b = b + 32;
if (a != b)
{
same = false;
break;
}
}
if (same)
{
best = n;
length[0] = name.Length;
}
}
return best;
}
/// An offset at `at`: `+hh:mm`, `+hhmm` or `+hh` (`-` too), into `found`; how many units it took (0: none).
Zone(str text, i32 at, DateFields found) : i32
{
if (at >= text.Length || text[at] != 43 && text[at] != 45)
return 0;
i32 i = at + 1;
i32 hourDigits = DigitCount(text, i, 2);
if (hourDigits == 0)
return 0;
i64 hours = Number(text, i, 1, 2);
i64 minutes = 0;
i += hourDigits;
if (i < text.Length && text[i] == 58)
{
if (DigitCount(text, i + 1, 2) != 2)
return 0;
minutes = Number(text, i + 1, 2, 2);
i += 3;
}
else if (hourDigits == 2 && DigitCount(text, i, 2) == 2)
{
minutes = Number(text, i, 2, 2);
i += 2;
}
if (hours > 14 || minutes > 59)
return 0;
found.HasOffset = true;
found.Offset = (hours * 60 + minutes) * 600000000 * (text[at] == 45 ? -1 : 1);
return i - at;
}
/// `text` in exactly a custom pattern.
ReadExact(str text, str format, DateFields found) : bool
{
i32[] length = new i32[1];
i32 at = 0;
i32 i = 0;
loop (i < format.Length)
{
u8 c = format[i];
i32 run = Run(format, i);
if (c == 121 || c == 77 && run <= 2 || c == 100 && run <= 2 || c == 72 || c == 104 || c == 109 || c == 115)
{
i32 least = run >= 2 ? run : 1;
i32 most = c == 121 && run != 2 ? run > 4 ? run : 4 : 2;
if (c == 121 && run == 1)
most = 2;
i32 digits = DigitCount(text, at, most);
if (digits < least)
return false;
i32 n = (i32)Number(text, at, least, most);
at += digits;
if (c == 121)
found.Year = run <= 2 ? n < 50 ? 2000 + n : 1900 + n : n;
else if (c == 77)
found.Month = n;
else if (c == 100)
found.Day = n;
else if (c == 109)
found.Minute = n;
else if (c == 115)
found.Second = n;
else
found.Hour = n;
if (c == 72 || c == 104 || c == 109 || c == 115)
found.HasTime = true;
}
else if (c == 77 || c == 100)
{
str[] names = c == 77 ? run == 3 ? shortMonthNames : monthNames : run == 3 ? shortDayNames : dayNames;
i32 which = Name(text, at, names, length);
if (which < 0)
return false;
at += length[0];
if (c == 77)
found.Month = which + 1;
else
found.Weekday = which;
}
else if (c == 102 || c == 70)
{
if (run > 7)
return false;
i32 digits = DigitCount(text, at, run);
if (c == 102 && digits != run)
return false;
i64 fraction = digits == 0 ? 0 : Number(text, at, 1, run);
at += digits;
loop (i32 k = digits; k < 7; k++)
fraction = fraction * 10;
found.Fraction = fraction;
}
else if (c == 116)
{
if (at >= text.Length)
return false;
u8 h = text[at];
if (h != 65 && h != 80 && h != 97 && h != 112)
return false;
found.Half = h == 80 || h == 112 ? 1 : 0;
at++;
if (run > 1)
{
if (at >= text.Length || text[at] != 77 && text[at] != 109)
return false;
at++;
}
}
else if (c == 75 || c == 122)
{
if (at < text.Length && (text[at] == 90 || text[at] == 122) && c == 75)
{
found.Utc = true;
at++;
}
else
{
i32 took = Zone(text, at, found);
if (took == 0 && c == 122)
return false;
at += took;
}
}
else if (c == 39 || c == 34)
{
run = 1;
i32 j = i + 1;
loop (j < format.Length && format[j] != c)
{
if (at >= text.Length || text[at] != format[j])
return false;
at++;
j++;
}
if (j >= format.Length)
BadFormat();
i = j;
}
else if (c == 92)
{
if (i + 1 >= format.Length || at >= text.Length || text[at] != format[i + 1])
return false;
at++;
run = 2;
}
else if (c == 37)
run = 1;
else
{
loop (i32 k = 0..run)
{
if (at >= text.Length || text[at] != c)
return false;
at++;
}
}
i += run;
}
return at == text.Length;
}
/// `text` in any of the forms Parse reads.
ReadAny(str text, DateFields found) : bool
{
i32[] length = new i32[1];
i32 at = 0;
i32 end = text.Length;
loop (at < end && text[at] == 32)
at++;
loop (end > at && text[end - 1] == 32)
end--;
if (at == end)
return false;
str t = at == 0 && end == text.Length ? text : text.Substring(at, end - at);
at = 0;
end = t.Length;
i32 weekday = Name(t, 0, dayNames, length);
if (weekday < 0)
weekday = Name(t, 0, shortDayNames, length);
if (weekday >= 0)
{
found.Weekday = weekday;
at = length[0];
if (at < end && t[at] == 44)
at++;
loop (at < end && t[at] == 32)
at++;
}
i32 first = DigitCount(t, at, 9);
if (first > 0 && at + first < end && t[at + first] == 58)
{
}
else if (first > 0)
{
i64 a = Number(t, at, 1, first);
at += first;
if (at < end && (t[at] == 45 || t[at] == 47 || t[at] == 46))
{
u8 separator = t[at];
at++;
i32 secondDigits = DigitCount(t, at, 4);
i32 month = -1;
i64 b = -1;
if (secondDigits > 0)
{
b = Number(t, at, 1, secondDigits);
at += secondDigits;
}
else
{
month = Name(t, at, monthNames, length);
if (month < 0)
month = Name(t, at, shortMonthNames, length);
if (month < 0)
return false;
at += length[0];
}
i64 c = -1;
i32 thirdDigits = 0;
if (at < end && t[at] == separator)
{
at++;
thirdDigits = DigitCount(t, at, 4);
if (thirdDigits == 0)
return false;
c = Number(t, at, 1, thirdDigits);
at += thirdDigits;
}
if (first >= 3)
{
found.Year = (i32)a;
found.Month = month >= 0 ? month + 1 : (i32)b;
found.Day = c >= 0 ? (i32)c : 1;
}
else if (month >= 0)
{
found.Day = (i32)a;
found.Month = month + 1;
found.Year = c >= 0 ? Year((i32)c, thirdDigits) : DateTime.Now.Year;
}
else
{
found.Month = (i32)a;
found.Day = (i32)b;
found.Year = c >= 0 ? Year((i32)c, thirdDigits) : DateTime.Now.Year;
}
}
else
{
loop (at < end && t[at] == 32)
at++;
i32 month = Name(t, at, monthNames, length);
if (month < 0)
month = Name(t, at, shortMonthNames, length);
if (month < 0 || first > 2)
return false;
at += length[0];
loop (at < end && (t[at] == 32 || t[at] == 44))
at++;
i32 yearDigits = DigitCount(t, at, 4);
found.Day = (i32)a;
found.Month = month + 1;
if (yearDigits > 0 && !(at + yearDigits < end && t[at + yearDigits] == 58))
{
found.Year = Year((i32)Number(t, at, 1, yearDigits), yearDigits);
at += yearDigits;
}
else
found.Year = DateTime.Now.Year;
}
}
else
{
i32 month = Name(t, at, monthNames, length);
if (month < 0)
month = Name(t, at, shortMonthNames, length);
if (month < 0)
return false;
at += length[0];
loop (at < end && t[at] == 32)
at++;
i32 dayDigits = DigitCount(t, at, 2);
if (dayDigits == 0)
return false;
found.Month = month + 1;
found.Day = (i32)Number(t, at, 1, dayDigits);
at += dayDigits;
loop (at < end && (t[at] == 32 || t[at] == 44))
at++;
i32 yearDigits = DigitCount(t, at, 4);
if (yearDigits > 0 && !(at + yearDigits < end && t[at + yearDigits] == 58))
{
found.Year = Year((i32)Number(t, at, 1, yearDigits), yearDigits);
at += yearDigits;
}
else
found.Year = DateTime.Now.Year;
}
bool iso = at < end && (t[at] == 84 || t[at] == 116) && found.Year >= 0;
if (iso)
at++;
else
{
loop (at < end && t[at] == 32)
at++;
}
i32 hourDigits = DigitCount(t, at, 2);
if (hourDigits > 0 && at + hourDigits < end && t[at + hourDigits] == 58)
{
found.HasTime = true;
found.Hour = (i32)Number(t, at, 1, hourDigits);
at += hourDigits + 1;
if (DigitCount(t, at, 2) != 2)
return false;
found.Minute = (i32)Number(t, at, 2, 2);
at += 2;
if (at < end && t[at] == 58)
{
if (DigitCount(t, at + 1, 2) != 2)
return false;
found.Second = (i32)Number(t, at + 1, 2, 2);
at += 3;
if (at < end && (t[at] == 46 || t[at] == 44))
{
i32 digits = DigitCount(t, at + 1, 40);
if (digits == 0)
return false;
i64 fraction = Number(t, at + 1, 1, digits > 7 ? 7 : digits);
loop (i32 k = digits; k < 7; k++)
fraction = fraction * 10;
if (digits > 7 && t[at + 8] >= 53)
fraction++;
found.Fraction = fraction;
at += digits + 1;
}
}
loop (at < end && t[at] == 32)
at++;
if (!iso && at + 1 < end && (t[at] == 65 || t[at] == 97 || t[at] == 80 || t[at] == 112) && (t[at + 1] == 77 || t[at + 1] == 109))
{
found.Half = t[at] == 80 || t[at] == 112 ? 1 : 0;
at += 2;
}
}
else if (found.Year < 0)
return false;
loop (at < end && t[at] == 32)
at++;
if (at < end && (t[at] == 90 || t[at] == 122))
{
found.Utc = true;
at++;
}
else if (at + 2 < end && t[at] == 71 && t[at + 1] == 77 && t[at + 2] == 84 || at + 2 < end && t[at] == 85 && t[at + 1] == 84 && t[at + 2] == 67)
{
found.Utc = true;
at += 3;
at += Zone(t, at, found);
}
else
at += Zone(t, at, found);
return at == end;
}
/// A year written with `digits` digits (two: 1950–2049).
Year(i32 value, i32 digits) : i32 => digits <= 2 ? value < 50 ? 2000 + value : 1900 + value : value;
}
}