Как лучше всего определить количество рабочих месяцев в заданных диапазонах дат - PullRequest
0 голосов
/ 01 мая 2020

У меня есть требование, чтобы найти количество эффективных рабочих месяцев на заданных диапазонах дат. Где, как

effective working month = Number of working days work per month/ total working days of that month

working days considered as days without weekends. 

Текущий алгоритм, который мы используем, таков:

   Total working months= number of working months in first month + number of working months in last month + number of months in between

Здесь мы смогли упростить наш расчет для работы без l oop. Однако код длинный и состоит из множества функций. к сожалению, из-за чувствительности информации, я не могу разместить код здесь. Я был бы признателен, если бы кто-нибудь смог дать мне лучший алгоритм, позволяющий максимально повысить эффективность кода, потому что производительность является для нас главным приоритетом.

//Unit test would be start date = 2020-05-20 , end date = 2021-08-11 expected result is 14.74458875

Number of months in first month  = workingDays(2020-05-20,2020-05-31)/workingdays(2020-05-01,2020-05-31)
                                 = 0.380952381 
Number of months in last month   = workingDays(2021-08-01,2021-08-11)/workingdays(2021-08-01,2021-08-31) 
                                  = 0.363636364
months in between = 14
total months = 0.380952381 +14+0.363636364
= 14.74458875

Ответы [ 2 ]

1 голос
/ 02 мая 2020

Я бы предложил функцию ниже. Обе даты обрабатываются одинаково, каждая за одну итерацию l oop. Для облегчения подсчета используется шаблонная строка, которая имеет «x» для рабочих дней. После получения правильной подстроки из этого шаблона нехарактерные символы (выходные дни) удаляются из него, поэтому итоговая длина представляет собой количество рабочих дней. Остальное просто складывает вещи:

function workingDays(fromDate, toDate) {
    let monthCount = -1;
    for (let i = 0; i < 2; i++) {
        let date = i ? toDate : fromDate;
        let year = +date.slice(0, 4);
        let month = date.slice(5, 7) - 1;
        let day = +date.slice(8);
        let weekday = new Date(year, month, 1).getDay();
        let pattern = ":xxxxx::xxxxx::xxxxx::xxxxx::xxxxx::x"
                .slice(weekday, weekday + new Date(year, month + 1, 0).getDate());
        monthCount += (year * 12 + month) * (i ? 1 : -1) 
           + pattern.slice(i ? 0 : day - 1, i ? day : 31).replace(/:/g, "").length
             / pattern.replace(/:/g, "").length;
    }
    return monthCount;
}

// The "unit test" example from the question
console.log(workingDays("2020-05-20", "2021-08-11")); // 14.7445887445...
1 голос
/ 01 мая 2020

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

// General-purpose utility functions

const range = (lo, hi) => 
  [... Array (hi - lo + 1)] .map ((_, i) => lo + i)

const parseDate = (s, [y, m, d] = s.split('-') .map (Number)) => 
  [y, m -1 , d]  // m - 1 because JS Dates are screwy about months

const isLeapYear = (y) => 
  (y % 4 == 0) && (y % 100 != 0 || y % 400 == 0)

const daysInMonth = (y, m) => 
  isLeapYear (y)
    ? [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]
    : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]

// Helper functions

const fullMonthsBetween = (y1, m1, y2, m2) => // excludes both endpoints
  Math.max((12 * y2 + m2) - (12 * y1 + m1) - 1, 0)

const workingDaysInRemainderOfMonth = (y, m, d, day = new Date (y, m, d) .getDay ()) => 
  range (d, daysInMonth (y, m)) 
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes((day + i) % 7)) 
    .length

const workingDaysInStartOfMonth = (y, m, d) =>
  range (1, d) 
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes ((d + i) % 7)) 
    .length

// Main function

const workingMonthsBetween = (start, end) => {
  const [y1, m1, d1] = parseDate (start)
  const [y2, m2, d2] = parseDate (end)
  return fullMonthsBetween (y1, m1, y2, m2)
         + workingDaysInRemainderOfMonth (y1, m1, d1) 
            / workingDaysInRemainderOfMonth(y1, m1, 1)
         + workingDaysInStartOfMonth (y2, m2, d2)
            / workingDaysInRemainderOfMonth(y2, m2, 1)
}

// Demo
console .log (
  workingMonthsBetween ('2020-05-20', '2021-08-12')  //~> 14.744588744588745
)

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

  • range создает целочисленный диапазон. range (3, 12) //=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

  • parseDate преобразует строку типа '2020-08-12' в поля года / месяца / дня [2010, 7, 12]. Это не опечатка. Остальная часть обработки JS даты использует месяц с индексом 0, поэтому мы вычитаем один здесь.

  • isLeapYear должно быть очевидным, хотя високосный год правила немного сложны

  • daysInMonth принимает год и месяц и возвращает общее количество дней в месяце с учетом високосного года

  • fullMonthsBetween сообщает количество месяцев строго между двумя комбинациями год / месяц. Таким образом, между 2020-05 и 2020-09, есть три месяца, июнь, июль и август.

  • workingDaysInRemainderOfMonth занимает год, месяц и date и сообщает, сколько рабочих дней осталось в этом месяце (включая нашу указанную дату и последний день месяца). Это выполняется путем фильтрации всех суббот (6 мод. 7) и воскресений (0 мод. 7) из диапазона дней между этой датой и последним днем ​​месяца. Возможно, мы могли бы сделать интересную арифметику c, чтобы вычислить начальный день недели и избежать использования конструктора даты здесь, но это потребовало бы более глубокого размышления.

  • workingDaysInStartOfMonth делает нечто похожее для дней между первым месяцем и указанной датой.

  • workingMonthsBetween - основная функция, которая принимает две строки даты в формате ISO-8601 и вычисляет количество месяцев между ними, используя различные вспомогательные функции, описанные выше.

Все это, конечно, игнорирует праздники. Хотя добавить их не составит особого труда, это тоже не тривиально.

Обновление

Я рассмотрел использование Соответствие Зеллера , и хотя я понятия не имею, это значительно ускоряет весь алгоритм, он значительно быстрее в изоляции, чем использование конструктора Date. Следующий фрагмент демонстрирует эту альтернативу.

// General-purpose utility functions
const range = (lo, hi) => 
  [... Array (hi - lo + 1)] .map ((_, i) => lo + i)

const parseDate = (s, [y, m, d] = s.split('-') .map (Number)) => 
  [y, m - 1, d]

const isLeapYear = (y) => 
  (y % 4 == 0) && (y % 100 != 0 || y % 400 == 0)

const daysInMonth = (y, m) => 
  isLeapYear (y)
    ? [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]
    : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]

// Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence)
const dayOfWeek = (
  year, month, date,
  y = month < 3 ? year - 1 : year,
  m = ((month + 9) % 12) + 3
) => (
  date 
  + Math .floor ((13 * m - 1) / 5)
  + Math .floor (y / 4)
  - Math .floor (y / 100) 
  + Math .floor (y / 400)
) % 7

  
// Helper functions
const fullMonthsBetween = (y1, m1, y2, m2) => // excludes both endpoints
  Math.max((12 * y2 + m2) - (12 * y1 + m1) - 1, 0)

const workingDaysInRemainderOfMonth = (y, m, d, day = dayOfWeek(y, m + 1, d)) => 
  range (d, daysInMonth (y, m)) 
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes((day + i) % 7)) 
    .length

const workingDaysInStartOfMonth = (y, m, d) =>
  range (1, d) 
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes ((d + i) % 7)) 
    .length


// Main function
const workingMonthsBetween = (start, end) => {
  const [y1, m1, d1] = parseDate (start)
  const [y2, m2, d2] = parseDate (end)
  return fullMonthsBetween (y1, m1, y2, m2)
         + workingDaysInRemainderOfMonth (y1, m1, d1) 
            / workingDaysInRemainderOfMonth(y1, m1, 1)
         + workingDaysInStartOfMonth (y2, m2, d2)
            / workingDaysInRemainderOfMonth(y2, m2, 1)
}


// Demo
console .log (
  workingMonthsBetween ('2020-05-20', '2021-08-12')  //~> 14.744588744588745
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...