csv_plus v1.3.0
pub.dev GitHub

Dates and times

Dates stay text until you ask for them, because 03/04/2024 means two different days depending on where the file came from.

Install#

dart pub add csv_plus
import 'package:csv_plus/csv_plus.dart';

Turning it on#

final codec = CsvCodec(const CsvConfig(parseDates: true));

codec.decode('when,who\n2024-01-31,Alice');
// [[when, who], [DateTime(2024, 1, 31), Alice]]

It applies everywhere inference already applies: decode, decodeToTable, decodeToMaps, the streaming CsvDecoder, and bindBytes.

What is accepted#

TextResult
2024-01-31local midnight
2024-01-31T09:30:00local date and time
2024-01-31 09:30:00a space works as the separator
2024-01-31T09:30:00.123fractional seconds kept
2024-01-31T09:30:00ZUTC
2024-01-31T09:30:00+05:30offset applied, UTC returned

A value has to start with YYYY-MM-DD. A value with no offset reads as local time, one with an offset reads as UTC.

What stays text#

codec.decode('a,b,c,d\n03/04/2024,2024-13-45,20240131,"2024-01-31"');
// ['03/04/2024', '2024-13-45', 20240131, '2024-01-31']

Ambiguous locale formats are left alone. So are unpunctuated runs such as 20240131, which are far more likely to be identifiers than dates, and quoted fields, which always opt out of inference.

An impossible date stays a string#

2024-13-45 is the case worth knowing about. DateTime.parse accepts it and quietly rolls it over to 14 February 2025, so a typo in a source file becomes a real date that is simply wrong.

DateTime.parse('2024-13-45');                 // 2025-02-14  (!)
FastDecoder.tryParseIsoDateTime('2024-13-45'); // null

csv_plus range-checks the year, month, day, hour, minute and second before parsing, and honours leap years. 2024-02-29 parses; 2023-02-29 does not.

Other date formats#

For anything that is not ISO 8601, convert the column yourself with a decoderTransform. It runs on every data cell and receives the column header, so you can target one column.

final codec = CsvCodec(CsvConfig(
  hasHeader: true,
  decoderTransform: (value, index, header) {
    if (header != 'when' || value is! String) return value;
    final parts = value.split('/');            // 03/04/2024, day first
    if (parts.length != 3) return value;
    return DateTime(
      int.parse(parts[2]),
      int.parse(parts[1]),
      int.parse(parts[0]),
    );
  },
));

Writing dates back#

A DateTime encodes to a form that decodes to the same value, in both local and UTC, so a decode and encode round trip is lossless.