VBA Script to Extract data every 30s Interval - excel

I have a list of voltage outputs and the time stamps at which they were recorded. For some background, my test increments a voltage by 1mV every 30s from 0 - 5V. The system reads every second or so, it can be inconsistent. The sheet has over 70000 rows but I only need just over 5000.
So far I have used =RIGHT(TEXT(A1, "hh:mm:ss"),2) to extract the seconds from the time. Unfortunately, the timestamps are always perfectly every 30s so I can't simply filter for every 0s and 30s. Sometimes 30s is skipped and it reads at say 35s.
How would I create a VBA script that extracts the increments at every 30s interval and if there is no perfect 30s jump, select the closest match to it (i.e. 29 or 31)?
Any guidance would be greatly appreciated!

OK, I think that the following will work. Note that this needs to be entered as an Excel formula array in the Flags output column, with the function's input parameter being the corresponding column that contains the times. (So the output column's formula should look like: {=FlagEvery30Sec(D2:D36)})
' Returns 1 if the corresponding row should be used, and 0 otherwise
Public Function FlagEvery30Sec(SourceTimes As Range) As Integer()
Dim Times() As Variant
Dim Flags() As Long
' get the source values
Times = SourceTimes
' set the output values array to the same size
Dim First As Long, Last As Long
First = LBound(Times, 1)
Last = UBound(Times, 1)
ReDim Flags(First To Last, 1 To 1)
Dim curr As Long, prev As Long
prev = First
curr = First
Flags(curr, 1) = 1
curr = curr + 1
Dim currTime As Date
Dim currSecs As Double, prevSecs As Double, lastFlagSecs As Double
lastFlagSecs = CDbl(Times(curr, 1)) * 24 * 60 * 60
While curr <= Last
Flags(curr, 1) = 0 ' assume not flagged, change later
currSecs = CDbl(Times(curr, 1)) * 24 * 60 * 60
If (currSecs - lastFlagSecs) >= 30 Then
If ((currSecs - lastFlagSecs) - 30) <= (30 - (prevSecs - lastFlagSecs)) Then
Flags(curr, 1) = 1
lastFlagSecs = currSecs
Else
Flags(prev, 1) = 1
lastFlagSecs = prevSecs
End If
End If
prevSecs = currSecs
prev = curr
curr = curr + 1
Wend
FlagEvery30Sec = Flags
End Function
Note that there are basically two approaches to this:
Take the first timestamp, mark off every 30 seconds, then find the entry that is closest to each mark and flag it. Or,
Take the first timestamp and find the following entry that is closest to 30 seconds after it. Then take that entry's time as the base timestamp and find the next entry that is closest to 30 seconds after that. Repeat this until you reach the end.
My function above uses the second method. You should note that this can drift though and the final result may be more or less marks than TotalSeconds/30.

Related

Excel VBA Check if time is between two time values, including midnight spans

I've come into a problem that I am really struggling to find the solution to. I have 3 time values (without the date component) stored as strings, e.g. 01:00. Two of these values represent an after and before time, and the other is the current time. I need to check if the current time is between the after and before times (inclusive).
This can simple be checked with the below:
Dim res As Boolean
res = TimeValue(tVal) >= TimeValue(tAfter) And TimeValue(tVal) <= TimeValue(tBefore)
This works for time values that do not span midnight. If for example you have an after time of 23:00, a before time of 04:00 and a current time of 02:00, this will not work.
How do you check if a time is between two time values that span midnight? Or even how do you check if the two time values actually span midnight (as checking if the before or after times are </> 00:00 doesn't seem to work either)?
If the interval doesn't exceed 24 hours, this should do:
If TimeValue(tBefore) <= TimeValue(tAfter) Then
' Within one day.
res = TimeValue(tVal) >= TimeValue(tBefore) And TimeValue(tVal) <= TimeValue(tAfter)
Else
' Crossing Midnight.
res = TimeValue(tVal) >= TimeValue(tBefore) Or TimeValue(tVal) <= TimeValue(tAfter)
End If
This is hard to answer without seeing your data. If you only have time data, ie with no date, then you're going to have to make some assumptions. A simple assumption might be: if tAfter is less than tBefore, add a day (or vice versa depending on which came first). You could do the same with tVal.
If the difference between tBefore and tAfter can be more than 12 hours, then you'll need something more sophisticated.
Skeleton code for the simple example would be something like this:
Dim tBefore As String, tAfter As String, tVal As String
Dim b As Double, a As Double, v As Double
tBefore = "20:00"
tAfter = "02:00"
tVal = "01:00"
b = TimeValue(tBefore)
a = TimeValue(tAfter)
If a < b Then a = a + 1
v = TimeValue(tVal)
If v < b Then v = v + 1
If v >= b And v <= a Then
Debug.Print "Within"
Else
Debug.Print "Outside"
End If

Generate passwords

I need generate passwords that do not repeat and do not have the same consecutive number, in addition to having a length of 8 digits, only numbers. All this in Visual Basic for applications Excel.
Well, if you only want numbers, a random number between 0 and 9 can be achieved by Int(Rnd() * 10).
Int() rounds down, Rnd() returns a number between 0 and <1.
If you then loop that 8 times and add the numbers after each other (with & rather than +) with an If checking the last last added value to the one we want to add, then you have an 8 digit number with no repeating numbers.
Then you just have to print or save them somewhere.
Something like:
Sub random()
Dim test As Long, ran As Long, i As Long, j As Long
For j = 1 To 500 '- Amount of numbers to print
ran = 0
For i = 1 To 8 '- Length of random number
test = Int(Rnd() * 10)
If test = Right(ran, 1) Then
i = i - 1 '-Same number, try again
Else
ran = ran & test '-New number, add this
End If
Next i
Range("A" & Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row).Value = ran '- Print on last row of column "A"
Next j
Range("A:A").RemoveDuplicates Columns:=1 '- Remove duplicates from column "A"
End Sub
This would then print 500 random 8 digit numbers starting from A2, or the last empty cell in column A. Then proceed with removing the duplicates.
You could check for duplicates each time, and thus making sure you actually end up with the specified amount of numbers. But it's extremely slow, and the chance to get a duplicate is really small (testrun of 10.000 numbers had a single duplicate for me).

How to get response time to turnaround weekends and after business hours?

I have been working on this project for a while and i'm just getting nowhere so I figured i'd ask you guys here. There are a bunch of tasks: Quoting, Binding, Issuance...and they each have their own response times.
Quoting has to be done within 3 hours, while binding is 8 hours and issuance has a 2 day turnaround time. But, the issue is that the response times are based on only a 9:00 - 8:00 pm (est) time, excluding weekends and holidays. I have a holiday lookup table, as well as the task times indexed from another lookup table.
The part that I'm stuck is in regards to "stopping the clock" and having the task response time turn around to next day if it's after 8:00 pm.
This is the formula that I created to do so, but it's not working as it should because it will show the same time if I changed Time to (48,0,0) for issuance or Time(8,0,0) for binding. Column P3 has the start time.
=IF(AND(TEXT(P3,"dddd")="Friday",HOUR(P3)+MINUTE(P3)/60+SECOND(P3)/(60*60)>17),P3+TIME(15,0,0)+DAY(2),IF(HOUR(P3)+MINUTE(P3)/60+SECOND(P3)/(60*60)>17,P3+TIME(15,0,0),P3+Time(3,0,0)))
Thank you! Any help will be greatly appreciated guys!
Here's some untested and not fully implemented code for you to start with:
Function GetTurnaroundDateAndTime(TaskType As String, StartTime As Date, TaskTimeRange As Range, HolidayLookupRange As Range)
Dim taskTime As Double
Dim dayBegin As Double 'could be a parameter
Dim dayEnd As Double 'could be a parameter
Dim result As Date
Dim isValid As Boolean
Dim offset As Double
dayBegin = 9 'could be a parameter
dayEnd = 20 'could be a parameter
offest = 0
'Get Task Time in hours
taskTime = GetTaskTime(TaskType, TaskTimeRange)
'Calculate initial turnaround time (without regard to nights/weekends/holidays)
result = DateAdd("h", taskTime + offset, StartTime)
'check if it's a valid turnaround date and time, return if so
isValid = False
Do While isValid = False
'check #1 - is the turnaround time before the day begins?
If Hour(result) < 9 Then
If Hour(StartTime) < 20 Then
offset = offset - 20 + Hour(StartTime) 'check to see if a portion of the task time would be before end of day, subtract this amount from the offset
End If
offset = offset + 9 = Hour(result) 'gets the offset to the beginning of day
ElseIf Weekday(result, vbSaturday) = 1 Then
offset = offset + 48 'if we're on a Saturday, add two days
ElseIf Weekday(result, vbSunday) = 1 Then
offset = offset + 24 'if we're on a Sunday, add one day
ElseIf IsHoliday(result, HolidayLookupRange) Then
offset = offset + 24 'if we're on a holiday, add one day
Else
isValid = True
End If
result = DateAdd("h", taskTime + offset, StartTime) 're-evaluate result
Loop
GetTurnaroundDateAndTime = result
End Function
Function GetTaskTime(TaskType As String, TaskTimeRange As Range) As Double
'TODO: implement function to lookup the task time from the table
GetTaskTime = 3
End Function
Function IsHoliday(DateToLookup As Date, HolidayLookupRange As Range) As Boolean
'TODO: implement function to lookup if date is a holiday
IsHoliday = False
End Function
Here are some links that should help you get started with VBA:
https://support.office.com/en-us/article/create-custom-functions-in-excel-2f06c10b-3622-40d6-a1b2-b6748ae8231f
https://www.fontstuff.com/vba/vbatut01.htm
You'll want to test a lot of different scenarios before feeling comfortable with the code. I just put something quick together!

Combination Generator yet keep in order

Wondering if anyone could help me. I'm stumped. It's been ages since I used excel....
I have 9 columns with different values in each cell, different numbers of cells per column.
I need a formula/macro to spit out all combinations of the cells and yet still remain in the exact same order of the columns.
For example
Columns:
D / 003 / 23 / 3 / 3R / C / VFX
... / 005 / 48 / 3 / 12 / .. / VDF
... / 007 / ... / 1 / ... /... / HSF
And it spits out like this:
D0032333RCVFX
D0032333RCVDF
D0032333RCHSF
D0034833RCVFX
D0034833RCVDF
and so on....
and so on.....
Presumably you will want to call this function with a "serial number" - so that you can call "the Nth combination". The problem then breaks into two parts:
Part 1: figure out, for a given "serial number", which element of each column you need. If you had the same number of elements E in each column it would be simple: it's like writing N in base E. When the number of elements in each column is different, it's a little bit trickier - something like this:
Option Base 1
Option Explicit
Function combinationNo(r As Range, serialNumber As Integer)
' find the number of entries in each column in range r
' and pick the Nth combination - where serialNumber = 0
' gives the top row
' assumes not all columns are same length
' but are filled starting with the first row
Dim ePerRow()
Dim columnIndex As Integer
Dim totalElements As Integer
Dim i, col
Dim tempString As String
ReDim ePerRow(r.Columns.Count)
totalElements = 1
i = 0
For Each col In r.Columns
i = i + 1
ePerRow(i) = Application.WorksheetFunction.CountA(col)
totalElements = totalElements * ePerRow(i)
Next
If serialNumber >= totalElements Then
combinationNo = "Serial number too large"
Exit Function
End If
tempString = ""
For i = 1 To UBound(ePerRow)
totalElements = totalElements / ePerRow(i)
columnIndex = Int(serialNumber / totalElements)
tempString = tempString & r.Cells(columnIndex + 1, i).Value
serialNumber = serialNumber - columnIndex * totalElements
Next i
combinationNo = tempString
End Function
You call this function with the range where your columns are, and a serial number (starting at 0 for "top row only"). It assumes that any blank space is at the bottom of each column. Otherwise, it will return a string that is the concatenation of combinations of values in each column, just as you described.
EDIT perhaps the following picture, which shows how this is used and what it actually does, helps. Note that the first reference (to the table of columns of different length) is an absolute reference (using the $ sign, so when you copy it from one cell to another, it keeps referring to the same range) while the second parameter is relative (so it points to 0, 1, 2, 3 etc in turn).

VBA: Generating Data that mimics specific parameters (Avg, StdDev..etc)

I have modified a VBA array function given to me here: Excel Generate Normalized Data
That question will explain what I am after.
Download the excel I am using to completely understand:
http://www.mediafire.com/?smq5tl9poitdacc
I am using the following data (The left side are values I enter for the data to be based upon, the right side is the results of the generated data):
As you can see, the % Diff is very good for Avg Click, but Click/Time is off when there is a high Day StdDev (Day +/-). The difference when there is a low Day Stddev is close to 0.
I think this is because the var NoClickDaysPerClick_Running_Avg becomes inaccurate because the NoClickDays_Total (Which is used indirectly to determine the running avg) is "guessed" at the start, and needs to be reassessed each click because the high StdDev adds randomness and the original "guess" becomes more and more inaccurate.
I am not sure if this is the problem, or if it is how I can even solve it.
I am just looking for advice on the best way to do what it is I want. I am not sure why the stdDev are so far off either, but thats not a big deal. I'd rather have a more accurate Click/Time than anything else- reguardless what the Day StdDev is.
here is the function in my VBA:
Function ClickSpacer(Total_Days As Long, ClicksPerDay_Desired_Avg As Double, Clicks_Desired_Deviation As Double, Clicks_Min As Integer, Clicks_Max As Integer, TotalClicksOverTotalDays_Desired_Avg As Double, NoClickDays_Desired_Deviation As Double, NoClickDays_Min As Integer, NoClickDays_Max As Integer)
Dim Day_Array() As Integer
ReDim Day_Array(1 To Total_Days, 1 To 1)
Dim NumDaysToGetClicks As Double
Dim ClickOffset As Long
Dim Clicks_Total As Long
Dim Clicks_SoFar As Long
Dim Clicks_Remaining As Long
Dim NoClickDaysPerClick_Desired_Avg As Double
' Number of clicks that are needed to Achieved desired Avg of clicks over time
Clicks_Total = Round(Total_Days * TotalClicksOverTotalDays_Desired_Avg, 0)
' Number of days in which the user has to click atleast once to achieve desired Avg. clicks per day
NumDaysToGetClicks = Round(Clicks_Total / ClicksPerDay_Desired_Avg, 0)
' The number of non-click days in order fill out the total days
NoClickDays_Total = Round(Total_Days - NumDaysToGetClicks, 0)
' The guessimated average of non-click days per click to fill out total non-click days
' This is never used, just used for comparsion of the running Avg
NoClickDaysPerClick_Desired_Avg = NoClickDays_Total / NumDaysToGetClicks
'This variable is here to achieved closer results to the desired StdDev.
'a higher multiplyer will not limit the deviation but just give an average deviation
'For example, if the Average was 3 with a +/- 2, then with a StdDevMulti of 1
'ALL numbers will be 1 (3-2) through 5 (3+2) with an avg of 3 and stddev of 2, the numbers will NEVER exceed the StdDev.
'With a StdDevMulti of 2, the numbers will be 0 through 6, but should still have an
'Avg deviation of 2.
StdDevMulti = 1
NoClickDays_Desired_Deviation = NoClickDays_Desired_Deviation * StdDevMulti
Clicks_Desired_Deviation = Clicks_Desired_Deviation * StdDevMulti
'Set the obvious defaults
ClickedDaysSoFar = 0
Clicks_SoFar = 0
NoClickDays_SoFar = 0
'Give the ClickOffset a starting value
ClickOffset = NoClickDaysPerClick_Desired_Avg
Do
'used to find the "running" average of days not clicked
NoClickDays_Remaining = NoClickDays_Total - NoClickDays_SoFar
'used to find the "running" average of clicks per day
Clicks_Remaining = (Clicks_Total - Clicks_SoFar)
'used in both "running" averages mentioned above and also will
'mark the end of the while loop.
RemainingClickedDays = (NumDaysToGetClicks - ClickedDaysSoFar)
' Find what the average num. click should be based on the remaining
' and then apply the deviation. Only accept a click below its max
' above its min.
Do
' Generate a random number between -1 and 1
SignChanger = Rnd() - Rnd()
' Apply the randomized StdDev
Clicks_Deviation = Clicks_Desired_Deviation * SignChanger
'Figure out the "running" average
ClicksPerDay_Running_Avg = Clicks_Remaining / RemainingClickedDays
'Figure out a click value and round to the nearest whole number
Generated_Clicks = Round(ClicksPerDay_Running_Avg + Clicks_Deviation, 0)
' Make sure it meets the requirements, if not, try again
Loop While Generated_Clicks < Clicks_Min Or Generated_Clicks > Clicks_Max
' Set the click value to the spaced-out array index
Day_Array(ClickOffset, 1) = Generated_Clicks
'Find a random space based upon the "running" avg. and desired deviation
'Make sure it between the min and max required.
Do
' Generate a random number between -1 and 1
SignChanger = Rnd() - Rnd()
' Apply the randomized StdDev
NoClickDays_Deviation = NoClickDays_Desired_Deviation * SignChanger
'Figure out the "running" average
NoClickDaysPerClick_Running_Avg = NoClickDays_Remaining / RemainingClickedDays
'Figure out a space value and round to the nearest whole number
Generated_NoClickDays = Round(NoClickDaysPerClick_Running_Avg + NoClickDays_Deviation, 0)
' Make sure it meets the requirements, if not, try again
Loop While Generated_NoClickDays < NoClickDays_Min Or Generated_NoClickDays >= NoClickDays_Max
'Define the array index based upon the spacing previously generated.
' Make sure to "add" upon the already known index. Add 1 because you
'have to account for the index the click occupies
ClickOffset = ClickOffset + Generated_NoClickDays + 1
'These should be self-explaintory
ClickedDaysSoFar = ClickedDaysSoFar + 1
Clicks_SoFar = Clicks_SoFar + Generated_Clicks
NoClickDays_SoFar = NoClickDays_SoFar + Generated_NoClickDays
Loop While ClickOffset < Total_Days And RemainingClickedDays > 0
'Set the array equal to the clicks so that it returns the array as
'we want. Ideally this will be just replace Total_Days fields under
'the base, so not to require a array-function. Neither of these work:
'ClickSpacer = Range("P1:P" & UBound(Day_Array) + 1).Value
'Range("P1:P" & UBound(Day_Array) + 1) = Application.Transpose(Day_Array)
ClickSpacer = Day_Array
End Function
I think your assumption is correct. The "problem" with the code you have above is that it uses StdDev as the basis for generating random numbers, so the standard deviation will tend to be accurate and the mean will be less accurate.
If you want more accuracy with the mean and less with the standard deviation, then you'll have to "flip" how numbers are generated: they'll need to center around your desired mean and use the desired standard deviation as a guide, rather than the other way around.
I have an idea about how this can be done, but it will take more concentration than I can apply at work, so I'll have to come back and edit this later. I'll see what I can do.

Resources