Skip to content

Practical Patterns for datetime

Common patterns and best practices for working with dates and times in real-world applications.

Mental Model

Real-world datetime code revolves around a handful of recurring patterns: computing "time ago" labels, finding business-day boundaries, generating date ranges, and converting between timezones. Mastering these recipes means you rarely need to reinvent the wheel — most datetime tasks are a variation of one of these patterns.

Human-Readable Relative Times

Display times relative to now (e.g., '2 hours ago').

```python from datetime import datetime, timedelta

def time_ago(dt): now = datetime.now() delta = now - dt

if delta.days > 0:
    return f"{delta.days} days ago"
elif delta.seconds > 3600:
    return f"{delta.seconds // 3600} hours ago"
elif delta.seconds > 60:
    return f"{delta.seconds // 60} minutes ago"
else:
    return "just now"

past = datetime.now() - timedelta(hours=2) print(time_ago(past)) ```

2 hours ago

Date Range Iteration

Iterate through a range of dates.

```python from datetime import date, timedelta

def date_range(start: date, end: date): current = start while current <= end: yield current current += timedelta(days=1)

start = date(2024, 12, 23) end = date(2024, 12, 26)

for d in date_range(start, end): print(d) ```

2024-12-23 2024-12-24 2024-12-25 2024-12-26

Scheduling and Cron-like Logic

Check if a task should run based on schedule.

```python from datetime import datetime, timedelta

class Task: def init(self, name, interval_hours): self.name = name self.interval = timedelta(hours=interval_hours) self.last_run = None

def should_run(self):
    if self.last_run is None:
        return True
    return datetime.now() >= self.last_run + self.interval

def run(self):
    print(f"Running {self.name}")
    self.last_run = datetime.now()

task = Task("cleanup", 24) print(f"Should run: {task.should_run()}") task.run() print(f"Should run again: {task.should_run()}") ```

Should run: True Running cleanup Should run again: False


Exercises

Exercise 1. Write an improved time_ago function that also handles weeks and months (approximate, 30 days). It should return strings like "3 weeks ago" or "2 months ago" for longer durations. Test it with various timedelta offsets.

Solution to Exercise 1

```python from datetime import datetime, timedelta

def time_ago(dt): now = datetime.now() delta = now - dt

if delta.days >= 30:
    months = delta.days // 30
    return f"{months} month{'s' if months > 1 else ''} ago"
elif delta.days >= 7:
    weeks = delta.days // 7
    return f"{weeks} week{'s' if weeks > 1 else ''} ago"
elif delta.days > 0:
    return f"{delta.days} day{'s' if delta.days > 1 else ''} ago"
elif delta.seconds >= 3600:
    hours = delta.seconds // 3600
    return f"{hours} hour{'s' if hours > 1 else ''} ago"
elif delta.seconds >= 60:
    minutes = delta.seconds // 60
    return f"{minutes} minute{'s' if minutes > 1 else ''} ago"
else:
    return "just now"

Test

print(time_ago(datetime.now() - timedelta(days=45))) # 1 month ago print(time_ago(datetime.now() - timedelta(days=14))) # 2 weeks ago print(time_ago(datetime.now() - timedelta(hours=3))) # 3 hours ago print(time_ago(datetime.now() - timedelta(seconds=30))) # just now ```


Exercise 2. Write a function business_days_between that takes two date objects and returns the number of business days (Monday-Friday) between them, exclusive of both endpoints. For example, between Monday 2024-12-23 and Friday 2024-12-27, there are 3 business days (Tue, Wed, Thu).

Solution to Exercise 2

```python from datetime import date, timedelta

def business_days_between(start, end): if start > end: start, end = end, start count = 0 current = start + timedelta(days=1) while current < end: if current.weekday() < 5: # Monday=0 to Friday=4 count += 1 current += timedelta(days=1) return count

Test

Mon Dec 23 to Fri Dec 27: Tue, Wed, Thu = 3

print(business_days_between(date(2024, 12, 23), date(2024, 12, 27)))

3

```


Exercise 3. Write a class Countdown that takes a target datetime and provides a method remaining() that returns a human-readable string of the time remaining (days, hours, minutes). If the target has passed, return "Event has passed".

Solution to Exercise 3

```python from datetime import datetime

class Countdown: def init(self, target): self.target = target

def remaining(self):
    now = datetime.now()
    if now >= self.target:
        return "Event has passed"
    delta = self.target - now
    days = delta.days
    hours = delta.seconds // 3600
    minutes = (delta.seconds % 3600) // 60
    return f"{days} days, {hours} hours, {minutes} minutes"

Test

from datetime import timedelta future = datetime.now() + timedelta(days=10, hours=5, minutes=30) cd = Countdown(future) print(cd.remaining()) # ~10 days, 5 hours, 30 minutes

past = datetime.now() - timedelta(days=1) cd2 = Countdown(past) print(cd2.remaining()) # Event has passed ```