static class · System

TimeFormat

static class TimeFormat

Dates and lengths of time as text, and read back.

Tipo
static class
Espacio de nombres
System
Ensamblado
System.Core
Acceso
internal, static, abstract
Archivo fuente
Time.ant

Herencia

Clases base
Any TimeFormat

Métodos

General(DateTime value) : str
#

static

MM/dd/yyyy HH:mm:ss, written straight into its 19 bytes.

RoundTrip(DateTime value) : str
#

static

yyyy-MM-ddTHH:mm:ss.fffffff and the kind's suffix (the "o" format), straight into its bytes.

Date(DateTime value, str format) : str
#

static

A date by a standard or custom format.

Span(TimeSpan value, str format) : str
#

static

A length of time by a format.

NotADate(str text)
#

static

ReadSpan(str text) : TimeSpan?
#

static

A length of time: [-]d, [-][d.]h:mm[:ss[.f…]], [-]d:h:mm:ss[.f…], spaces around.

Has(str text, i32 from, i32 end, u8 unit) : bool
#

static

Whether unit is in text[from..end).

ReadDate(str text, str? format, DateTimeStyles styles) : DateTime?
#

static

A date, in format (standard or custom) or, with none, in any of the forms Parse reads.

Two(str text, i32 at) : i32
#

static

Two digits at at (-1 when they are not).

RoundTripRead(str text, DateTimeStyles styles) : DateTime?
#

static

yyyy-MM-ddTHH:mm:ss.fffffff and nothing, Z, or +hh:mm.

Made(DateFields found, DateTimeStyles styles) : DateTime?
#

static

The date the fields make, by the styles.

Kinded(i64 ticks, bool utc, bool hasOffset, i64 offset, DateTimeStyles styles) : DateTime?
#

static

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.

Number(str text, i32 at, i32 least, i32 most) : i64
#

static

Digits at at (at least least, at most most) as a number; -1 when there are too few.

DigitCount(str text, i32 at, i32 most) : i32
#

static

How many digits follow at (up to most).

Name(str text, i32 at, str[] names, i32[] length) : i32
#

static

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
#

static

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
#

static

text in exactly a custom pattern.

ReadAny(str text, DateFields found) : bool
#

static

text in any of the forms Parse reads.

Year(i32 value, i32 digits) : i32
#

static

A year written with digits digits (two: 1950–2049).

Miembros privados e internos (11)

str[] dayNames
#

private static

str[] shortDayNames
#

private static

str[] monthNames
#

private static

str[] shortMonthNames
#

private static

BadFormat()
#

private static

Put2(u8[] b, i32 at, i64 v)
#

private static

Put4(u8[] b, i32 at, i64 v)
#

private static

Run(str format, i32 i) : i32
#

private static

How many times the unit at i repeats from there.

Standard(u8 letter) : str?
#

private static

The custom pattern a one-letter standard date format stands for (null: not one).

ZoneOf(DateTime value) : i64
#

private static

The offset from UTC a date's z and K write: none for Unspecified (K), 0 for UTC, the zone's for local.

Custom(DateTime value, str format, TimeText text)
#

private static

value by a custom date pattern.

Miembros heredados (5)

Código fuente

TimeFormat en Antril, descompilado a partir de su Larva HIR.

System.TimeFormat.ant
// 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;
	}
}
Esc