Skip to content

How to parse dates with an explicit format in Pandas (with Code)

The Short Answer (TL;DR)

Pass the documented date order to pandas.to_datetime through format, and choose errors="raise" or errors="coerce" deliberately. Preserve the raw strings and flag failed nonmissing inputs separately from dates that were already missing. Add a full-string pattern when the input contract requires fixed-width DD/MM/YYYY text.

import pandas as pd
raw = pd.Series(["03/04/2026", "31/02/2026", None], dtype="string")
print(pd.to_datetime(raw, format="%d/%m/%Y", exact=True, errors="coerce"))

Detailed Implementation

This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.

python -m pip install numpy==2.5.4 pandas==3.0.6
from pathlib import Path
import pandas as pd

def parse_day_month_year(values):
    if not isinstance(values, pd.Series):
        raise ValueError("Provide a Series of date strings or missing values.")
    raw = values.copy(deep=True)
    if not all(isinstance(x, str) for x in raw[raw.notna()]):
        raise ValueError("Nonmissing inputs must be strings, not numeric epochs.")
    # The application requires two-digit days/months and a four-digit year.
    shape_ok = raw.astype("string").str.fullmatch(r"[0-9]{2}/[0-9]{2}/[0-9]{4}", na=False)
    parsed = pd.to_datetime(raw.where(shape_ok), format="%d/%m/%Y",
                            exact=True, errors="coerce")
    missing = raw.isna()
    invalid = raw.notna() & parsed.isna()
    return pd.DataFrame({"raw": raw, "parsed": parsed,
                         "source_missing": missing, "invalid": invalid})

# Synthetic import: DD/MM/YYYY is a declared source contract.
raw = pd.Series(["03/04/2026", "29/02/2024", "31/02/2026", None, "",
                 "3/4/2026", "2026-04-03", "13/11/2026", "29/02/2025",
                 "03/04/2026 extra"], index=range(100,110), dtype="string")
result = parse_day_month_year(raw)
nonmissing = int(raw.notna().sum())
invalid_count = int(result["invalid"].sum())
invalid_rate = invalid_count/nonmissing if nonmissing else None
print(result.to_string())
print({"valid": int(result["parsed"].notna().sum()),
       "source_missing": int(result["source_missing"].sum()),
       "invalid_nonmissing": invalid_count,
       "invalid_rate_among_nonmissing": invalid_rate})
Path("explicit-date-parsing.txt").write_text(result.to_string()+"\n"+
    f"Invalid rate among nonmissing inputs: {invalid_count}/{nonmissing} = {invalid_rate}\n",
    encoding="utf-8")

The ten rows contain three valid dates, one original missing input and six invalid nonmissing inputs. The first date is 3 April 2026, the leap-day date is 29 February 2024, and the last valid date is 13 November 2026. The invalid rate among nonmissing inputs is 6/9, approximately 66.7 percent.

Declare the date order before parsing

In this source, 03/04/2026 means 3 April 2026 because the data contract is DD/MM/YYYY. The format %d/%m/%Y encodes that interpretation. The same string parsed as %m/%d/%Y means 4 March, so successful conversion alone cannot prove the intended meaning. Obtain the order from the source specification; guessing from a few rows is unsafe. The synthetic example tests both interpretations to make the ambiguity visible.

Keep failed conversion separate from original missingness

The example retains all ten raw records and their indexes. Three dates parse, one source value was missing, and six nonmissing inputs fail the declared contract. An empty string counts as an invalid nonmissing input here. With errors="coerce", an impossible date and an original missing value both become NaT, so checking only parsed.isna() loses their different causes. The invalid flag uses raw.notna() together with parsed.isna() and leaves the original text available for correction.

Use a lexical check when fixed width matters

The three-line answer demonstrates calendar parsing with an explicit order. A format directive is not a complete input-schema validator: date parsers may accept one-digit day or month fields. The full implementation adds a full-string ASCII pattern before parsing, so 3/4/2026, ISO-formatted text, whitespace and trailing text fail this application contract. The pattern alone still cannot reject 31 February. Calendar parsing supplies that second check. Other projects can accept variable width or trim whitespace deliberately, but should document those choices.

Choose between stopping and collecting errors

Use errors="raise" when one invalid date must stop the import. Use errors="coerce" with a retained error flag when the goal is to collect and report all bad rows. Coercion is not permission to discard records silently, fill failed dates with today or impute a chronology. The helper also rejects nonmissing numbers, booleans and already parsed objects rather than treating them as date strings. Numeric epoch data need an explicit unit and origin under a separate contract.

A date label does not establish a timezone

The parsed values are timezone-naive midnight date labels. The example does not assign UTC or Europe/Prague, and a date without a time does not identify an observation instant. If a later task includes local clock times, define the source timezone and resolve clock-change ambiguity before conversion. Passing utc=True to naive local clock text would label those values as UTC; it would not discover their actual timezone.

Report the denominator and the validation scope

Six failures among nine nonmissing inputs give an invalid rate of 2/3, approximately 66.7 percent. Among all ten rows the same six failures would be 60 percent, so name the denominator. For an empty or all-missing source there are no nonmissing inputs and that rate is undefined, not zero. These counts describe input quality; they are not a statistical confidence interval. Validation on November 3, 2026 checked the current Pandas 3.1.0 documentation and executed the preserved Pandas 3.0.6 environment. It covers parsing, calendar validity, missingness, source/index preservation and invalid types; it does not validate that the source supplied truthful dates.

Mathematical Context

Let M_i indicate that source row i was already missing and let I_i indicate a nonmissing input that failed the declared text and calendar contract. If N is the total row count, the nonmissing denominator is N minus the sum of M_i. The invalid-input rate is the sum of I_i divided by that denominator when it is positive. Here N=10, one input is missing and six are invalid, giving 6/9=2/3. Valid, missing and invalid flags form disjoint groups and account for every source row. This is a descriptive proportion for the supplied import, not an estimate of uncertainty about a population.

\[ r_{\mathrm{invalid}}=\frac{\sum_{i=1}^{N} I_i}{N-\sum_{i=1}^{N} M_i},\quad N-\sum_{i=1}^{N}M_i>0 \]

Reproducibility

Executed with Python 3.12.14; numpy 2.5.4, pandas 3.0.6. Automated checks confirm the stated output properties.

Primary documentation

Related guides