Вычисление количества дней в месяце

By | 2018-09-13

При работе с датами в системах без RTC, например в простых часах на микроконтроллере, в обработчике прерывания от таймера инкрементируются переменная секунды и при её выходе за границу диапазона, увеличивается минута, при выходе минуты за границу — час и так далее. И если с секундами, минутами и часами всё просто, то при увеличении дней необходимо учитывать количество дней в месяце, которое мало того, что не регулярное, так ещё и зависит от года. Как-то раз, я задумался — а нет ли зависимости между номером месяца и его длиной?

Так как родная система счисления для вычислительных машин — двоичная, то переведём в неё номера месяцев и зададим соответствие с количеством дней:

|Месяц | Месяц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     |

Пока ничего не видно, но была уверенность, что что-то есть, праздник всё-таки.

Сгруппируем длинные и короткие месяцы:

Длинные
|Месяц | Месяц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, — високосные.

В коде, на языке Си, это выглядит так:

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, где флеш и ОЗУ — ресурс крайне ценный, который будет делать инкремент даты, для простоты — только день и месяц, четырьмя разными способами:

  • по таблице;
  • непосредственным сравнением дат;
  • методом с Хабра;
  • «бинарным методом».
#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;
}

По очереди раскомментируем блоки кода, компиллируем и смотрим результаты:

|Метод                            |Flash|RAM| 
|Объявление переменных, выход     |  98 |   |
|Таблица                          | 282 | 12|
|Сравнение                        | 256 | 0 |
|Хабр                             | 250 | 0 |
|Бинарный                         | 244 | 0 |

Вывод: метод имеет право на существование. Всех с праздником!

Добавить комментарий

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