stdlib: datetime, zoneinfo, json, csv
Работа с временем и сериализация в JSON/CSV - повседневные backend-задачи. В этом уроке - модули datetime и zoneinfo для timezone-aware дат, json для API-сообщений и csv для табличных данных. Открывать файлы будем через знакомые по прошлому уроку open и pathlib.
datetime - даты и время
from datetime import datetime, date, time, timedelta
# Текущее время (naive - без таймзоны)
now = datetime.now()
print(now) # 2026-05-27 14:30:00.123456
# Конкретная дата
d = date(2026, 5, 27)
print(d) # 2026-05-27
# Время без даты
t = time(14, 30, 0)
# Из строки
dt = datetime.strptime("2026-05-27 14:30:00", "%Y-%m-%d %H:%M:%S")
# В строку
s = dt.strftime("%Y-%m-%d %H:%M:%S")
strptime - parse строки в datetime, strftime - формат datetime в строку. Format codes:
%Y- год 4 цифры%m- месяц с ведущим нулём%d- день%H- часы 24-часовой формат%M- минуты%S- секунды%z- UTC offset%Z- timezone name
ISO 8601 - стандартный формат
# Текущее в ISO формате
dt = datetime.now()
iso = dt.isoformat()
# '2026-05-27T14:30:00.123456'
# Парсинг ISO 8601
dt = datetime.fromisoformat("2026-05-27T14:30:00")
# С Python 3.11+ поддерживает и timezone-aware с Z и +HH:MM
ISO 8601 - предпочтительный формат для API и логов. Сортируется лексикографически, понятен людям и машинам.
timedelta - разница и арифметика
from datetime import timedelta
dt = datetime.now()
yesterday = dt - timedelta(days=1)
in_one_hour = dt + timedelta(hours=1)
in_30_min = dt + timedelta(minutes=30)
# Разница
delta = dt - yesterday
print(delta.days) # 1
print(delta.total_seconds()) # 86400
timedelta принимает days, hours, minutes, seconds, microseconds, milliseconds, weeks. Можно складывать/вычитать с datetime.
timezone aware - современная практика
from datetime import datetime, timezone
# UTC
now = datetime.now(timezone.utc)
print(now) # 2026-05-27 14:30:00.123456+00:00
# Naive (без таймзоны)
naive = datetime.now()
# print(naive.timestamp()) # OS-specific интерпретация - опасно!
Правило: всегда используй timezone-aware datetimes. Naive datetimes - источник багов в multi-timezone applications.
zoneinfo - таймзоны (Py 3.9+)
from datetime import datetime
from zoneinfo import ZoneInfo
# Текущее время в Москве
moscow = datetime.now(ZoneInfo("Europe/Moscow"))
print(moscow) # 2026-05-27 17:30:00+03:00
# Конкретная дата в таймзоне
dt = datetime(2026, 5, 27, 14, 0, tzinfo=ZoneInfo("UTC"))
print(dt.astimezone(ZoneInfo("America/New_York")))
# 2026-05-27 10:00:00-04:00
zoneinfo использует IANA database, корректно обрабатывает переход на летнее время, исторические изменения.
До Python 3.9 использовалась библиотека pytz. Сейчас zoneinfo встроенный и рекомендуется.
Сравнение и форматирование
dt1 = datetime(2026, 5, 27, 14, 0, tzinfo=timezone.utc)
dt2 = datetime(2026, 5, 27, 17, 0, tzinfo=ZoneInfo("Europe/Moscow"))
# Сравнение timezone-aware работает корректно
print(dt1 == dt2) # True - один момент времени
# Сравнение naive и aware - TypeError
naive = datetime(2026, 5, 27, 14, 0)
# dt1 == naive # TypeError - нельзя сравнивать
Mixing aware и naive - TypeError. Это спасает от багов: либо везде aware, либо везде naive.
Распространённые задачи
from datetime import datetime, timedelta, timezone
# Сейчас в UTC
now_utc = datetime.now(timezone.utc)
# Начало дня
start_of_day = now_utc.replace(hour=0, minute=0, second=0, microsecond=0)
# Возраст по дате рождения
from datetime import date
birth = date(1990, 5, 15)
today = date.today()
age = (today - birth).days // 365 # упрощённо
# Unix timestamp
ts = now_utc.timestamp()
# Из timestamp
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
# Только дата
just_date = now_utc.date()
json - сериализация
import json
# Object → JSON string
data = {"name": "Alice", "age": 30, "tags": ["python", "backend"]}
json_str = json.dumps(data)
# '{"name": "Alice", "age": 30, "tags": ["python", "backend"]}'
# Pretty
json_str = json.dumps(data, indent=2)
# С Unicode
json_str = json.dumps({"name": "Алиса"}, ensure_ascii=False)
# Без ensure_ascii=False: {"name": "Алиса"}
# JSON string → Python object
data = json.loads('{"name": "Bob"}')
# Из/в файла
with open("data.json", "w") as f:
json.dump(data, f, indent=2)
with open("data.json") as f:
data = json.load(f)
dumps/loads для строк (s = string). dump/load для файлов. Это convention для всех serialization модулей в Python.
JSON и нестандартные типы
JSON поддерживает только: str, int, float, bool, None, list, dict. Чтобы сериализовать datetime, UUID, set:
import json
from datetime import datetime
from uuid import UUID
data = {
"id": UUID("12345678-1234-5678-1234-567812345678"),
"created": datetime.now(timezone.utc),
"tags": {"a", "b"},
}
# json.dumps(data) # TypeError
# Custom encoder
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, set):
return list(obj)
return super().default(obj)
json_str = json.dumps(data, cls=CustomEncoder)
Или через default= параметр:
def custom(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, set):
return list(obj)
raise TypeError(f"Not serializable: {type(obj)}")
json.dumps(data, default=custom)
В реальном коде backend часто использует Pydantic или dataclasses_json - они автоматически сериализуют сложные типы, включая dataclass и Enum.
json.tool - CLI форматирование
echo '{"name":"Alice","age":30}' | python -m json.tool
# {
# "name": "Alice",
# "age": 30
# }
python -m json.tool < input.json > formatted.json
Удобно для проверки/форматирования JSON в shell.
csv - работа с табличными данными
import csv
# Запись
with open("data.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["name", "age", "city"])
writer.writerow(["Alice", 30, "Moscow"])
writer.writerow(["Bob", 25, "London"])
# Чтение
with open("data.csv") as f:
reader = csv.reader(f)
for row in reader:
print(row) # список значений
newline="" в open важно - csv модуль сам обрабатывает line endings.
DictReader / DictWriter
Удобнее для именованных полей:
import csv
# Запись с заголовками
with open("data.csv", "w", newline="") as f:
fields = ["name", "age", "city"]
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerow({"name": "Alice", "age": 30, "city": "Moscow"})
writer.writerow({"name": "Bob", "age": 25, "city": "London"})
# Чтение как словари
with open("data.csv") as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['name']}: {row['age']}")
DictReader первая строка - заголовки, остальные - dict. Удобнее чем индексирование по позиции.
CSV dialect
# Tab-separated
with open("data.tsv", "w", newline="") as f:
writer = csv.writer(f, delimiter="\t")
writer.writerow(["a", "b", "c"])
# Excel dialect (default)
csv.writer(f, dialect="excel")
# Кастомный dialect
csv.register_dialect("pipe", delimiter="|", quoting=csv.QUOTE_ALL)
writer = csv.writer(f, dialect="pipe")
CSV не строгий стандарт - разные программы используют разные форматы. dialect помогает работать с конкретным форматом.
Большие CSV файлы
# Чтение построчно - не загружает весь файл в память
with open("huge.csv") as f:
reader = csv.DictReader(f)
for row in reader:
process(row)
csv.reader/DictReader ленивые - читают построчно. Подходит для очень больших файлов.
Для очень крупных и сложных табличных данных лучше pandas или polars. Стандартный csv подходит для простых случаев.
Другие форматы из stdlib
import configparser
config = configparser.ConfigParser()
config.read("config.ini")
print(config["section"]["key"])
import tomllib # Python 3.11+, только чтение
with open("config.toml", "rb") as f:
data = tomllib.load(f)
Запись TOML - через сторонние tomli_w или tomlkit. Чтение - в стандартной библиотеке с Python 3.11.
Распространённые ошибки
1. Naive datetime в производственном коде
# Плохо - не таймзонно-aware
dt = datetime.now()
expires = dt + timedelta(hours=1)
# Если сервер в одной таймзоне, клиент в другой - баги
# Хорошо
dt = datetime.now(timezone.utc)
expires = dt + timedelta(hours=1)
Всегда timezone-aware в API, БД, логах.
2. JSON без ensure_ascii=False для нелатинских символов
json.dumps({"name": "Иван"})
# '{"name": "Иван"}' # читаемо но многословно
json.dumps({"name": "Иван"}, ensure_ascii=False)
# '{"name": "Иван"}' # компактнее
Для frontend-API и человекочитаемых логов - ensure_ascii=False.
3. csv без newline=""
with open("data.csv", "w") as f: # без newline
csv.writer(f).writerow(["a", "b"])
# На Windows может быть двойные line endings
Всегда newline="" при работе с csv.
4. strftime/strptime для парсинга ISO
# Плохо - сложный format string
datetime.strptime("2026-05-27T14:30:00", "%Y-%m-%dT%H:%M:%S")
# Лучше для ISO
datetime.fromisoformat("2026-05-27T14:30:00")
fromisoformat для ISO 8601, strptime для специфичных форматов.
5. Сравнение naive и aware datetimes
naive = datetime(2026, 5, 27)
aware = datetime(2026, 5, 27, tzinfo=timezone.utc)
# naive < aware # TypeError
Используй единый стиль (aware) везде.
Сравнение с Go
В Go есть time пакет:
now := time.Now()
inOneHour := now.Add(time.Hour)
// Часовые пояса
loc, _ := time.LoadLocation("Europe/Moscow")
moscow := now.In(loc)
// JSON через encoding/json
data, _ := json.Marshal(map[string]any{"name": "Alice"})
Go более строго с типами и timezone-awareness (Time всегда aware). Python flexible (aware и naive) но требует дисциплины. Семантически близко.
Мини-задание
- Datetime с таймзоной:
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# Текущее время UTC и в Москве
utc_now = datetime.now(timezone.utc)
msk_now = utc_now.astimezone(ZoneInfo("Europe/Moscow"))
print(f"UTC: {utc_now.isoformat()}")
print(f"MSK: {msk_now.isoformat()}")
# Через 30 дней
from datetime import timedelta
future = utc_now + timedelta(days=30)
print(f"In 30 days: {future.isoformat()}")
- JSON с custom encoder:
import json
from datetime import datetime, timezone
from uuid import uuid4
data = {
"id": uuid4(),
"created": datetime.now(timezone.utc),
"name": "Алиса",
"tags": {"backend", "python"},
}
def encode(obj):
if isinstance(obj, datetime):
return obj.isoformat()
from uuid import UUID
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, set):
return sorted(obj)
raise TypeError(f"Not serializable: {type(obj)}")
print(json.dumps(data, default=encode, ensure_ascii=False, indent=2))
- CSV с DictReader/Writer:
import csv
users = [
{"name": "Alice", "age": 30, "email": "alice@example.com"},
{"name": "Bob", "age": 25, "email": "bob@example.com"},
{"name": "Charlie", "age": 35, "email": "charlie@example.com"},
]
# Запись
with open("users.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "email"])
writer.writeheader()
writer.writerows(users)
# Чтение
with open("users.csv") as f:
for row in csv.DictReader(f):
print(f"{row['name']} - {row['email']}")
Что дальше
Освоили работу с датами и сериализацию. В следующем уроке (последнем в Модуле 8) - logging: стандартный механизм журналирования вместо print, конфигурация, обработчики.