Нюанс№1 Если в функции =РАБДЕНЬ.МЕЖД(ДатаНачала, ЧислоДней, [Выходные], [Праздники]) указать 0 в качестве ЧислоДней, то она перестаёт работать и выдаёт просто ДатаНачала без учёта выходных и праздников. Всё надо тестить у мелкомягких
Выход: проверять на 0 и, в таком случае, ставить 1 для ЧислоДней и отнимать 1 от ДатаНачала.
Пример: я получаю дату начала работ на объекте и передаю в эту функцию, чтобы получить первый рабочий день с учётом выходных и праздников, начиная с этой даты. Просто это сделать, как выяснилось, не получится…
Очередной ни хрена не очевидный и неудобный нюанс, о котором, к тому же, в справке — ни слова Продолжение следует…
UPD: Та же функция, но с нормальным названием и описанием =РАБДЕНЬ.ПОСЛЕД(НачДата, КолРабДней, [Выходные], [Праздники]) • Функция возвращает дату последнегорабочего дня после НачДата спустя КолРабДней. • То есть, между НачДата и результатом функции находится КолРабДней, с учётом настроек выходных и праздничных дней. • НачДатаНЕ входит в их число, а результат функциивходит, т.к. является последним рабочим днём из числа указанных. • При указании 0 в качестве КолРабДней, функция вернёт НачДата.
Аналог на VBA. Файл и коды (я неожиданно проиграл)
Option Base 1
Option Explicit
Option Private Module
'====================================================================================================
Dim aParam() As Long, cyc&
Dim dic As Dictionary, aDic()
'====================================================================================================
Sub DishonestTest()
Dim x, arr, aWD()
Dim t1!, t2!, tt!, ttDif!, p&, n&, res1&, res2&
tt = Timer
'CreateDic
CreateParam 3
aWD = Array(0, 0, 0, 0, 0, 1, 1)
Debug.Print "Param #", "Method", "Beg", "Long", "Result", "Time", "Cycles = " & Format$(cyc, "#,##0")
For p = 1 To UBound(aParam)
t1 = Timer
For n = 1 To cyc
res1 = PRDX_LastWorkDay2(aParam(p, 1), aParam(p, 2), aWD)
Next n
t1 = Timer - t1: Debug.Print p, "PRDX", aParam(p, 1), aParam(p, 2), res1, Format$(t1, "0.0")
t2 = Timer
For n = 1 To cyc
res2 = WorksheetFunction.WorkDay_Intl(aParam(p, 1), aParam(p, 2), 1)
Next n
t2 = Timer - t2: Debug.Print p, "WF", aParam(p, 1), aParam(p, 2), res2, Format$(t2, "0.0")
If res1 <> res2 Then Stop Else t1 = t1 - t2
Debug.Print , , , , , , "Dif PRDX-WF", Format$(t1, "0.0"): ttDif = ttDif + t1
Next p
Debug.Print "Total time: " & Format$(Timer - tt, "0.0 sec") & ". Total Dif: " & Format$(ttDif, "0.0 sec")
End Sub
'====================================================================================================
Sub Check()
Dim x, t!, dtBeg&, dtEnd&, d&, i&, n&, res1&, res2&, aOfs(), aWDtx(), aWDarr()
t = Timer
dtBeg = CDate("1/1")
dtEnd = CDate("31/12")
aOfs = Array(-100, -50, -20, -1, 0, 1, 20, 50, 100)
aWDtx = Array("0000000", "0000010", "0000001", "0000011")
aWDarr = Array(Array(0, 0, 0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 1, 0), Array(0, 0, 0, 0, 0, 0, 1), Array(0, 0, 0, 0, 0, 1, 1))
On Error Resume Next
For d = dtBeg To dtEnd
For Each x In aOfs
For i = 1 To UBound(aWDtx)
res1 = WorksheetFunction.WorkDay_Intl(d, x, aWDtx(i))
If Err Then Err.Clear: Exit For Else n = n + 1
res2 = PRDX_LastWorkDay2(d, x, aWDarr(i))
If res1 <> res2 Then Debug.Print res1 & "<>" & res2, d, x, aWDtx(i)
Next i
Next x
nx:
Next d
On Error GoTo 0
Debug.Print "Total time: " & Format$(Timer - t, "0.0 sec") & ". Dates Found: " & Format$(n, "#,##0") ' Total time: 0,1 sec. Dates Found: 13 140
End Sub
'====================================================================================================
'====================================================================================================
Sub CreateDic()
Dim x
Set dic = New Dictionary: aDic = [a1:a14].Value2
For Each x In aDic
dic.Add x, 0
Next x
aDic = dic.Keys
End Sub
'====================================================================================================
Sub CreateParam(nType&) ' 1=short, 2=mid, 3=long, 4=full
If nType = 1 Then
ReDim aParam(6, 2): cyc = 1000000
aParam(1, 1) = CDate("20/1"): aParam(1, 2) = 5
aParam(2, 1) = CDate("20/1"): aParam(2, 2) = 3
aParam(3, 1) = CDate("20/1"): aParam(3, 2) = 1
aParam(4, 1) = CDate("20/1"): aParam(4, 2) = -1
aParam(5, 1) = CDate("26/12"): aParam(5, 2) = -3
aParam(6, 1) = CDate("26/12"): aParam(6, 2) = -5
ElseIf nType = 2 Then
ReDim aParam(6, 2): cyc = 1000000
aParam(1, 1) = CDate("20/1"): aParam(1, 2) = 30
aParam(2, 1) = CDate("20/1"): aParam(2, 2) = 20
aParam(3, 1) = CDate("20/1"): aParam(3, 2) = 10
aParam(4, 1) = CDate("26/12"): aParam(4, 2) = -10
aParam(5, 1) = CDate("26/12"): aParam(5, 2) = -20
aParam(6, 1) = CDate("26/12"): aParam(6, 2) = -30
ElseIf nType = 3 Then
ReDim aParam(6, 2): cyc = 100000
aParam(1, 1) = CDate("20/1"): aParam(1, 2) = 100
aParam(2, 1) = CDate("20/1"): aParam(2, 2) = 80
aParam(3, 1) = CDate("20/1"): aParam(3, 2) = 60
aParam(4, 1) = CDate("26/12"): aParam(4, 2) = -60
aParam(5, 1) = CDate("26/12"): aParam(5, 2) = -80
aParam(6, 1) = CDate("26/12"): aParam(6, 2) = -100
ElseIf nType = 4 Then
ReDim aParam(18, 2): cyc = 100000
aParam(1, 1) = CDate("20/1"): aParam(1, 2) = 100
aParam(2, 1) = CDate("20/1"): aParam(2, 2) = 80
aParam(3, 1) = CDate("20/1"): aParam(3, 2) = 60
aParam(4, 1) = CDate("20/1"): aParam(4, 2) = 30
aParam(5, 1) = CDate("20/1"): aParam(5, 2) = 20
aParam(6, 1) = CDate("20/1"): aParam(6, 2) = 10
aParam(7, 1) = CDate("20/1"): aParam(7, 2) = 5
aParam(8, 1) = CDate("20/1"): aParam(8, 2) = 3
aParam(9, 1) = CDate("20/1"): aParam(9, 2) = 1
aParam(10, 1) = CDate("20/1"): aParam(10, 2) = -1
aParam(11, 1) = CDate("26/12"): aParam(11, 2) = -3
aParam(12, 1) = CDate("26/12"): aParam(12, 2) = -5
aParam(13, 1) = CDate("26/12"): aParam(13, 2) = -10
aParam(14, 1) = CDate("26/12"): aParam(14, 2) = -20
aParam(15, 1) = CDate("26/12"): aParam(15, 2) = -30
aParam(16, 1) = CDate("26/12"): aParam(16, 2) = -60
aParam(17, 1) = CDate("26/12"): aParam(17, 2) = -80
aParam(18, 1) = CDate("26/12"): aParam(18, 2) = -100
Else
Stop: End
End If
End Sub
Модуль «Work»
Код
Option Base 1
Option Explicit
Option Private Module
'====================================================================================================
Function PRDX_LastWorkDay(dateBeg, nWorkDaysOfs, Optional arr7Graph, Optional dicHolidays As Dictionary) As Long
Dim dtLast&, ofs&, n&, WD&, iStep&, fDic As Boolean
dtLast = Fix(dateBeg)
ofs = Fix(nWorkDaysOfs)
If ofs > 0 Then iStep = 1 Else iStep = -1
WD = Weekday(dtLast) - 1: If WD = 0 Then WD = 7
fDic = Not dicHolidays Is Nothing
Do Until n = ofs
dtLast = dtLast + iStep: WD = WD + iStep
If WD = 8 Then WD = 1 Else If WD = 0 Then WD = 7
If arr7Graph(WD) = 1 Then GoTo nx
If fDic Then If dicHolidays.Exists(dtLast) Then GoTo nx
n = n + iStep
nx:
Loop
PRDX_LastWorkDay = dtLast
End Function
'====================================================================================================
Function PRDX_LastWorkDay2(dateBeg, nWorkDaysOfs, Optional arr7Graph, Optional dicHolidays As Dictionary) As Long
Dim dtLast&, ofs&, n&, tmp&, curWD&, jobWD&, iStep&, fDic As Boolean
dtLast = Fix(dateBeg)
ofs = Fix(nWorkDaysOfs)
For iStep = 1 To 7
If arr7Graph(iStep) = 0 Then jobWD = jobWD + 1
Next iStep
If ofs > 0 Then iStep = 1 Else iStep = -1
curWD = Weekday(dtLast) - 1: If curWD = 0 Then curWD = 7
fDic = Not dicHolidays Is Nothing
If ofs < jobWD * 2 Then
Do Until n = ofs
dtLast = dtLast + iStep: curWD = curWD + iStep
If curWD = 8 Then curWD = 1 Else If curWD = 0 Then curWD = 7
If arr7Graph(curWD) = 1 Then GoTo nx1
If fDic Then If dicHolidays.Exists(dtLast) Then GoTo nx1
n = n + iStep
nx1:
Loop
Else
Do Until curWD = 1 ' To Monday
dtLast = dtLast + iStep: curWD = curWD + iStep: If curWD = 8 Then curWD = 1
If arr7Graph(curWD) = 1 Then GoTo nx2
If fDic Then If dicHolidays.Exists(dtLast) Then GoTo nx2
n = n + iStep
nx2:
Loop
tmp = Fix((ofs - n) / jobWD) ' Whole Weeks Count
n = n + iStep * tmp * jobWD
dtLast = dtLast + iStep * tmp * 7: curWD = 1
If n = ofs Then
If arr7Graph(curWD) = 0 Then GoTo fin Else iStep = -iStep
Do
dtLast = dtLast + iStep: curWD = curWD + iStep
If curWD = 8 Then curWD = 1 Else If curWD = 0 Then curWD = 7
If arr7Graph(curWD) = 0 Then GoTo fin
Loop
End If
Do Until n = ofs
dtLast = dtLast + iStep: curWD = curWD + iStep
If curWD = 8 Then curWD = 1 Else If curWD = 0 Then curWD = 7
If arr7Graph(curWD) = 1 Then GoTo nx3
If fDic Then If dicHolidays.Exists(dtLast) Then GoTo nx3
n = n + iStep
nx3:
Loop
End If
fin: PRDX_LastWorkDay2 = dtLast
End Function
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
в описании функции: Количество_дней Обязательный. Количество рабочих дней до или после начальной даты. Положительное значение обозначает дату в будущем, отрицательное — дату в прошлом. Смещение усекается до целого числа. сказано что количество дней должно быть ПОЛОЖИТЕЛЬНЫМ или ОТРИЦАТЕЛЬНЫМ числом с нулем нет смысла пытаться что-то считать
Программисты - это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете!
Ігор Гончаренко, знал, что вы обязательно заглянете, чтобы меня поучить) Рад, что в этот раз с какими-никакими, а аргументами
Итак, вы правы, и я видел что ЭТО там написано, НО: • также там написано, что будет, если передавать в качестве "необычных аргументов", но про 0 - ни слова. • что мешало дать пользователям вводить 0 и получать корректный и ожидаемый результат работы функции? Зачем эти сложности? Это как дать калькулятор, в котором нельзя использовать цифру 4 потому что "иди лесом" — вот почему. • этот аргумент даже не является опциональным (при неуказании которого был бы 0 и хоть как-то можно бы было понять тупую логику).
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
написал: Очередной ни хрена не очевидный и неудобный нюанс
это почему? если ты указываешь, что прибавлять надо "нифига" - то это и прибавляется По-моему более чем очевидно. Если к сегодня прибавить 0 дней - то получится как ни крути сегодня, а не какая-то другая дата. В этом весь принцип. Для меня как раз было бы совсем неочевидным, если я указываю никуда не смещать дату(т.е. у меня идет ссылка на ячейку, где 0 дней), а она куда-то смещается.
абсолютно верно, если только не учитывать, что функция работает с выходными и праздниками. В таком случае, я хочу найти первый рабочий день, начиная прямо с указанной даты.
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Jack Famous, выше дополнил. На мой взгляд все корректно. Уже обсуждалось не раз - всем не угодишь. И делать какое-то смещение там, где пользователь его не указал - это еще большая подлость, чем сделать ожидаемо-прогнозируемое "ничего". Представь два столбца: 1 - дата 2 - кол-во дней для прибавления Ты вписываешь начальные даты и если не хочешь ничего смещать - пишешь 0 дней, т.к. все завершилось одним днем, пусть и выходным. Вполне жизненная ситуация, у меня были такие проекты. Протягиваешь формулу с указанием в качестве аргументов ссылки на эти столбцы и БАЦ! У тебя вместо ожидаемой даты начала(она же окончание) - какой-то другой день...Так себе фишка.
логика есть, но проще обойти 0 и писать дату начала в третьем столбце в таком случае, чем менять при нуле 2 аргумента функции…
«Возвращает дату в числовом номере, отсвеяданную [прикольное слово] до или после заданного количества рабочих дней с настраиваемой датой выходных» Есть Дата, есть нолевое смещение, но есть праздники и выходные. Задача функции в таком случае, как мне кажется, вернуть первый рабочий день с учётом настроек. Если указанная Дата не попала в выходные и праздники, то остаётся она, но, если попала — я жду первый рабочий день, всё-таки…
Ладно — для меня главным было описать это, чтобы другие тоже учли, кто увидит… Тем не менее, интересно, сколько найдётся людей с моей точкой зрения, а то пока что 1:2
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Jack Famous, а не проблема ли просто в том что функция возвращает дату между которой и заданной указанное количество дней, а не первый рабочий день? Другое дело, что применение её может помочь в этом. Что касается описания 0, то также нет описания того что что указав +- очень большое число это приведет к сбою ведь предельные даты 1 (01.01.1900) и 2 958 465 (31.12.9999) Но главное не это, а то что нужно пользоваться первоисточником Days Required. The number of workdays before or after the start_date. A positive value yields a future date; a negative value yields a past date; a zero value yields the start_date. Day-offset is truncated to an integer. И на фоне
БМВ: функция возвращает дату между которой и заданной указанное количество дней
ну давай так: подсчётом количества рабочих дней между 2мя датами у нас занимается ЧИСТРАБДНИ.МЕЖД, остаётся только, обратная ей функция — вычисление даты, отстоящей от заданной на указанное количество рабочих дней с указанием выходных и праздников. Чем и занимается РАБДЕНЬ.МЕЖД. С одной стороны, действительно, если смещаться на 0 рабочих дней, то останешься на месте, но вся соль-то в том, что заданная дата сама может попадать на выходной или праздник и очень неплохо было бы иметь возможность её легко и быстро проверить. Отнимать там и прибавлять тут мне видится сложнее, чем оставить обязательным только первый аргумент и при нулевом втором проверять саму дату. Это проще и логичнее. С другой стороны, разрабы не обязаны делать, как я это вижу . Димин пример с нолём для игнорирования вполне себе наглядный. За оригинал спасибо - я подозревал, что он может отличаться и сраной локали нельзя верить (отдельная тема, что огромная многомиллиардная корпорация не может нормально перевести свою документацию). Да - там указано.
Сделаю свой аналог для VBA и потестирую в нечестном тесте на скорость
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
написал: сама может попадать на выходной или праздник
вопрос: сколько рабочих дней нужно прибавить к субботе, чтобы получить понедельник? Я вот вбил в ячейку А1 дату 21.05.2022(Суббота). В А2 формулу: =РАБДЕНЬ.МЕЖД(A1;1;1) И получил ожидаемую дату 23.05.2022. Т.е. понедельник. Было бы очень странно, если бы вбивая 0 я бы получал опять понедельник - ведь я не указал ни одного рабочего дня для прибавления. Мне кажется ты просто сильно зациклился именно на своём видении работы функции Повторюсь: на всех не угодишь, как ни крутись и в формулу на мой взгляд заложена более чем логичная цепочка вычислений, понятная большинству.
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
наверное, так и есть Пример — отличный, ещё лучше, чем со столбцами Спасибо!
UPD: Дмитрий(The_Prist) Щербаков, а если трактовать "в лоб", то есть "верни мне дату первого рабочего дня, начиная с субботы", то при 0, 1 и 2 в качестве смещения, должно возвращать понедельник
Но я проникся твоей логикой — всё-таки рабочие дни не могут быть нулевыми… Признаю — функция работает исправно, хотя описание я бы, конечно, другое сделал, как и название функции (добавил в шапку)
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
ну вот как раз это можно применить к 80% функций Excel Если описания были бы понятны каждому - то необходимость проводить тренинги точно бы отпала. Так что спасибо Microsoft, что не оставляешь без работы
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
Jack Famous, спасибо за столь развернутое описание данной функции, именно то что я искал, тоже столкнулся с проблемой что не считается корректно если начало периода праздничный день. Ваша статья помогла разобраться
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄