Explanation
In this example, the goal is to list the next n upcoming birthdays from a larger set of 25 birthdays based on the current date. The set of birthdays are in an Excel Table named data in the range B5:C29. In the example shown, we are using 7 for n, so the result will be the next 7 upcoming birthdays, but this number can be changed as desired. The formula in E5 is:
=LET(
n,7,
tday,TODAY(),
calendar,TEXT(SEQUENCE(EDATE(tday,12)-tday,1,tday),"mmdd"),
birthdays,TEXT(data[Birthday],"mmdd"),
sorted,SORTBY(data,XMATCH(birthdays,calendar)),
INDEX(
sorted,
SEQUENCE(MIN(n,ROWS(data))),
SEQUENCE(1,COLUMNS(data))
)
)
The main challenge with a problem like this is to sort the list of birthdays into a rolling list of upcoming dates. This formula works in two parts: (1) sort all birthdays according to a rolling calendar and (2) return the first 7 birthdays from the sorted list.
Setup
To make this formula easier to read and more efficient, we are using the LET function to define variables that we need later in the calculation. We start off by defining n and tday:
=LET(
n,7,
tday,TODAY(),
Although n is used just once in the formula that follows, defining it here makes it easier to edit later. The tday variable is defined with the TODAY function, which returns the current date. The current date is used three times in the formula, so it makes sense to define the value as a variable.
Sorting the birthdays
For this formula to work, we need to sort the birthdays in an "upcoming order" based on the current date. There are two challenges that we need to overcome to sort the birthdays like this. First, the birthdates include a birth year, so if we try to sort these dates (which are just numbers underneath) as-is, the result will be birthdays sorted first by year, then by month and year, which won't work. The second challenge is that we need to sort the birthdays according to a rolling calendar year that always begins with the current date. The solution is to create a special sorting index based on day and month only, that begins on the current date.
Rolling calendar
To make a rolling calendar, we use the following snippet to define calendar:
calendar,TEXT(SEQUENCE(EDATE(tday,12)-tday,1,tday),"mmdd"),
Here we create a list of all dates in the next year with the SEQUENCE function:
SEQUENCE(EDATE(tday,12)-tday,1,tday) // next 365 days
Working from the inside out, we use the EDATE function to get a count:
EDATE(tday,12)-tday // count days
Starting with the current date, EDATE returns a date 12 months (1 year) in the future. From this date, the current date is subtracted. The result is a count of days between the current date and the same date next year. This is done to catch the 366 days that might occur in a leap year that includes February 29. In the example shown, the current year is 2021, and the following year is 2022, so the result is 365. This number is returned as the rows argument to SEQUENCE:
SEQUENCE(365,1,tday)
SEQUENCE then builds an array of 365 dates, beginning with tday (the current date). The resulting array is handed off to the TEXT function:
TEXT(SEQUENCE(EDATE(tday,12)-tday,1,tday),"mmdd")
Inside TEXT, value is the array returned by SEQUENCE, and format_text is provided as "mmdd". The result is an array of text values in the form "mmdd". So, for instance, the date November 2, 2021 becomes "1102". Notice the year value is simply discarded. Inside the LET function, the result from TEXT is assigned to the variable calendar.
Birthdays
At this point, we have a rolling calendar, calendar, but the birthdays in the Excel table still include a birth year. To sort the birthdays according to the calendar we created above, we need to process them with the TEXT function in the same way we created the rolling calendar above:
birthdays,TEXT(data[Birthday],"mmdd"),
The TEXT returns an array of text values in the same format as calendar, and this result is assigned to the variable birthdays.
Sorting the birthdays
At this point, we have what we need to sort the list of birthdays. The trick is we need to sort the birthdays by the order they will occur in the next year. To do this, we use the SORTBY function together with the XMATCH function:
sorted,SORTBY(data,XMATCH(birthdays,calendar)),
The entire table (data) is given to SORTBY as array. The by_array1 argument is generated with the XMATCH function like this:
XMATCH(birthdays,calendar) // get position of birthdays in calendar
XMATCH is used to get the position of each birthday inside the rolling calendar. XMATCH performs an exact match by default, so no other arguments are required. We want an exact match in this case because calendar is not sorted in ascending or descending order, but rather in the order the dates occur in the next year.
The result from XMATCH is an array of positions. Each number in this array is the position at which a birthday will occur in the rolling calendar. This result is provided to SORTBY as by_array1, which uses these positions to sort the birthday table in the same order as calendar. The result from SORTBY is assigned to the variable sorted.
Next n birthdays
At this point, we have a list of upcoming birthdays sorted in the order that they will occur over the next year, defined as sorted. The last step is to slice off just the first 7 entries. For this, we use the INDEX function:
INDEX(
sorted,
SEQUENCE(MIN(n,ROWS(data))),
SEQUENCE(1,COLUMNS(data))
For array, we give INDEX sorted, which is the entire list of sorted birthdays. For row_num and col_num, we use the SEQUENCE function to create the arrays we need. For row_num, we generate an array with 7 rows, or the count of all birthdays in the table, whichever is smaller:
SEQUENCE(MIN(n,ROWS(data))) // returns {1;2;3;4;5;6;7}
Notice the actual number given to SEQUENCE is either 7 or the count of all birthdays in data, whichever is smaller. This prevents errors from appearing in the final output when n is larger than the total birthdays available in the table. This little trick is handled by the MIN function, which is a nice alternative to the IF function in a situation like this:
MIN(n,ROWS(data)) // smaller of 7 or birthday count
For col_num, we create a two column array, since there are two columns in the source data:
SEQUENCE(1,COLUMNS(data)) // returns {1,2}
The final result from INDEX is the first 7 rows from the sorted birthdays. The birthdays in column F have the custom number format "mmm d" applied, to show only the month name and day.