I have transaction data which includes a trade date. I want to be able to match the trade date of the transaction with the matching time period.
I have a table with 5 different time periods like so:
Period 1: 1/1/2000 - 3/31/2000
Period 2: 4/4/2001 - 6/6/2001
Period 3: 10/10/2002 - 12/31/2002
etc.
I want to be able to match the dates from the transaction data with their matching period for further calculation purposes.
The only solution I could thing of was nested if formulas but those are always ugly and depending on how many different periods I have not usable.
Thanks for the help!
Try this:
Public Sub RangeChecker()
Dim date1, date2, myDate As Date
Dim myDateStatus As String
date1 = CDate("January 1, 2000")
date2 = CDate("June 1, 2000")
myDate = CDate("May 1, 2000")
If (myDate > date1 And myDate < date2) Then
myDateStatus = "The date is inside the range"
Else
myDateStatus = "The date is outside the range"
End If
MsgBox (myDateStatus)
End Sub
You should only need one if statement per date range.
It's difficult to answer when you don't specify enough the layout of your data, which thing is in which column etc. Supposing the periods are defined in columns A, B and C, and the date for which you want to find the period is F2, you can use this CSE formula:
=INDEX($A$2:$A$7,MATCH(1,($B$2:$B$7<=F2)*(F2<=$C$2:$C$7),0))
' enter then press Ctrl+Shift+Enter
Related
I'm trying to figure out how to get the total of Mondays in a month then multiply by working hours.
This is my example code it works but it counts wrong output:
If UCase(val) Like "EVERY MONDAY" Then
Dim numString As Integer
Dim strDays() As String
Dim wordCount As Long
numString = 2
strDays = VBA.Split(val, " ")
wordCount = UBound(strDays)
strWhEveryDay = ThisWorkbook.Sheets("Input").Cells(X, 4).Value
strWhEveryDay = strWhEveryDay * var_month
Debug.Print "Every = " & strWhEveryDay
Explanation:
It depends on the user if what they like to input in a TEXTBOX. However, the CALCULATION it depends on the date where the user input in TEXTBOX.
I have Textbox which is the target month where the user input the format of date like this:
**Jan-2023 or Feb-2023 **
I have a table like this:
Place this text in a table start in Column B Row 2:
**every Monday**
Place this text in a table Column D Row 4:
**1.2**
All I need is to get all the total of Mondays based on the given month and year. The calculation of the days in a table of "every Monday" once I change the text from "every Monday" to "every Tuesday" so the calculation will adjust or automatically knows where the calculation days start to end:
Example of expected calculation: every Monday (January 2023 = 5 Days) * 1.2 so, the expected result will be 5.
Note: use Debug.Print to see the result or output
So, using networkdays.intl() as suggested in my comment:
NETWORKDAYS.INTL($A$3,$A$33,"0111111",)
The result shown is 5, which is correct by inspection, for cells A3:A33 the long date format was used.
So multiplying by 1.2 is:
NETWORKDAYS.INTL($A$3,$A$33,"0111111",)*1.2
and 5 * 1.2 = 6
Also, the string "0111111" can be put in cell F5 and referred to so it is easier to edit.
The easiest way I know to recognise any day of the week is the following:
=WEEKDAY(B2,2)
The "2" means that weekdays are counted, starting with "Monday" as 1, "Tuesday" as 2, ...
So, if you want to know if your date is a Monday, you can use this formula:
=IF(WEEKDAY(B2,2)=1,...)
This can easily be translated into VBA, using a standard IF-clause.
I have data with the following structure:
Date
Time
01-01-2021
0800-1600
01-01-2021
2000-0400
Each line is an employee and their worked hours. Meaning the first line employee #1 meets 0800 and leaves at 1600 the same day. However employee #2 meets 2000 and leaves 0400 the following day.
My issue is that I'm working showing no. of employees present at specific times. The first employee is easy to do as meeting and leaving is the same day. However the second is a bit more problematic, as in my current setup the employee is shown on work the same day.
The data is updated automatically into excel so I don't want to make any manual adjustments.
In order to show it correctly, and from my point of view, I need to make an additional line with the hours for the following day. I could make this with VBA, but I'm not sure if this is the easiest and best way to do it.
So any ideas on how to handle an issue like this?
Thanks!
This formula will give your the hours worked:
=LET(DateValue, A2,
TimeValues,FILTERXML("<t><c>"&SUBSTITUTE(B2,"-","</c><c>")&"</c></t>","//c"),
Start,INDEX(TimeValues,1),
End,INDEX(TimeValues,2),
StartTime, SUM(DateValue, Start/2400),
EndTime, IF(Start<End,SUM(DateValue,End/2400),SUM(DateValue+1,End/2400)),
EndTime - StartTime)
This will give you the hours worked in day one or day two (change the last variable to Day1Hours):
=LET(DateValue, A2,
TimeValues,FILTERXML("<t><c>"&SUBSTITUTE(B2,"-","</c><c>")&"</c></t>","//c"),
Start,INDEX(TimeValues,1),
End,INDEX(TimeValues,2),
StartTime, SUM(DateValue, Start/2400),
EndTime, IF(Start<End,SUM(DateValue,End/2400),SUM(DateValue+1,End/2400)),
Day1Hours, IF(Start<End,EndTime-StartTime,(DateValue+1)-StartTime),
Day2Hours,IF(End<Start, EndTime-(DateValue+1),0),
Day2Hours)
This I was hoping would return both results, but FILTERXML didn't seem to work within the LET function when returning the results:
=LET(DateValue, A2,
TimeValues,FILTERXML("<t><c>"&SUBSTITUTE(B2,"-","</c><c>")&"</c></t>","//c"),
Start,INDEX(TimeValues,1),
End,INDEX(TimeValues,2),
StartTime, SUM(DateValue, Start/2400),
EndTime, IF(Start<End,SUM(DateValue,End/2400),SUM(DateValue+1,End/2400)),
Day1Hours, IF(Start<End,EndTime-StartTime,(DateValue+1)-StartTime),
Day2Hours,IF(End<Start, DateValue+1-EndTime,0),
TRANSPOSE(FILTERXML("<t><c>" & Day1Hours & "</c><c>" & Day2Hours & "</c></t>","//c")))
Create a User Defined Function (UDF) to return a datetime in cols C and D from the values in col A and B. Put the UDF code in a module. For example the formula in C2 would be =date_time($A2,$B2,0) and in D2 =date_time($A2,$B2,1)
Option Explicit
' i=0 for start , i=1 for end
Function date_time(dt As Date, hrs As String, i As Integer) As Date
Dim ar
ar = Split(hrs, "-")
If ar(1) < ar(0) Then ar(1) = ar(1) + 2400 ' next day
' add minutes
date_time = DateAdd("n", Left(ar(i), 2) * 60 + Right(ar(i), 2), dt)
End Function
Microsoft Excel does not recognise pre-1900 dates and there is plenty of information online which documents this, including the question here.
The best work around (which many other posts link to) seems to be at ExcelUser
However, although the work around gets Excel to recognise a pre-1900 date as a date, it still does not allow it to be used in calculations e.g. when wanting to calculate the number of years since a pre-1900 date.
My question is whether the work around described at ExcelUser can be modified to allow the result to be used in a calculation.
To put things simply, for example, I want to calculate in Excel the number of years since 1/4/1756 - is this possible?
Or does another solution have to be adopted? Perhaps there are plug-ins which address this problem?
First of all I highly recommend to use the ISO 8601 format yyyy-mm-dd for dates because even if you only have strings and no real numeric dates this is properly sortable and the only date format that is defined clearly and cannot be misinterpret like 02/03/2021 where no one can ever say if it is mm/dd/yyyy or dd/mm/yyyy because both actually exist.
Since old dates cannot be real numeric dates but only entered as strings (looking like a date) that means misinterpretation needs to be avioded or you get wrong results. Therefore a date format that cannot be misinterpret is a clear advantage.
Second there is more than one way to calculate "how many years since the birth of Mr. X": For example lets take the birthday of Maryam Mirzakhani 1977-05-12 compared to the date today 2021-04-15. Today she would not have had birthday yet this year and therefore she would be 43 years old. But this year she would have turned 44 years (2021 - 1977 = 44). So the question needs to be asked more precisely. Either "how old would Mr. X be today?" or "how old would Mr. X be this year". The calculation for that would be different.
So let's start and assume the following data. We already know the fact that Excel cannot calculate with dates before 1900. You can see that if we enter pre-1900 dates that they are formatted as string (red dates) and post-1900 dates get formatted as numeric dates (green dates).
Image 1: #WERT! means #VALUE! (sorry for the German screenshot).
Also in column D where the formula =DATEDIF($B2,TODAY(),"y") was used the string dates cannot be calculated with. But since VBA can actually handle pre-1900 dates we can write our own UDF (user defined function) for that. Since as I explained above there is 2 different methods to calculate there is 2 different functions:
OldDateDiff(Date1, Date2, Interval) called like =OldDateDiff($B2,TODAY(),"yyyy")
OldDateAge(Date1, Date2) called like =OldDateAge($B2,TODAY())
Option Explicit
Public Function OldDateDiff(ByVal Date1 As Variant, ByVal Date2 As Variant, ByVal Interval As String) As Long
Dim RetVal As Long 'variable for the value we want to return
Dim localDate1 As Date
If VarType(Date1) = vbDate Or VarType(Date1) = vbDouble Then 'check if Date1 is numeric
localDate1 = CDate(Date1) 'if numeric take it
ElseIf VarType(Date1) = vbString Then 'check if Date1 is a string
localDate1 = ISO8601StringToDate(Date1) 'if it is a string convert it to numeric
Else 'neither string nor numeric throw an error
RetVal = CVErr(xlErrValue)
Exit Function
End If
Dim localDate2 As Date 'same as for Date1 but with Date2
If VarType(Date2) = vbDate Or VarType(Date2) = vbDouble Then
localDate2 = CDate(Date2)
ElseIf VarType(Date2) = vbString Then
localDate2 = ISO8601StringToDate(Date2)
Else
RetVal = CVErr(xlErrValue)
Exit Function
End If
If localDate1 <> 0 And localDate2 <> 0 Then 'make sure both dates were filled with values
RetVal = DateDiff(Interval, localDate1, localDate2) 'calculate the difference between dates with the desired interaval eg yyyy for years
End If
OldDateDiff = RetVal 'return the difference as result of the function
End Function
Public Function OldDateAge(ByVal Date1 As Variant, ByVal Date2 As Variant) As Long
Dim RetVal As Long 'variable for the value we want to return
Dim localDate1 As Date
If VarType(Date1) = vbDate Or VarType(Date1) = vbDouble Then 'check if Date1 is numeric
localDate1 = CDate(Date1) 'if numeric take it
ElseIf VarType(Date1) = vbString Then 'check if Date1 is a string
localDate1 = ISO8601StringToDate(Date1) 'if it is a string convert it to numeric
Else 'neither string nor numeric throw an error
RetVal = CVErr(xlErrValue)
Exit Function
End If
Dim localDate2 As Date 'same as for Date1 but with Date2
If VarType(Date2) = vbDate Or VarType(Date2) = vbDouble Then
localDate2 = CDate(Date2)
ElseIf VarType(Date2) = vbString Then
localDate2 = ISO8601StringToDate(Date2)
Else
RetVal = CVErr(xlErrValue)
Exit Function
End If
If localDate1 <> 0 And localDate2 <> 0 Then 'make sure both dates were filled with values
RetVal = WorksheetFunction.RoundDown((localDate2 - localDate1) / 365, 0)
'subtract date1 from date2 and divide by 365 to get years, then round down to full years to respect the birthday date.
End If
OldDateAge = RetVal 'return the age as result of the function
End Function
' convert yyyy-mm-dd string into numeric date
Private Function ISO8601StringToDate(ByVal ISO8601String As String) As Date
Dim ISO8601Split() As String
ISO8601Split = Split(ISO8601String, "-") 'split input yyyy-mm-dd by dashes into an array with 3 parts
ISO8601StringToDate = DateSerial(ISO8601Split(0), ISO8601Split(1), ISO8601Split(2)) 'DateSerial returns a real numeric date
' ≙yyyy ≙mm ≙dd
End Function
Note that here column B contains 2 different kind of data. Strings (that look like a date) and real numeric dates. If you sort them, all the numeric dates will sort before the string dates (which is probably not what you want). So if you want this to be sortable by birthday column make sure you turn all dates into strings. This can be done by adding an apostrophe ' infront of every date. This will not display but ensure the entered date is considered to be a string.
If your date is in an unambiguous format (eg ISO or a format corresponding to your Windows Regional Settings, or a real date if after 1900), you can use VBA which will recognize early dates.
Function Age(dt As Date)
Age = DateDiff("yyyy", dt, Date)
End Function
You should be aware that, because of how the function calculates years differences, depending on what you want exactly for a result, you may need to adjust the answer if the birthdate is before/after today's date.
In other words, if the day of the year of the birthdate is after the day of the year of Today, you may need to subtract 1 from the result.
But this should get you started.
There's a much easier way than the accepted answer. Simply convert your dates to Unix time:
Function nUnixTime(dTimestamp As Date) As LongLong
' Return given VB date converted to a Unix timestamp.
Const nSecondsPerDay As Long = 86400 ' 24 * 60 * 60
nUnixTime = Int(CDbl(CDate(dTimestamp) - CDate("1/1/1970"))) * nSecondsPerDay
End Function
Unix time is the number of seconds since Jan. 1, 1970, with times before that date being negative. So if you convert your dates to Unix time, you can just subtract them and divide the result by 86,400 to have the difference in days, or by 31,557,600 for years (31,557,600 = 60 * 60 * 24 * 365.25).
Example results of the above VB function called from Excel:
Column A
Column B formula
Column B value
2/2/2022
=nUnixTime(A1)
1,643,760,000
1/1/1970
=nUnixTime(A2)
0
12/14/1901
=nUnixTime(A3)
-2,147,472,000
12/13/1901
=nUnixTime(A4)
-2,147,558,400
1/1/1900
=nUnixTime(A5)
-2,208,988,800
1/1/1800
=nUnixTime(A6)
-5,364,662,400
1/1/100
=nUnixTime(A7)
-59,011,459,200
The reason I included the two dates in 1901 is because their magnitudes in Unix time are just smaller than and just larger than the largest magnitude of a signed 32-bit integer, i.e., a Long in VBA. If the output of the above function were a Long, then values for dates before Dec. 14, 1901 would be the error #Value!. That is the reason the output of the function is defined as LongLong, which is VBA's signed 64-bit integer.
I need to trace expire date of materials. To do so, I need to search for specific time interval (e.g. 80 days prior and 40 days later from today).
First, I set today's date in dd/mm/yyyy format. Then, take two input from user such that the first one is to identify the date after today (say x), and the second input for how many days before today (say y).
Now, I need to compare (today + x) and (today - y) dates with expiration date of materials such that whether shelf life of the materials fall within [y,x] interval. However, I am stuck.
I define rear and further as inputBox variables. Value entered to rear would be assigned to the variable x and give "rear" days prior today, and value entered to further would be assigned to variable y and take us "further" days after.
Here I identify today's date ("G1" cell) and time interval with x and y dates as well
Public Sub date()
Dim DateToProcess As Date
DateToProcess = Date
BUBD.Cells.Range("G1") = DateToProcess
rear = InputBox("How many days do you want to trace back?")
x = DateAdd("d", -rear, CDate(Range("G1")))
further = InputBox("How many days after do you want to check?")
y = DateAdd("d", further, CDate(Range("G1")))
End Sub
Then I want to compare materials BUBD with x and y
If rngRisk(j) > x Or rngRisk(j) < y
When I compare x and y with expire dates in rngRisk column, all values are pasted regardless of being in the interval
How can I compare expire dates of materials with x and y?
If I understood correctly your code is not being able to compare dates.
I will assume that rngRisk(j) is a Excel cell, in that case what might be happening is the type of the data in the cell is coming on the wrong format. If the cell is formated as Date, you could do rngRisk(j).Value2. It should returns the date or the number of days since 1900 until the specified date.
OBS: x and y should be date or long type in order to compare dates
in case the cell is in date format
If rngRisk(j).Value2 > x
in case the cell is in text format
If CDate(rngRisk(j).Value2) > x
Edit:
Sample for compare dates
Sub CompareDates()
Dim rngDates As Range
Dim initialDate As Date
Dim finalDate As Date
Dim dateInColumn As Variant
initialDate = Date ' today
finalDate = Date + 3 ' 3 days from now
Set rngDates = ActiveSheet.Range("b2:b21") ' range with dates
For Each dateInColumn In rngDates
If dateInColumn.Value2 > initialDate And dateInColumn.Value2 < finalDate Then
MsgBox CDate(dateInColumn.Value2) & " is in between " & initialDate & " and " & finalDate
End If
Next
End Sub
Welcome!
Firstly, always use Option Explicit on the very top of your code before your Subs.
This will force you to declare your variables which will save you from errors that have to do with data types.
Second, x, y and rngRisk should be declared as date in order to be reliably compared to a date.
Finally, you can just add integers to a variable declared as date like so
dim tomorrow as date
tomorrow = Date + 1
I am currently working on function in Excel that will display the status of an activity based on the due date provided.
This function would display:
"Overdue" if Today()> Due Date;
"Due Soon" If the Due date was within one week
"Due Later" if Today() < Due Date +7
Below is an example of what I was able to muster up:
Function Status_of_Date()
If Sheets("Issue_Log").Range("Due_Date").Value < Date Then
Sheets("Issue_Log").Range("Date_Status").Value = "Overdue" 'overdue
ElseIf Sheets("Issue_Log").Range("Due_Date").Value < 7 + Date Then
Sheets("Issue_Log").Range("Date_Status").Value = "Due Later" ' Due Soon
ElseIf Sheets("Issue_Log").Range("Due_Date").Value > 7 + Date Then
Sheets("Issue_Log").Range("Date_Status").Value = "Due Later" ' Due Later
Else
End If
End Function
Codeless Solution
Add a column to your table, to count the days left - since anything negative is overdue anyway, make all negatives -1:
Use a table formula to calculate it:
=IF([#[Due Date]]-TODAY()<0,-1,[#[Due Date]]-TODAY())
Next, have another table to hold the status given a number of days:
Since you have 3 statuses, and they're really ranges of values, to achieve the values you're after you'll need:
A row with -1 for everything Overdue
A row with 0 for everything due Soon
A row with 7 for everything due Later
Now your "Date Status" column can be a simple VLOOKUP formula:
Again, a table formula is used; note the "approximate match" last parameter:
=VLOOKUP([#Days],tblStatusLookup,2,TRUE)
Adjust tblStatusLookup to whatever you've named your lookup table.
Look 'ma, not a single line of code!
Then you can hide the [Days] column if you don't need it shown, and the lookup table can be anywhere you want - and if the thresholds need to change, or if new statuses need to be added, you just tweak the lookup table (important: keep the [Days] sorted ascending, that's how approximate match VLOOKUP works!)
Bugs in OP
Your function needs to know what row to work with. That should be a parameter; change the signature to accept one - or even better, change it to accept a DueDate parameter - then you simply don't need to care about anything other than the date you're given:
Public Function GetDateStatus(ByVal dueDate As Date) As String
If dueDate - Date < 0 Then
GetDateStatus = "Overdue"
ElseIf dueDate - Date < 7 Then
GetDateStatus = "Due Soon"
Else
GetDateStatus = "Due Later"
End If
End function
And then in your table the formula would be:
=GetDateStatus(#[Due Date])
No need to be bothered with ranges and the nitty-gritty details of how and where every value is coming from - code gets much, much simpler when you work at the right abstraction level!
Note that a worksheet function is not allowed to change other cells' values: it calculates a value.