Даты в 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 сам поймёт, что перед ним дата, и упакует её в файл как положено.
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 дней» считается одной строкой.
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;
- просрочка: знак разницы между опорной датой и сроком из файла.
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 ГГГГ-ММ-ДД в стандартной библиотеке есть готовый парсер:
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, каким бы похожим ни выглядело представление.
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())
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»?
Собери график приёмки зерна: начиная с datetime(2026, 10, 1), запиши в первый столбец семь дат подряд (по одной на строку, i от 0 до 6), прибавляя timedelta(days=i), и поставь каждой ячейке маску «DD.MM.YYYY». Затем выведи дату и маску каждой из семи ячеек через разделитель « | ».
Как записать дату в 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-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
openpyxl · Урок 8
Числовые форматы в openpyxl: рубли, проценты и разделители
Строка number_format превращает голое 1234.5 в «1 234.50 ₽»: задаём рубли, проценты и разделители тысяч — и разбираемся, почему значение ячейки при этом не меняется.
sqlite3 · Урок 9
Транзакции и индексы в SQLite: целостность и скорость
Перевод денег не бывает наполовину: либо обе операции, либо ни одной. Разбираем транзакции и индексы SQLite — кнопку «отменить» и оглавление для таблиц.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Pandas · Урок 1
Что такое Pandas и как установить через pip: первые Series и DataFrame
Первая таблица DataFrame в библиотеке pandas, умный столбец Series и быстрый осмотр данных через head, info и describe — старт сквозного анализа данных интернет-магазина.
pandas для начинающихpandas или excel
json · Урок 1
Что такое JSON и зачем он Python: первый json.dumps
Первое превращение словаря в json-строку одной командой: import json, json.dumps и честный взгляд на кракозябры в выводе — всё исполняется прямо на странице.
json для начинающихpython json формат
openpyxl · Урок 1
Excel на Python: первая книга xlsx через openpyxl
Первая книга xlsx из кода: Workbook, запись в ячейки и wb.save — настоящий Excel-файл создаётся прямо в браузере, без установленного Excel.
openpyxlopenpyxl python