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. Turn inference on, and say which order your file uses.
Install#
dart pub add csv_plusimport '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#
| Text | Result |
|---|---|
2024-01-31 | local midnight |
2024-01-31T09:30:00 | local date and time |
2024-01-31 09:30:00 | a space works as the separator |
2024-01-31T09:30:00.123 | fractional seconds kept |
2024-01-31T09:30:00Z | UTC |
2024-01-31T09:30:00+05:30 | offset 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'); // nullcsv_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.
Numeric dates like 03/04/2024#
A CSV carries no locale, so that value is 3 April in most of the world and 4 March in the United States, and nothing in the file says which. csv_plus will not guess. Tell it the order your file uses with dateOrder and it reads them.
const au = CsvConfig(parseDates: true, dateOrder: CsvDateOrder.dayFirst);
const us = CsvConfig(parseDates: true, dateOrder: CsvDateOrder.monthFirst);
CsvCodec(au).decode('when\n03/04/2024'); // 3 April 2024
CsvCodec(us).decode('when\n03/04/2024'); // 4 March 2024Slash, dash and dot all separate, day and month may be one or two digits, and a trailing HH:mm or HH:mm:ss is kept. A two-digit year follows the spreadsheet convention: up to 68 is this century, 69 and above the last one. Range checking still applies, so under monthFirst a value like 25/12/2024 has no 25th month and stays text. ISO 8601 is recognised whichever order you set, and the default CsvDateOrder.iso leaves ambiguous values alone.
Month names#
Setting either order also reads dates that name their month in English: 3 April 2024, April 3, 2024, 03-Apr-2024 and 1st Jan 24, with an optional time after them. The name makes the order explicit, so these read the same under dayFirst and monthFirst, and an impossible day such as 31 April stays text.
Month names in another language#
English names are built in. For any other language, hand csv_plus the names with monthNames rather than writing a whole transform. Keys are lowercase, and yours are checked before the English ones, so a spelling both languages use can mean what your file intends.
const fr = CsvConfig(
parseDates: true,
dateOrder: CsvDateOrder.dayFirst,
monthNames: {'janvier': 1, 'fevrier': 2, 'mars': 3, 'avril': 4},
);Other formats#
For anything the built-in forms do not cover, 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.