我的工作表部分格式化为水平日历。每行包含一个人的姓名,并且在每个人的同一行中,从1月1日到12月31日共有365个单元格。每个同事,在他/她自己的行中,将在他/她想要休假的那些日子插入“V”。
现在我希望Excel从1月到12月阅读整行,并列出已连接的假期。
假期的第一个连接日期范围将插入同一行,但是在两个单独的列中:“From-1”到“Till-1”,第二个连接范围分为“From-2”到“Till-2” “等等。
我想要的是,如果假期之间有假期,那么excel会在假期之前和之后一起度假。喜欢人1。 假期之间有工作日,我想要分开的范围:像人6一样。
有人用以下公式帮助我:
=DATE(YEAR($H$1),MONTH($H$1),DAY(MATCH("V",$H4:$NI4,0)))
=DATE(YEAR($H$1),MONTH($H$1),DAY(MATCH("V",$H4:$NI4,1)))
其中:
H1
是“01.01.2017”(在我的屏幕截图中将其格式化为月份名称“2017年1月”)H4
这是Person-1插入“V”作为休假的行的第一个单元格NI4
这是Person-1行的最后一个单元格,将“V”作为假期插入上面的公式可以列出范围,一个月只有一个范围。这意味着,如果一个人在一个月内完成两个不同的假期,则只会列出第一个假期。
例如,如果某些人从2017年1月2日到2017年1月5日第一次休假,第二次休假从2017年1月10日到2017年1月12日,则excel将只计算第一个范围。 我的愿望是像Person-6一样自动插入日期(在屏幕截图中我手动完成)。
如果我将人员插入V的单元格更改为日期格式并要求他们更改休假日的颜色,是否会简化问题?
答案 0 :(得分:2)
虽然我仍然认为如果使用udf会更好,但我仍然希望展示一个有效的公式......在B4中:
{=IFERROR(SMALL(IF(NETWORKDAYS($AA$3:$CG$3+0,$AA$3:$CG$3+0,$E$10:$E$17)*($AA$4:$CG$4="v"),IF(NETWORKDAYS($AA$3:$CG$3-1,$AA$3:$CG$3-1,$E$10:$E$17)*($Z$4:$CF$4<>"v"),$AA$3:$CG$3,IF(NETWORKDAYS($AA$3:$CG$3-1,$AA$3:$CG$3-1,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3-2,$AA$3:$CG$3-2,$E$10:$E$17)*($Y$4:$CE$4<>"v"),$AA$3:$CG$3-1,IF(NETWORKDAYS($AA$3:$CG$3-2,$AA$3:$CG$3-2,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3-3,$AA$3:$CG$3-3,$E$10:$E$17)*($X$4:$CD$4<>"v"),$AA$3:$CG$3-2,IF(NETWORKDAYS($AA$3:$CG$3-3,$AA$3:$CG$3-3,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3-4,$AA$3:$CG$3-4,$E$10:$E$17)*($W$4:$CC$4<>"v"),$AA$3:$CG$3-3,IF(NETWORKDAYS($AA$3:$CG$3-4,$AA$3:$CG$3-4,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3-5,$AA$3:$CG$3-5,$E$10:$E$17)*($V$4:$CB$4<>"v"),$AA$3:$CG$3-4)))))))))),FLOOR(COLUMN(),3)/3+1),"")}
假设:
此数组公式可以简单地粘贴到其他行和列E,H,...
我仍然建议E4将其包裹在=IF(B4="","",.......)
中以缩短计算时间。
对于C4(结束日期),您可以使用这个稍微不同的公式:
{=IF(B4="","",SMALL(IF(NETWORKDAYS($AA$3:$CG$3+0,$AA$3:$CG$3+0,$E$10:$E$17)*($AA$4:$CG$4="v"),IF(NETWORKDAYS($AA$3:$CG$3+1,$AA$3:$CG$3+1,$E$10:$E$17)*($AB$4:$CH$4<>"v"),$AA$3:$CG$3,IF(NETWORKDAYS($AA$3:$CG$3+1,$AA$3:$CG$3+1,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3+2,$AA$3:$CG$3+2,$E$10:$E$17)*($AC$4:$CI$4<>"v"),$AA$3:$CG$3+1,IF(NETWORKDAYS($AA$3:$CG$3+2,$AA$3:$CG$3+2,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3+3,$AA$3:$CG$3+3,$E$10:$E$17)*($AD$4:$CJ$4<>"v"),$AA$3:$CG$3+2,IF(NETWORKDAYS($AA$3:$CG$3+3,$AA$3:$CG$3+3,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3+4,$AA$3:$CG$3+4,$E$10:$E$17)*($AE$4:$CK$4<>"v"),$AA$3:$CG$3+3,IF(NETWORKDAYS($AA$3:$CG$3+4,$AA$3:$CG$3+4,$E$10:$E$17)=0,IF(NETWORKDAYS($AA$3:$CG$3+5,$AA$3:$CG$3+5,$E$10:$E$17)*($AF$4:$CL$4<>"v"),$AA$3:$CG$3+4)))))))))),FLOOR(COLUMN(),3)/3))}
此公式可以直接从其他公式直接复制到任何地方。
但是如上所述:这不是我要去的方式......它仍然显示,如何没有任何帮助单元格/行/列和/或vba。我会寻找一个快速的vba解决方案,但这可能需要一些时间;)
编辑3
再次阅读完所有评论后,我可能错了。这样我就会添加一个缩短的代码,该代码在第一个和最后一个真正的休假日开始和结束。 (由于我误读了你想要的东西而删除了编辑1和2)
Option Explicit
Dim vacArr As Variant
Public Function getVacArr(ByVal FirstDay As Long, ByVal LastDay As Long, Optional ByVal Holidays As Variant) As Long
If IsMissing(Holidays) Then Holidays = 0
vacArr = Application.Transpose(Evaluate("Row(" & FirstDay & ":" & LastDay & ")"))
vacArr = Application.NetworkDays(vacArr, vacArr, Holidays)
getVacArr = FirstDay
End Function
Public Function vacDays(ByVal initVal As Long, ByVal VRng As Range, Optional ByVal Cnt As Long = 1) As Variant
Dim i As Long, j As Long, k As Long, l As Long, VRngVal As Variant, InH As Boolean
If Not (IsArray(vacArr) And initVal > 0 And Cnt > 0) Then vacDays = Split(" ", " "): Exit Function
VRngVal = VRng.Resize(1).Value
k = Application.Min(UBound(vacArr), VRng.Columns.Count)
For i = 1 To k
While vacArr(i) = 0 And i < k: i = i + 1: Wend
If vacArr(i) = 0 Then Exit For
If InH <> (VRngVal(1, i) = "V") Then
InH = Not InH
Cnt = Cnt + InH
If Cnt = 0 And InH Then vacDays = initVal + i - 1
End If
If Cnt = 0 And VRngVal(1, i) = "V" Then j = initVal + i - 1: l = l + 1
If Not CBool(Cnt Or InH) Then Exit For
Next
If IsEmpty(vacDays) Then
vacDays = Split(" ", " ")
Else
vacDays = Array(vacDays, j, l)
End If
End Function
{=vacDays($AA$3,$AA4:$OA4,RIGHT(B$3))}
,可以将其复制(所有三个单元格)到您需要它的所有其他位置
RIGHT(B$3)
只是为第k次比赛获得1,2,3 ...... =AA3
并被复制到右侧=getVacArr(AA1,OA3,$E$11:$E$20)
(非数组)=AA1+1
(以避免循环引用)=AB3+1
,也可以复制到右侧TODAY
一样易变,这可能会减慢excel,所以我建议直接键入第一个日期,然后只需+1
每个单元格另一个)
一如既往:如果您有任何疑问,请询问。答案 1 :(得分:2)
Download XLCalendar.xlsx
如果您将员工放在两个休假期的开始日期和结束日期,这将会容易得多
然后,电子表格将在休假期间的每个工作日显示 V 。
如果(AND(OR(AND(H $ 2 - ; = $ B4,H $ 2'= $ C4),和(h $ 2 - ; = $ E4,H $ 2'= $ F4)),WEEKDAY( ħ2,2 $)&10 6), “V”, “”)
NETWORKDAYS功能可以轻松返回实际休假天数。
= NETWORKDAYS(B4,C4,HolidayListRange)
优点:
答案 2 :(得分:1)
假设您可以将第二个假期块标记为不同的字符或一组字符,例如V1和V2或V和X,则以下更改应该对您有用。
在单元格B4中使用:
=$H$1+MATCH("V1",$H4:$NI4,0)-1
在C4单元格中使用:
=WORKDAY(B4,D4-1)
在单元格D4中使用
=COUNTIF($H4:$NI4,"v1")
在E到F列中重复相同的公式,更新地址以指向正确的位置,并将V1更改为V2或您选择的任何不同的标识符。
答案 3 :(得分:1)
以下功能可能会引导您走向正确的方向。
Function DetermineVacations(RangeDates As Range, RangeDays As Range, RangeTextVacation As Range, PeriodEvaluated As Long)
Dim ItemDates As Range
Dim ItemDays As Range
Dim ItemText As Range
Dim RangeFoundVacations As Range
Dim FoundVacationPeriod As Range
Dim xPrevious As Long
Dim AddressCell As String
Dim SecondCellRange As String
Dim TotalVacations As Long
Dim CounterVacations As Long
Const TextVacation = "V"
If RangeTextVacation.Find("V", LookAt:=xlPart) Is Nothing Then DetermineVacations = "N/A": Exit Function
Set RangeFoundVacations = RangeTextVacation
On Error GoTo Err01DetermineVacations
AddressCell = Right(RangeFoundVacations.Address(False, False), InStr(RangeFoundVacations.Address(False, False), ":") - 1)
TotalVacations = Application.WorksheetFunction.CountIf(RangeTextVacation, TextVacation)
For CounterVacations = 1 To TotalVacations
'find next doesn't work on functions, re set the range
Set FoundVacationPeriod = RangeFoundVacations.Find("V", LookAt:=xlPart)
'here you need to relate PeriodEvaluated vs the searc being performed
Set RangeFoundVacations = Range(FoundVacationPeriod.Address(False, False) & ":" & AddressCell)
Next CounterVacations
If 1 = 2 Then '99. If error
Err01DetermineVacations:
DetermineVacations = "N/A"
End If '99. If error
End Function
答案 4 :(得分:1)
我已经敲了这个,只要有2个假期就可以了。
明天我会尽力完善它,但我现在必须回家了。
首先,我使用以下脚本将周末添加为W:
Sub Weekend()
Dim X, Y As Integer
Dim mydate As Range
For Y = 4 To 9
For X = 8 To 372
Set mydate = Cells(3, X)
If Weekday(mydate, vbMonday) > 5 Then
Sheets("Urlaub (2017)").Cells(Y, X).Value = "W"
End If
Next X
Next Y
End Sub
然后使用下面的代码我完成了其他字段。如上所述,我将在完成一些事情后明天编辑。
Sub Test()
Dim Y, Col As Integer
Dim VStart1, VEnd1, V1 As Range
Dim VStart2, VEnd2, V2 As Range
Dim Days1, Days2 As Long
For Y = 4 To 9
On Error Resume Next
With Sheets("Urlaub (2017)")
'Find first V
Set VStart1 = .Rows(Y).Find("V", Cells(Y, 8), xlValues, xlPart)
If Not VStart1 Is Nothing Then
'Jump to the end of that block
Col = VStart1.End(xlToRight).Column
'If ends on a weekend then strip off 2 days
If .Cells(Y, Col).Value = "W" Then
Col = Col - 2
End If
Set VEnd1 = Cells(Y, Col)
'Set the full vacation range
Set V1 = .Range(VStart1.Address & ":" & VEnd1.Address)
End If
Set VStart2 = .Rows(Y).Find("V", VEnd1, xlValues, xlPart)
If Not VStart2 Is Nothing Then
Col = VStart2.End(xlToRight).Column
If .Cells(Y, Col).Value = "W" Then
Col = Col - 2
End If
Set VEnd2 = Cells(Y, Col)
Set V2 = .Range(VStart2.Address & ":" & VEnd2.Address)
End If
'Now you have the 2 vacations as V1 and V2 (this could be repeated again if you wanted to expand)
Range("B" & Y).Value = Cells(3, VStart1.Column)
Range("C" & Y).Value = Cells(3, VEnd1.Column)
Range("D" & Y).Value = WorksheetFunction.CountIf(V1, "V")
Range("E" & Y).Value = Cells(3, VStart2.Column)
Range("F" & Y).Value = Cells(3, VEnd2.Column)
Range("G" & Y).Value = WorksheetFunction.CountIf(V2, "V")
End With
Next Y
End Sub
这不是完美的,但它是一个开始,给你一些工作。一点点的压气和折叠,这将是
答案 5 :(得分:0)
请注意,为了按照编写方式使用此功能,您需要实施以下更改:
在第3行,从H列到NI,您需要将数字1
更改为格式为01.01.17
的日期"dd"
。您可以通过在最左侧单元格中输入01.01.17
,然后单击并拖动选择框右下角的小方块来轻松实现此目的。填充完所有单元格后,只需右键单击并应用自定义格式。
此外,您需要使用您正在使用的任何颜色更新此行:
If C.Interior.color <> 6013439
要查找单元格的颜色,请选择该单元格,切换到VBA编辑器,打开立即窗口(Ctrl + G),然后输入? Selection.Interior.Color
。结果编号将是您需要在上面的代码行中输入的内容(除非背景没有填充(并注意&#34;白色&#34;和&#34;没有填充&#34;不是同样!),在这种情况下它是xlNone
)。
Function VacationRangeCalculator(rng As String, v As Integer, Cycle As Integer) As String
' rng = set to A-column, whatever row you want to evaluate, v = 1 or 2, depending on whether you want the start or the end of a range _
' Cycle = 1 or 2, depending on whether you want the 1st or 2nd vacation range
Dim r As Long ' Row
Dim streak As Boolean
Dim streakNum As Integer
r = Range(rng).Row
For Each C In Range("H" & r, "NI" & r)
If LCase(C.Value) = "v" Then ' If this day is a vacation day
If streak = False Then ' If we're not on a streak
streak = True ' Start streak
streakNum = streakNum + 1 ' Increment streak counter
End If
If v = 1 And streakNum = Cycle Then ' The first range of the given cycle
VacationRangeCalculator = Cells(3, C.Column).Value ' Return result
Exit For ' Terminate calculation
End If
Else ' This is not a vacation day
If C.Interior.color <> 6013439 Then ' If this day is not a weekend
streak = False ' End streak
If v = 2 And streakNum = Cycle Then ' The second range of the given cycle
VacationRangeCalculator = Cells(3, C.Column - 1).Value ' Return result
Exit For ' Terminate calculation
End If
End If
End If
Next
End Function
使用示例:
Sub vacations()
Debug.Print VacationRangeCalculator("A4", 1, 1)
Debug.Print VacationRangeCalculator("A4", 2, 1)
Debug.Print VacationRangeCalculator("A4", 1, 2)
Debug.Print VacationRangeCalculator("A4", 2, 2)
Debug.Print VacationRangeCalculator("A5", 1, 1)
Debug.Print VacationRangeCalculator("A5", 2, 1)
Debug.Print VacationRangeCalculator("A5", 1, 2)
Debug.Print VacationRangeCalculator("A5", 2, 2)
End Sub
产生以下结果:
03.01.2016
10.01.2016
03.01.2016
04.01.2016
10.01.2016
11.01.2016
当我的工作表看起来像这样:
对于屏幕截图所示的情况,请输入以下内容:
在B4中:=VacationRangeCalculator(B4, 1, 1)
在C4中:=VacationRangeCalculator(B4, 2, 1)
在E4:=VacationRangeCalculator(B4, 1, 2)
在F4中:=VacationRangeCalculator(B4, 2, 2)
这会给你一些看起来像这样的东西:
公式只需输入4次,每列一次。之后,您可以选择整个范围并向下拖动(使用与帖子开头所述相同的方法,只是垂直而不是水平完成)。
也可以修改UDF,使其自动引用正在调用的单元格(但这也意味着如果你想从VBA调用它,还需要进一步的检查/黑客攻击) - I将这作为练习留给读者,但会提示它涉及使用Application.Caller
。