При работе с датами в системах без RTC, например в простых часах на микроконтроллере, в обработчике прерывания от таймера инкрементируются переменная секунды и при её выходе за границу диапазона, увеличивается минута, при выходе минуты за границу — час и так далее. И если с секундами, минутами и часами всё просто, то при увеличении дней необходимо учитывать количество дней в месяце, которое мало того, что не регулярное, так ещё и зависит от года. Как-то раз, я задумался — а нет ли зависимости между номером месяца и его длиной?
Так как родная система счисления для вычислительных машин — двоичная, то переведём в неё номера месяцев и зададим соответствие с количеством дней:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|Месяц | Месяц2 | Кол-ко дней| | 1 | 0001 | 31 | | 2 | 0010 | 28 | | 3 | 0011 | 31 | | 4 | 0100 | 30 | | 5 | 0101 | 31 | | 6 | 0110 | 30 | | 7 | 0111 | 31 | | 8 | 1000 | 31 | | 9 | 1001 | 30 | | 10 | 1010 | 31 | | 11 | 1011 | 30 | | 12 | 1100 | 31 | |
Пока ничего не видно, но была уверенность, что что-то есть, праздник всё-таки.
Сгруппируем длинные и короткие месяцы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Длинные |Месяц | Месяц2 | Кол-ко дней| | 1 | 0001 | 31 | | 3 | 0011 | 31 | | 5 | 0101 | 31 | | 7 | 0111 | 31 | | 8 | 1000 | 31 | | 10 | 1010 | 31 | | 12 | 1100 | 31 | Короткие | 2 | 0010 | 28 | | 4 | 0100 | 30 | | 6 | 0110 | 30 | | 9 | 1001 | 30 | | 11 | 1011 | 30 | |
Внимательно присмотревшись, можно заметить, что если результат выполнения операции исключающее или над первым и четвёртым битом номера месяца равен «1», то месяц длинный, иначе — короткий.
Осталось только проверить год на високосность, напомню, что правила следующие:
- год, номер которого кратен 400, — високосный;
- остальные годы, номер которых кратен 100, — невисокосные;
- остальные годы, номер которых кратен 4, — високосные.
В коде, на языке Си, это выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 |
uint8_t year_is_leap(uint16_t year){ if(!(year % 400)) return 1; if(!(year % 100)) return 0; if(!(year & 3)) return 1; return 0; } uint8_t day, month, year; if(day >= (month != 2 ? (((month >> 3)^(month & 1)) ? 31 : 30) : (28 + year_is_leap(year))) ){ day = 1; ++month; } |
Как правило, если ты что-либо придумал, то скорее всего это уже придумали ещё в эпоху Возрождения ранее. Но беглый поиск в Интернете показал, что нет. Однако, на Хабре была найдена отличная статья, описывающая метод, решающий подобную задачу.
Ну и наконец проведём замеры объёма кода. Напишем абстрактный код для микроконтроллера AVR, где флеш и ОЗУ — ресурс крайне ценный, который будет делать инкремент даты, для простоты — только день и месяц, четырьмя разными способами:
- по таблице;
- непосредственным сравнением дат;
- методом с Хабра;
- «бинарным методом».
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <stdint.h> uint8_t year_is_leap(uint16_t year){ if(!(year % 400)) return 1; if(!(year % 100)) return 0; if(!(year & 3)) return 1; return 0; } static uint8_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int main() { volatile uint8_t day = 13, month = 9;//, hour = 0, minute = 0, second = 0; volatile uint16_t year = 2018; // if(day >= (month != 1 ? days_in_month[month] : days_in_month[month] + year_is_leap(year))){ // day = 1; // ++month; // } // if(day == 30 && (month == 4 || month == 6 || month == 9 || month == 11)){ // day = 1; // ++month; // } // else if(month == 1 && day == 28 + year_is_leap(year)){ // day = 0; // ++month; // } // else if(day == 31){ // day = 0; // ++month; // } // if(day > (28 + ((month + (month >> 3)) & 1) + 2 % month + (2 / month))){ // day = 1; // ++month; // } // if(day >= (month != 2 ? (((month >> 3)^(month & 1)) ? 31 : 30) : (28 + year_is_leap(year))) ){ // day = 1; // ++month; // } return 0; } |
По очереди раскомментируем блоки кода, компиллируем и смотрим результаты:
1 2 3 4 5 6 |
|Метод |Flash|RAM| |Объявление переменных, выход | 98 | | |Таблица | 282 | 12| |Сравнение | 256 | 0 | |Хабр | 250 | 0 | |Бинарный | 244 | 0 | |
Вывод: метод имеет право на существование. Всех с праздником!