Расписание полива печатало в журнал день недели цифрой — 0, 1, 2 — и через месяц чтение лога превратилось в работу с календарём на коленке: какая это была среда, что за единица. Я завёл массив имён и тут же поймал классику: в бумажных заметках мои дни шли с понедельника, в массиве — с воскресенья, и после «исправления» лог стал врать ровно на один день. Поливалка честно докладывала «суббота» в пятницу, и я неделю искал несуществующую погрешность часов; журнал при этом выглядел безупречно — ровные колонки, стабильный формат, врал один словарь имён. С тех пор у меня правило: нумерация дня недели — свойство библиотеки, а не истина, и калибруется она по дате, которую вы точно знаете.
В этом уроке достанем из dayOfTheWeek() русские понедельник-вторник-среду, соберём месяцы в родительном падеже для красивой даты «16 сентября 2026», разберёмся с ведущими нулями и соберём три формата вывода — от сортируемого машинного до человеческого — без единой дополнительной библиотеки.
У той истории был и второй акт, не про журнал. Я вынес день недели на экран прибора цифрой — просто «3» — и вскоре услышал вопрос, который запомнил на годы: «а третий день — это что?» Число удобно машине: с ним сравниваются, по нему строятся расписания — вся сила числовых полей, о которой говорил урок про часы DS3231. Но человек читает слова, и между этими двумя языками стоит ровно один массив строк — собственно, вся тема этого урока.
Нумерация дней: где ноль и как не врать на день
Метод dayOfTheWeek() возвращает число от 0 до 6, но не хранит его в чипе: библиотека вычисляет день из календарной даты по григорианскому календарю — DS3231 про недели вообще не знает; седьмой геттер DateTime — тот же, что разбирался в уроке про разбор объекта. На настоящей плате в RTClib нумерация такая: 0 — воскресенье, 1 — понедельник, 6 — суббота. В симуляторе движок нумерует по другой привычке — с понедельника: 0 — понедельник, 6 — воскресенье. Оба варианта законны, они просто разные, и это лучшая иллюстрация главного правила: не верить памяти, калибровать по дате.
Пример калибровки в числах: 1 января 2025 года — среда. На плате RTClib вернёт для неё тройку (Вс=0, Пн=1, Вт=2, Ср=3), симулятор движка — двойку, потому что нумерует с понедельника. Если ваша таблица даёт другие числа — она врёт, и лучше увидеть это на известной дате при сборке, чем в журнале через месяц. Порядок имён внутри массива важнее самих имён: латиница или русский — вопрос монитора, а вот сдвиг на день — вопрос чести расписания. Проверка стоит одну строку в setup(): напечатали день недели для даты, которую помните, сверили — и можно спать спокойно.
Массив имён обязан идти в порядке нумерации метода, а не в порядке вашего любимчика. На плате это const char* DAYS[7] с воскресеньем первым; в симуляторе — с понедельником. Грабля усугубляется тем, что смещённый массив не роняет программу: строки просто сдвигаются на день, и программа работает год, пока кто-нибудь не сверит с настенным календарём — как моя поливалка, неделю рапортовавшая «субботу» в пятницу.
Месяцы устроены так же, но с лингвистическим нюансом. Для строки «16 сентября 2026» нужен родительный падеж: массив из двенадцати строк «января, февраля, марта…» по индексу month() минус один — месяцы в RTClib идут от единицы, а индексы массива от нуля, и это смещение — вторая по популярности калибровочная грабля после дней недели. Английские имена в этой ловушке не тонут — «1 May» звучит терпимо в обоих падежах, — а русские просят точности: «1 май» режет ухо, нужен именно родительный.
Ведущие нули — третий слой того же ремесла. Без них 9:05 выглядит как 9:5, а «1.2.26» двусмысленно до отвращения. Решение — крошечная функция two(): если число меньше десяти, сначала печатаем символ нуля, потом само значение; в скетчах она у меня переезжает из проекта в проект без изменений: два вызова two() заменяют четыре строки if в каждом месте вывода. Отдельный вопрос — год: now.year() % 100 даёт две последние цифры для коротких меток, но в журналах я печатаю год целиком: две цифры старятся, а четыре стоят те же символы, пока не сменился экран.
Красивая дата собирается склейкой, и здесь мой совет — не собирать её вообще. Печать кусками через несколько Serial.print() не создаёт временных строк и не тратит ОЗУ: печать раз в секунду на Uno живёт спокойно, а вот сборка длинной String на каждой итерации при частом цикле уже заметна. Проводить форматирование строками стоит только когда строку надо куда-то передать целиком — на экран, в файл; для монитора кусочков хватает. Паттерн «массив как таблица имён» вообще один из самых ходовых: он же работает в журналах и меню, как разобрано в уроке про массивы для журналов.
#include <Wire.h>#include <RTClib.h> RTC_DS3231 rtc; // порядок обязан совпадать с нумерацией: в RTClib 0 = воскресеньеconst char* DAYS[7] = { "Voskresenye", "Ponedelnik", "Vtornik", "Sreda", "Chetverg", "Pyatnica", "Subbota"};const char* MES[12] = { "yanvarya", "fevralya", "marta", "aprelya", "maya", "iyunya", "iyulya", "avgusta", "sentyabrya", "oktyabrya", "noyabrya", "dekabrya"}; void two(int v) { // 5 -> 05 if (v < 10) Serial.print("0"); Serial.print(v);} void setup() { Serial.begin(9600); rtc.begin();} void loop() { DateTime now = rtc.now(); Serial.print(DAYS[now.dayOfTheWeek()]); Serial.print(", "); Serial.print(now.day()); Serial.print(" "); Serial.print(MES[now.month() - 1]); Serial.print(" "); Serial.print(now.year()); Serial.print(", "); two(now.hour()); Serial.print(":"); two(now.minute()); Serial.println(); delay(1000);}Три формата на один снимок DateTime
Формат стоит выбирать по потреблению, а не по вкусу. Машинный ISO 8601 — год, месяц, день с ведущими нулями: 2026-09-16 22:31:05. Его главный талант — сортировка: строки в таком виде сортируются по времени как обычный текст, потому что старшие разряды идут первыми. Для журналов на карте и логов в мониторе это единственный формат, который я использую; дата 16.09.2026 при строковой сортировке перепутает сентябрь с декабрём, потому что «09» больше «12» лишь в человеческой голове.
Человеческий формат — «среда, 16 сентября 2026, 22:31» — для дисплеев и отчётов: слово дня недели читается без календаря, месяц в родительном падеже звучит по-русски. Короткий формат — 16.09.26 22:31 — для беглых строк статуса, где дорог каждый символ: на LCD 16x2 в одну строку помещается ровно шестнадцать знаков, и короткий формат влезает вместе с данными, а длинный нет. Отдельный вопрос — двенадцатичасовой формат с PM: он стоит трёх строк кода (час больше двенадцати — минус двенадцать и литерал PM), но в русских интерфейсах почти не нужен; я печатаю 24-часовой всегда. Все три формата собираются из одного снимка now за одно проходжение loop(): геттеры вызываются один раз в переменные, дальше три ветки печати — и ни одна не пересекается с логикой будильника, которую мы строили в уроке про часы с будильником. Переключение формата в живом приборе — та же механика, что в симуляторе ниже: переменная fmt живёт глобально и переживает циклы, а перебирают её кнопкой меню или командой из монитора. Хранить формат в EEPROM — моё правило: прибор, который после включения показывает не тот формат, что вчера, выглядит сломанным, хотя код просто забыл состояние. А если у вашего монитора ещё не настроено окно, загляните в урок про первый запуск Serial-монитора: красивая дата и кракозябры легко путаются на глазах новичка.
| Формат | Пример | Сильная сторона | Слабая сторона |
|---|---|---|---|
| ISO 8601 | 2026-09-16 22:31:05 | сортируется как строка | длинный, машинный |
| человеческий | sreda, 16 sentyabrya 2026 | читается без календаря | не сортируется, широкая строка |
| короткий | 16.09.26 22:31 | влезает в 16 клеток LCD | двусмысленен за пределами года |
- Переключите fmt 1 и сверьте слово дня недели с настоящим календарём — так калибруется нумерация и в симуляторе, и на плате.
- Добавьте четвёртый формат: только дата без времени, одной строкой — пригодится для метки начала суток в журнале.
- Выведите день недели и месяц латиницей, а затем замените строки на русские и посмотрите, как их показывает ваш Serial-монитор.
- Соберите строку формата fmt 1 в одну переменную String и сравните расход ОЗУ с печатью кусками — разницу почувствуете на Uno со сложным скетчем.
- dayOfTheWeek() вычисляется из даты, а не хранится в чипе: в RTClib 0 — воскресенье, в симуляторе 0 — понедельник
- Массив имён идёт в порядке нумерации метода; калибровка — печать дня недели для даты, которую вы точно знаете
- Месяцы в RTClib от 1 до 12, индексы массива от нуля: MES[now.month() - 1] — вторая калибровочная грабля
- Для «16 сентября» нужен родительный падеж — массив из двенадцати строк решает лингвистику без кода
- ISO 8601 сортируется строками, короткий формат влезает в 16 клеток LCD, человеческий — для дисплеев
- Печать кусками через несколько print() не создаёт временных String — на Uno это заметная экономия ОЗУ
- 1. Что вернёт dayOfTheWeek() в RTClib на настоящей плате во вторник?
- 2. Как откалибровать нумерацию дня недели за одну проверку?
- 3. Почему в массиве месяцев индекс — это month() - 1?
- 4. Какой формат даты сортируется как обычная строка?
- 5. Почему для печати в монитор я предпочитаю несколько Serial.print() вместо сборки одной String?
Теперь момент времени одинаково понятен и машине, и человеку: числа для арифметики, красивая строка для экрана. Дальше по разделу — тревожная электроника модуля: будильники и таймеры самого DS3231, а до того стоит заглянуть в урок про точность и температурную компенсацию, чтобы понимать, откуда берётся та самая минута ухода в год, ради которой всё и затевалось. А массивы DAYS и MES из скетча забирайте в свои приборы целиком — они переживут и этот проект.
Частые вопросы по теме урока
- Что вернёт dayOfTheWeek() в RTClib на настоящей плате во вторник?
- В RTClib нумерация с воскресенья: Вс=0, Пн=1, Вт=2. В симуляторе движок нумерует с понедельника — там вторник это 1. 1
- Как откалибровать нумерацию дня недели за одну проверку?
- Калибровка по известной дате, например 1 января 2025 — среда, мгновенно показывает, куда сдвинут ноль и порядок массива. Напечатать день недели для даты, которую точно знаете
- Почему в массиве месяцев индекс — это month() - 1?
- RTClib нумерует месяцы по-человечески: январь — 1. Массив начинается с нулевого индекса, отсюда минус единица. Месяцы в RTClib от 1 до 12, а индексы массива от нуля
- Какой формат даты сортируется как обычная строка?
- ISO 8601 идёт от старших разрядов к младшим: строковая сортировка совпадает с хронологической. День-месяц-год при такой сортировке путается. 2026-09-16
- Почему для печати в монитор я предпочитаю несколько Serial.print() вместо сборки одной String?
- Каждая склейка String выделяет память в куче. Печать кусками обходит это; на 2 КБ ОЗУ Uno разница ощутима. Не создаётся временная строка — экономится ОЗУ на Uno