# How Outfancy Detects and Parses Date/Time Formats in Table Columns

> Learn how Outfancy detects and parses date and time formats in table columns. Explore its two-step pipeline for accurate data classification.

- Repository: [Carlos A. Planchón/outfancy](https://github.com/carlosplanchon/outfancy)
- Tags: internals
- Published: 2026-02-26

---

**Outfancy uses a two-step pipeline that first normalizes and pattern-matches individual cells using `datetime.strptime` in [`widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/widgets.py), then applies a configurable majority-vote threshold (defaulting to 70%) in [`table.py`](https://github.com/carlosplanchon/outfancy/blob/main/table.py) to classify entire columns as dates or times.**

Outfancy is a Python library designed for rendering tables in terminal environments with intelligent type inference. When processing tabular data, the library must accurately distinguish between temporal data and other types to apply appropriate formatting. According to the carlosplanchon/outfancy source code, this detection relies on a hybrid approach combining string normalization, multi-pattern parsing, and statistical aggregation.

## The Two-Step Type Detection Pipeline

Outfancy determines column types through a hierarchical process that moves from individual cell inspection to column-level analysis.

### Step 1: Normalization and Pattern Matching in [`outfancy/widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/widgets.py)

The first layer of detection occurs in [`outfancy/widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/widgets.py), where helper functions inspect raw string values. The **`is_date()`** function serves as the primary entry point for date detection, while **`is_complete_hour()`** handles time values.

Before attempting to parse a string, Outfancy calls **`normalise_date()`** to standardize separators. This preprocessing replaces common delimiters—including slashes (`/`), colons (`:`), periods (`.`), and at symbols (`@`)—with hyphens (`-`). This normalization allows the parser to handle varied input formats like `12/08/2023` or `12.08.2023` uniformly.

After normalization, `is_date()` attempts to parse the string against multiple `datetime.strptime` patterns covering European formats with both two-digit and four-digit years:

```python

# From outfancy/widgets.py – date detection logic (lines 83-94)

def is_date(text: str) -> bool:
    text = normalise_date(text)  # Normalizes separators to hyphens

    for fmt in ["%d-%m-%Y", "%d-%m-%y",
                "%d-%m-%Y %H-%M-%S", "%d-%m-%y %H-%M-%S"]:
        try:
            strptime(text, fmt)
            return True
        except ValueError:
            pass
    return False

```

For time detection, **`is_complete_hour()`** employs similar pattern-matching logic to identify valid hour formats before the column-level aggregation phase.

### Step 2: Majority Voting in [`outfancy/table.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/table.py)

After individual cells are classified, [`outfancy/table.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/table.py) implements a **majority-vote algorithm** to determine definitive column types. The `Table` class iterates through each row, calling the widget helpers (lines 38-44), and accumulates provisional type labels including `'date'`, `'time'`, `'id'`, and `'value'`.

If the percentage of cells classified as dates exceeds the threshold stored in **`self.chk_dtli_date`** (defaulting to **70%**), the entire column is labeled as a date column. The same statistical approach applies to time columns using **`self.chk_dtli_time`** (lines 103-105):

```python

# From outfancy/table.py – column classification logic

elif widgets.is_complete_hour(field):
    the_type = 'time'
elif widgets.is_date(field):
    the_type = 'date'

# Aggregation phase determines final type:

if date_type >= self.chk_dtli_date:
    the_type = 'date'
elif time_type >= self.chk_dtli_time:
    the_type = 'time'

```

## Configurable Detection Thresholds

The default **70% threshold** prevents misclassification when columns contain occasional date strings among predominantly non-date data. You can adjust these sensitivity levels by modifying the attributes on your `Table` instance before calling `analyze()`. Lowering `chk_dtli_date` makes the detection more permissive, while raising it requires stronger consensus across the column.

## Practical Usage Examples

### Detecting Individual Date Strings

You can use the widget functions directly to validate single values without processing an entire table:

```python
from outfancy import widgets

s = "12/08/2023 14-30-00"
if widgets.is_date(s):
    print("Detected a date!")
else:
    print("Not a date.")

```

### Automatic Column Detection in Tables

When processing tabular data, the `Table` class handles detection automatically during the analysis phase:

```python
from outfancy import Table

# Assume data is a list of lists (e.g., from a CSV)

tbl = Table(data)
tbl.analyze()  # Runs the full detection pipeline

print(tbl.column_types)  # Output: ['id', 'name', 'date', 'value']

```

## Summary

- Outfancy detects dates through a **two-step pipeline**: cell-level pattern matching followed by column-level threshold analysis.
- The **`is_date()`** function in [`outfancy/widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/widgets.py) normalizes separators and attempts multiple `strptime` patterns to identify valid dates.
- **Column classification** occurs in [`outfancy/table.py`](https://github.com/carlosplanchon/outfancy/blob/main/outfancy/table.py) using a majority-vote system with configurable thresholds.
- The system supports **European date formats** (day-month-year) with optional time components and handles various input separators through automatic normalization.

## Frequently Asked Questions

### What specific date formats does Outfancy support?

Outfancy primarily supports European date formats (day-month-year) including `"%d-%m-%Y"`, `"%d-%m-%y"`, and their variants with time components (`"%d-%m-%Y %H-%M-%S"`). The `normalise_date()` function in [`widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/widgets.py) preprocesses strings to handle various separators like slashes, colons, and periods before the pattern matching occurs, effectively supporting inputs like `12/08/2023` or `12.08.2023` interchangeably.

### How can I adjust the detection sensitivity for date columns?

You can modify the `chk_dtli_date` attribute on your `Table` instance, which represents the percentage of cells that must match date patterns before the column is classified accordingly. The default value is `70` (meaning 70%), but you can raise this for stricter detection or lower it for more permissive classification when dealing with noisy datasets.

### What happens if a column contains mixed dates and text values?

If fewer than 70% of cells (or your configured threshold) match the date or time patterns, Outfancy will not classify the column as temporal data. Instead, the column will likely receive a label such as `'value'` or `'id'` based on the majority content type, preventing date-specific formatting from being applied to mixed-type columns.

### How does Outfancy distinguish time-only columns from datetime columns?

Time-only columns are processed using the **`is_complete_hour()`** function in [`widgets.py`](https://github.com/carlosplanchon/outfancy/blob/main/widgets.py), which checks for valid hour formats separate from date detection. Similar to the date logic, the system requires a configurable percentage of cells (controlled by `self.chk_dtli_time`) to match time patterns before classifying the column as `'time'` in [`table.py`](https://github.com/carlosplanchon/outfancy/blob/main/table.py), allowing the renderer to apply time-specific formatting distinct from full datetime values.