Date and Time Periods
Date calculations in VisiData use Python's datetime module. After casting a column to date type (@), you can use .year, .month, .day, and other date attributes directly in expressions. For more complex operations, import datetime in .visidatarc.
Learning Focus
Always cast your date column to type @ before using date expressions — VisiData can not compute .year on a string. The cast converts "2025-01-15" into a Python date object.
Setup: Import in .visidatarc
# ~/.visidatarc
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta # optional: for month arithmetic
Date Function Reference
| Excel / Google Sheets | VisiData Python | Description |
|---|---|---|
=TODAY() | = date.today() | Current date |
=NOW() | = __import__('datetime').datetime.now() | Current datetime |
=YEAR(A2) | = date_col.year | Extract year |
=MONTH(A2) | = date_col.month | Extract month (1–12) |
=DAY(A2) | = date_col.day | Extract day of month |
=WEEKDAY(A2, 2) | = date_col.weekday() | Day of week (0=Mon, 6=Sun) |
=WEEKNUM(A2) | = date_col.isocalendar()[1] | ISO week number |
=DATEDIF(A2, B2, "d") | = (date_b - date_a).days | Days between dates |
=DATEDIF(A2, B2, "m") | = (date_b.year - date_a.year) * 12 + (date_b.month - date_a.month) | Months between dates |
=DATEDIF(A2, B2, "y") | = date_b.year - date_a.year | Years between dates |
=EOMONTH(A2, 0) | = date(d.year, d.month % 12 + 1, 1) - timedelta(days=1) | End of current month |
=EOMONTH(A2, -1) | = date(d.year, d.month, 1) - timedelta(days=1) | End of previous month |
=DATE(year, month, day) | = date(year_col, month_col, day_col) | Build a date from parts |
=NETWORKDAYS(A2, B2) | Custom function in .visidatarc | Business days between dates |
=TEXT(A2, "YYYY-MM") | = f"{d.year}-{d.month:02d}" | Year-month string |
=TEXT(A2, "MMM YYYY") | = d.strftime('%b %Y') | Month abbreviation + year |
=TEXT(A2, "Q") | = (d.month - 1) // 3 + 1 | Fiscal quarter (1–4) |
=DAYS(B2, A2) | = (date_b - date_a).days | Same as DATEDIF "d" |
=EDATE(A2, 3) | = date(d.year, d.month + 3, d.day) | Add N months |
Casting to Date Type
Before using any date expression, cast the column:
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# Cast 'order_date' to date type
# Move to 'order_date' column → press @
@
# VisiData auto-detects common formats:
# YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, etc.
# If auto-detection fails, set format in .visidatarc:
# options.disp_date_fmt = '%d/%m/%Y'
Date Extraction
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# After casting order_date to @...
# Extract year
=
# Enter: order_date.year
# Name: order_year
# Extract month number
=
# Enter: order_date.month
# Name: order_month
# Extract month name
=
# Enter: order_date.strftime('%B')
# Name: month_name
# Extract quarter
=
# Enter: (order_date.month - 1) // 3 + 1
# Name: quarter
Days Between Dates (DATEDIF)
# Days since order date (aging analysis)
=
# Enter: (date.today() - order_date).days
# Name: days_since_order
# Days until deadline
=
# Enter: (deadline_date - date.today()).days
# Name: days_remaining
Year-Month Period Column
# Create YYYY-MM period column for grouping
=
# Enter: f"{order_date.year}-{order_date.month:02d}"
# Name: period
# Now frequency table by period shows monthly counts
# Move to 'period' → Shift+F
Fiscal Period (Offset Calendar)
For fiscal years starting in April (common in UK/Singapore accounting):
# Fiscal year: April = start of FY
=
# Enter: order_date.year if order_date.month >= 4 else order_date.year - 1
# Name: fiscal_year
# Fiscal quarter (FY starting April)
=
# Enter: ((order_date.month - 4) % 12) // 3 + 1
# Name: fiscal_quarter
Aging Analysis
Classic accounts receivable aging buckets:
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# Cast due_date to @
# Move to 'due_date' → @
# Calculate days overdue
=
# Enter: (date.today() - due_date).days
# Name: days_overdue
# Classify into aging buckets
=
# Enter: 'current' if days_overdue <= 0 else '1-30 days' if days_overdue <= 30 else '31-60 days' if days_overdue <= 60 else '61-90 days' if days_overdue <= 90 else '90+ days'
# Name: aging_bucket
# Frequency table of aging buckets
# Move to 'aging_bucket' → Shift+F
Business Days Calculation
Add to .visidatarc for a business days function:
# ~/.visidatarc
from datetime import date, timedelta
def business_days_between(start, end):
"""Count business days (Mon-Fri) between two dates."""
days = 0
current = start
while current < end:
if current.weekday() < 5: # Mon=0, Fri=4
days += 1
current += timedelta(days=1)
return days
Then use in VisiData:
=
# Enter: business_days_between(order_date, delivery_date)
# Name: business_days
Date Comparison and Filtering
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# Filter orders from 2025 only
z|
# Enter: order_date.year == 2025
# Filter Q1 2025
z|
# Enter: order_date.year == 2025 and order_date.month in [1, 2, 3]
# Filter overdue orders
z|
# Enter: (date.today() - due_date).days > 30
Month-over-Month Comparison Pivot
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# After casting order_date to @...
# Create period column
=
# Enter: f"{order_date.year}-{order_date.month:02d}"
# Name: period
# Set amount aggregator
# Move to 'amount' → + sum
# Mark period as key column
# Move to 'period' → !
# Open frequency table for period totals
# Move to 'period' → Shift+F
# Shows: period, order count, total amount per month
Hands-On Practice
vd ~/github/practice-folder/visidata/04-cleaning/01-dirty_orders.csv
# 1. Cast 'order_date' to date: move to column → @
# 2. Extract year:
=
# Enter: order_date.year
# Name: year
# 3. Extract quarter:
=
# Enter: (order_date.month - 1) // 3 + 1
# Name: quarter
# 4. Create period string:
=
# Enter: f"{order_date.year}-{order_date.month:02d}"
# Name: period
# 5. Days since order:
=
# Enter: (date.today() - order_date).days
# Name: age_days
# 6. Filter recent orders: z| → order_date.year == 2025
# 7. Open filtered: "
# 8. Press Ctrl+S → save as /tmp/orders_2025.csv