Примечание: данный пост является переводом статьи cmcenroe.me/2014/12/05/days-in-month-formula.html (Часть I), а также авторским к нему дополнением (Часть II). Не следует относиться к материалу серьёзно, а скорее как к разминке для ума, требующей не более чем школьных знаний арифметики и не имеющей практического применения. Всем приятного чтения!

Часть I


Вступление


Недавно, после очередной бессонной ночи, я размышлял о методах запоминания количества дней в каждом месяце года. Для этого есть считалочка, а также способ считать на костяшках пальцев, но ни то, ни другое меня не устроило. Я задумался, а не существует ли какой-нибудь математической формулы для решения такой задачи, и — не обнаружив при беглом изучении таковую — бросил себе вызов её создать.

ФормализуяДругими словами, необходимо найти функцию f, такую, что значение f(x) для каждого месяца x, представленного числом от 1 до 12, равняется количеству дней в этом месяце. Таблица значений аргумента и функции1:
x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 31 28 31 30 31 30 31 31 30 31 30 31

Если у вас возникло желание попробовать самому до прочтения моего решения, то сейчас самое время. Если же вы предпочитаете немедленно увидеть готовый ответ, то посмотрите под спойлер.
Ответ

Ниже следуют мои шаги по нахождению решения.

Математический аппарат


Сначала бегло освежим в памяти два жизненно необходимых в решении этой задачи оператора: целочисленное деление и остаток от деления.

Целочисленное деление это оператор, применяемый во многих языках программирования при делении двух целых чисел и отбрасывающий от частного дробную часть. Я буду изображать его как . Например:

Остаток от деления это оператор, находящий остаток от деления. Во многих языках программирования применяется символ %, я же буду использовать конструкции вида , например:

Замечу, что остаток от деления имеет равный с делением приоритет.

Основы


Итак, применим наш математический аппарат для получения базовой формулы.2 В обычном месяце 30 или 31 день, так что мы можем использовать для получения поочерёдно 1 или 0, а затем просто прибавить к этому числу константу:

Получаем таблицу, полужирным выделены корректные значения:
x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 31 30 31 30 31 30 31 30 31 30 31 30

Неплохое начало! Уже есть правильные значения для января и для месяцев с марта по июль включительно. Февраль — особый случай, и с ним мы разберёмся чуть позже. После июля для оставшихся месяцев порядок получения 0 и 1 должен быть изменён на обратный.
Для этого мы может прибавить к делимому 1:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 30 31 30 31 30 31 30 31 30 31 30 31

Теперь правильные значения с августа по декабрь, но, как и предполагалось, значения для прочих месяцев неверны. Давайте посмотрим как мы можем объединить эти формулы.

Наложение маски


Для этого необходима кусочно-заданная функция, но — так как мне это показалось скучным — я задумался о другом пути решения, использующем одну часть функции на одном интервале, другую — на другом.
Полагаю, что проще всего будет найти выражение, равное 1 в одной области применения и 0 — в остальной. Метод, в котором умножая аргумент на выражение мы исключаем его из формулы вне области его применения, я назвал «наложением маски», потому такое поведение подобно некой битовой маске.
Для применения этого метода в последней части нашей функции необходимо найти выражение, равное 1 при , и — так как значения аргумента всегда меньше 16 — для этого прекрасно подходит целочисленное деление на 8.
x 1 2 3 4 5 6 7 8 9 10 11 12
?x?8? 0 0 0 0 0 0 0 1 1 1 1 1

Теперь с помощью этой маски, используя в делимом выражение вместо 1, мы можем заменить порядок получения 0 и 1 формуле на обратный:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 31 30 31 30 31 30 31 31 30 31 30 31

Эврика! Всё правильно, кроме февраля. Сюрприз-сюрприз.

Февраль


В любом месяце 30 или 31 день, кроме февраля с его 28 (високосный год выходит за рамки этой задачи).3 На текущий момент по нашей формуле в нём 30 дней, поэтому неплохо бы вычесть выражение, равное 2 при .
Лучшее что мне удалось придумать это , что накладывает маску на все месяцы после февраля:
x 1 2 3 4 5 6 7 8 9 10 11 12
2 mod x 0 0 2 2 2 2 2 2 2 2 2 2

Изменив базовую константу на 28 с добавлением 2 к остальным месяцам получим формулу:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 29 28 31 30 31 30 31 31 30 31 30 31

К сожалению, январь теперь короче на 2 дня. Но, к счастью, получить выражение, которое будет применяться только для первого месяца очень легко: это округлённое вниз обратное к число. Умножив его на 2 получаем окончательную формулу:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x) 31 28 31 30 31 30 31 31 30 31 30 31


Послесловие


Вот она — формула для получения количества дней в любом месяце года, использующая простейшую арифметику. В следующий раз когда вы будете вспоминать сколько же дней в сентябре, просто выполните с помощью этой однострочной функции на JavaScript:

function f(x) { return 28 + (x + Math.floor(x/8)) % 2 + 2 % x + 2 * Math.floor(1/x); }

Часть II


Вступление


В первой части была получена короткая и даже немного изящная формула, основными достоинствами которой являются простота математического аппарата, отсутствие ветвлений и условных выражений, лаконичность. К недостаткам — кроме того, что вы не будете применять её в вашем проекте — можно отнести отсутствие проверки на вискокосный и не високосный год.
Поэтому я поставил перед собой задачу создать функцию f, такую, что значение f(x, y) для каждого месяца x, представленного числом от 1 до 12 и года y, большего 0, равняется количеству дней в месяце x в году y.
Для нетерпеливых под спойлером находится готовый ответ, остальных же прошу следовать за мной.
Ответ


Остаток от деления: mod и ??


Для визуальной наглядности договоримся, что в некоторых формулах оператор деления с остатком заменён нижними скобками, там где это показалось мне необходимым:


Високосный год


В високосный год вводится дополнительный день календаря: 29 февраля. Как известно, високосным является год, кратный 4 и не кратный 100, либо кратный 400. Запишем тождественное этому высказыванию выражение:



Для приведения этого выражения в алгебраическое, необходимо применить к результату выражения инъекцию вида:


Что позволит получить 1 при делении без остатка и 0 при делении с остатком, чтобы использовать её в формуле определения количества дней в месяце.

В качестве функции g' можно использовать 1 минус остаток от деления для :
x 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
g'(x) Infinity 1 0 0 0 0 0 0 0 0 0 0 0 0 0

Легко заметить, что увеличив делимое и делитель на 1 мы получим правильную формулу при :
x 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
g'(x) 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0


Таким образом выражение запишем как:

А выражение запишем как:


Применяя этот подход получим следующую функцию g(y), значением которой будет 1, если год високосный, или 0 в обратном случае:

y 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000
g(y) 0 0 1 0 0 0 1 0 0 0 1
y 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000
g(y) 1 0 0 0 1 0 0 0 1 0 0

Полужирным выделены високосные года.

Напоминаю, что в рамках принятой договорённости оператор получения остатка от деления может быть изображен как mod, так и ??.

Наложение маски


В формуле часть является поправкой, прибавляющей 2 дня к январю. Если убрать множитель 2 и в числителе заменить 1 на 2, тогда эта формула будет прибавлять 2 дня к январю и 1 день к февралю, что даёт нам ключ к добавлению дня в високосном году. Для наглядности используем в формуле промежуточное значение g(y) и в качестве y используем 2000 (високосный) и 2001 (не високосный) годы:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x, 2000) 31 29 31 30 31 30 31 31 30 31 30 31
f(x, 2001) 30 28 31 30 31 30 31 31 30 31 30 30

Значения для всех месяцев, кроме января не високосного года верны.

Для исправления этого досадного недоразумения добавим к январю 1 день уже известной нам формулой :

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x, 2000) 32 29 31 30 31 30 31 31 30 31 30 31
f(x, 2001) 31 28 31 30 31 30 31 31 30 31 30 30


Теперь необходимо вычесть 1 день из января в случае високосного года, для чего нам поможет знание того, что для любого x , а .

Тогда формула примет вид:


Или:

x 1 2 3 4 5 6 7 8 9 10 11 12
f(x, 2000) 31 29 31 30 31 30 31 31 30 31 30 31
f(x, 2001) 31 28 31 30 31 30 31 31 30 31 30 30

Заключение


В результате получена уже значительно более громоздкая, но более универсальная формула, которую также можно использовать для получения количества дней в месяце определённого года:

function f(x, y) { return 28 + ((x + Math.floor(x / 8)) % 2) + 2 % x + Math.floor((1 + (1 - (y % 4 + 2) % (y % 4 + 1)) * ((y % 100 + 2) % (y % 100 + 1)) + (1 - (y % 400 + 2) % (y % 400 + 1))) / x) + Math.floor(1/x) - Math.floor(((1 - (y % 4 + 2) % (y % 4 + 1)) * ((y % 100 + 2) % (y % 100 + 1)) + (1 - (y % 400 + 2) % (y % 400 + 1)))/x); }

Пример на C# ideone.com/fANutz.


1. Я не умею пользоваться подобной мнемоникой, поэтому подсмотрел табличку в интернете.
2. «Основы», или «Правило С Многими Исключениями», как и большинство правил.
3. Изначально в римском календаре февраль был последним месяцем года, поэтому есть логика в том, что он короче всех остальных. Также есть логика в добавлении или удалении дня именно в конце года, поэтому его длина является переменной.

Upd. 1:
Альтернативный перевод первой части в сообществе вконтакте.
Upd. 2: Благодаря комментарию keksmen исправлена ошибка в формуле определения високосного года (g(y)) и исправлена итоговая формула.

Комментарии (54)


  1. Stalker_RED
    03.07.2015 19:51

    Вообще забавно, но на практике, эффективнее использовать что-то типа
    function getDays(month)
    {
        switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
           days = 31;
           break;
        case 4:
        case 6:
        case 9:
        case 11:
           days = 30;
           break;
        case 2:
           days = 28;
           break;
        default:
            days = -1;
        }
        return days;
    }

    при этом, в февральской ветке можно добавить проверку на високосность.


    1. deNULL
      03.07.2015 21:04
      +22

      или [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month — 1]


      1. khim
        03.07.2015 22:28
        +10

        Можно ещё 28 + ((0x2eeeecc >> (2*month)) & 3). Ваш вариант выиграет на x86-64, мой — на x86 просто, хотя разница там копеечная.

        А вышеупомянутый вариант со switch'ом будет всё-таки разика в полтора медленнее… хотя так ли это всё важно?


        1. lorc
          03.07.2015 23:13

          Почему это switch будет медленный? Ныне компиляторы оптимизируют switch только в путь. В данном случае свитч скорее всего будет оптимизирован в таблицу и соответственно будет иметь константную сложность.


          1. Danov
            04.07.2015 09:33

            не быстрее: ветвления,… вычисление адреса, выборка,…


          1. khim
            04.07.2015 13:56

            Не будет тут switch оптимизирован в таблицу. Обработку defaut'а в таблицу всё равно не засунешь, её всё равно придётся отдельно делать, в с учётом того, что вариантов немного компилятор, скорее всего, сгенерирует одно-два сравнения сверх этого.

            Ни до моего варианта, ни до варианта deNULL'а ни один известных мне компилятор не додумался.


        1. NeoCode
          04.07.2015 10:02
          +4

          Ваш вариант самый крутой, умножение заменить сдвигом еще. Вариант с массивом чисел самый очевидный, но закодировать количества дней в 32-битное число — просто и гениально, в стиле олдскульной оптимизации, когда каждый байт был на счету.


          1. khim
            04.07.2015 13:58
            +2

            Умножение я предоставил оптимизировать компилятору. Умножения на константу — это то, что они делают прекрасно. В большинстве случаев компилятор заменит его на сложение и задействует при этом LEA, чтобы регистр не портить.


        1. withkittens
          04.07.2015 15:37
          +5

          Почему-то никто не обратил внимания на ошибку в константе.

          days = 28 + ((0x3bbeecc >> (month * 2)) & 3);
          И таки идеальный вариант!


    1. Keyten
      03.07.2015 23:43

      Если не учитывать високосность, то для практики проще всего что-то такое:

      function getDays(m){
       return m === 2 ? 28 : 30 + (m > 7 ? m+1 : m) % 2;
      }
      


      1. Viacheslav01
        04.07.2015 15:55
        +5

        Условный переход детектед


  1. andrew_tch
    03.07.2015 19:52
    +17

    Требую вариант с преобразованием Фурье ))


  1. aktuba
    03.07.2015 19:58
    +28

    Как-же вы запарили… Да читают, читают ваши теги!


  1. kirillzorin
    03.07.2015 20:21
    +3

    Самый простой способ запомнить (без всяких костяшек и мнемонических правил) — помнить о двух цезарях.
    При Юлие Цезаре всё было просто: все нечётные месяцы были по 31 дню. Потом пришёл Август; в августе стало 31 день, и начиная с августа чётные месяцы стали по 31 дню.


    1. vvovas
      03.07.2015 20:28
      +6

      Занятно, но сам с детства использовал «правило костяшек». Если считать костяшки пальцев на руках и выемки между ними слева направо, то костяшка будет указывать на 31 день, а выемка на 30(или 28 в феврале). При этом переход с левой руки на правую как раз попадет на июль-август.


      1. MiXei4
        04.07.2015 09:22
        +7

        А теперь открытие — костяшки можно считать и справа налево и вообще только на одной руке, потому что они симметричны :)


    1. Enam Автор
      03.07.2015 20:41
      +3

      Ещё можно помнить о Тиберие, который — по легенде* — пресёк традицию переименования месяцев в честь императоров.

      * Когда к Тиберию пришёл сенат с заискивающим предложением назвать в его честь месяц в календаре, тот, засмеявшись, бросил им в ответ:
      — Глупцы! Что же вы предложите 13-му императору?!


    1. AllexIn
      04.07.2015 20:00
      +2

      И вы действительно считаете что это запомнить проще чем костяшки?


      1. kirillzorin
        06.07.2015 19:50

        Проведите эксперимент: нужно определить сколько дней в ноябре. Ноябрь -> 11-й месяц -> нечётный и больше 7 => 30 дней. В уме быстрее выйдет, чем все костяшки пересчитывать.


        1. khim
          06.07.2015 21:11
          +1

          Каждый по своему это считает. Для меня есть четыре с половиной «реперные» точки: январь и декабрь (все знают что начинает и кончается год «длинными» месяцами) и вышеупомянутые «июль» и «август» (которые тоже оба длинные потому что император Август не хотел, чтобы его месяц был короче, чем у предшественника). Ну и февраль, как «очень особый» случай (там и от года количество дней зависит). Ну и чего там осталось? 2-3 шага от любой реперной точки — и мы добираемся до любого месяца.

          Ваш метод хорош, если помнить какой по номеру каждый месяц, но я таких людей немного знаю. Особенно если учесть что названия врут (судя по названию тот же Ноябрь как бы должен бы быть девятым, да? правда на чётность это не влияет, но всё же...). Всё равно считать нужно откуда-то, а «реперных точек» меньше (что январь первый и декабрь последний, двенадцатый, все знают, а вот что посредине — уже нужно «на пальцах» считать).

          Так-то самый быстрый способ — просто запомнить где сколько дней, но если мы тут что-то обсуждаем, то, похоже, что с запоминаем у нас всё не слишком хорошо, не так ли?


  1. floppox
    03.07.2015 20:21
    +1

    Это круто!


  1. hellman
    03.07.2015 20:38
    +11

    Можно также воспользоваться интерполяцией Лагранжа:

    sage: PolynomialRing(QQ, 'x').lagrange_polynomial(zip(range(1, 13), (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)))
    
    -11/907200*x^11 + 163/181440*x^10 - 37/1260*x^9 + 13481/24192*x^8 - 2055371/302400*x^7 + 240683/4320*x^6 - 28268521/90720*x^5 + 85774775/72576*x^4 - 446998571/151200*x^3 + 46351537/10080*x^2 - 221017/56*x + 1416
    


    1. hellman
      03.07.2015 20:43
      +6

      Или, если смущают дроби, можно сделать по модулю например 37:

      sage: PolynomialRing(GF(37), 'x').lagrange_polynomial(zip(range(1, 13), (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)), algorithm="neville")[-1]
      
      16*x^11 + 12*x^10 + 4*x^8 + 21*x^7 + 29*x^6 + 10*x^5 + 12*x^4 + 34*x^3 + 26*x^2 + 5*x + 10
      


      1. lorc
        03.07.2015 20:52
        +3

        О! спасибо! Очевидным ответом был «интерполяционый полином». Я всё ждал, когда кто-то запостит формулу.


      1. Danov
        03.07.2015 20:56
        +1

        Так не честно! :)
        У вас информации в формуле заметно больше, чем в самом ряду. В статье более компактный вариант.


      1. KvanTTT
        04.07.2015 02:22

        А теперь посчитайте с учетом високосного года :)


  1. BubaVV
    03.07.2015 21:02
    +1

    Интересно, насколько хорошо справится с задачей генетический алгоритм, если скормить ему достаточно квадратных скобок, операций взятия остатка и небольших целых чисел


    1. khim
      03.07.2015 22:30
      +1

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


  1. Mrrl
    03.07.2015 21:25
    +4

    31-[2*{3/7*(x-1)}]-2*[1/(1+(x-2)^2)]


  1. Mercury13
    03.07.2015 21:32
    +3

    Другой механизм описан в книге Романовского «Микрокалькуляторы в рассказах и играх». Пара «месяц-год» переводится к нумерации «с марта» (март = 1, февраль = 12), а затем [30,59*месяц] + день ? 30.

    Правда, ПМК — зверь особый, ветвление там работает медленнее и расходует больше команд, чем умножение. А целую часть даже на Б3-34 можно вычислить через КИПr; r — любой свободный регистр памяти не менее 7 (0…3 автодекрементные, 4…6 автоинкрементные).


    1. Mercury13
      03.07.2015 21:40
      +1

      Немного офтопа. Я ухитрялся на МК-61 на экзамене обращать матрицу 3?3. Заняло это всю память, для вычисления миноров по-чёрному использовались автоинкремент и автодекремент — поэтому матрица вписывалась не в логичные регистры 1…9, а в 7, 8, 9, 1, 2, 3, A, B, C.


      1. speakingfish
        04.07.2015 20:49

        Насколько я помню, существовали и алгоритмы и для матриц 4x4. Только они были уже интерактивные — эпизодически запрашивали ввод данных, не умещавшихся в памяти. Для МК-52 были возможны и ещё более сложные алгоритмы, не влезающие в 104 (вроде) ячейки оперативной памяти — там была возможность ручной подгрузки программ из энергонезависимой памяти. Были также и библиотеки программ и во внешних ПЗУ. Также можно было сохранять/восстанавливать данные оперативной памяти. Но необходимость делать это вбивая адреса вручную, ручное переключение режимов записи и чрезвычайно долгий процесс сохранения/восстановления убивал все желание работать. Старались обходится более отзывчивыми алгоритмами, разбиением задач на части и записью/восстановлением данных с помощью карандаша и бумаги.


    1. lany
      04.07.2015 16:33
      +1

      Давно думал написать статью на Хабр в духе «как сделать вычисление дня недели по дате на советском программируемом калькуляторе в 40-50 шагов памяти», но посчитал, что мелко и никому не интересно. А тут только из одного месяца такое развели :-)


      1. AllexIn
        04.07.2015 20:01
        +3

        Ну теперь-то вы сделаете статью?


        1. Danov
          04.07.2015 22:32

          Мой алгоритм для вычисления в уме:
          habrahabr.ru/post/217389


  1. Mrrl
    03.07.2015 22:56
    +7

    30^(x&1)^(x/8)^(4/x&2)


    1. Danov
      04.07.2015 09:45

      Красиво!
      На шарпе можно скобки убрать:

      30 ^ m & 1 ^ m / 8 ^ 4 / m & 2;
      


      1. Danov
        04.07.2015 09:52
        +1

        Для сравнения, в предложенной в статье формуле 9 операций

        28 + (m + m / 8) % 2 + 2 % m + 1 / m * 2;
        
        а у вас только 7. Интересно, можно ли компактнее?


        1. Mrrl
          05.07.2015 18:18
          +1

          Есть несколько вариантов с 6 действиями:

          ((m+m/8)|30)-(4/m&2)
          31-(102/m&1)-(4/m&2)
          ((~8)>>m&30)|(3-102/m&5)

          Соптимизировать до пяти ни один не удалось.
          ~8 за действие не считается — это константа.


          1. khim
            05.07.2015 18:27
            +1

            Может я чего не понимаю в колбасных обрезках, но в предложенной мною (и исправленной withkittens) формуле всего четыре действия… или там как-то по другому считать нужно?


            1. Mrrl
              05.07.2015 18:31

              Я тоже не понимаю. В формуле со сдвигом их действительно четыре. Возможно, смущает слишком магическая константа?


              1. khim
                05.07.2015 19:02
                +1

                Я думаю тут скорее психология: мы тут думаем над всякими чётностями/нечётностми, цезарями и прочим, а оказывается что самый оптимальный вариант — «спресовать» данные о длинах и всё, думать ни о чём не нужно. Обидна, да?


            1. Danov
              05.07.2015 21:04
              +1

              Ваше решение хорошее. Но, решение включает полную таблицу, пусть и свернутую в 32битную константу. Поиск альтернативных функций интересен сам по себе, как реализация переборных алгоритмов.


  1. mkot
    04.07.2015 10:13
    +8

    Если бы не февраль, то:


    1. mapron
      04.07.2015 14:46
      -1

      Самое красивое и эстетичное из всех =)


      1. AllexIn
        04.07.2015 20:02
        +3

        Но без февраля.


  1. michael_vostrikov
    04.07.2015 17:04
    +1

    Можно вот так еще:

    31
    -
    (3) * (m == 2)
    +
    (1) * (m == 2) * ((y % 4 == 0) - (y % 100 == 0) + (y % 400 == 0))
    -
    (1) * (m == 4 || m == 6 || m == 9 || m == 11)
    


  1. keksmen
    05.07.2015 10:14
    +2

    Вы уверены, что хорошо проверили свои выкладки?

    Функция
    image
    Спокойно утверждает 2100 год как високосный, не смотря на оговорку о том, что это не так.

    И далее, функция
    image
    Ведет себя так же некорректно:

    f(2,2100)===29 //true
    


    1. Enam Автор
      06.07.2015 14:37

      Вы правы, я вижу ошибку. Я поспешно воспользовался этой формулой. Вместо неё следовало использовать следующую формулу:

      (1 - (y % 4 + 2) % (y % 4 + 1)) * ((y % 100 + 2) % (y % 100 + 1)) + (1 - (y % 400 + 2) % (y % 400 + 1))



      ideone.com/VDHV3z

      Я внесу соответствующие исправления в пост.


  1. agrrh
    06.07.2015 05:18

    Октябрь 1582?


  1. Enam Автор
    06.07.2015 19:39

    Благодаря комментарию keksmen исправлена ошибка в формуле определения високосного года (g(y)) и исправлена итоговая формула.


  1. Mrrl
    07.07.2015 07:42

    С високосным годом (для C/C++):

    31-(102/m&1)-((4/m&2)>>((!y%4)-(!y%100)+(!y%400)))

    Операция !y — это не условный переход. В зависимости от желания компилятора он компилируется либо в

        neg ecx
        sbb eax,eax
        inc eax
    

    либо в
        xor eax,eax
        test ecx,ecx
        sete al
    


    1. Enam Автор
      07.07.2015 11:00

      Удостоверьтесь, что не допустили ошибки. Когда я выполняю ваш код, в феврале всегда 28 дней.

      ideone.com/QaTplf


      1. Mrrl
        07.07.2015 11:07
        +1

        Да, скобки не там.

        31-(102/m&1)-((4/m&2)>>(!(y%4)-!(y%100)+!(y%400)))