Nested IF statement to read and output custom date groupings - excel

I am trying to create a nested IF statement in Excel 2016 to read a date value and output the corresponding financial month. Since a financial month is unique to the company I work for there is no available alternative other than to create a custom function or statement.
So far I have tried:
=IF(D2 <= DATEVALUE("28/07/2017"),DATEVALUE("Jul-2017"),
IF(D2 <= DATEVALUE("26/08/2017"),DATEVALUE("Aug-2017"),
IF(D2 <= DATEVALUE("29/09/2017"), DATEVALUE("Sep-2017"),
IF(D2 <= DATEVALUE("27/10/2017"), DATEVALUE("Oct-2017"), 0)))) +
IF(D2 <= DATEVALUE("24/11/2017"), DATEVALUE("Nov-2017"),
IF(D2 <= DATEVALUE("29/12/2017"), DATEVALUE("Dec-2017"),
IF(D2 <= DATEVALUE("26/01/2018"), DATEVALUE("Jan-2018"),
IF(D2 <= DATEVALUE("23/02/2018"), DATEVALUE("Feb-2018"), 0)))) +
IF(D2 <= DATEVALUE("20/03/2018"), DATEVALUE("Mar-2018"),
IF(D2 <= DATEVALUE("27/04/2018"), DATEVALUE("Apr-2018"),
IF(D2 <= DATEVALUE("25/05/2018"), DATEVALUE("May-2018"),
IF(D2 <= DATEVALUE("29/06/2018"), DATEVALUE("Jun-2018"),
IF(D2 <= DATEVALUE("27/07/2018"), DATEVALUE("Jul-2018"),
IF(D2 <= DATEVALUE("24/08/2018"), DATEVALUE("Aug-2018"), 0)))))) +
IF(D2 <= DATEVALUE("28/09/2018"), DATEVALUE("Sep-2018"),
IF(D2 <= DATEVALUE("26/10/2018"), DATEVALUE("Oct-2018"),
IF(D2 <= DATEVALUE("23/11/2018"), DATEVALUE("Nov-2018"),
IF(D2 <= DATEVALUE("28/12/2018"), DATEVALUE("Dec-2018"),
IF(D2 <= DATEVALUE("25/01/2019"), DATEVALUE("Jan-2019"),
IF(D2 <= DATEVALUE("22/02/2019"), DATEVALUE("Feb-2019"),
IF(D2 <= DATEVALUE("29/03/2019"), DATEVALUE("Mar-2019"), 0))))))) +
IF(D2 <= DATEVALUE("26/04/2019"), DATEVALUE("Apr-2019"),
IF(D2 <= DATEVALUE("24/05/2019"), DATEVALUE("May-2019"),
IF(D2 <= DATEVALUE("28/06/2019"), DATEVALUE("Jun-2019"), D2)))
It isn't working - I get a numeric output instead of a date.
There must be a better/effective way of doing this.
Can somebody recommend another approach? Possible via UDF in VBA?
Edit - posting a table of the ranges and required output
Start date End date Month
12/30/2017 1/26/2018 Jan-18
1/27/2018 2/23/2018 Feb-18
2/24/2018 3/30/2018 Mar-18
3/31/2018 4/27/2018 Apr-18
4/28/2018 5/25/2018 May-18
5/26/2018 6/29/2018 Jun-18
6/30/2018 7/27/2018 Jul-18
7/28/2018 8/24/2018 Aug-18
8/25/2018 9/28/2018 Sep-18
9/29/2018 10/26/2018 Oct-18
10/27/2018 11/23/2018 Nov-18
11/24/2018 12/28/2018 Dec-18
12/29/2018 1/25/2019 Jan-19
1/26/2019 2/22/2019 Feb-19
2/23/2019 3/29/2019 Mar-19
3/30/2019 4/26/2019 Apr-19
4/27/2019 5/24/2019 May-19
5/25/2019 6/28/2019 Jun-19

The pattern in your table is:
The last Friday in each month is the month cut off, which means....
The last Saturday in each month is the start of the next month
This will take a input, verify that it is a date, and see if the date is before, equal to, or after the last Friday in the input month.
If before, CSTMonth = Inputs Month
If equal, CSTMonth = Inputs Month
If after, CSTMonth = Month after Inputs Month
Option Explicit
Public Function CSTMonth(Target As Range)
If IsDate(Target) Then
Dim LastDay As Date: LastDay = WorksheetFunction.EoMonth(Target, 0)
Dim LastFri As Date: LastFri = LastDay - (Weekday(LastDay + 1) Mod 7)
If Target <= LastFri Then
CSTMonth = Format(Target, "MMM-YY")
Else
CSTMonth = Format(LastDay + 1, "MMM-YY")
End If
End If
End Function
The benefit of finding the pattern in your table is that this will never need to be updated with brute force. Taking your approach would mean hard coding each and every months start and end dates.

If you are using a table with the starting dates for each period the try the following:
Assuming the table with the period starting dates is located at B2:D25 and the dates are located at F2:F25 and J2:J25
Enter this formula to obtain the Financial Period as date serial in G2 then copy it to G3:G25 and K2:K25:
=VLOOKUP(F3,$B$2:$D$25,3,1)
Enter this formula to obtain the Financial Period as text in H2 then copy it to H3:H25 and L2:L25:
=TEXT(VLOOKUP(F3,$B$2:$D$25,3,1),"mmm-yyyy")

I am not sure about the complications you are facing but to get the desired results per your table, you can also use a simpler formula like
=TEXT(EOMONTH(A2,1),"YY-MMM")
OR
=EOMONTH(A2,1)
and format the cell in yy-mmm format.
where A2 holds the start date.

Related

Select records with at one day in centain year

I want to make a selection of records where date range (start date - end date) includes at least 1 day of 2020. So for example: if a record has 2018-01-13 as the start date and 2020-01-09 as end date, it has to be included in my selection, as there is at least one day active in 2020.
Sample data:
How can I achieve this?
Just check the years with the standard between notation
Start Year <= 2020 <= End Year
=AND(YEAR(A1)<=2020,YEAR(B1)>=2020)
=IF(OR(YEAR(A2)=2020,YEAR(B2)=2020),1,0)
Excel store dates as numbers, so, you must compare them like numbers
Make a column with: =IF(OR(B2 < $C$2; $D$2 < A2); 0; 1)
where
A: start date
B: final date
C: 2020-01-01
D: 2020-12-31
, Now you can use only values 1

Combine work days and calendar days for a calculator

Im trying to create a calculator for detention of containers.
Each provider has different rules that have a breakdown between work days and calendar days.
for example:
the first 5 working days (excl Saturday and Sunday) are free of cost
After that the next 3 calendar days are at a cost of 135
After the above, the next 5 calendar days are at a cost of 160
After that 180 onward.
is this possible to do in excel? My idea is to have a difference between 2 dates: date of arrival versus date of return. and based on the 4 rules below use "IF" to give me a cost.
what would be a more efficient way to do this?
HardCoded option:
With Dynamic array formula SEQUENCE()
=SUM(IF(SEQUENCE(B1-A1+1,,A1)<WORKDAY(A1,5),0,IF(SEQUENCE(B1-A1+1,,A1)<WORKDAY(A1,5)+3,135,IF(SEQUENCE(B1-A1+1,,A1)<WORKDAY(A1,5)+8,160,180))))
Older version:
=SUM(IF(ROW(INDEX($ZZ:$ZZ,A1):INDEX($ZZ:$ZZ,B1))<WORKDAY(A1,5),0,IF(ROW(INDEX($ZZ:$ZZ,A1):INDEX($ZZ:$ZZ,B1))<WORKDAY(A1,5)+3,135,IF(ROW(INDEX($ZZ:$ZZ,A1):INDEX($ZZ:$ZZ,B1))<WORKDAY(A1,5)+8,160,180))))
As an array formula, using Ctrl-Shift-Enter to confirm instead of Enter when exiting edit mode.
Using a lookup table.
Put 0 in the first cell in the lookup table. The second cell would get the formula =WORKDAY(A1,5) the next below: =F2+3 and the last =F3+5 and put the corresponding values:
Then use SUMPRODUCT and LOOKUP:
=SUMPRODUCT(LOOKUP(SEQUENCE(B1-A1+1,,A1),F1:F4,G1:G4))
older version:
=SUMPRODUCT(LOOKUP(ROW(INDEX($ZZ:$ZZ,A1):INDEX($ZZ:$ZZ,B1)),F1:F4,G1:G4))
Below is a VBA function that accepts three arguments - the start date, the end date, and the name of the carrier, and returns a cost. Only costing for one carrier is included, but it can be expanded for other carriers.
Function fContainerCost(dtmStart As Date, dtmEnd As Date, strCarrier As String) As Currency
Dim lngDays As Long
Select Case strCarrier
Case "CarrierA"
dtmStart = WorksheetFunction.WorkDay(dtmStart, 5)
If dtmStart > dtmEnd Then
fContainerCost = 0
Else
lngDays = DateDiff("d", dtmStart, dtmEnd) + 1 ' need to get the actual days, so get the difference in dates and add 1.
If lngDays <= 3 Then
fContainerCost = fContainerCost + (135 * lngDays)
Else
fContainerCost = fContainerCost + (135 * 3)
lngDays = lngDays - 3
If lngDays <= 5 Then
fContainerCost = fContainerCost + (160 * lngDays)
Else
fContainerCost = fContainerCost + (160 * 5)
lngDays = lngDays - 5
fContainerCost = fContainerCost + (180 * lngDays)
End If
End If
End If
Case "CarrierB"
End Select
End Function
To use it, just treat it as a regular Excel function:
=fContainerCost(A1,B1,C1)
Regards,

Write VBA function to find period no. and week no. for 13 periods

Overview: I basically want to write a version of the MONTH function to work for a 13 period work year...
(Disclaimer: I'm new to VBA) What is the best way to reformat a date dd/mm/yyyy to be in terms of Period Number (1-13) and Week Number (1-4)? I've used the WEEKNUM function to sort of figure it out, but I can't quite seem to get it. I think the problem is that the start of the new year (Period 1, Week 1) is on 12/30/18 and in excel, it counts this date as being the 5th or even 6th week by how it's measured.
Weeks start on Sundays and every period of the year (13) has 4 weeks. So far I have tried:
this basic portion of a self-written function to just get the week number portion to work and it just gives me #value, but no errors in msgbox (if it did work I may be able to figure out the rest):
Function PeriodNum(serial_num As Date, number_periods As Integer, start_date As Date)
Dim first_week As Integer
Dim second_week As Integer
Dim third_week As Integer
Dim fourth_week As Integer
Dim day As Integer
first_week = 1
second_week = 2
third_week = 3
fourth_week = 4
day = serial_num
If day >= start_date Or day <= start_date + 7 Then
Selection.Value = first_week
ElseIf day > start_date + 7 Or day <= start_date + 14 Then
Selection.Value = second_week
ElseIf day > start_date + 14 Or day <= start_date + 21 Then
Selection.Value = third_week
ElseIf day > start_date + 21 Or day <= start_date + 28 Then
Selection.Value = fourth_week
Else
Selection.Value = "error"
End If
End Function
-I have also tried using the WEEKNUM function but I don't know how to get it to give me numbers 1-4 for each perspective period
Thanks so much for your help! Much appreciated
1) There is one problem according to code rules of VBA:
to each
Function PeriodNum(serial_num As Date, number_periods As Integer, start_date As Date)
definition you should also use row:
PeriodNum = ...
by this way you are returning a value from a function (similar to return x; in other languages)
2) Using a Selection object is unpredictable and can change any cell you have active in Excel at time of calling of method. If you are using method for computation you should use similar way:
Function weekCompute(date1)
'...
weekCompute = "val1"
End Function
With this function you can use this cell formula:
=weekCompute(A1)
In Excel, Functions whether built-in (i.e. SUM, COUNT) or user-defined like your PeriodNum, usually return a value to the cell they are entered in. They don't modify workbook objects such as ranges which Selection is a member of. Procedures Subs modify objects but don't have return values.
As #VitezslavSimon alluded in his answer, you need to assign a value to the function name somewhere in your code - PeriodNum = 1 or PeriodNum ="error".
It's also a good idea to explicitly add a return Type to your functions. In your case you could use String if you aren't doing any arithmetic calculations on the return value or Variant if you want to use the return in further calculations. Your function definition would then be:
Function PeriodNum(serial_num As Date, number_periods As Integer, start_date As Date) As Variant
Iterative approach to function errors
I copied your code into a standard VBA module and put a break-point on line first_week = 1 so i could step through the function after entering it in a cell. To test it I put your start_date in cell C2 and serial_num in C3. I called the function by entering =PeriodNum(C3,13,$C$2) in D3.
When stepping through your code, the line day = serial_num caused the function to end and return #VALUE! error in D3.
#VALUE! is the only error returned by UDFs that have error in them. You don't get the luxury of run-time errors in UDFs so the only way to investigate is to step through the code and see where it falls over.
The date I was testing was 12/30/18 which has a Serial No of 43464. You defined day As Integer which must be from -32,768 to 32,767. So the underlying error is Overflow because 43464 is outside the allowable values for Integer.
2nd. Iteration
Changing Integer to Long overcomes that error but then
If day >= start_date Or day <= start_date + 7 Then
PeriodNum = first_week
Is always going to return 1 for serial_num >= start_date or "error" for serial_num < start_date. The second part of your test Or day <= start_date + 7 doesn't really matter because if it's FALSE, the other test is TRUE. I tested this by extending dates down to C381 to take them into 2020. So back to the drawing board!
3rd Iteration
Changing all the Or to And ensured the second part would be evaluated.
If day >= start_date And day <= start_date + 7 Then
PeriodNum = first_week
ElseIf day > start_date + 7 And day <= start_date + 14 Then
PeriodNum = second_week
ElseIf day > start_date + 14 And day <= start_date + 21 Then
PeriodNum = third_week
ElseIf day > start_date + 21 And day <= start_date + 28 Then
PeriodNum = fourth_week
Else
PeriodNum = "error"
End If
Back in Excel I notice that 1/6/19 which should be the 1st day of period 2 is showing as 1. So something is wrong with the test. Changing day <= start_date + 7 to day < start_date + 7 should fix it but it returns "error". This is because the subsequent tests don't allow for day = start_date + 7.
4th. iteration
If day >= start_date And day < start_date + 7 Then
PeriodNum = first_week
ElseIf day >= start_date + 7 And day < start_date + 14 Then
PeriodNum = second_week
ElseIf day >= start_date + 14 And day < start_date + 21 Then
PeriodNum = third_week
ElseIf day >= start_date + 21 And day < start_date + 28 Then
PeriodNum = fourth_week
Else
PeriodNum = "error"
End If
Back to Excel and it looks good until scrolling down. 1/27/19 and everything below returns "error". day obviously can't be more than start_date + 28.
5th. iteration
Change day = serial_num to day = (serial_num - start_date) Mod 28 and the tests to look at that number in relation to 0, 7, 14, 21 and 28.
day = (serial_num - start_date) Mod 28
If day >= 0 And day < 7 Then
PeriodNum = first_week
ElseIf day >= 7 And day < 14 Then
PeriodNum = second_week
ElseIf day >= 14 And day < 21 Then
PeriodNum = third_week
ElseIf day >= 21 And day < 28 Then
PeriodNum = fourth_week
Else
PeriodNum = "error"
End If
Back in Excel and it all looks good.
Final iteration - polish it
There are still some improvements:
number_periods As Integer isn't used so delete it
the function PeriodNum name is deceptive. It isn't returning a period but a week in a period soWeekInPeriod`
It isn't good practice to use reserved words or VBA functions as variable names - -
day is a VBA function (returns the day in the month). I changed it to DayInPeriod28. Descriptive variable names are easier for the user to parse.
I also changed serial_num to MyDate.
Instead of nested Ifs, Select Case is more compact.
Don't declare and define variable you are only going to use once. It's a waste of space and time.
Function WeekInPeriod(MyDate As Date, start_date As Date) As Variant
Dim DayInPeriod28 As Long
DayInPeriod28 = (MyDate - start_date) Mod 28
Select Case DayInPeriod28
Case Is < 7
WeekInPeriod = 1
Case Is < 14
WeekInPeriod = 2
Case Is < 21
WeekInPeriod = 3
Case Is < 28
WeekInPeriod = 4
Case Else
WeekInPeriod = "error"
End Select
End Function
You could just calculate the week:
LngPeriod = int((day - start_date +1)/7)
If lngPeriod >4 then
PeriodNum = “error”
Else
PeriodNum = lngPeriod
End if
Where The PeriodNum function returns a variant as suggested.

Excel date formula to find if date in a cell is of current, next or previous month

Can anyone advise how can I find if the given date in a cell belongs to the current month or previous month or next month of the year.
For Example
A B (result I want to achieve...)
05/12/2014 Last month
06/12/2014 Current month
07/17/2014 Next month
06/14/2012 Past date
I am sorry if you find this question is a duplicate. I'll appreciate if you could guide me to the post where this question was answered.
FirstOfThisMonth:
=DATE(YEAR(NOW()), MONTH(NOW()), 1)
FirstOfNextMonth:
=DATE(YEAR(NOW()), MONTH(NOW()) + 1, 1)
FirstOfLastMonth:
=DATE(YEAR(NOW()), MONTH(NOW()) - 1, 1)
FirstOfMonthAfterNext:
=DATE(YEAR(NOW()), MONTH(NOW()) + 2, 1)
Therefore:
=IF(A2 < FirstOfLastMonth,
"Past Date",
IF(A2 >= FirstOfMonthAfterNext,
"Future Date",
If(A2 >= FirstOfNextMonth,
"Next Month",
"Current Month"
)
)
)
You can say something like:
=(YEAR(A1) = YEAR(B1)) AND (MONTH(A1)=MONTH(B1) )
This will give you a boolean value which you can use in other cells.

IF statement for binning data

I have a list of numbers in the 1st column. Based on the number in the 1st column I want to give each line another number i.e. if cell A2 has a value of bigger than 1300 and less than 1400, in B2 I want the cell to show 6.75.
If A1 has a value of 1350, then B1 will update with a value of "6.75".
If A1 has a value of 1450, then B1 will update with a value of "7.25", and so on.
There are 17 groupings that I need:
<1300 >1400 =6.75
<1400 >1500 =7.25
<1500 >1600 =7.75
<1600 >1700 =8.25
.
.
.
Bigger than 2900 =14.75
I could have numerous values on the spreadsheet in the 1st column so need to put them into a grouping bucket using some formula.
Any ideas?
Something like =VLOOKUP(A3,bArray,2) should suit, copied to suit, where bArray is the name for a two column list of the breakpoints and the values up to the respective breakpoint.
The break points may require slight adjustment to suit whatever is actually required.
For a simple linear relationship like that, you could use a formula along the lines of:
=if(a1<1300, 0, if(a1>=2900, 14.75, (trunc(a1 / 100, 0) - 13) * 0.5 + 6.75))
In other words, check the too-low and too-high values first to deliver fixed results, otherwise use the final formula to convert to the desired number.
This involves dividing by 100 to turn (for example) 1727 into 17, subtracting 13 to get 4, multiplying that by 0.5 and adding the 6.25 base to get 8.75.
That will give you what you've asked for:
x < 1300: 0.00
1300 <= x < 1400: 6.75
1400 <= x < 1500: 7.25
1500 <= x < 1600: 7.75
1600 <= x < 1700: 8.25
1700 <= x < 1800: 8.75
1800 <= x < 1900: 9.25
1900 <= x < 2000: 9.75
2000 <= x < 2100: 10.25
2100 <= x < 2200: 10.75
2200 <= x < 2300: 11.25
2300 <= x < 2400: 11.75
2400 <= x < 2500: 12.25
2500 <= x < 2600: 12.75
2600 <= x < 2700: 13.25
2700 <= x < 2800: 13.75
2800 <= x < 2900: 14.25
2900 <= x : 14.75
You can see it in action from the following screen shot, showing the edge cases:
Note that you have a problem with your description of numbers like 1400 since you don't specify which range they should fall in. For the formula given above, the ranges are inclusive at the low end and exclusive at the high end (such as 1300..1399.9999).
If the relationship isn't so linear (or, more accurately, formulaic), you will probably need to consider the use of lookup tables as per the excellent suggestion by pnuts.

Resources