Python · Syntax · Intermediate

datetime and json modules

10 tasks

Working with dates and times using the datetime module, and serializing data with the json module.

datetime basics: naive vs aware, zoneinfo, strptime/strftime

#
**datetime basics** ```python from datetime import datetime, date, time, timezone # Current UTC time (naive vs aware) datetime.now() # naive — no timezone, uses local clock datetime.utcnow() # naive UTC — deprecated in Python 3.12 datetime.now(timezone.utc) # aware UTC — preferred # Creating specific dates d = date(2024, 3, 15) # year, month, day dt = datetime(2024, 3, 15, 10, 30) # year, month, day, hour, minute ``` **Naive vs aware datetimes** A *naive* datetime has no timezone info — Python doesn't know if it's UTC, New York, or Tokyo. An *aware* datetime carries a `tzinfo` and can be compared across time zones: ```python from datetime import datetime, timezone, timedelta naive = datetime(2024, 3, 15, 10, 0) # no tzinfo aware = datetime(2024, 3, 15, 10, 0, tzinfo=timezone.utc) # UTC # You cannot compare naive and aware: naive < aware # TypeError: can't compare offset-naive and offset-aware # Custom offset timezone: tz_kyiv = timezone(timedelta(hours=3)) # UTC+3 dt_kyiv = datetime(2024, 3, 15, 13, 0, tzinfo=tz_kyiv) ``` **zoneinfo — named timezones (Python 3.9+)** ```python from zoneinfo import ZoneInfo tz = ZoneInfo('Europe/Kyiv') dt = datetime(2024, 3, 15, 10, 0, tzinfo=tz) dt.isoformat() # '2024-03-15T10:00:00+02:00' # Convert between zones: dt_ny = dt.astimezone(ZoneInfo('America/New_York')) # Same moment, different local time ``` **Parsing and formatting** ```python # Parse from string dt = datetime.strptime('2024-03-15 10:30', '%Y-%m-%d %H:%M') # Format to string dt.strftime('%d %B %Y') # '15 March 2024' dt.isoformat() # '2024-03-15T10:30:00' # Parse ISO format directly (Python 3.7+): datetime.fromisoformat('2024-03-15T10:30:00') # no Z support until 3.11 ``` Common `strftime` / `strptime` codes: ``` %Y 4-digit year %m month (01-12) %d day (01-31) %H hour (00-23) %M minute (00-59) %S second (00-59) %A weekday name %B month name %f microseconds ```

timedelta arithmetic, calendar module, and isoweekday

#
**timedelta — date arithmetic** ```python from datetime import datetime, timedelta now = datetime(2024, 3, 15, 10, 0) # Add / subtract time tomorrow = now + timedelta(days=1) next_week = now + timedelta(weeks=1) two_hours = now + timedelta(hours=2) yesterday = now - timedelta(days=1) # Difference between two datetimes gives a timedelta start = datetime(2024, 1, 1) end = datetime(2024, 3, 15) diff = end - start # timedelta(days=74) diff.days # 74 diff.total_seconds() # 6393600.0 ``` **timedelta components** ```python td = timedelta(days=2, hours=3, minutes=30) td.days # 2 td.seconds # 12600 (only the sub-day seconds!) td.total_seconds() # 185400.0 (always use this for comparisons) # Decompose total_seconds into hours and minutes: total = int(td.total_seconds()) hours, rem = divmod(total, 3600) minutes, _ = divmod(rem, 60) print(f'{hours}h {minutes}m') # '51h 30m' ``` **calendar module** ```python import calendar # Days in a month (handles leap years): calendar.monthrange(2024, 2) # (3, 29) — starts Thursday, 29 days calendar.monthrange(2024, 2)[1] # 29 # Day of week (0=Monday, 6=Sunday): calendar.weekday(2024, 3, 15) # 4 — Friday # Check for leap year: calendar.isleap(2024) # True # .isoweekday() on datetime: 1=Monday, 7=Sunday (ISO standard) from datetime import date date(2024, 3, 15).isoweekday() # 5 — Friday date(2024, 3, 15).weekday() # 4 — Friday (0-indexed) ``` **date.today() vs datetime.now()** ```python from datetime import date, datetime date.today() # date(2024, 3, 15) — date only, no time datetime.today() # naive datetime with local time datetime.now(timezone.utc) # aware UTC datetime — use for logging/APIs ```

json.dumps/loads vs dump/load (files), serializing datetimes, object_hook

#
**json.dumps / json.loads — strings** ```python import json data = {'name': 'Alice', 'age': 30, 'active': True} # Serialize to a JSON string text = json.dumps(data) # '{"name": "Alice", "age": 30, "active": true}' text = json.dumps(data, indent=2) # pretty-printed text = json.dumps(data, ensure_ascii=False) # preserve unicode chars # Deserialize from a JSON string obj = json.loads(text) # {'name': 'Alice', 'age': 30, 'active': True} ``` **json.dump / json.load — files** `json.dump` / `json.load` (no 's') work with **file objects**, not strings: ```python # Write to file with open('data.json', 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) # Read from file with open('data.json', 'r', encoding='utf-8') as f: obj = json.load(f) ``` Memory aid: `dumps`/`loads` → **s**tring; `dump`/`load` → file. **Serializing datetimes** JSON has no date type. The standard approach is ISO 8601 strings: ```python from datetime import datetime, timezone import json now = datetime.now(timezone.utc) # json.dumps doesn't know how to serialize datetime by default: json.dumps({'created': now}) # TypeError: Object of type datetime is not JSON serializable # Fix 1 — convert manually: json.dumps({'created': now.isoformat()}) # '{"created": "2024-03-15T10:30:00+00:00"}' # Fix 2 — custom default function: def json_default(obj): if isinstance(obj, datetime): return obj.isoformat() raise TypeError(f'Not serializable: {type(obj)}') json.dumps({'created': now}, default=json_default) ``` **Parsing datetimes back** ```python # json.loads gives you strings, not datetimes: obj = json.loads('{"created": "2024-03-15T10:30:00+00:00"}') obj['created'] # '2024-03-15T10:30:00+00:00' — still a string # Convert manually: dt = datetime.fromisoformat(obj['created']) # Python 3.7+ # Or use a hook on json.loads: ISO_KEYS = {'created', 'updated', 'timestamp'} def parse_dates(d): for key in ISO_KEYS & d.keys(): try: d[key] = datetime.fromisoformat(d[key]) except (ValueError, TypeError): pass return d obj = json.loads(text, object_hook=parse_dates) # obj['created'] is now a datetime object ```
01

#

Parse the string '2024-03-15' into a `datetime.date` object and return it.

from datetime import date

def parse_date(s):
    # your code here
    pass

print(parse_date('2024-03-15'))  # 2024-03-15
print(type(parse_date('2024-03-15')))  # <class 'datetime.date'>
Solution
from datetime import date

def parse_date(s):
    return date.fromisoformat(s)
02

#

Parse the string '15/03/2024 14:30' into a `datetime` object using `strptime`.

from datetime import datetime

def parse_datetime(s):
    # your code here
    pass

dt = parse_datetime('15/03/2024 14:30')
print(dt.year, dt.month, dt.day)   # 2024 3 15
print(dt.hour, dt.minute)           # 14 30
Solution
from datetime import datetime

def parse_datetime(s):
    return datetime.strptime(s, '%d/%m/%Y %H:%M')
03

#

Given a `datetime` object, format it as 'March 15, 2024' (e.g., full month name, day, year).

from datetime import datetime

def format_date(dt):
    # your code here
    pass

from datetime import datetime
print(format_date(datetime(2024, 3, 15)))   # March 15, 2024
print(format_date(datetime(2024, 11, 7)))   # November 7, 2024
Solution
from datetime import datetime

def format_date(dt):
    return dt.strftime('%B %-d, %Y')
04

#

Given two `date` objects, return the number of days between them (always positive).

from datetime import date

def days_between(d1, d2):
    # your code here
    pass

from datetime import date
print(days_between(date(2024, 1, 1), date(2024, 3, 15)))  # 74
print(days_between(date(2024, 3, 15), date(2024, 1, 1)))  # 74
Solution
from datetime import date

def days_between(d1, d2):
    return abs((d2 - d1).days)
05

#

Serialize a dictionary to a JSON string. The dict may contain date objects — convert them to ISO strings (YYYY-MM-DD) before serializing.

import json
from datetime import date

def serialize(data):
    # your code here
    pass

d = {'name': 'Alice', 'birthday': date(1990, 5, 21), 'score': 42}
print(serialize(d))  # '{"name": "Alice", "birthday": "1990-05-21", "score": 42}'
Solution
import json
from datetime import date

def serialize(data):
    def default(obj):
        if isinstance(obj, date):
            return obj.isoformat()
        raise TypeError(f'Object of type {type(obj)} is not JSON serializable')
    return json.dumps(data, default=default)
06

#

Parse a JSON string into a Python object. If a key ends with '_date', convert its value from a string to a `datetime.date`.

import json
from datetime import date

def deserialize(s):
    # your code here
    pass

s = '{"name": "Alice", "start_date": "2024-01-15", "score": 42}'
result = deserialize(s)
print(result['start_date'])        # 2024-01-15
print(type(result['start_date']))  # <class 'datetime.date'>
print(result['score'])             # 42
Solution
import json
from datetime import date

def deserialize(s):
    def object_hook(d):
        for k, v in d.items():
            if k.endswith('_date') and isinstance(v, str):
                d[k] = date.fromisoformat(v)
        return d
    return json.loads(s, object_hook=object_hook)
07

#

Given a list of ISO date strings, return them sorted from oldest to newest.

def sort_dates(dates):
    # your code here
    pass

dates = ['2024-11-01', '2023-03-15', '2024-01-20', '2022-12-31']
print(sort_dates(dates))
# ['2022-12-31', '2023-03-15', '2024-01-20', '2024-11-01']
Solution
def sort_dates(dates):
    return sorted(dates)
08

#

Given a list of event dicts with an 'date' key (ISO string), return the event with the most recent date.

def latest_event(events):
    # your code here
    pass

events = [
    {'name': 'Conference', 'date': '2024-09-12'},
    {'name': 'Workshop',   'date': '2024-11-03'},
    {'name': 'Meetup',     'date': '2024-07-20'},
]
print(latest_event(events)['name'])  # Workshop
Solution
def latest_event(events):
    return max(events, key=lambda e: e['date'])
09

#

Given a date, return the date of the first day of that month.

from datetime import date

def first_of_month(d):
    # your code here
    pass

from datetime import date
print(first_of_month(date(2024, 3, 15)))  # 2024-03-01
print(first_of_month(date(2024, 12, 31))) # 2024-12-01
Solution
from datetime import date

def first_of_month(d):
    return d.replace(day=1)
10

#

Given a list of JSON records (each with an 'amount' field), compute the total. Return 0 if the list is empty.

import json

def total_amount(records_json):
    # your code here
    pass

records = '[{"item": "book", "amount": 12.5}, {"item": "pen", "amount": 3.0}, {"item": "bag", "amount": 25.0}]'
print(total_amount(records))    # 40.5
print(total_amount('[]'))       # 0
Solution
import json

def total_amount(records_json):
    records = json.loads(records_json)
    return sum(r['amount'] for r in records)