📦 Репозиторий занятия 58

Урок 58. Файлы JSON и модуль datetime

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции240 строк

Модуль json: сериализация и десериализация

less_30__JSON__datetime/theory_01__JSON.md

Вводит JSON как текстовый формат, близкий к словарям Python, и разбирает встроенный модуль json: loads/load для разбора JSON в Python-объекты и dumps/dump для обратного превращения. Отдельно разобраны параметры dumps — indent, sort_keys, ensure_ascii — и способ сериализовать объекты, не поддерживаемые «из коробки» (например, datetime), через параметр default=str. Последняя часть — обработка некорректного JSON через json.JSONDecodeError.

  • Соответствие JSON ↔ Python: объект↔dict, массив↔list, null↔None
  • json.loads()/json.load() — десериализация из строки и файла
  • json.dumps()/json.dump() — сериализация в строку и файл
  • Параметры indent, sort_keys, ensure_ascii=False (кириллица без \uXXXX)
  • default=str — сериализация datetime и других «неродных» объектов
  • json.JSONDecodeError при некорректном JSON, обёрнутый в try/except
Показать начало файла (240 строк всего)
## 1. Что такое JSON

**JSON (JavaScript Object Notation)** — это текстовый формат для хранения и обмена данными.  
Он похож на словари Python:

* Объекты JSON ↔ словари Python (`dict`)
* Массивы JSON ↔ списки Python (`list`)
* Строки, числа, булевы значения и `null` ↔ строки, числа, `True/False`, `None` в Python

**Пример JSON:**

```json
{
  "name": "Alice",
  "age": 30,
  "is_student": false,
  "courses": ["Math", "Physics"],
  "is_active": true
}
```

---

## 2. Модуль `json` в Python

Python имеет встроенный модуль `json`, который позволяет:

1. **Парсить JSON в Python объекты (Десериализация)** (`json.loads`, `json.load`)
2. **Конвертировать Python объекты в JSON (Сериализация)** (`json.dumps`, `json.dump`)

---

## 3. Основные функции модуля

### 3.1 Десериализация: преобразование JSON в Python (`load` и `loads`)

* `json.loads()` — преобразует JSON-строку в Python-объект
* `json.load()` — считывает JSON из файла и преобразует в Python-объект

```python
import json

json_string = '{"name": "Alice", "age": 30, "is_student": false}'
data = json.loads(json_string)  # str -> dict
print(data)
print(data['name'])
```

Для чтения из файла:

```python
with open("data.json", "r") as f:
    data = json.load(f)
```

---

### 3.2 Сериализация: преобразование Python в JSON (`dump` и `dumps`)

* `json.dumps()` — конвертирует Python-объект в JSON-строку
…
Проверьте себя: Что вернёт json.dumps({'time': datetime.now()}) без параметра default, и как это исправить одним аргументом?
2
MarkdownРазбор концепции67 строк

JSON и Python dict — похожи, но не одно и то же

less_30__JSON__datetime/theory_02__JSON_vs_dict.md

Разбирает синтаксические и семантические различия между JSON и словарём Python: обязательные двойные кавычки в JSON против произвольных в Python, запрет висячей запятой после последнего элемента в JSON (при этом PEP 8 её рекомендует в Python), более узкий набор типов и запрет комментариев в JSON. Завершается сводной таблицей соответствия типов и ключевым уточнением — ключи объекта в JSON обязаны быть строками, а в dict подойдёт любой hashable-объект.

  • Кавычки: только двойные в JSON, любые в Python
  • Висячая запятая: ошибка в JSON, рекомендуется PEP 8 в Python
  • Таблица соответствия типов: object/array/string/number/bool/null
  • Ключи объекта: только строки в JSON vs любой hashable в dict
  • Комментарии: недопустимы в JSON, допустимы в Python-коде
Показать начало файла (67 строк всего)
## Различие форматов JSON и Python dict

JSON и словарь Python выглядят похожими: 
* оба представляют собой структуру “ключ–значение” и позволяют вкладывать 
  * массивы, 
  * объекты 
  * и простые типы (не коллекции). 

Однако между ними есть важные синтаксические и семантические различия:

### 1. Кавычки

**JSON:**

* Строго требует **двойные кавычки** для строк и ключей: `"name": "Alice"`.
* Одинарные кавычки **недопустимы**.

**Python dict:**

* Строки и ключи могут быть в **одинарных или двойных кавычках** — это на усмотрение разработчика: `'name': "Alice"`.

### 2. Запятая после последнего элемента

**JSON:**

* **Запрещает** запятую после последнего элемента объекта или массива.

```json
{
    "name": "Alice",
    "age": 25, // Ошибка!
}
```

**Python dict:**

* Запятая не просто разрешения, а даже рекомендована PEP8:

```python
{
    "name": "Alice",
    "age": 25,
}
```

### 3. Типы данных

JSON поддерживает более узкий набор типов и требует более строгого синтаксиса.  
Python dict — боле гибок, но его содержимое нужно сериализовать, чтобы оно стало валидным JSON.

---

## Таблица соответствия типов данных

| Концепция / тип          | JSON                   | Python (dict / данные)                      |
| ------------------------ | ---------------------- |---------------------------------------------|
| Объект                   | `{ ... }`              | `dict`                                      |
| Массив                   | `[ ... ]`              | `list` (иногда `tuple`, но в JSON → list)   |
| Строка                   | `"text"` (только `"`!) | `'text'` или `"text"`                       |
| Числа целые              | `123`                  | `int`                                       |
…
Проверьте себя: Почему словарь с ключом-числом или ключом-кортежем нельзя напрямую превратить в валидный JSON без предварительного преобразования ключей?
3
MarkdownРазбор концепции136 строк

Модуль datetime: классы datetime, date, time, timedelta

less_30__JSON__datetime/theory_03__datetime.md

Полный обзор стандартного модуля datetime — с таблицами методов и результатами примеров для четырёх классов: datetime (now, strptime/strftime, isoformat, timestamp), date (today, weekday/isoweekday), time и timedelta. Отдельно разобраны три формата представления времени (ISO 8601, Unix timestamp, произвольная строка через strftime/strptime) и правило арифметики: два datetime можно вычесть, но нельзя сложить, а datetime и timedelta — можно и складывать, и вычитать.

  • Класс datetime: now/today/fromtimestamp/strptime/strftime/isoformat/timestamp
  • Класс date: today/weekday/isoweekday/strftime
  • Класс time: isoformat/replace/strftime
  • Класс timedelta: арифметика с датами, total_seconds()
  • Три формата: ISO 8601, UNIX timestamp, строковый по шаблону
  • Функции модуля: datetime.combine(), fromisoformat() для всех трёх классов
Показать начало файла (136 строк всего)
## Модуль `datetime`

Модуль **`datetime`** — стандартная библиотека Python для работы с 
* датами, 
* временем, 
* часовыми поясами 
* и временными интервалами. 

Он предоставляет классы, которые позволяют удобно 
* создавать, 
* сравнивать, 
* форматировать 
* и производить вычисления с датами и временем.

---

## Обзор популярных методов `datetime` **с результатами примеров**

### 1. Класс `datetime`

```python
from datetime import datetime as dt
```

[Коды форматирования](https://docs.python.org/3.12/library/datetime.html#format-codes)

| Метод / Атрибут     | Описание                                          | Пример                                  | Результат                      |
| ------------------- |---------------------------------------------------|-----------------------------------------| ------------------------------ |
| `now()`             | Текущие дата и время                              | `dt.now()`                              | `2025-11-22 14:36:12.123456`   |
| `today()`           | Текущая дата и время (как now)                    | `dt.today()`                            | `2025-11-22 14:36:12.123456`   |
| `fromtimestamp(ts)` | Из unix-timestamp                                 | `dt.fromtimestamp(1700000000)`          | `2023-11-14 02:13:20`          |
| `strptime()`        | **STR**ing **P**arse **T**ime: str -> datetime    | `dt.strptime("2024-01-01", "%Y-%m-%d")` | `2024-01-01 00:00:00`          |
| `strftime()`        | **STR**ing **F**ormat **T**ime: datetime -> str   | `dt.now().strftime("%Y-%m-%d")`         | `"2025-11-22"`                 |
| `replace()`         | Новый объект с изменёнными значениями             | `dt.now().replace(year=2030)`           | `2030-11-22 14:36:12.123456`   |
| `isoformat()`       | ISO строка                                        | `dt.now().isoformat()`                  | `"2025-11-22T14:36:12.123456"` |
| `date()`            | Только дата                                       | `dt.now().date()`                       | `2025-11-22`                   |
| `time()`            | Только время                                      | `dt.now().time()`                       | `14:36:12.123456`              |
| `timestamp()`       | Unix-timestamp                                    | `dt.now().timestamp()`                  | `1763793372.123456`            |

---

#### Форматы времени, доступные в `datetime`

| Формат                          | Что это                           | Как получить              | Как разобрать                 | Пример                      |
| ------------------------------- | --------------------------------- | ------------------------- | ----------------------------- | --------------------------- |
| **ISO 8601**                    | Международный стандарт строки     | `dt.isoformat()`          | `datetime.fromisoformat(str)` | `2025-11-22T14:30:15+03:00` |
| **UNIX Timestamp**              | Количество секунд с 1.01.1970 UTC | `dt.timestamp()`          | `datetime.fromtimestamp(ts)`  | `1732253400.0`              |
| **Строковый формат (strftime)** | Произвольная строка по шаблону    | `dt.strftime("%Y-%m-%d")` | `datetime.strptime(str, fmt)` | `"2025-11-22"`              |

---

### 2. Класс `date`

```python
from datetime import date

d = date(2025, 11, 22)
```

[Коды форматирования](https://docs.python.org/3.12/library/datetime.html#format-codes)
…
Проверьте себя: Почему `date(2025,1,10) - date(2025,1,1)` работает, а `date(2025,1,1) + date(2025,1,1)` — нет, хотя оба объекта одного типа?
4
MarkdownРазбор концепции115 строк

Часовые пояса: datetime.timezone vs zoneinfo.ZoneInfo

less_30__JSON__datetime/theory_04__timezone_zoneinfo.md

Разделяет ответственность в архитектуре работы с часовыми поясами: datetime.datetime делает все вычисления, а объект tzinfo (timezone или ZoneInfo) лишь хранит информацию о смещении. timezone — простой класс для фиксированного смещения вида UTC+3 без учёта летнего времени и исторических изменений; zoneinfo.ZoneInfo — современный стандарт с полноценной поддержкой реальных мировых зон IANA (DST, законы, исторические переходы). Показан перевод между зонами через astimezone() и корректное вычитание datetime из разных зон.

  • Архитектура: datetime считает, tzinfo/timezone/ZoneInfo хранят зону
  • timezone(timedelta(hours=3)) — фиксированное смещение, без DST
  • ZoneInfo('Europe/Moscow') — реальная зона с летним временем
  • astimezone() — перевод времени между зонами
  • Вычитание datetime из разных зон — учитывает смещение автоматически
  • Сравнительная таблица timezone vs zoneinfo
Показать начало файла (115 строк всего)
## Работа с временными зонами в Python (`datetime`, `timezone`, `zoneinfo`)


### 1. Архитектура работы с временными зонами

В Python разделены **хранение информации о зоне** и **вычисления**:

| Компонент                             | За что отвечает                                                          |
|---------------------------------------| ------------------------------------------------------------------------ |
| **`datetime.datetime`**               | выполняет все вычисления (сложение, вычитание, перевод часовых поясов)   |
| **`datetime.timezone` (класс)**       | хранит *фиксированное* смещение от UTC (например, UTC+3)                 |
| **`datetime.tzinfo` (базовый класс)** | общий интерфейс описания зон                                             |
| **`zoneinfo.ZoneInfo`**               | хранит реальные часовые пояса мира (DST, законы, исторические изменения) |

\* `datetime.datetime` не содержит знания о часовых поясах — он лишь использует предоставленный объект `tzinfo`

---

### 2. `timezone` — фиксированные часовые пояса

Используется, если нужна простая зона вида *UTC+X*:

```python
from datetime import datetime, timezone, timedelta

utc_plus_3 = timezone(timedelta(hours=3))
dt = datetime(2025, 1, 1, 12, tzinfo=utc_plus_3)
```

### ✦ Ограничения:

* нет DST (летнего времени)
* нет исторических изменений
* нет политических переходов

Использовать только для простых случаев.

---

### 3. `zoneinfo` — реальные часовые пояса

Поддерживает:

* летнее/зимнее время (DST)
* изменения законов
* исторические переходы
* все временные зоны IANA (Europe/Moscow, America/New_York…)

Пример:

```python
from datetime import datetime
from zoneinfo import ZoneInfo

dt = datetime(2025, 3, 1, 12, tzinfo=ZoneInfo("Europe/Moscow"))
```

---

### 4. Перевод времени между часовыми поясами
…
Проверьте себя: Почему для зоны Europe/Moscow рекомендуется zoneinfo, а не datetime.timezone(timedelta(hours=3)), если смещение сейчас совпадает?
5
MarkdownРазбор концепции35 строк

dateutil.relativedelta: разница дат в годах и месяцах, а не только в днях

less_30__JSON__datetime/theory_05__dateutil_relativedelta.md

Представляет сторонний пакет dateutil и его главный инструмент relativedelta — вычисление разницы между датами в годах/месяцах/днях (в отличие от timedelta, который даёт только дни и секунды), а также смещение даты на заданное число лет/месяцев/дней. Отдельно упомянуты parser.parse() для разбора произвольных строковых дат и rrule для генерации повторяющихся дат. Практический пример — подсчёт возраста через relativedelta(enroll, birth).years/.months/.days.

  • relativedelta(date1, date2) — разница в годах/месяцах/днях/часах
  • relativedelta(years=+n, months=+n) — смещение даты
  • parser.parse('12 June 1983') — умный разбор строки в datetime
  • rrule.rrule(DAILY, dtstart=..., count=10) — генератор повторяющихся дат
  • tz.gettz('Europe/Moscow') — работа с часовыми поясами через dateutil
  • Пример подсчёта возраста: relativedelta(enroll, birth).years/.months/.days
Показать начало файла (35 строк всего)
### Пакет `dateutil`

Удобен методом `relativedelta`, который чаще всего используют для вычисления 
* возрастов, 
* разницы между датами 
* и манипуляции датами.

| Метод / Класс                                      | Описание                                                                                                                                   | Пример использования                                                                                      |
| -------------------------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|
| `relativedelta(date1, date2)`                      | Основной класс для вычисления разницы между двумя датами. <br>Позволяет получить разницу в годах, месяцах, днях, часах, минутах, секундах. | `relativedelta(datetime(2023,4,29), datetime(1983,6,12)).years` → 39                                      |
| `relativedelta(years=+n, months=+n, days=+n, ...)` | Создает смещение даты на указанное количество лет, месяцев, дней и т.д.                                                                    | `datetime(2023,4,29) + relativedelta(years=1, months=-2)` → 29.02.2024                                    |
| `parser.parse(date_string)`                        | Умный разбор строковых дат в объект `datetime`. Поддерживает множество форматов.                                                           | `parser.parse("12 June 1983")` → `datetime(1983,6,12)`                                                    |
| `rrule`                                            | Генератор повторяющихся дат (повторяющиеся события).                                                                                       | `rrule.rrule(rrule.DAILY, dtstart=start, count=10)` → <br>последовательность из 10 дней начиная с `start` |
| `tz` (timezone)                                    | Работа с часовыми поясами. Позволяет локализовать и конвертировать время.                                                                  | `tz.gettz("Europe/Moscow")`                                                                               |

**Полезные комбинации для возраста:**

```python
from datetime import datetime
from dateutil.relativedelta import relativedelta

birth = datetime(1983, 6, 12)
enroll = datetime(2023, 4, 29)

age = relativedelta(enroll, birth).years       # Возраст в полных годах
months = relativedelta(enroll, birth).months  # Оставшиеся месяцы
days = relativedelta(enroll, birth).days      # Оставшиеся дни
```

Особенность `relativedelta` по сравнению с обычными вычитаниями `datetime`:

* `datetime` вычитание возвращает `timedelta` → только дни и секунды.
* `relativedelta` → полноценная разница по **годам, месяцам, дням**, что идеально для подсчёта возраста.

Проверьте себя: Чем relativedelta(date1, date2) принципиально отличается от результата date1 - date2 при вычислении возраста человека?