Python · Синтаксис · Проміжний

Модулі datetime і json

10 завдань

Робота з датами та часом за допомогою модуля datetime та серіалізація даних за допомогою модуля json.

Основи datetime: naive vs aware, zoneinfo, strptime/strftime

#
**Основи datetime** ```python from datetime import datetime, date, time, timezone # Поточний UTC час (naive vs aware) datetime.now() # naive — без часового поясу, використовує локальний годинник datetime.utcnow() # naive UTC — застарілий у Python 3.12 datetime.now(timezone.utc) # aware UTC — рекомендований # Створення конкретних дат d = date(2024, 3, 15) # рік, місяць, день dt = datetime(2024, 3, 15, 10, 30) # рік, місяць, день, година, хвилина ``` **Naive vs aware datetime** Об'єкт *naive* datetime не має інформації про часовий пояс — Python не знає, чи це UTC, Нью-Йорк, чи Токіо. Об'єкт *aware* datetime містить `tzinfo` і може порівнюватись між часовими поясами: ```python from datetime import datetime, timezone, timedelta naive = datetime(2024, 3, 15, 10, 0) # без tzinfo aware = datetime(2024, 3, 15, 10, 0, tzinfo=timezone.utc) # UTC # Порівняння naive і aware неможливе: naive < aware # TypeError: can't compare offset-naive and offset-aware # Кастомний зсув часового поясу: tz_kyiv = timezone(timedelta(hours=3)) # UTC+3 dt_kyiv = datetime(2024, 3, 15, 13, 0, tzinfo=tz_kyiv) ``` **zoneinfo — іменовані часові пояси (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' # Конвертувати між часовими поясами: dt_ny = dt.astimezone(ZoneInfo('America/New_York')) # Той самий момент, інший локальний час ``` **Парсинг та форматування** ```python # Парсити з рядка dt = datetime.strptime('2024-03-15 10:30', '%Y-%m-%d %H:%M') # Форматувати в рядок dt.strftime('%d %B %Y') # '15 March 2024' dt.isoformat() # '2024-03-15T10:30:00' # Парсити ISO-формат напряму (Python 3.7+): datetime.fromisoformat('2024-03-15T10:30:00') # без Z до Python 3.11 ``` Поширені коди `strftime` / `strptime`: ``` %Y 4-значний рік %m місяць (01-12) %d день (01-31) %H година (00-23) %M хвилина (00-59) %S секунда (00-59) %A назва дня тижня %B назва місяця %f мікросекунди ```

Арифметика timedelta, модуль calendar та isoweekday

#
**timedelta — арифметика дат** ```python from datetime import datetime, timedelta now = datetime(2024, 3, 15, 10, 0) # Додати / відняти час tomorrow = now + timedelta(days=1) next_week = now + timedelta(weeks=1) two_hours = now + timedelta(hours=2) yesterday = now - timedelta(days=1) # Різниця між двома datetime дає 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** ```python td = timedelta(days=2, hours=3, minutes=30) td.days # 2 td.seconds # 12600 (лише секунди, що не входять у дні!) td.total_seconds() # 185400.0 (завжди використовуй для порівнянь) # Розкласти total_seconds на години та хвилини: total = int(td.total_seconds()) hours, rem = divmod(total, 3600) minutes, _ = divmod(rem, 60) print(f'{hours}год {minutes}хв') # '51год 30хв' ``` **Модуль calendar** ```python import calendar # Кількість днів у місяці (враховує високосні роки): calendar.monthrange(2024, 2) # (3, 29) — починається у четвер, 29 днів calendar.monthrange(2024, 2)[1] # 29 # День тижня (0=Понеділок, 6=Неділя): calendar.weekday(2024, 3, 15) # 4 — П'ятниця # Перевірити високосний рік: calendar.isleap(2024) # True # .isoweekday() на datetime: 1=Понеділок, 7=Неділя (ISO стандарт) from datetime import date date(2024, 3, 15).isoweekday() # 5 — П'ятниця date(2024, 3, 15).weekday() # 4 — П'ятниця (з нуля) ``` **date.today() vs datetime.now()** ```python from datetime import date, datetime date.today() # date(2024, 3, 15) — лише дата, без часу datetime.today() # naive datetime з локальним часом datetime.now(timezone.utc) # aware UTC datetime — використовуй для логів/API ```

json.dumps/loads vs dump/load (файли), серіалізація datetime, object_hook

#
**json.dumps / json.loads — рядки** ```python import json data = {'name': 'Alice', 'age': 30, 'active': True} # Серіалізувати в JSON-рядок text = json.dumps(data) # '{"name": "Alice", "age": 30, "active": true}' text = json.dumps(data, indent=2) # з відступами text = json.dumps(data, ensure_ascii=False) # зберегти unicode символи # Десеріалізувати з JSON-рядка obj = json.loads(text) # {'name': 'Alice', 'age': 30, 'active': True} ``` **json.dump / json.load — файли** `json.dump` / `json.load` (без 's') працюють з **файловими об'єктами**, не рядками: ```python # Записати у файл with open('data.json', 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) # Прочитати з файлу with open('data.json', 'r', encoding='utf-8') as f: obj = json.load(f) ``` Підказка: `dumps`/`loads` → **s**tring (рядок); `dump`/`load` → файл. **Серіалізація datetime** JSON не має типу дати. Стандартний підхід — рядки ISO 8601: ```python from datetime import datetime, timezone import json now = datetime.now(timezone.utc) # json.dumps не знає, як серіалізувати datetime: json.dumps({'created': now}) # TypeError: Object of type datetime is not JSON serializable # Рішення 1 — конвертувати вручну: json.dumps({'created': now.isoformat()}) # Рішення 2 — кастомна функція default: def json_default(obj): if isinstance(obj, datetime): return obj.isoformat() raise TypeError(f'Не серіалізується: {type(obj)}') json.dumps({'created': now}, default=json_default) ``` **Парсинг datetime назад** ```python # json.loads дає рядки, не datetime: obj = json.loads('{"created": "2024-03-15T10:30:00+00:00"}') obj['created'] # '2024-03-15T10:30:00+00:00' — ще рядок # Конвертувати вручну: dt = datetime.fromisoformat(obj['created']) # Python 3.7+ # Або через hook: 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'] тепер є об'єктом datetime ```
01

#

Розберіть рядок '2024-03-15' в об'єкт `datetime.date` і поверніть його.

from datetime import date

def parse_date(s):
    # ваш код тут
    pass

print(parse_date('2024-03-15'))  # 2024-03-15
print(type(parse_date('2024-03-15')))  # <class 'datetime.date'>
Рішення
from datetime import date

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

#

Розберіть рядок '15/03/2024 14:30' в об'єкт `datetime` за допомогою `strptime`.

from datetime import datetime

def parse_datetime(s):
    # ваш код тут
    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
Рішення
from datetime import datetime

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

#

Для заданого об'єкта `datetime` поверніть рядок у форматі 'March 15, 2024' (повна назва місяця, день, рік).

from datetime import datetime

def format_date(dt):
    # ваш код тут
    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
Рішення
from datetime import datetime

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

#

Для двох об'єктів `date` поверніть кількість днів між ними (завжди додатнє).

from datetime import date

def days_between(d1, d2):
    # ваш код тут
    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
Рішення
from datetime import date

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

#

Серіалізуйте словник в рядок JSON. Словник може містити об'єкти date — перетворіть їх у рядки ISO (YYYY-MM-DD) перед серіалізацією.

import json
from datetime import date

def serialize(data):
    # ваш код тут
    pass

d = {'name': 'Alice', 'birthday': date(1990, 5, 21), 'score': 42}
print(serialize(d))  # '{"name": "Alice", "birthday": "1990-05-21", "score": 42}'
Рішення
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

#

Розберіть рядок JSON в об'єкт Python. Якщо ключ закінчується на '_date', перетворіть його значення з рядка в `datetime.date`.

import json
from datetime import date

def deserialize(s):
    # ваш код тут
    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
Рішення
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

#

Для списку рядків дат у форматі ISO поверніть їх відсортованими від найстарішої до найновішої.

def sort_dates(dates):
    # ваш код тут
    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']
Рішення
def sort_dates(dates):
    return sorted(dates)
08

#

Для списку словників подій з ключем 'date' (рядок ISO) поверніть подію з найновішою датою.

def latest_event(events):
    # ваш код тут
    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
Рішення
def latest_event(events):
    return max(events, key=lambda e: e['date'])
09

#

Для заданої дати поверніть дату першого дня того самого місяця.

from datetime import date

def first_of_month(d):
    # ваш код тут
    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
Рішення
from datetime import date

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

#

Для списку JSON-записів (кожен з полем 'amount') обчисліть загальну суму. Поверніть 0, якщо список порожній.

import json

def total_amount(records_json):
    # ваш код тут
    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
Рішення
import json

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