3

I have a sheet i am working on that i need to populate all the days between 2 dates for a month 54 times.

I have got together a loop that can do this for the first section - I now need ti replicated 54 times.

I have figured out a loop to copy and paste this range the 54 times which works as it should. however I am wondering whether there is a way to put the date generation loop inside the duplication loop and generate every date rather than copy and paste?

I am mainly looking for the most efficient method as this will potentially be scaled up in future so any pointers with my code would be greatly appreciated.

Sub WriteDatesLoopTest()

'Disables Screen Flickering on Copy/Paste
Application.ScreenUpdating = False
OffsetValue = 42
'----------------------------------------------
    Dim StartDate       As Range
    Dim EndDate         As Range
    Dim OutputRange     As Range
    Dim ClearRange      As Range
    Dim StartValue      As Variant
    Dim EndValue        As Variant
    Dim DateRangeCopy   As Range
    Dim EmployeeCount   As Range
    Dim MonthValue      As Range
'----------------------------------------------
    Set ClearRange = Range("A9:A39")
    Set StartDate = Range("T4")
    Set EndDate = Range("T5")
    Set OutputRange = Range("A9")
    Set DateRangeCopy = Range("A9:A39")
    Set EmployeeCount = Range("O1")
    Set MonthValue = Range("J1")
    StartValue = StartDate
    EndValue = EndDate

'----------Date Generation Loop----------------

    If EndValue - StartValue <= 0 Then
        Exit Sub
        End If
        ColIndex = 0
            For i = StartValue To EndValue
                OutputRange.Offset(ColIndex, 0) = i
                ColIndex = ColIndex + 1
            Next

'----------Copy & Paste------------------------
n = EmployeeCount
For j = 0 To (n - 1)
    'ClearRange.Offset(OffsetValue * j, 0).ClearContents
    DateRangeCopy.Copy
    With DateRangeCopy.Offset(OffsetValue * j, 0)
      .PasteSpecial Paste:=xlPasteValuesAndNumberFormats
       SkipBlanks = False
    End With

    'Show Status Bar in Bottom Left
    Application.StatusBar = "Progress: " & Format(j / n, "0%")

   Next

'Display Message on completion
MsgBox "Dates Generated"
'Removes 'Walking Ants' From copied selection
Application.CutCopyMode = False
'Enables Screen Flickering on Copy/Paste
Application.ScreenUpdating = True
'Reset Status Bar in Bottom Left
Application.StatusBar = False
'-----------------------------------

    End Sub

enter image description here

Thank you

2
  • 2
    If your code already works then this might be a better question for Code Review Stack Exchange if you are looking for code optimization. Commented Jul 4, 2018 at 8:20
  • 1
    Thank you i never realised this existed! i will move on over there :) Commented Jul 4, 2018 at 8:22

2 Answers 2

3

Just seen the comments. Yes Code Review would be good. You probably want to move the entire process into an array.

This demonstrates all the required elements.

Option Explicit
Public Sub GenerateDates()
    Const LOOPCOUNT As Long = 54
    Dim i As Long, j As Long
    Dim startDate As Long, endDate As Long, rowCounter As Long
    startDate = CLng(Now)
    endDate = startDate + 7
    Application.ScreenUpdating = False
    With ActiveSheet
        For i = 1 To LOOPCOUNT
            For j = startDate To endDate
                rowCounter = rowCounter + 1
                .Cells(rowCounter, 1) = j
            Next j
             rowCounter = rowCounter + 5 '<== Add gap
        Next i
        .Columns("A").NumberFormat = "m/d/yyyy"
    End With
    Application.ScreenUpdating = True
End Sub

Doing the same thing in memory (I have included a second dimension as you may have additional columns in your data. My principle was really about showing the dates increment with row gap.)

Option Explicit
Public Sub GenerateDates() '697
    Const LOOPCOUNT As Long = 54      
    Dim i As Long, j As Long
    Dim startDate As Long, endDate As Long, rowCounter As Long
    startDate = CLng(Now)
    endDate = startDate + 7
    Dim ROWGAP As Long: ROWGAP = 41-(Enddate-StartDate)
    Dim outputArr()
    ReDim outputArr(1 To (((endDate - startDate + 1) + ROWGAP) * LOOPCOUNT) - ROWGAP, 1 To 1)
    Application.ScreenUpdating = False
    With ActiveSheet
        For i = 1 To LOOPCOUNT
            For j = startDate To endDate
                rowCounter = rowCounter + 1
                outputArr(rowCounter, 1) = j
            Next j
            rowCounter = rowCounter + ROWGAP '<== Add gap
        Next i
        .Cells(1, 1).Resize(UBound(outputArr), UBound(outputArr, 2)) = outputArr 'This is only with one dimensional
        .Columns("A").NumberFormat = "m/d/yyyy"
    End With
    Application.ScreenUpdating = True
End Sub

tl;dr;

The principle is basically that you want an outer loop that increments from 1 to 54. Then an inner loop that increments from start date to end date. I treat date as a Long and simply add one to the startDate until I reach the endDate in the inner loop. For i = 1 To LOOPCOUNT is doing the repeat work... here you could be using your copy paste. I increment the rowCounter variable by 5 before the next repeat to leave some blank rows between repeats.

The first version writes to the sheet for every row with .Cells(rowCounter, 1) = j . That is an expensive operation "touching" the sheet each time. The second version does the same process but doesn't write to the sheet until the very end. Instead, it writes to an array. This is much faster as is all done in memory (no going to disk).

I know how many rows I will have in the array because I know how many times I am repeating the entire process (54), the number of days from startDate and endDate (8) and the number of padding rows I am adding (5). So I can size my array to write to with ReDim outputArr(1 To (((endDate - startDate + 1) + ROWGAP) * LOOPCOUNT) - ROWGAP, 1 To 1). I don't need 5 rows padding on the 54th loop so I remove these from the total row count.


For understanding working with arrays and data in the worksheet the article VBA Arrays And Worksheet Ranges is worth a read, a long with the more general VBA Arrays

Sign up to request clarification or add additional context in comments.

9 Comments

Thank you for this, I am still relatively new to VBA could you give me some quick pointers on incorporating it to my code?
This is exactly what I am after! I am going to work through and familiarise myself with what is going on. do you mind me asking the difference in doing in memory or not? what problems i may face with either method? Thank you
That makes a lot more sense, I assume my orignal method would be very simialr to the first code in as much as it "touches" the sheet a lot of times? whereas your array like you said figures out how many cells to change before going ahead? I may have made a mistake asking my question though. i need the dates to be generated in teh same blocks each time. so the gap needs to be 42 cells from the top of the first block to the top of the second block if that makes sense? i assume this could be done by altering the rowgap?
Yes. I size the array to make sure it can hold all the rows and columns. I could keep using ReDim to resize but that is inefficient as it keeps making copies of the array. You know in advance how many rows and columns there will be. You work with that and then just write once to the sheet with .Cells(1, 1).Resize(UBound(outputArr), UBound(outputArr, 2)) = outputArr
Note I am writing to range A1 in activesheet and resizing that to the number of rows and columns in the array. outputArr,1 is the rows outputArr,2 is the number of columns.
|
2

The fewer tasks that a subroutine performs, the easier it is to write, test, and modify. For this reason I created a function to generate the output Array.

OffsetValue has a somewhat ambiguous name. I used SectionLength instead.

Sub AddDates()
    Const OffsetValue = 42
    Dim data() As Variant
    data = getDatesArray(#6/1/2018#, #6/30/2018#)
    With Worksheets("Sheet1")
        .Columns(1).ClearContents
        .Range("A1").Resize(UBound(data)).Value = data
    End With
End Sub

Function getDatesArray(StartDate As Date, EndDate As Date, Optional SectionLength As Long = 42, Optional RepeatCount As Long = 54) As Variant()
    Dim results() As Variant
    Dim count As Long, n As Long
    ReDim results(1 To SectionLength * RepeatCount, 1 To 1)

    If EndDate >= StartDate Then
        Do
            count = count + 1
            For n = 0 To UBound(results) - SectionLength Step SectionLength
                results(n + count, 1) = StartDate
            Next
            StartDate = StartDate + 1
        Loop Until StartDate = EndDate
    End If
    getDatesArray = results
End Function

7 Comments

Thank you, I have had a quick go with this and it seems to run fine, how can I refer the start and end dates to cells in my sheet? just substitute dates with Range("b1") for example?
The first two parameters of getDatesArray( ) are StartDate and EndDate .
so I wouldn't be able to change data = getDatesArray(#6/1/2018#, #6/30/2018#) to data = getDatesArray(Range("J4"), Range("J5")) for example? Ultimately i need the flexibility to change dates within the sheet itself by a data validation dropdown EDIT: that worked
Just pass the variable to the function. data = getDatesArray(startDate, endDate)
Sorry, I don't understand what you mean by 'pass the variable to the function'
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.