如何在Excel 2010中将DATE函数分解为不同的段

时间:2016-05-03 10:44:35

标签: excel excel-vba excel-formula vba

第一个场景:

第一步,用户输入:

我的工作表部分格式化为水平日历。每行包含一个人的姓名,并且在每个人的同一行中,从1月1日到12月31日共有365个单元格。每个同事,在他/她自己的行中,将在他/她想要休假的那些日子插入“V”。

第二步,Excel自动计算:

现在我希望Excel从1月到12月阅读整行,并列出已连接的假期。

假期的第一个连接日期范围将插入同一行,但是在两个单独的列中:“From-1”到“Till-1”,第二个连接范围分为“From-2”到“Till-2” “等等。

以下是截图: Screenshot

条件:

我想要的是,如果假期之间有假期,那么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的单元格更改为日期格式并要求他们更改休假日的颜色,是否会简化问题?

6 个答案:

答案 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),"")}

假设:

  • 您的日历从第AA栏开始
  • 列V到Z为空
  • 假期日期为E10至E17
  • 连续最多4天可以是非工作日(与假期本身无关)
  • 日历一直到列CG(应该是2月28日)并且需要扩展

此数组公式可以简单地粘贴到其他行和列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

enter image description here

  • B4:D4使用第一个udf作为:{=vacDays($AA$3,$AA4:$OA4,RIGHT(B$3))},可以将其复制(所有三个单元格)到您需要它的所有其他位置
    • RIGHT(B$3)只是为第k次比赛获得1,2,3 ......
  • AA1的直接日期为2017年1月的第一个月
    • 右边的所有合并的rages都是手动编码的(好吧,我用了一个sub来做但没关系)
  • AA2具有:=AA3并被复制到右侧
  • AA3将帮助器udf保存为:=getVacArr(AA1,OA3,$E$11:$E$20)(非数组)
  • AB3是:=AA1+1(以避免循环引用)
  • AC3为:=AB3+1,也可以复制到右侧
  • AA2:OA3保留实际日期(如果初始值像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)

优点:

  • 最终用户无需滚动浏览此巨幅电子表格
  • 用户输入将限制为4列。使调试和密码更容易保护您的公式

enter image description here

答案 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

当我的工作表看起来像这样:

enter image description here

对于屏幕截图所示的情况,请输入以下内容:

在B4中:=VacationRangeCalculator(B4, 1, 1)
在C4中:=VacationRangeCalculator(B4, 2, 1)
在E4:=VacationRangeCalculator(B4, 1, 2)
在F4中:=VacationRangeCalculator(B4, 2, 2)

这会给你一些看起来像这样的东西:

enter image description here

公式只需输入4次,每列一次。之后,您可以选择整个范围并向下拖动(使用与帖子开头所述相同的方法,只是垂直而不是水平完成)。

也可以修改UDF,使其自动引用正在调用的单元格(但这也意味着如果你想从VBA调用它,还需要进一步的检查/黑客攻击) - I将这作为练习留给读者,但会提示它涉及使用Application.Caller