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



В воскресенье я как обычно бездельничал, просматривая 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 ноября». Хотя такой вариант не совсем согласуется с человеческой интуицией о разнице дат. Но в любом случае это менее неправильно, чем было.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (101):

  1. inoyakaigor
    /#20298786

    Когда исходники были закрытыми подобные посты читать было интереснее. Раньше ведь как:
    1) Присоединился дебаггером к процессу
    2) *какая-то магия с ассемблером*
    3) Профит!
    А сейчас что? Скачал исходники, поставил току останова и отладил. Скукота!

    • developerxyz
      /#20298994

      Так ведь исходники могут не совпадать с версией, которая уже установлена.
      Компилировать чужие исходники (иногда свои) бывает очень трудно.
      И надо знать, куда ставить breakpoint.

      • inoyakaigor
        /#20299800

        Я писал это с некоей долей иронии если что)

    • Alexey2005
      /#20300300 / +1

      Да зачастую в дебаггере с дизассемблером баг найти не в пример проще, чем в исходниках. Которые разбиты на 100500 файлов, и алгоритм размазан по нескольким десяткам из них. Вот так смотришь, что куда передаётся — а там интерфейсы поверх интерфейсов, и тонны абстрактных фабрик фабрик, за которыми понять, как и где конкретно происходит собственно расчёт, не так-то просто. Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.

      • barbanel
        /#20300456

        Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.
        Такой же точно калькулятор, написанный в девяностых, был бы раз в сто меньше, как по объему бинарника, так и по потребляемой памяти.
        У меня впечатление что этот код не только компилируется, но еще и пару миллионов пустых циклов добавляет.

        • Victor_koly
          /#20301172 / +1

          Калькулятор Win XP мог посчитать 250000! (по логике — суммирование логарифмов с достаточной точностью). Но функции типа a^b считал до куда меньшего предела.
          А вот в Win 7 уже не воспринимает результаты размером 1010000 и более.

      • ainoneko
        /#20302634

        Создаётся впечатление, что код на 99% состоит из «воздуха», который реально ни во что не компилируется.
        Вы только что описали ДНК?

      • vlivyur
        /#20303534

        Думаю у этого калькулятора и внутри ассемблерного кода фабрика фабрик с воздухом внутри.

    • undbsd
      /#20300542

      ну почему сразу «какая-то магия», просто переписал калькулятор с нуля и готово :D

  2. manyakRus
    /#20299160 / +1

    «31 июля + 4 месяца = 30 ноября»
    до исправления было лучше — сразу видно что ошибка.
    а щас никто не заметит ошибку и будет использовать неправильный результат :(

    • Deerenaros
      /#20299560

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


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

      • manyakRus
        /#20299606 / +1

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

        • Deerenaros
          /#20300178

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

        • Golickoff
          /#20302782

          Дата = Дата("20190731");
          Дата = ДобавитьМесяц(Дата, 4); //30.11.2019 0:00:00

          • kuza2000
            /#20303476

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

            Ну вот, теперь в 1С все проверили :)

      • Eldhenn
        /#20299790

        > Математически операция складывания месяцев с конкретным числом — это бред

        А «следующая зарплата через месяц после 30 января» — тоже бред?

        • InChaos
          /#20299852 / +1

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

          • Goron_Dekar
            /#20302834

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

            • artoym
              /#20302992

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

              • transcengopher
                /#20305042

                Не согласен.
                Когда вы прибавляете к дате один месяц, то вы прибавляете следующий месяц, а не средний.
                Потому, для 31 января длина следующего месяца не 30, а 28 (29) дней, и именно 28 (29) дней и следует прибавить. Получим 28 (29) февраля.

                • Lissov
                  /#20305284

                  то вы прибавляете следующий месяц

                  Или же текущий. 1 июня + 1 месяц — я ожидаю 1-е июля, а не 2-е (в июле 31 день, в июне 30).

                  • ainoneko
                    /#20305682

                    «Прибавить длину текущего месяца (будет то же число месяца, если оно есть в следующем месяце), но остановиться на последнем дне при переполнении»?

                    • Lissov
                      /#20306142

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

        • Deerenaros
          /#20300164

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

        • FYR
          /#20300374

          Нет не бред, потому что используют не просто «месяц», а «календарный месяц». Что уже сложнее ибо привязанно к календарю. И да скорее всего 28.01 +1 месяц = 28.02 а [29-31].01 + 1 месяц = 01.03

          • eranthis
            /#20300964

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

            • slonpts
              /#20301750

              В таком случае это очень плохая идея — называть такую операцию «сложением».

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

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

              • tbl
                /#20302252

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

              • karavan_750
                /#20302444

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

                • slonpts
                  /#20302504

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

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

                  И снова все будет работать.

                  • eranthis
                    /#20304408

                    Почему нельзя определить месяц? Это единичный интервал, для которого год и столетие кратные. Выразить эти интервалы в днях нельзя, но это не мешает их определению.

              • kuza2000
                /#20303502

                Очень во многих языках операция конкатенации строк обозначается символом "+", что тоже ничего общего не имеет со сложением.

              • eranthis
                /#20304368

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

        • AC130
          /#20301854

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

      • KarasikovSergey
        /#20303314

        Математически операция складывания месяцев с конкретным числом — это бред. Без уточнений по крайне менее.

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

      • vlivyur
        /#20303604

        Когда дело касается дат и времени, ожидаемо что правила математики перестают работать. Можно ещё вспомнить что к дате прибавить 1 год 1 месяц и 1 день не то же самое, что к той же дате прибавить 1 месяц 1 день 1 год.

      • ksr123
        /#20307184

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


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

  3. KvanTTT
    /#20299270 / -2

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

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

    • InChaos
      /#20299806 / +1

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

      • KvanTTT
        /#20300162

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

        • InChaos
          /#20303366

          О, спасибо, годная статейка.

        • Lissov
          /#20305264

          Была даже статья (не могу найти) с переводом доклада с конференции, где это использовалось как реально работающий метод взлома. Зарегистрировать кучу похожих на google доменов с отличием в 1 бит, и из миллиардов запросов некоторые попадают.

    • izuware
      /#20300236 / +1

      Лично видел 2 + 2 = 5. В прошлом веке на 286м процессоре шлейфом от флоповода перекрыло вентилятор.

      • amarao
        /#20300416 / +1

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

        • Zagrebelion
          /#20300506 / +1

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

          • MacIn
            /#20301474

            Да, но он направлен наружу и в XT и в AT корпусах.

        • izuware
          /#20308382

          Точно помню кулер на процессоре, и рабочее место расчетника лимитов, на котором стоял писюк желтой сборки. А вот могли поставить чтото дороже 286/287 или нет — мой склероз сообщать отказывается.

      • Dmitri-D
        /#20300690

        даже если по мнению процессора 2 + 2 уже 5, сообщить об этом он не сможет, будет не в состоянии )))

    • CryptoPirate
      /#20300424

      Не совсем так. Есть атака RawHammer, есть ещё классная атака где в URL по одному биту меняют Bit-squatting. Ситуаций, когда меняется один бит очень много.

  4. Dmitri-D
    /#20300652 / +1

    любопытно, а 29 февраля + 1год это будет 28е февраля?

    • alizar
      /#20300722

      Конечно!

      • developerxyz
        /#20301016

        Но тут появляется другая ошибка — ошибка локализации. Не «365 дни», а «365 дней».

        • N0Good
          /#20301448

          Да что вы знаете об ошибках локализации калькулятора =) Вот мой калькулятор:
          image

  5. Fragster
    /#20301318

    Я как-то делал через долю месяца + округление.

  6. danghyan
    /#20301342

    1. Калькулятор нужен что бы работать на человека, а не на машину. Поэтому угождать надо человеку и 5 мая + месяц должно быть 5 июня… В целом математическую операцию +месяц в программировании можно свести к: берем число месяца, прибавляем 1, вставляем обратно.
    2. В разнице между дат в программах всегда надо учитывать что именно за даты, какого года. Дальше уже понятно что +30 дней это +30 дней, а плюс месяц это плюс месяц…

    В целом очень молодец.

    • masai
      /#20301654 / +1

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

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

      • TRTHHRTS
        /#20301758

        31 февраля, очевидно.
        И срабатывает валидация результата, которая говорит, что как-бы нет такой даты.

      • begin_end
        /#20301792

        Тогда должна действовать поправка: если такого числа в новом месяце нет, берем раннее ближайшее существующее. Кстати, с +год для високосных и февраля то же нужно.

      • Osnovjansky
        /#20302932

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

        • yea
          /#20303438

          (31 января + 1 месяц) + 1 месяц = 28/29 марта
          31 января + (1 месяц + 1 месяц) = 31 января + 2 месяца = 31 марта

          Достаточно логично, как по мне. Ассоциативность придётся закопать, конечно, но у нас ведь тут особое сложение.

          • krylov_sn
            /#20304080

            такая же логика в древнем FoxPro)

          • Osnovjansky
            /#20304090

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

        • masai
          /#20303896

          Выше уже обсудили

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


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

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

  7. stuq1
    /#20301974 / +1

    В старом калькуляторе из Windows 7 есть аналогичный баг, но выдается немного иной ответ

    Изображение
    image

  8. red_andr
    /#20302152

    Ох уж эти вечные проблемы с годом в 365,2425 дней. Когда я работал с климатическими моделями мы просто использовали 360-дневный год. 12 месяцев по 30 дней и всё. Красота!

    • sumanai
      /#20302254 / +4

      То то прогнозы погоды врут!

      • red_andr
        /#20305818 / +1

        Ну погоду то считают метеорологические модели, а не климатические. У них нормальный земной календарь.

  9. Porohovnik
    /#20302250 / +2

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


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

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


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

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

  10. prostofilya
    /#20302680

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

  11. tuxi
    /#20302698

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

    • prostofilya
      /#20302774

      Что-то не совсем понял, какие могут быть проблемы с номерами недель?

      • m1rko
        /#20302788

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

        • prostofilya
          /#20302814

          Ну это уже головная боль того, кто ставил задачу.

          • tuxi
            /#20302838

            Не совсем. «Бизнесу» часто нужно сравнить продажи по производственным неделям, но в рамках календарного года. И вот тут начинается самое интересное.

            • prostofilya
              /#20302856

              Как бы там бизнесу не было нужно, календарь не пододвинется. Если аналитики не могут в анализ информации и в грамотную постановку задач, хотя бы опуститься с недель до дней недели на необходимых участках, то это всё же их вина/проблема.

          • alizar
            /#20302852

            Ну он ставит задачу, типа, «Сравнить показатели первой недели месяца с предыдущим»

            • prostofilya
              /#20302864

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

              • alizar
                /#20302884 / +1

                При суммировании финансовых результатов всех недель месяца и всех дней месяца получатся разные цифры. Бизнес может выдвинуть претензии, что ваша программа глючная.

                • prostofilya
                  /#20302914

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

                  • tuxi
                    /#20303068

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

                    • prostofilya
                      /#20303190 / +1

                      Разработчик должен включать мозги, но если разработчик не понимает бизнес-процесс, то он должен довериться тому, кто поставил задачу. А если логика бизнес-процесса была донесена ему неверно и все уточняющие вопросы от разработчика игнорировались, то это не вина разработчика.

                      • ainoneko
                        /#20305702 / +1

                        "Девятый вагон — это тот, который сразу после восьмого, а не тот, который перед десятым".

                        • tuxi
                          /#20306914

                          Очень точная характеристика ситуации, как я мог забыть эту эпохальную вещь :) прям в точку!

    • Odrin
      /#20303558

      Первая неделя года — это неделя, которая включает в себя 4 января (в странах где неделя начинается с понедельника).

      • tuxi
        /#20303718

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

  12. mikserok
    /#20302792

    У меня на семерке работает без аномалий — 5 месяцев и 6 дней. Видимо у автора виндовс 10.
    image

    • Skerrigan
      /#20302802

      Оффтоп не по теме
      Эх, прекрасное стекло в интерфейсе. Как же я тоскую по нему…

    • Symphel
      /#20302886 / +2

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

    • anton9843
      /#20302906 / +2

      По идее между 31.07 и 30.12 не должно быть больше 5 месяцев
      Если по человечески подумать, то 31.07 + 5 месяцем, это 31.12,
      а тут 30.12. По мне это 5 месяцев без одного дня,
      а не 5 месяцев и 6 дней

      • unC0Rr
        /#20303574

        Кроме того, 152 дня никак не соответствуют 5 месяцам и 6 дням. В месяце получается 29,2 дня в таком случае.

    • almaredan
      /#20303994

      Похоже от 30-г декабря до 31-го -6 дней
      image

    • developerxyz
      /#20308538

      Как раз 5 месяцев 6 дней — аномалия.
      31.07.19 + 5m6d = 06.01.20
      30.12.19 - 5m6d = 24.07.19

  13. Alex023
    /#20303856

    Да у калькулятора и перевод на русский знатный: From — От, To — КОМУ.
    КОМУ, Карл! :)

  14. Eldhenn
    /#20304362

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

  15. transcengopher
    /#20304978

    В Java:

    var first  = LocalDate.of(2019, JULY, 31);
    var second = LocalDate.of(2019, DECEMBER, 30);
    
    print(Period.between(first, second));
    


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

  16. sergey-b
    /#20313182

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


    28.02.2016


    • vlivyur
      /#20313280

      Вроде всё правильно.

      • developerxyz
        /#20313370

        Тогда от 28 февраля 2016 года до 29 февраля 2016 года всего 0 дней, таким образом мы доказали, что 28 и 29 февраля 2016 года — один и тот же день.