toDateAutoFormat method

DateTime toDateAutoFormat({
  1. String? locale,
  2. bool useCurrentLocale = false,
  3. bool utc = false,
})

Attempts to parse the date using a variety of known formats.

Returns a local DateTime for calendar-style inputs (e.g. yyyyMMdd, MM/dd/yyyy, long month names) unless utc is true, in which case the parsed value is converted to UTC.

Inputs that describe an instant (ISO strings with offsets/Z, HTTP-date with GMT, or Unix epochs) preserve their UTC meaning. If utc is false, the result is converted back to the local time zone.

Parsing Priority

  1. Unix Epoch: 9–10 digits (seconds) or 12–13 digits (milliseconds).
  2. ISO-8601 / RFC3339: Standard DateTime.parse.
  3. HTTP Date: RFC 7231 (e.g., EEE, dd MMM yyyy HH:mm:ss 'GMT').
  4. Ambiguous Numeric: Slashed dates (e.g., MM/dd/yyyy) resolved by locale.
  5. Compact Numeric: yyyyMMdd (8 digits), yyyyMMddHHmm (12 digits), etc.
  6. Long Form: MMMM d, yyyy, etc.
  7. Time Only: HH:mm:ss (defaults to today's date).

Implementation

DateTime toDateAutoFormat({
  String? locale,
  bool useCurrentLocale = false,
  bool utc = false,
}) {
  final raw = trim();
  if (raw.isEmpty) {
    throw const FormatException('Invalid or unsupported date format', '');
  }

  // Effective locale for ambiguous decisions.
  final effectiveLocale =
      locale ?? (useCurrentLocale ? Intl.getCurrentLocale() : null);
  final isUS = (effectiveLocale ?? Intl.getCurrentLocale()).startsWith(
    'en_US',
  );

  // 0) Unix epoch first (handles pure digits), with 12-digit disambiguation.
  final unix = _tryParseUnix(raw);
  if (unix != null) {
    // _tryParseUnix returns a UTC DateTime.
    return utc ? unix.toUtc() : unix.toLocal();
  }

  // 1) ISO/RFC3339
  try {
    final iso = DateTime.parse(raw);
    return utc ? iso.toUtc() : iso;
  } catch (_) {}

  // 2) HTTP-date (RFC 7231 IMF-fixdate)
  final http = _tryParseHttpDate(raw);
  if (http != null) return utc ? http.toUtc() : http;

  // Normalize variants we might want to try
  final variants = <String>{
    raw,
    _normalize(raw),
    raw.replaceAll('_', ' '),
  }.where((s) => s.isNotEmpty).toList();

  // 3) Slashed ambiguous numeric using intl (ensures formatter/parser symmetry)
  //    Try the preferred interpretation first, then the alternative.
  final slashedCandidates = isUS
      ? const [
          'MM/dd/yyyy HH:mm:ss',
          'MM/dd/yyyy',
          'dd/MM/yyyy HH:mm:ss',
          'dd/MM/yyyy',
        ]
      : const [
          'dd/MM/yyyy HH:mm:ss',
          'dd/MM/yyyy',
          'MM/dd/yyyy HH:mm:ss',
          'MM/dd/yyyy',
        ];

  for (final fmt in slashedCandidates) {
    for (final text in variants) {
      final dt = _tryParseWith(fmt, text, effectiveLocale);
      if (dt != null) return utc ? dt.toUtc() : dt;
    }
  }

  // 4) Compact numeric (yyyyMMdd[HHmm[ss]]) and underscored/space variants
  final compact = _tryParseCompactDate(raw, utc: utc);
  if (compact != null) return compact;

  // 5) Long/alpha forms via intl
  const intlPatterns = <String>[
    "EEEE, MMMM d, yyyy 'at' h:mm a",
    'EEEE, MMMM d, yyyy',
    'MMMM d, yyyy h:mm a',
    'MMMM d, yyyy',
    'd MMMM yyyy HH:mm:ss',
    'd MMMM yyyy',
    // Safety net
    'yyyy-MM-dd HH:mm:ss',
  ];

  for (final fmt in intlPatterns) {
    for (final text in variants) {
      final dt = _tryParseWith(fmt, text, effectiveLocale);
      if (dt != null) return utc ? dt.toUtc() : dt;
    }
  }

  // 6) Time-only → today (local)
  for (final fmt in ['HH:mm:ss', 'HH:mm', 'hh:mm:ss a', 'hh:mm a']) {
    for (final text in variants) {
      final t = _tryParseWith(fmt, text, effectiveLocale);
      if (t != null) {
        final now = DateTime.now();
        final today = DateTime(
          now.year,
          now.month,
          now.day,
          t.hour,
          t.minute,
          t.second,
          t.millisecond,
          t.microsecond,
        );
        return utc ? today.toUtc() : today;
      }
    }
  }

  throw FormatException('Invalid or unsupported date format', raw);
}