Skip to content

Fuzzy Dates

Stash v0.30.0+ introduced support for partial dates (fuzzy dates), allowing you to store dates with varying levels of precision. This guide explains how to work with fuzzy dates in the client library.

What Are Fuzzy Dates?

Fuzzy dates allow you to express dates when you don't know the complete information:

  • Year only: "2024" - You know the year but not the month/day
  • Year-Month: "2024-03" - You know the year and month but not the day
  • Full date: "2024-03-15" - Complete date information

This is particularly useful for:

  • Performer birthdates when exact date is unknown
  • Scene release dates with only year/month known
  • Historical events with approximate dates

Date Precision Levels

The library defines three precision levels:

from stash_graphql_client.types import DatePrecision

DatePrecision.YEAR   # "YYYY" format (e.g., "2024")
DatePrecision.MONTH  # "YYYY-MM" format (e.g., "2024-03")
DatePrecision.DAY    # "YYYY-MM-DD" format (e.g., "2024-03-15")

Working with Fuzzy Dates

Creating Fuzzy Dates

from stash_graphql_client.types import FuzzyDate

# Year precision
year_date = FuzzyDate("2024")
print(year_date.precision)  # DatePrecision.YEAR
print(year_date.value)      # "2024"

# Month precision
month_date = FuzzyDate("2024-03")
print(month_date.precision)  # DatePrecision.MONTH
print(month_date.value)      # "2024-03"

# Day precision (full date)
day_date = FuzzyDate("2024-03-15")
print(day_date.precision)  # DatePrecision.DAY
print(day_date.value)      # "2024-03-15"

# Invalid date strings raise StashIntegrationError
FuzzyDate("not-a-date")  # raises StashIntegrationError

Validating Date Strings

Use the validation utility to check if a date string is valid:

from stash_graphql_client.types import validate_fuzzy_date

# Valid formats
validate_fuzzy_date("2024")         # True
validate_fuzzy_date("2024-03")      # True
validate_fuzzy_date("2024-03-15")   # True

# Invalid formats
validate_fuzzy_date("2024-3")       # False - month must be zero-padded
validate_fuzzy_date("2024-03-5")    # False - day must be zero-padded
validate_fuzzy_date("24")           # False - year must be 4 digits
validate_fuzzy_date("2024/03/15")   # False - wrong separator

Converting to Python datetime

Convert fuzzy dates to Python datetime objects:

from stash_graphql_client.types import FuzzyDate

# Year precision - defaults to January 1st
year_date = FuzzyDate("2024")
dt = year_date.to_datetime()  # datetime(2024, 1, 1)

# Month precision - defaults to 1st of month
month_date = FuzzyDate("2024-03")
dt = month_date.to_datetime()  # datetime(2024, 3, 1)

# Day precision - exact date
day_date = FuzzyDate("2024-03-15")
dt = day_date.to_datetime()  # datetime(2024, 3, 15)

Normalizing Dates

Convert between different precision levels:

from stash_graphql_client.types import normalize_date

full_date = "2024-03-15"

# Reduce precision
normalize_date(full_date, "year")   # "2024"
normalize_date(full_date, "month")  # "2024-03"
normalize_date(full_date, "day")    # "2024-03-15" (unchanged)

# Increase precision (adds defaults)
year_only = "2024"
normalize_date(year_only, "month")  # "2024-01"
normalize_date(year_only, "day")    # "2024-01-01"

month_only = "2024-03"
normalize_date(month_only, "day")   # "2024-03-01"

Using Fuzzy Dates with Stash

Performer Birthdates

from stash_graphql_client.types import Performer

async with StashContext(conn=conn) as client:
    # Exact birthdate known
    performer1 = await client.create_performer(
        Performer(name="Jane Doe", birthdate="1990-05-15")
    )

    # Only birth year known
    performer2 = await client.create_performer(
        Performer(name="John Smith", birthdate="1985")
    )

    # Birth year and month known
    performer3 = await client.create_performer(
        Performer(name="Alice Johnson", birthdate="1992-07")
    )

Scene Dates

from stash_graphql_client.types import Scene

async with StashContext(conn=conn) as client:
    # Exact date
    scene1 = await client.update_scene(
        Scene(id="scene-id-1", date="2024-03-15")
    )

    # Month precision
    scene2 = await client.update_scene(
        Scene(id="scene-id-2", date="2024-03")
    )

    # Year only
    scene3 = await client.update_scene(
        Scene(id="scene-id-3", date="2024")
    )

Comparing Fuzzy Dates

from stash_graphql_client.types import FuzzyDate

date1 = FuzzyDate("2024")
date2 = FuzzyDate("2024-03")
date3 = FuzzyDate("2024-03-15")

# Compare precision
print(date1.precision == DatePrecision.YEAR)   # True
print(date2.precision == DatePrecision.MONTH)  # True
print(date3.precision == DatePrecision.DAY)    # True

# Compare values
print(date1.value)  # "2024"
print(date2.value)  # "2024-03"
print(date3.value)  # "2024-03-15"

# Compare as datetime (all convert to first day)
dt1 = date1.to_datetime()  # 2024-01-01
dt2 = date2.to_datetime()  # 2024-03-01
dt3 = date3.to_datetime()  # 2024-03-15

print(dt1 < dt2 < dt3)  # True

Handling Unknown Dates

When dates are completely unknown, use None:

from stash_graphql_client.types import Performer

async with StashContext(conn=conn) as client:
    # Birthdate unknown
    performer = await client.create_performer(
        Performer(name="Unknown Birthdate", birthdate=None)
    )

    # Check if birthdate is set
    if performer.birthdate is None:
        print("Birthdate unknown")
    elif performer.birthdate is not UNSET:
        print(f"Birthdate: {performer.birthdate}")

Best Practices

1. Use the Most Specific Precision Available

# ✅ Good - Use what you know
birthdate = "1990-07"  # Year and month known

# ❌ Bad - Guessing the day
birthdate = "1990-07-01"  # Day unknown, shouldn't guess

2. Validate Before Saving

from stash_graphql_client.types import validate_fuzzy_date, Performer

user_input = "2024-3"  # From user

# ✅ Good - Validate first
if validate_fuzzy_date(user_input):
    await client.create_performer(Performer(name="Name", birthdate=user_input))
else:
    # Normalize or fix the input
    normalized = user_input if len(user_input.split('-')[1]) == 2 else f"2024-03"

3. Handle All Precision Levels

# ✅ Good - Handle all cases
date = FuzzyDate(performer.birthdate)

if date.precision == DatePrecision.DAY:
    print(f"Born on {date.value}")
elif date.precision == DatePrecision.MONTH:
    print(f"Born in {date.value}")
else:
    print(f"Born in {date.value}")

# ❌ Bad - Assuming full date
print(f"Born on {performer.birthdate}")  # Might be "1990"!

4. Use Normalization for Consistency

from stash_graphql_client.types import normalize_date

# ✅ Good - Normalize for comparison
dates = ["2024", "2024-03", "2024-03-15"]
normalized = [normalize_date(d, "day") for d in dates]
# All normalized to full dates for comparison

# Sort by normalized dates
sorted_dates = sorted(dates, key=lambda d: normalize_date(d, "day"))

Common Patterns

Age Calculation with Fuzzy Dates

from datetime import datetime
from stash_graphql_client.types import FuzzyDate

def calculate_age(birthdate_str: str) -> int:
    """Calculate age from fuzzy birthdate."""
    fuzzy_date = FuzzyDate(birthdate_str)
    birth_dt = fuzzy_date.to_datetime()
    today = datetime.now()

    age = today.year - birth_dt.year

    # Adjust if birthday hasn't occurred this year yet
    # (Only accurate for full dates)
    if fuzzy_date.precision == DatePrecision.DAY:
        if (today.month, today.day) < (birth_dt.month, birth_dt.day):
            age -= 1

    return age

# Usage
age = calculate_age("1990-05-15")  # Exact
approx_age = calculate_age("1990")  # Approximate

Display Formatting

from stash_graphql_client.types import FuzzyDate, DatePrecision

def format_date(date_str: str) -> str:
    """Format fuzzy date for display."""
    date = FuzzyDate(date_str)

    if date.precision == DatePrecision.YEAR:
        return date.value
    elif date.precision == DatePrecision.MONTH:
        # Convert to "March 2024"
        dt = date.to_datetime()
        return dt.strftime("%B %Y")
    else:
        # Convert to "March 15, 2024"
        dt = date.to_datetime()
        return dt.strftime("%B %d, %Y")

# Usage
print(format_date("2024"))         # "2024"
print(format_date("2024-03"))      # "March 2024"
print(format_date("2024-03-15"))   # "March 15, 2024"

Next Steps