tryParseIsoDateTime static method
Parses value as an ISO-8601 date or date-time, or returns null when
it is not one.
The value must start with YYYY-MM-DD, and a time part may follow after
a T or a space (HH, HH:mm, or HH:mm:ss, with optional fractional
seconds and a Z or numeric offset).
This is deliberately stricter than DateTime.parse, which accepts
unpunctuated runs such as 20240131 that are far more likely to be
identifiers than dates, and which silently rolls impossible values over
(2024-13-45 becomes 2025-02-14). Every field is range-checked first, so
an impossible date or time stays text instead of becoming the wrong one.
Implementation
static DateTime? tryParseIsoDateTime(String value) {
final len = value.length;
// 'YYYY-MM-DD' at the shortest; a fractional offset date-time fits in 32.
if (len < 10 || len > 40) return null;
if (value.codeUnitAt(4) != _minus || value.codeUnitAt(7) != _minus) {
return null;
}
final year = _twoOrFourDigits(value, 0, 4);
final month = _twoOrFourDigits(value, 5, 7);
final day = _twoOrFourDigits(value, 8, 10);
if (year < 0 || month < 1 || month > 12) return null;
if (day < 1 || day > _daysInMonth(year, month)) return null;
if (len > 10) {
final sep = value.codeUnitAt(10);
if ((sep != _upperT && sep != _space) || len < 13) return null;
final hour = _twoOrFourDigits(value, 11, 13);
if (hour < 0 || hour > 23) return null;
if (len > 13 && value.codeUnitAt(13) == _colon) {
if (len < 16) return null;
final minute = _twoOrFourDigits(value, 14, 16);
if (minute < 0 || minute > 59) return null;
if (len > 16 && value.codeUnitAt(16) == _colon) {
if (len < 19) return null;
final second = _twoOrFourDigits(value, 17, 19);
if (second < 0 || second > 59) return null;
}
} else if (len > 13) {
// Unpunctuated time (T0930): rejected, as the date part is.
final c = value.codeUnitAt(13);
if (c >= _zero && c <= _nine) return null;
}
}
return DateTime.tryParse(value);
}