Исправляя мелкий баг в calc.exe

Original author: Peter Tissen
  • Translation
В воскресенье я как обычно бездельничал, просматривая Reddit. Прокручивая щенячьи забавы и плохой юмор программистов, моё внимание привлёк один конкретный пост. Речь шла о баге в calc.exe.


Неверный результат вычисления диапазона дат в Калькуляторе Windows

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

Заинтересовавшись причиной, я сделал то, что вы делаете в таких случаях: попробовал на своей машине, чтобы запостить «У меня всё работает». И повторение ситуации из поста «31 июля − 31 декабря» на моей машине дало правильный результат «5 месяцев». Но немного потестировав, я обнаружил, что «31 июля – 30 декабря» на самом деле вызывает ошибку. Выводится не совсем корректное значение «5 месяцев, 613566756 недель, 3 дня».

Я ещё не закончил расшатывать программу и тут вспомнил: «О, а разве калькулятор — не одна из тех вещей, для которых Microsoft открыла исходники?» И действительно. Эта ошибка не могла быть слишком сложной, поэтому я подумал, что попробую найти её. Скачать исходники было достаточно просто, и добавление требуемой рабочей нагрузки UWP в Visual Studio также прошло без сучка и задоринки.

Навигация по кодовым базам, с которыми вы не знакомы, — это то, к чему привыкаешь со временем. Особенно когда вы хотите внести вклад в проекты с открытым исходным кодом, где находите баг. Однако незнание XAML или WinRT, конечно, не облегчает дело.

Я открыл файл solution и заглянул в проект “Calculator” в поисках любого файла, который должен иметь отношение к багу. Нашёл DateCalculator.xaml, затем вроде бы подходящий по названию DateDiff_FromDate to DateCalculatorViewModel.cpp и, наконец, DateCalculator.cpp.

Установив точку останова и посмотрев некоторые переменные, я увидел, что конечное значение DateDifference уже неверно. То есть это была не просто ошибка преобразования в строку, а ошибка фактического вычисления.

Фактическое вычисление в упрощённом псевдокоде выглядит примерно так:

DateDifference calculate_difference(start_date, end_date) {
    uint[] diff_types = [year, month, week, day]
    uint[] typical_days_in_type = [365, 31, 7, 1]
    uint[] calculated_difference = [0, 0, 0, 0]
    date temp_pivot_date
    date pivot_date = start_date
    uint days_diff = calculate_days_difference(start_date, end_date)

    for(type in differenceTypes) {
        temp_pivot_date = pivot_date
        uint current_guess = days_diff /typicalDaysInType[type] 
        if(current_guess !=0)
            pivot_date = advance_date_by(pivot_date, type, current_guess)
        
        int diff_remaining
        bool best_guess_hit = false
        do{
            diff_remaining = calculate_days_difference(pivot_date, end_date)
            if(diff_remaining < 0) {
                // pivotDate has gone over the end date; start from the beginning of this unit
                current_guess = current_guess - 1
                pivot_date = temp_pivot_date
                pivot_date = advance_date_by(pivot_date, type, current_guess)
                best_guess_hit = true
            } else if(diff_remaining > 0) {
                // pivot_date is still below the end date
                if(best_guess_hit)
                    break;
                current_guess = current_guess + 1
                pivot_date = advance_date_by(pivot_date, type, 1)
            }
        } while(diff_remaining!=0)

        temp_pivot_date = advance_date_by(temp_pivot_date, type, current_guess)
        pivot_date = temp_pivot_date 
        calculated_difference[type] = current_guess
        days_diff = calculate_days_difference(pivot_date, end_date)
    }
    calculcated_difference[day] = days_diff
    return calculcated_difference
}

Выглядит нормально. В логике проблем нет. По сути, функция делает следующее:

  • отсчитывает полные годы от стартовой даты
  • с момента даты последнего полного года отсчитывает месяцы
  • с момента даты последнего полного месяца отсчитывает недели
  • с момента даты последней полной недели отсчитывает оставшиеся дни

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

date = advance_date_by(date, month, somenumber)
date = advance_date_by(date, month, 1)

равен

date = advance_date_by(date, month, somenumber + 1)

Обычно это одно и то же. Но возникает вопрос: «Если вы попали на 31-е число месяца, в следующем месяце 30 дней, вы прибавляете один месяц, то куда попадёте?»

Похоже, для Windows.Globalization.Calendar.AddMonths(Int32) ответ будет «на 30-е число».

А это значит, что:
«31 июля + 4 месяца = 30 ноября»
«30 ноября + 1 месяц = 30 декабря»
«31 июля + 5 месяцев = 31 декабря»

Таким образом, операция AddMonths не является ни дистрибутивной (с AddMonth-умножением), ни коммутативной, ни ассоциативной. Какой вообще-то должна быть операция «сложения». Разве не весело работать со временем и календарями?

Почему в данном случае ошибка задания диапазона приводит к такому огромному числу недель? Как вы могли догадаться, это возникает из-за того, что days_diff является беззнаковым типом. Это превращает -1 дней в огромное количество, которое затем передаётся на следующую итерацию цикла с неделями. Которая затем пытается исправить ситуацию, уменьшая current_guess, но не уменьшая беззнаковую переменную.

Что ж, это был интересный способ провести воскресенье. Я создал пулл-запрос на Github с минимальным «исправлением». Я ставлю «исправление» в кавычки, потому что теперь вычисление выглядит так:



Думаю, технически это правильный результат, если считать, что «31 июля + 4 месяца = 30 ноября». Хотя такой вариант не совсем согласуется с человеческой интуицией о разнице дат. Но в любом случае это менее неправильно, чем было.
Support the author
Share post

Similar posts

Comments 108

    +51
    Когда исходники были закрытыми подобные посты читать было интереснее. Раньше ведь как:
    1) Присоединился дебаггером к процессу
    2) *какая-то магия с ассемблером*
    3) Профит!
    А сейчас что? Скачал исходники, поставил току останова и отладил. Скукота!
      +1
      Так ведь исходники могут не совпадать с версией, которая уже установлена.
      Компилировать чужие исходники (иногда свои) бывает очень трудно.
      И надо знать, куда ставить breakpoint.
        +2
        Я писал это с некоей долей иронии если что)
        +18
        Да зачастую в дебаггере с дизассемблером баг найти не в пример проще, чем в исходниках. Которые разбиты на 100500 файлов, и алгоритм размазан по нескольким десяткам из них. Вот так смотришь, что куда передаётся — а там интерфейсы поверх интерфейсов, и тонны абстрактных фабрик фабрик, за которыми понять, как и где конкретно происходит собственно расчёт, не так-то просто. Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.
          +12
          Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.
          Такой же точно калькулятор, написанный в девяностых, был бы раз в сто меньше, как по объему бинарника, так и по потребляемой памяти.
          У меня впечатление что этот код не только компилируется, но еще и пару миллионов пустых циклов добавляет.
            +1
            Калькулятор Win XP мог посчитать 250000! (по логике — суммирование логарифмов с достаточной точностью). Но функции типа a^b считал до куда меньшего предела.
            А вот в Win 7 уже не воспринимает результаты размером 1010000 и более.
            +3
            Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.
            Вы только что описали ДНК?
              +1
              Думаю у этого калькулятора и внутри ассемблерного кода фабрика фабрик с воздухом внутри.
              +1
              ну почему сразу «какая-то магия», просто переписал калькулятор с нуля и готово :D
              +3
              «31 июля + 4 месяца = 30 ноября»
              до исправления было лучше — сразу видно что ошибка.
              а щас никто не заметит ошибку и будет использовать неправильный результат :(
                +1

                Математически операция складывания месяцев с конкретным числом — это бред. Без уточнений по крайне менее. Мы можем взять месяц как стандартные 30 дней (что является математическим округлением среднего ~30.44), тогда, например, мы будем "пропускать" февраль: 31 января + 1 месяц = 1~2 марта. Можно "обрезать" месяц, тогда операция теряет ассоциативность: (31 января + 1 месяц) + 1 месяц != 31 января + (1 месяц + 1 месяц). Можно "сохранять" число при обрезании, но тогда повляются "странные" элементы: 28</28> февраля, 28</29> февраля, 28</30> февраля, 28</31> февраля (да и стремление максимально узаконить такие операции — странная, так как есть же ещё разные календари со свойствами транзитивности — всё это учитывать… непонятно зачем).


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

                  +1
                  "… Не факт, что это ошибка"
                  — такая же команда есть в языках программирования, например в 1С Предприятие.
                  Лень проверять, но думаю что 1С скажет что это точно ошибка :)
                    +1

                    Лучше проверить. Я проверил на python — у него в timedelta нельзя определить именно месяц. А вычитание конкретных дат приводит к конкретному количеству дней.

                      0

                      dateutil.relativedelta

                      +2
                      Дата = Дата("20190731");
                      Дата = ДобавитьМесяц(Дата, 4); //30.11.2019 0:00:00
                        +1
                        1C язык запросов:
                        ВЫБРАТЬ ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(2019, 7, 31), МЕСЯЦ, 4) КАК Поле1
                        Результат: 30.11.2019 0:00:00

                        Ну вот, теперь в 1С все проверили :)
                      +3
                      > Математически операция складывания месяцев с конкретным числом — это бред

                      А «следующая зарплата через месяц после 30 января» — тоже бред?
                        +1
                        С обычной человеческой точки зрения это нормально, 5 февраля + месяц = 5 марта, и даже неважно високосный год или нет.
                        Но с математической это действительно бред, т.к. понятие месяц = неизвестная величина (28, 29, 30, 31), и в этом случае можно прибавлять только кол-во дней или недель (часов, минут и т.д.), т.е. строго детерминированные величины.
                          +1

                          А что с человеческой точки зрения будет 31 января + месяц?

                            0
                            Если с точки зрения «месяцев», то «31 января» стоит читать как «конец января», значит через месяц — это «конец февраля», ну и это равно «28 (или 29) февраля».
                            Если же с точки зрения «дней», то надо определить значение «месяц» в днях. Думаю для большинства месяц = 30 дней, что даёт нам 1-2 марта.
                              0
                              Не согласен.
                              Когда вы прибавляете к дате один месяц, то вы прибавляете следующий месяц, а не средний.
                              Потому, для 31 января длина следующего месяца не 30, а 28 (29) дней, и именно 28 (29) дней и следует прибавить. Получим 28 (29) февраля.
                                +2
                                то вы прибавляете следующий месяц

                                Или же текущий. 1 июня + 1 месяц — я ожидаю 1-е июля, а не 2-е (в июле 31 день, в июне 30).
                                  0
                                  «Прибавить длину текущего месяца (будет то же число месяца, если оно есть в следующем месяце), но остановиться на последнем дне при переполнении»?
                                    +1
                                    Вообще мне кажется логичным проинформировать пользователя. Например, надписью «результат был скорректирован» и явно описать, в чём проблема. Выдавать возможно неверный результат как будто он правильный — не лучшая идея.
                                    Всё таки стоит исходить из того, что нет однозначно верного ответа, что такое «через месяц после 31 января». И так как калькулятор не может знать, что ожидает пользователь, любой ответ может оказаться неверным для задачи пользователя.
                          +1

                          Может показаться странно, но я ни разу не слышал — через месяц после 30 января. Через месяц — да, но это подразумевает уже само по себе ± пара дней. Но вообще контекст в таких вещах плохо работает и часто уточняют — в конце контекст.следующий_месяц, и даже в этом случае 146% переспросят "то есть в январе?". А именно такая формулировка встречается разве что в анекдотах про математиков (и задачах по спортивному программированию).

                            –1
                            Нет не бред, потому что используют не просто «месяц», а «календарный месяц». Что уже сложнее ибо привязанно к календарю. И да скорее всего 28.01 +1 месяц = 28.02 а [29-31].01 + 1 месяц = 01.03
                              +6
                              В операциях с месяцами основная ошибка — это попытка «взвесить» месяц в днях, что по определению невозможно, да и в корне неверно. Чтобы избежать неоднозначности, этого делать не нужно. В этом плане, очень правильно эта математика реализована, например, в PostgreSQL. Чтобы понять логику прибавления месяцев, проще всего взять пример зарплаты. Если заплата выплачивается каждый месяц, то месяц не может быть пропущен по определению. Иными словами, при добавлении месяца к любой дате в январе должна быть дата в феврале, но никак не в марте. Отсюда правда вытекают такие порой неочевидные моменты, как например:
                              28/29/30/31 января + 1 месяц = 28 февраля
                              (дата + N месяцев) - N месяцев не всегда равно дата
                              (дата + 1 месяц) + 1 месяц не всегда равно дата + 2 месяца
                              и т.п.
                                0
                                В таком случае это очень плохая идея — называть такую операцию «сложением».

                                Потому что сложение коммутативно, ассоциативно и дистрибутивно (с умножением), и все слишком сильно к этому привыкли.

                                Лучше назвать ее как-нибудь по-другому, и не пытаться использовать здесь знаки "+" и "-", чтобы не вводить людей в заблуждение.
                                  0
                                  но люди привыкли к дате прибавлять и вычитать периоды, поэтому "+" и "-", хоть это и не совсем те же ассоциативные операции
                                    0

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

                                      –3
                                      ОК, если ввести 2 типа time и timeinterval, то можно ввести операцию их сложения.

                                      Тогда можно определить значения типа timeinterval: '1 second', '1 day', '1 week'.
                                      Но нельзя определить значения '1 month', '1 year', '1 century'.

                                      И снова все будет работать.
                                        0
                                        Почему нельзя определить месяц? Это единичный интервал, для которого год и столетие кратные. Выразить эти интервалы в днях нельзя, но это не мешает их определению.
                                      +1
                                      Очень во многих языках операция конкатенации строк обозначается символом "+", что тоже ничего общего не имеет со сложением.
                                        0
                                        Начнём с того, что не всегда. Второй момент, мы оперируем с разными величинами — датами и интервалами. Последние, в довершение всего, ещё и могут быть выражены в величинах, однозначно не приводимых друг к другу. А как называть/обозначать эту операцию — дело десятое.
                                    0
                                    Не бред, но и не операция сложения, как в комментариях уже указали.
                                    +1
                                    Математически операция складывания месяцев с конкретным числом — это бред. Без уточнений по крайне менее.

                                    Не бред, а недостаточная проработка логики. Если уж введена сама возможность складывать месяцы, то при выполнении такой операции должны фоном проводиться проверки: заданное условие DD.MM.YYYY проверяется на високосность, месяц точки отсчёта, таким образом прибавка 3х месяцев учитывает календарный состав следующих за заданным трёх месяцев и система точно знает — сколько там на самом деле дней. Это не так сложно, календарь всегда доступен для обращения за актуальными данными.
                                      0
                                      Когда дело касается дат и времени, ожидаемо что правила математики перестают работать. Можно ещё вспомнить что к дате прибавить 1 год 1 месяц и 1 день не то же самое, что к той же дате прибавить 1 месяц 1 день 1 год.
                                        0

                                        Тут важно, для чего считать. Месяц на бытовом уровне может быть примерным, особенно если речь идет о десятилетия.


                                        А если о платежах каких-то — то может быть важен каждый день.

                                      –3
                                      Но это всегда может быть какой-то перевёрнутый бит каким-то высокоэнергетическим лучом от какого-то дружественного космического соседа.

                                      Я конечно понимаю, что это шутка, но вероятность того, что из-за этого повредится какой-то бит в памяти компьютера крайне мала, не говоря о вероятности повреждения конкретного. Скорее всего произойдте просто синий экран и компьютер перезгарузится. Ну или в какой-нибудь картинке пиксель или блок пикселей неправильно отобразится.

                                        +3
                                        В расчете на один бит вероятность очень мала, а если взять сервер с терабайтами памяти то вероятность далека не такая маленькая, поэтому и используют ECC память. Насчет синего экрана, то как раз процент занимаемой памяти процессами очень мал, относительно данных на таких огромных объемах, и скорее подпортятся данные, чем использующий их процесс.
                                        Исследования, проведенные IBM в 1990-х годах, показывают, что компьютеры обычно испытывают около одной ошибки, вызванной космическим лучом, на 256 мегабайт оперативной памяти в месяц.
                                          0

                                          Ок, наверное был не прав. Кстати, на хабре уже была статья про статистику отказов в серверной памяти.

                                            0
                                            О, спасибо, годная статейка.
                                              0
                                              Была даже статья (не могу найти) с переводом доклада с конференции, где это использовалось как реально работающий метод взлома. Зарегистрировать кучу похожих на google доменов с отличием в 1 бит, и из миллиардов запросов некоторые попадают.
                                            +4
                                            Лично видел 2 + 2 = 5. В прошлом веке на 286м процессоре шлейфом от флоповода перекрыло вентилятор.
                                              +2

                                              Вентилятор на 286? Откуда?

                                                +2

                                                В блоках питания вроде бы были.

                                                  0
                                                  Да, но он направлен наружу и в XT и в AT корпусах.
                                                  0
                                                  Точно помню кулер на процессоре, и рабочее место расчетника лимитов, на котором стоял писюк желтой сборки. А вот могли поставить чтото дороже 286/287 или нет — мой склероз сообщать отказывается.
                                                  +1
                                                  даже если по мнению процессора 2 + 2 уже 5, сообщить об этом он не сможет, будет не в состоянии )))
                                                  0
                                                  Не совсем так. Есть атака RawHammer, есть ещё классная атака где в URL по одному биту меняют Bit-squatting. Ситуаций, когда меняется один бит очень много.
                                                  0
                                                  любопытно, а 29 февраля + 1год это будет 28е февраля?
                                                    0
                                                    Конечно!
                                                      0
                                                      Но тут появляется другая ошибка — ошибка локализации. Не «365 дни», а «365 дней».
                                                        +1
                                                        Да что вы знаете об ошибках локализации калькулятора =) Вот мой калькулятор:
                                                        image
                                                    0
                                                    Я как-то делал через долю месяца + округление.
                                                      0
                                                      1. Калькулятор нужен что бы работать на человека, а не на машину. Поэтому угождать надо человеку и 5 мая + месяц должно быть 5 июня… В целом математическую операцию +месяц в программировании можно свести к: берем число месяца, прибавляем 1, вставляем обратно.
                                                      2. В разнице между дат в программах всегда надо учитывать что именно за даты, какого года. Дальше уже понятно что +30 дней это +30 дней, а плюс месяц это плюс месяц…

                                                      В целом очень молодец.
                                                        +1
                                                        Поэтому угождать надо человеку и 5 мая + месяц должно быть 5 июня…

                                                        А чему в таком случае будет равно «31 января + 1 месяц»?

                                                          0
                                                          31 февраля, очевидно.
                                                          И срабатывает валидация результата, которая говорит, что как-бы нет такой даты.
                                                            0
                                                            Тогда должна действовать поправка: если такого числа в новом месяце нет, берем раннее ближайшее существующее. Кстати, с +год для високосных и февраля то же нужно.
                                                              0
                                                              Выше уже обсудили, что как правило, 31 января + 1 месяц = 28/29 февраля, в зависимости от года.
                                                              Куда интереснее, чему должно быть равно (31 января + 1 месяц) + 1 месяц. Нужно ли помнить предысторию получения текущего значения даты.
                                                                +1
                                                                (31 января + 1 месяц) + 1 месяц = 28/29 марта
                                                                31 января + (1 месяц + 1 месяц) = 31 января + 2 месяца = 31 марта

                                                                Достаточно логично, как по мне. Ассоциативность придётся закопать, конечно, но у нас ведь тут особое сложение.
                                                                  0
                                                                  такая же логика в древнем FoxPro)
                                                                    0
                                                                    Да, пожалуй, хотелось бы видеть именно 31 марта, но если, например, плюсуем по месяцу в цикле, то при наивной реализации получим 28 марта. Я пытался обратить внимание на это
                                                                    И да, я согласен, что вешать такое поведение на стандартный оператор сложения — готовить почву для багов
                                                                      0
                                                                      А 30 января + 1 месяц = 28/29 февраля или 27/28 февраля?
                                                                      если так идти назад по 1 дню, то 3 января + 1 месяц = 1 февраля?
                                                                        0
                                                                        Я себе это так представляю:

                                                                        31 января + 1 месяц = 28/29 февраля
                                                                        30 января + 1 месяц = 28/29 февраля
                                                                        ...
                                                                        28 января + 1 месяц = 28 февраля
                                                                        27 января + 1 месяц = 27 февраля


                                                                        Это более-менее соответствует моему повседневному опыту работы с датами и хорошо формализуется. Но, повторюсь, сложением в обычном математическом смысле это не является. А пытаться натянуть даты на обычное сложение — плохая затея, получится только хуже.
                                                                          0
                                                                          По вашей логике 15 февраля + 1 месяц = 15 марта
                                                                          Но 15 февраля — 2я половина месяца, а 15 марта — 1я половина месяца! Выходит что мы прыгнули меньше чем на 1 месяц!
                                                                          Должно быть 15 февраля + 1 месяц = 16/17 марта
                                                                            0
                                                                            Вторая/первая половина месяца — как бухгалтерия скажет, так оно и будет. Если 15.01 сказали что следующая з/п будет через месяц, то хоть ты тресни, но она будет 15.02, и доводы что это вторая половина месяца никто слушать не будет. Вас же не смущает что в третьей декаде месяца от 8 до 11 дней.
                                                                              0
                                                                              Если вам 15 февраля скажут «давай через месяц», в вашей голове возникнет 15 марта или 16/17 марта? Если второе, то нам не о чем спорить, поскольку у нас разная интуиция касательно расчетов с датами. А если первое, то ещё раз повторю, что во варианте, которого я придерживаюсь, не используется обычное арифметическое сложение, соответственно неверно находить ошибки в нём, придерживаясь правил обычной арифметики.
                                                                                0
                                                                                Представьте, что у вас первая зарплата в компании 15 февраля. Так же рассуждать будете? ;) Сдвиг на месяц не предполагает привяки к половинам и каким-либо другим частям месяца — это всегда либо то же число, что и в прошлом месяце, либо максимальное дата месяца, если число в месяце отсутствует.
                                                                          0
                                                                          Выше уже обсудили

                                                                          Ага, вижу. Я просто задал вопрос, когда обсуждения ещё не было.


                                                                          как правило, 31 января + 1 месяц = 28/29 февраля, в зависимости от года.
                                                                          Куда интереснее, чему должно быть равно (31 января + 1 месяц) + 1 месяц. Нужно ли помнить предысторию получения текущего значения даты.

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

                                                                      +1
                                                                      В старом калькуляторе из Windows 7 есть аналогичный баг, но выдается немного иной ответ
                                                                      Изображение
                                                                      image
                                                                        +5
                                                                        Ох уж эти вечные проблемы с годом в 365,2425 дней. Когда я работал с климатическими моделями мы просто использовали 360-дневный год. 12 месяцев по 30 дней и всё. Красота!
                                                                          +15
                                                                          То то прогнозы погоды врут!
                                                                            +1
                                                                            Ну погоду то считают метеорологические модели, а не климатические. У них нормальный земной календарь.
                                                                          +2

                                                                          А почему некто не догадался представить мечюсяцы как замкнутый двухсторонний цикл?
                                                                          И прибавление месяца, это просто переход на новое значение в списке…
                                                                          С годом тоде самое, но список не замкнутый
                                                                          Пример 1:
                                                                          10 февраля 2018


                                                                          • 5 месяцев
                                                                            должно работать вот как:
                                                                            Сохраняем в буфер количество дней
                                                                            Передвигаем замкнутый список месяцев на 5 позиций возвращаем дни.

                                                                          Пример 2:
                                                                          10 февраля 2018


                                                                          • 5 месяцев 25дней
                                                                            Повторяем все что в примере 1, а дальше
                                                                            Сохраняем сумму дней в переменную
                                                                            После чего
                                                                            Вытягиваем из двух связного списка количество дней в получившимся месяце
                                                                            И вычитаем из получившегося дней, если разность больше 0 переключаем месяц и добавляем разность, если меньше просто добавляем сумму дней

                                                                          Вроде всё логично кроме одного понятия: сначала прибавлять дни, а уже потом месяцы или наоборот?
                                                                          Лучший вариант-сделать галочку, что переключает это состояние

                                                                            +1
                                                                            А в чём проблема брать количество дней тех месяцев, которые мы складываем?
                                                                            Пример: 20 июня 2019 + 3 месяца.
                                                                            1) Берём текущий месяц + 2 следующих (июнь, июль, август).
                                                                            2) Берём количество дней в этих месяцах и складываем (30 + 31 + 31, 92).
                                                                            3) Прибавляем количество дней к дате отсчёта, предварительно преобразуя начальную дату в дни (01.01.1970), смотря как в каком ЯП реализована работа с датами.
                                                                            4) Преобразуем кол-во дней в дату.
                                                                              0

                                                                              Уххх, а еще есть боль от номеров недель. "Надо сравнить продажи за 2 года с группировкой по номерам недель. ....wtf!!! почему один год у тебя начинается с последних чисел декабря предыдущего, а другой не с первого января????" :)

                                                                                0
                                                                                Что-то не совсем понял, какие могут быть проблемы с номерами недель?
                                                                                  0
                                                                                  Предположим, что неделя должна начинаться с понедельника. Тогда если год начинается посреди недели, то первая неделя года начнётся в прошлом году. То же самое с месяцами. Первая неделя месяца начинается в прошлом месяце, отсюда и проблемы.
                                                                                    0
                                                                                    Ну это уже головная боль того, кто ставил задачу.
                                                                                      0
                                                                                      Не совсем. «Бизнесу» часто нужно сравнить продажи по производственным неделям, но в рамках календарного года. И вот тут начинается самое интересное.
                                                                                        +1
                                                                                        Как бы там бизнесу не было нужно, календарь не пододвинется. Если аналитики не могут в анализ информации и в грамотную постановку задач, хотя бы опуститься с недель до дней недели на необходимых участках, то это всё же их вина/проблема.
                                                                                        0
                                                                                        Ну он ставит задачу, типа, «Сравнить показатели первой недели месяца с предыдущим»
                                                                                          0
                                                                                          Я обязательно уточню что считать первой неделей месяца. Считаем с первого понедельника месяца? -Ок, не проблема.
                                                                                            0
                                                                                            При суммировании финансовых результатов всех недель месяца и всех дней месяца получатся разные цифры. Бизнес может выдвинуть претензии, что ваша программа глючная.
                                                                                              +1
                                                                                              А это та контора, где надо семь перпендикулярных линий прозрачным маркером нарисовать? У меня шмотки собраны, резюме разослано.
                                                                                                0

                                                                                                Вы слишком категоричны. Данная закавыка может возникнуть в любой конторе, в любой стране и никто не говорит, что она не решаема. Я говорю, что она решается через некоторую боль и страдания.
                                                                                                И разработчик все же должен включать мозги, не имплементировать тупо по "наиподробнейшему" ТЗ. Иначе это мартышка, а не девелопер

                                                                                                  +1
                                                                                                  Разработчик должен включать мозги, но если разработчик не понимает бизнес-процесс, то он должен довериться тому, кто поставил задачу. А если логика бизнес-процесса была донесена ему неверно и все уточняющие вопросы от разработчика игнорировались, то это не вина разработчика.
                                                                                                    +1
                                                                                                    "Девятый вагон — это тот, который сразу после восьмого, а не тот, который перед десятым".
                                                                                                      0
                                                                                                      Очень точная характеристика ситуации, как я мог забыть эту эпохальную вещь :) прям в точку!
                                                                                    0
                                                                                      0
                                                                                      Первая неделя года — это неделя, которая включает в себя 4 января (в странах где неделя начинается с понедельника).
                                                                                        0
                                                                                        У нас в стране есть такое понятие как «производственный календарь»
                                                                                        В его контексте, бывают не только недели которые начинаются с прошлого года, но даже года с разным кол-вом этих самых недель.
                                                                                        Эта тема не столько однозначная как может показаться на первый взгляд.
                                                                                      0
                                                                                      У меня на семерке работает без аномалий — 5 месяцев и 6 дней. Видимо у автора виндовс 10.
                                                                                      image
                                                                                        0
                                                                                        Оффтоп не по теме
                                                                                        Эх, прекрасное стекло в интерфейсе. Как же я тоскую по нему…
                                                                                          +6

                                                                                          Но ведь это неверно, результат меньше 5 месяцев

                                                                                            +2
                                                                                            По идее между 31.07 и 30.12 не должно быть больше 5 месяцев
                                                                                            Если по человечески подумать, то 31.07 + 5 месяцем, это 31.12,
                                                                                            а тут 30.12. По мне это 5 месяцев без одного дня,
                                                                                            а не 5 месяцев и 6 дней
                                                                                              0
                                                                                              Кроме того, 152 дня никак не соответствуют 5 месяцам и 6 дням. В месяце получается 29,2 дня в таком случае.
                                                                                              0
                                                                                              Похоже от 30-г декабря до 31-го -6 дней
                                                                                              image
                                                                                                0
                                                                                                Как раз 5 месяцев 6 дней — аномалия.
                                                                                                31.07.19 + 5m6d = 06.01.20
                                                                                                30.12.19 - 5m6d = 24.07.19
                                                                                                0
                                                                                                Да у калькулятора и перевод на русский знатный: From — От, To — КОМУ.
                                                                                                КОМУ, Карл! :)
                                                                                                  +1
                                                                                                  И до сих пор никто не вспомнил про таймзоны, летнее время, советский революционный календарь и особенности счёта времени при движении вокруг света. А кто-то даже до сих пор настолько наивен, что недели, дни, часы и минуты считает точно определёнными и неизменными величинами.
                                                                                                    +1
                                                                                                    Такая статья уже была на Хабре.
                                                                                                    0
                                                                                                    В Java:
                                                                                                    var first  = LocalDate.of(2019, JULY, 31);
                                                                                                    var second = LocalDate.of(2019, DECEMBER, 30);
                                                                                                    
                                                                                                    print(Period.between(first, second));
                                                                                                    


                                                                                                    Выведет P4M30D, что очень близко с результатом после починки (т.к. P30D равен P4W2D). Может это и «неправильно» — но неправильно скорее думать об этой операции как о каноничном сложении. Зато теперь калькулятор выдаёт тот же ответ, что многие другие приложения (в частности, написанные на Java, да).
                                                                                                      0

                                                                                                      Смотрите, похоже, с годами тоже аналогичная проблема проявляется


                                                                                                      28.02.2016


                                                                                                        0
                                                                                                        Вроде всё правильно.
                                                                                                          +1
                                                                                                          Тогда от 28 февраля 2016 года до 29 февраля 2016 года всего 0 дней, таким образом мы доказали, что 28 и 29 февраля 2016 года — один и тот же день.
                                                                                                            0
                                                                                                            Я выше уже писал, что прибавить один год затем один день это не то же самое, что прибавить день, а затем прибавить один год. При прибавлении целого года никто не ожидает что поменяется месяц, а с днями — легко.

                                                                                                      Only users with full accounts can post comments. Log in, please.