Перейти к содержанию

192 уроков, 14 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 9 из 20 Средний 40 мин 130 XP

Даты в Excel через openpyxl: запись, формат и арифметика

Пишем в ячейку настоящий datetime, показываем его по-русски маской DD.MM.YYYY и считаем дни между датами — а заодно узнаём, почему дату из файла нельзя сравнивать со строкой.

Редакция Питоники

Прайс без дат — недопрайс: «цены действуют с 1 по 30 сентября», «зёрна обжарки годны 21 день», «смена начинается в 8:00». Excel умеет хранить даты по-настоящему — как числа с маской, и в этом уроке мы запишем их из Python: положим в ячейку datetime, настроим показ по-русски и посчитаем дни между датами обычной арифметикой.

Маски отображения мы уже проходили в уроке про числовые форматы — даты живут по тем же правилам: значение одно, вид настраивается строкой. Все блоки запускаются прямо на странице: openpyxl ставится в песочницу сам.

Зачем дате быть настоящей датой, а не текстом? Три причины, и все они всплывут в первый же месяц работы прайса. Сортировка: настоящие даты выстраиваются по времени, текстовые — по алфавиту, где «01.10.2026» стоит раньше «24.09.2026». Фильтры: Excel предлагает группировку по годам и месяцам только для дат. Арифметика: срок годности и длительность акции считаются вычитанием — а из текста вычесть нечего.

Как записать дату в ячейку Excel?

Правило одно: в ячейку должен попасть объект datetime из стандартного модуля datetime, а не строка. openpyxl сам поймёт, что перед ним дата, и упакует её в файл как положено.

datetime в ячейке
from datetime import datetime
from openpyxl import Workbook

wb = Workbook()
ws = wb.active

ws["A1"] = "Дата старта акции"
ws["B1"] = datetime(2026, 9, 24)     # объект datetime, не строка

cell = ws["B1"]
print(cell.value)
print(type(cell.value).__name__)
print("Формат по умолчанию:", cell.number_format)
Вывод
2026-09-24 00:00:00
datetime
Формат по умолчанию: yyyy-mm-dd h:mm:ss

Смотри, что произошло. Значение ячейки — настоящий datetime, Python-объект со всей арифметикой. А формат openpyxl поставил сам: yyyy-mm-dd h:mm:ss — ведь Excel хранит дату всегда вместе с временем, просто при datetime(2026, 9, 24) время получилось нулевое.

Маска DD.MM.YYYY: показываем дату по-русски

Формат по умолчанию — американский, год вперёд. Читателю прайса привычнее день.месяц.год — ставим маску DD.MM.YYYY, она уже знакома тебе по предыдущему уроку. Проверим, что маска и тип значения переживают сохранение в файл:

формат даты едет в файл
from datetime import datetime
from openpyxl import Workbook, load_workbook
import os

NAME = "sales_dates.xlsx"

wb = Workbook()
ws = wb.active
ws["A1"] = "Смена"
ws["A2"] = datetime(2026, 9, 24)
ws["A2"].number_format = "DD.MM.YYYY"

wb.save(NAME)

wb2 = load_workbook(NAME)
cell = wb2.active["A2"]
print(cell.value)
print(type(cell.value).__name__)
print(cell.number_format)
# в Excel человек увидит: 24.09.2026

os.remove(NAME)
Вывод
2026-09-24 00:00:00
datetime
DD.MM.YYYY

Обрати внимание: print(cell.value) печатает 2026-09-24 00:00:00 — так Python показывает datetime-объект. В Excel эта же ячейка выглядит как 24.09.2026, потому что отрисовкой занимается маска. Если нужно и время — маска DD.MM.YYYY HH:MM покажет 24.09.2026 15:30.

  • DD и D — день с ведущим нулём и без: 04 и 4;
  • MM и M — месяц; MMMM в английской локале даст название месяца;
  • YY и YYYY — год двумя и четырьмя цифрами;
  • HH и MM (после времени) — часы и минуты, SS — секунды;
  • регистр кодов не важен для Excel: dd.mm.yyyy работает так же, но openpyxl сохранит строку ровно в том виде, в каком ты её дал.

Арифметика дат: сколько дней между двумя датами?

Вот зачем дате быть настоящей датой, а не строкой: из неё можно вычитать. Разность двух datetime — это объект timedelta, а его свойство days даёт число дней. Акция «прайс действует 14 дней» считается одной строкой.

разность дат и timedelta
from datetime import datetime, timedelta
from openpyxl import Workbook

wb = Workbook()
ws = wb.active

start = datetime(2026, 9, 1)
end = datetime(2026, 9, 24)
ws["A1"] = start
ws["A2"] = end

diff = end - start                   # разность двух datetime
print(type(diff).__name__)
print("Дней между датами:", diff.days)

ws["A3"] = diff.days                 # число дней можно записать в ячейку
ws["A4"] = start + timedelta(days=7) # и прибавить неделю к дате
print("Неделя спустя:", ws["A4"].value.date())
Вывод
timedelta
Дней между датами: 23
Неделя спустя: 2026-09-08

Разбор: end - start вернул timedelta — длительность, у которой есть .days (здесь 23). Обратно к дате timedelta прикладывают плюсом: start + timedelta(days=7) — дата через неделю, она тоже легко ложится в ячейку. Так собираются графики поставок: одна дата плюс цикл смещений.

Время в этой системе — дробная часть числа: полдень — это 0.5 дня, шесть утра — 0.25. Поэтому datetime(2026, 9, 24, 18, 0) минус datetime(2026, 9, 24, 6, 0) даст timedelta ровно в 12 часов, а вычитание дат с временем может «уменьшить» число дней на единицу, если часы не сошлись. Для графика поставок, где время не участвует, это неважно; для смен с почасовым учётом — уже важно.

Что пишем в ячейкуЧто в файлеЧто вернёт .value при чтении
"24.09.2026"текстстрока
date(2026, 9, 24)датаdatetime(2026, 9, 24, 0, 0)
datetime(2026, 9, 24, 15, 30)дата со временемdatetime с 15:30

Пара нюансов таблицы. Строка даже с идеально правильной датой — просто текст: Excel не распознает её как дату, арифметика и сортировка по датам сломаются. А date (без времени) openpyxl превращает в полночный datetime, потому что в формате xlsx чистых дат без времени не бывает.

  • сроки годности и гарантии: дата производства плюс timedelta с днями;
  • длительность акции: вычитание двух datetime и .days;
  • графики поставок: опорная дата и цикл смещений по timedelta;
  • просрочка: знак разницы между опорной датой и сроком из файла.
строка против datetime - типы в файле
from datetime import datetime
from openpyxl import Workbook

wb = Workbook()
ws = wb.active

ws["A1"] = "24.09.2026"                 # строка - в файле будет ТЕКСТ
ws["A2"] = datetime(2026, 9, 24)        # настоящая дата

print("A1:", type(ws["A1"].value).__name__)
print("A2:", type(ws["A2"].value).__name__)
Вывод
A1: str
A2: datetime

Если даты приезжают в скрипт строками — из CSV, JSON или API — их надо превратить в datetime до записи. Для формата ISO ГГГГ-ММ-ДД в стандартной библиотеке есть готовый парсер:

fromisoformat: строка становится датой
from datetime import datetime

raw = "2026-09-24"          # например, из CSV или JSON
d = datetime.fromisoformat(raw)
print(d, type(d).__name__)
print(d == datetime(2026, 9, 24))
Вывод
2026-09-24 00:00:00 datetime
True

Для других форматов строки пригодится strptime с явным шаблоном: datetime.strptime("24.09.2026", "%d.%m.%Y") вернёт тот же объект. Правило то же, что и с масками отображения: сначала строка превращается в datetime, потом datetime едет в ячейку. Порядок нельзя переворачивать — другого пути из текста в дату у Excel нет.

Что приходит из файла при чтении дат?

При чтении из Excel даты приходят как datetime — всегда, даже если записаны они были объектом date. Отсюда ловушка, на которой спотыкаются все: сравнение datetime со строкой всегда False, каким бы похожим ни выглядело представление.

дата из файла: сравнение со строкой и с datetime
from datetime import datetime
from openpyxl import Workbook, load_workbook
import os

NAME = "read_dates.xlsx"

wb = Workbook()
ws = wb.active
ws.append(["День", "Чашек"])
ws.append([datetime(2026, 9, 24), 213])
ws.append([datetime(2026, 9, 25), 178])
wb.save(NAME)

cell = load_workbook(NAME).active["A2"]
print("В ячейке:", repr(cell.value))
print("Сравнение со строкой:", cell.value == "2026-09-24 00:00:00")
print("Сравнение с datetime:", cell.value == datetime(2026, 9, 24))

os.remove(NAME)
Вывод
В ячейке: datetime.datetime(2026, 9, 24, 0, 0)
Сравнение со строкой: False
Сравнение с datetime: True

Собираем график поставок

Практика: неделя поставок зёрен. База — одна дата, дальше timedelta(days=i) в цикле, каждой ячейке — маска DD.MM.YYYY. Потом перечитываем файл и убеждаемся, что даты доехали настоящими датами.

неделя поставок из одной даты
from datetime import date, datetime, timedelta
from openpyxl import Workbook, load_workbook
import os

NAME = "menu_week.xlsx"

wb = Workbook()
ws = wb.active
ws["A1"] = "Поставки"
start_day = date(2026, 9, 21)
for i in range(5):
    cell = ws.cell(row=i + 2, column=1, value=start_day + timedelta(days=i))
    cell.number_format = "DD.MM.YYYY"

wb.save(NAME)

wb2 = load_workbook(NAME)
for row in wb2.active.iter_rows(min_row=2, max_col=1):
    print(row[0].value)

os.remove(NAME)
Вывод
2026-09-21 00:00:00
2026-09-22 00:00:00
2026-09-23 00:00:00
2026-09-24 00:00:00
2026-09-25 00:00:00

Пять дат из одной строки кода арифметики. Обрати внимание на cell() — второй способ доступа к ячейкам из урока про адреса и координаты: номер строки и столбца здесь удобнее адресных строк, потому что row считается в цикле. Последний штрих — срок годности обжарки: дата обжарки плюс 21 день, для каждого сорта своя. Той же самой парой «дата + timedelta».

срок годности обжарки
from datetime import datetime, timedelta
from openpyxl import Workbook, load_workbook
import os

NAME = "roasting.xlsx"

wb = Workbook()
ws = wb.active
ws.append(["Сорт", "Обжарка", "Годен до"])

roasted = datetime(2026, 9, 24)
for sort_name in ("Бразилия", "Эфиопия", "Колумбия"):
    ws.append([sort_name, roasted, roasted + timedelta(days=21)])

for row in ws.iter_rows(min_row=2, min_col=2, max_col=3):
    for cell in row:
        cell.number_format = "DD.MM.YYYY"

wb.save(NAME)
print("Сохранено:", os.path.exists(NAME))

wb2 = load_workbook(NAME)
print("Годен до:", wb2.active["C2"].value.date())
os.remove(NAME)
Вывод
Сохранено: True
Годен до: 2026-10-15

На чтении дат строится и обратная задача — проверка просрочки. Схема всегда одна: достань дату из файла, возьми опорную дату (заказ, отгрузка, инвентаризация — тоже фиксированным datetime), вычти и посмотри на знак разницы. Отрицательный timedelta — срок уже прошёл, положительный — в запасе .days дней. Ни циклов по календарю, ни ручного учёта длин месяцев: всё это уже внутри datetime.

Что дальше

Теперь прайс умеет даты: записывает datetime, показывает 24.09.2026 и считает дни между событиями. Следующая остановка — формулы: запишем в ячейку =SUM(B2:B10) и разберёмся, почему openpyxl её сохраняет, но не считает. А когда захочешь строить временные ряды по месяцам из таких дат — это уже временные ряды Pandas, механика та же, масштаб больше.

Дата в Excel — число с маской: поэтому из дат можно вычитать, а из разности получать дни.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from datetime import datetime
from openpyxl import Workbook

wb = Workbook()
ws = wb.active
ws["A1"] = datetime(2026, 9, 1)
ws["A2"] = datetime(2026, 9, 24)
print((ws["A2"].value - ws["A1"].value).days)
from datetime import datetime
from openpyxl import Workbook

wb = Workbook()
ws = wb.active
ws["A1"] = datetime(2026, 9, 24)
print(type(ws["A1"].value).__name__)
print(ws["A1"].value == "2026-09-24")
from datetime import datetime, timedelta
from openpyxl import Workbook

wb = Workbook()
ws = wb.active
d = datetime(2026, 9, 24)
ws["A1"] = d + timedelta(days=7)
print(ws["A1"].value.date())
Проверь себя
0 / 5

1. Как правильно записать дату в ячейку Excel через openpyxl?

2. Какой тип вернёт cell.value при чтении даты из xlsx-файла?

3. Что вернёт выражение datetime(2026, 9, 24) - datetime(2026, 9, 1)?

4. В ячейке настоящая дата 24.09.2026. Что вернёт cell.value == "24.09.2026"?

5. Какая маска number_format покажет дату как «24.09.2026»?

Карточки терминов
Запомнено: 0 / 6
Практика

Собери график приёмки зерна: начиная с datetime(2026, 10, 1), запиши в первый столбец семь дат подряд (по одной на строку, i от 0 до 6), прибавляя timedelta(days=i), и поставь каждой ячейке маску «DD.MM.YYYY». Затем выведи дату и маску каждой из семи ячеек через разделитель « | ».

practice.py
Вопросы и ответы по уроку

Как записать дату в Excel через openpyxl?

Импортируй datetime из стандартного модуля datetime и присвой его ячейке: ws["A1"] = datetime(2026, 9, 24). Вид настраивается маской number_format, например «DD.MM.YYYY». Писать дату строкой нельзя — в файле окажется текст без свойств даты: без сортировки по времени и без арифметики.

Почему при чтении дата приходит как datetime, а не date?

Формат xlsx хранит дату всегда вместе со временем — в файле нет «чистой даты». Поэтому openpyxl на чтении возвращает datetime, у которого время может быть нулевым. Если время не нужно, отбрось его методом .date(): cell.value.date() вернёт date(2026, 9, 24).

Как посчитать количество дней между двумя датами?

Вычти одну дату из другой: (end - start).days вернёт целое число дней, потому что разность двух datetime — это timedelta. Обратная операция тоже работает: прибавь к дате timedelta(days=21), чтобы получить «годен до». Так считаются сроки годности, длительности акций и графики поставок.

Почему дата из Excel не равна такой же строке?

Потому что в ячейке лежит объект datetime, а справа у тебя строка: разные типы всегда не равны. Собери эталон тоже объектом — datetime(2026, 9, 24) — или сравнивай только дни: cell.value.date() == date(2026, 9, 24). Для разбора строковых дат годится datetime.fromisoformat.

Понравился урок? Сошлитесь на него

«Строка даже с идеально правильной датой — просто текст: Excel не распознает её как дату, арифметика и сортировка по датам сломаются.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.