Google Sheets - Iterate through dates - google-sheets

I am working on redoing a Google Sheets spreadsheet my wife uses for work. Each sheet has a section for Monday, Tues, Wed, Thurs, and Sat (no programs on Friday and Sunday). Starting with early next year, the first sheet has Monday, January 2, Tuesday, January 3, and so on through Sunday, January 7.
What I am trying to do (instead of manually editing 52 different sheets) is come up with a function that iterates so that the second weeks' spreadsheet will show Monday, January 9, Tuesday, January 10, and so on. Each week would iterate the previous sheets corresponding day by 7.
Now, this is easy enough with a simple function, but where it gets complicated is when we get to the end of the month. I want to see if it is possible to iterate months as well. So, the week towards the end of the month would have Monday 29, Tuesday 30, Wednesday 31, but then Thursday Feb 1, and Saturday Feb 3.
Any ideas?

You can use this formula to create a list of dates in the format you want of a whole year (365 days):
=ArrayFormula(LAMBDA(FIRSTOFYEAR,MONTHS,WEEKS,
LAMBDA(DATES,
{DATES,BYROW(DATES,LAMBDA(DATE,
{
INDEX(WEEKS,WEEKDAY(DATE)),
INDEX(MONTHS,MONTH(DATE)),
DAY(DATE),
INDEX(WEEKS,WEEKDAY(DATE))&", "&INDEX(MONTHS,MONTH(DATE))&" "&DAY(DATE)
}
))}
)(BYROW(SEQUENCE(365,1,0),LAMBDA(NUM,FIRSTOFYEAR+NUM)))
)(
DATE(2023,1,1),
{"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"},
{"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"}
))
Result:
Which you can than use a QUERY() to easily remove what you don't need, such as:
(Assume the list of dates are placed in A:E)
=QUERY({$A:$E},"SELECT Col5 WHERE Col1 IS NOT NULL AND Col2!='Friday' AND Col2!='Sunday'",0)
result in something like this:
You can also use limit and offset to define the range to be shown in a QUERY(), such as:
(Assume the result of the last query is placed in F:F)
=QUERY(F1:F,"LIMIT 5 OFFSET "&((1-1)*5))
results:
With this formula, you only need to change the number x in ((x-1)*5) to the week number you want, and that will returns you the 5 days in the format you requested.
You can combine everything above to form something like this:
=ArrayFormula(
LAMBDA(SHOWWEEK,
LAMBDA(FIRSTOFYEAR,MONTHS,WEEKS,
LAMBDA(DATES,
LAMBDA(DATA,
LAMBDA(RESULTS,
RESULTS
)(QUERY({DATA},"SELECT Col5 WHERE Col1 IS NOT NULL AND Col2!='Friday' AND Col2!='Sunday' LIMIT 5 OFFSET "&((SHOWWEEK-1)*5),0))
)(
{DATES,BYROW(DATES,LAMBDA(DATE,
{
INDEX(WEEKS,WEEKDAY(DATE)),
INDEX(MONTHS,MONTH(DATE)),
DAY(DATE),
INDEX(WEEKS,WEEKDAY(DATE))&", "&INDEX(MONTHS,MONTH(DATE))&" "&DAY(DATE)
}
))}
)
)(BYROW(SEQUENCE(365,1,0),LAMBDA(NUM,FIRSTOFYEAR+NUM)))
)(
DATE(2023,1,1),
{"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"},
{"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"}
)
)(1)
)
in which all you need to do is changing the number inside the last () to the number of week you want, the 5 dates in the format you requested will be returned.
don't forget that you can always use INDEX() to define which value of an array do you want to return for your final result.
Since you have mentioned that What I am trying to do (instead of manually editing 52 different sheets) is come up with a function that iterates so that...,
We can go one step further, add a simple apps-script to get the sheet number count, we assume that your file only have 52 sheets, 1 for a week, and the sheets are arranged in ascending order (otherwise you will need another method), add this script into your spreadsheet:
function getSheetNum() {
const sss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (const [index,sheet] of sheets.entries()) {
if(sheet.getName() === sss.getActiveSheet().getName()) return index;
}
}
This function returns the index of sheet you are currently working on, start from 0, you can call it from your spreadsheet by simply type in =getSheetNum() in any cell.
p.s. if you have no idea what is apps-script, you may want to read some documentation: https://developers.google.com/apps-script/overview
Combining this custom function with our formula, like this:
=ArrayFormula(
LAMBDA(SHOWWEEK,
LAMBDA(FIRSTOFYEAR,MONTHS,WEEKS,
LAMBDA(DATES,
LAMBDA(DATA,
LAMBDA(DATA,
DATA
)(QUERY({DATA},"SELECT Col5 WHERE Col1 IS NOT NULL AND Col2!='Friday' AND Col2!='Sunday' LIMIT 5 OFFSET "&(SHOWWEEK*5),0))
)(
{DATES,BYROW(DATES,LAMBDA(DATE,
{
INDEX(WEEKS,WEEKDAY(DATE)),
INDEX(MONTHS,MONTH(DATE)),
DAY(DATE),
INDEX(WEEKS,WEEKDAY(DATE))&", "&INDEX(MONTHS,MONTH(DATE))&" "&DAY(DATE)
}
))}
)
)(BYROW(SEQUENCE(365,1,0),LAMBDA(ROW,FIRSTOFYEAR+ROW)))
)(
DATE(2023,1,1),
{"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"},
{"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"}
)
)(GETSHEETNUM())
)
be noticed that, instead of ((SHOWWEEK-1)*5), we do (SHOWWEEK*5) in this version, because the custom function we created using Zero-based array indexing.
results:
It will return a different week of dates in format you requested in each sheet.
Update 2022-12-30:
While I don't really understand why your week 1 start from 2nd of Jan and last for 7 days, is it 1st of Jan considered as Week 0 in that case??
Anyway, I come up with this formula which gives you a list of week count with date range for your reference.
According to the comments, if all you need is the week count of the year, you can simply use WEEKNUM(DATE):
=ArrayFormula(
LAMBDA(FIRSTOFYEAR,
LAMBDA(DATES,
LAMBDA(WEEKS,
LAMBDA(DATA,
BYROW(ARRAY_CONSTRAIN(DATA,MAX(WEEKS),4),LAMBDA(ROW,JOIN(" ",ROW)))
)(TO_TEXT(QUERY(QUERY({WEEKS,"Week "&WEEKS&":",DATES,DATES},"SELECT Col2,MIN(Col3),'~',MAX(Col4),Col1 GROUP BY Col1,Col2",0),"OFFSET 1 FORMAT Col2'm/d',Col4'm/d'",0)))
)(WEEKNUM(DATES))
)(BYROW(SEQUENCE(365,1,0),LAMBDA(NUM,FIRSTOFYEAR+NUM)))
)(DATE(2023,1,1))
)
The formula works in the same concept as my other formulas.

Related

Google sheets sumif with odd data

I have sales data that gives me dates in a bad format. Every new sale gets automatically added to the sheet. Looks like this:
Column A
Column B
Column C
Order 1
2022-12-02T02:09:37Z
$1025.19
Order 2
2022-12-02T01:25:15Z
$873.65
This will continue on for all sales. Now the date format is UTC for whatever reason and I can't adjust that, so within this formula I have to subtract 6 hours to get it to central time. I'm trying to create an auto-updating chart that shows an average day for 7 days, so I'm trying to do a sumif formula.
Here's what I have on Sheet2:
=sumif(Sheet1!C:C,index(split((index(split(Sheet1!B:B,"T"),1)+index(split(left(Sheet1!B:B,19),"T"),2))-0.25,"."),1),A1)
Where A1 is a single date. Testing this with one date and not the range shows that it does match. When I do the range, the total comes to 0, even though multiple different dates should match. What am I doing wrong?
Assume A1 has the value: 2022-12-02T02:09:37Z
Apply this formula:
=LAMBDA(RAW,TUNEHOUR,
LAMBDA(DATE,TIME,
TEXT((DATE&" "&TIME)+TUNEHOUR/24,"yyyy-mm-dd hh:mm:ss")
)(TEXT(INDEX(RAW,,1),"yyyy-mm-dd"),REGEXREPLACE(INDEX(RAW,,2),"Z",""))
)(SPLIT(A1,"T"),-6)
returns:
2022-12-01 20:09:37
And assume you have a set of data like this:
you can apply this formula:
=ArrayFormula(
LAMBDA(DATES,AMOUNTS,START,END,DFORMAT,TFORMAT,SKIPBLANK,TUNEHOUR,
LAMBDA(DATES,AMOUNTS,DTFORMAT,START,END,
LAMBDA(DATES,TIMES,
LAMBDA(VALIDDATES,AMOUNTS,
TEXT(SUM(FILTER(AMOUNTS,VALIDDATES>=START,VALIDDATES<=END)),"$#,##0.00")
)(TEXT((DATES&" "&TIMES)+TUNEHOUR/24,DTFORMAT),IF(ISNUMBER(AMOUNTS),AMOUNTS,VALUE(REGEXEXTRACT(AMOUNTS,"^\$(.+)"))))
)(TEXT(INDEX(DATES,,1),DFORMAT),REGEXREPLACE(INDEX(DATES,,2),"Z",""))
)(SPLIT(QUERY({DATES},SKIPBLANK),"T"),QUERY({AMOUNTS},SKIPBLANK),DFORMAT&" "&TFORMAT,TEXT(START,DFORMAT)&" 00:00:00",TEXT(END,DFORMAT)&" 23:59:59")
)($B$5:$B,$C$5:$C,$B$1,$B$2,"yyyy-mm-dd","hh:mm:ss","WHERE Col1 IS NOT NULL",-6)
)
Where you enter a start date and an end date at B1 & B2 to sum up the amount with.
The provided date column will be deducted by 6 hours.
What this formula does is...
format the date column into a valid date,
compare dates from step 1 with a given start and end date as filter condition,
filter the given amount column with conditions from step 2,
sum the result of filter from step 3 as an array,
format the output as price.
Use regexreplace() and query(), like this:
=arrayformula(
query(
{
weeknum(
regexreplace(B2:B, "([-\d]+)T(\d\d:\d\d).+", "$1 $2")
-
"6:00"
),
C2:C
},
"select Col1, avg(Col2)
where Col1 is not null
group by Col1
label Col1 'week #' ",
0
)
)
I think you're trying to split the values and sum them. I can't understand fully what's the purpose of 19 in LEFT function, and why are you again splitting it? Maybe some approach similar to yours is use LEFT function with 10 characters for the date, and MID from 12th character to get the time. Then substract .25 for the 6 hours as you did, and ROUNDDOWN with 0 digits to get the only the day
=ARRAYFORMULA(ROUNDDOWN(LEFT('Sheet1'!B:B,10)+MID('Sheet1'!B:B,12,8)-0.25,0))
And then you can insert it in your SUMIF:
=SUMIF(Sheet1!C:C,ARRAYFORMULA(ROUNDDOWN(LEFT(Sheet1!B:B,10)+MID(Sheet1!B:B,12,8)-0.25,0)),A1)

COUNTA(QUERY()) does not produce the desired result

The "mirror" sheet contains data.
Column B of the "bonus" sheet calculates the number of projects in a given month in which the specialist is involved.
=COUNTA(query(mirror!$A$2:$B, "select B where
A <= date'"&TEXT(EOMONTH($A6,0),"yyyy-mm-dd")&"'
and
A >= date'"&TEXT($A6,"yyyy-mm-dd")&"'
ORDER BY B"))
On the "mirror" there are no projects for Dec 2020 and Jan 2021.
The counts function substitutes 1 for the "bonus" in these months, although 0 is expected.
I've already broken my head, I don't know how to overcome it. I would be grateful for ideas.
Upd. Column A of the "bonus" sheet contains dates in the form of 8/1/2020, 9/1/2020, 10/1/2020, etc. beginning of the month.
In column A of the "mirror" sheet, the dates can be 10/5/2020, 10/31/2020, i.e. not necessarily the end of the month.
COUNTA counts an error as 1 (because it is "a non-null something"). So if your QUERY finds nothing, it will return an error — which will be counted as "one thing."
Try wrapping your QUERY in IFERROR, inside your COUNTA:
=COUNTA(IFERROR(QUERY(...)))
You can use SUMPRODUCT:
=SUMPRODUCT((MONTH(mirror!$A$2:$A)=MONTH(A2))*(YEAR(mirror!$A$2:$A)=YEAR(A2)))

Need help creating a formula for dynamic average of last 4 weeks expenses

I am looking to create a spreadsheet that my staff fill out, it then gives me a master sheet with all the data, then I import dynamically to my financial spreadsheet telling me the average cost of my client over the last 30 days.
I am looking to create an AVERAGE formula of the last 30 days when Date = Today (Monday) (I want the weekday Monday as that's when staff hand in invoices)
Hope this makes sense, it's really tough!
Here's a video of me explaining my desired outcome
https://www.loom.com/share/3a9cb75052b246d1af2ba2f9ce9180a7
I've followed several guides & can't figure it out.
=ArrayFormula(iferror(query(average(if(today() - weekday(today(),3)-30)))))
I expected $90 average and I just get blank
You could use this formula:
=AVERAGE(VLOOKUP(TODAY()-WEEKDAY(TODAY(),2)+1,A:H,2,FALSE),VLOOKUP(TODAY()-WEEKDAY(TODAY(),2)-6,A:H,2,FALSE),VLOOKUP(TODAY()-WEEKDAY(TODAY(),2)-13,A:H,2,FALSE),VLOOKUP(TODAY()-WEEKDAY(TODAY(),2)-20,A:H,2,FALSE))
To break it down in to its component parts, the AVERAGE is taken from VLOOKUP results:
VLOOKUP(TODAY()-WEEKDAY(TODAY(),2)+1,A:H,2,FALSE)
The VLOOKUP is looking for the last Monday from the current date:
TODAY()-WEEKDAY(TODAY(),2)+1
Then
TODAY()-WEEKDAY(TODAY(),2)-6
and so on...
When using on your sheet, you will have to specify the column you want to reference in your look up, for colunm B (brand1) use: A:H,2,FALSE), for colunm C (brand2) use: A:H,3,FALSE), for colunm d (brand3) use: A:H,4,FALSE) and so on...
=INDEX(QUERY({INDIRECT("A2:D"&ROW()-1)},
"select avg(Col2),avg(Col3),avg(Col4)
where Col1 <= date '"&TEXT(TODAY(), "yyyy-MM-dd")&"'
and Col1 >= date '"&TEXT(TODAY()-30, "yyyy-MM-dd")&"'"), 2, )

Sum values for the same date in google spreadsheet

I have a google spreadsheet that's tracking the duration of a particular activity over time. Imagine it looks something like this:
date | start time | end time | duration
5/21/18 10:00 AM 10:30AM 30
5/21/18 11:30 AM 12:30PM 60
5/21/18 2:00 PM 2:30PM 30
5/23/18 11:30 AM 12:30PM 60
5/24/18 9:30 AM 11:30AM 120
I want to make a chart with the dates along the X axis and the duration along the Y axis. However, I want each date to just be shown ONCE, with the TOTAL SUM duration for that date. So, the bar for 5/21/18 should be 120 min (i.e. the total of the three 5/21/18 entries), the bar for 5/22 should be 0 minutes (since there is no entry), and the bar for 5/23 should be 60 min.
If I make a chart as is, it treats each row as a separate entry (so there are three separate bars for each of the 5/21/18 entries), which doesn't work for my purposes since I want to combine entries with the same date, and have entries for in-between dates that have no entries.
Thank you!
You need to add any missing dates in the series.
In this case there is only one day but it could just as easily be two, three or a month's worth of dates.
Credit for missing dates in series: "--Hyde", Google Docs Help Forum Automatically Insert Missing Rows And Fill Values.
Assumption: the range shown in the question starts in cell A1 on a sheet named 'so_53684341'.
1) Create a new data series, including any missing values
a) Create a new sheet.
b) Cell A1, insert =arrayformula( { "Date"; datevalue("21/05/2018") + row(1:4) - 1 } ).
c) Cell B1, insert =arrayformula( {"Start time","End time","Duration"; if( len(A2:A), iferror( vlookup( A2:A, {datevalue(left(substitute(so_53684341!A2:A, " ", "-"), 10 ) ), so_53684341!B2:D }, column(so_53684341!B2:D2), false ), { "", "", 0 } ), iferror(1/0) ) } )
This creates data in columns B, C and D. It duplicates the existing data on the original range. and puts a zero value in the Duration column for any non-existing date(s).
You can format the date column as appropriate.
2) Create a query to be used as the basis for the chart
a) Cell F1, insert =query(A2:D5,"Select A, sum(D) group by A label (A) 'Date', sum(D) 'Duration' ")
This creates a range F1:G5
3) Create a chart using the range F1:G5
Something like this

How to autofill dates using arrayformula

I'm using Google sheets for data entry that auto-populates data from my website whenever someone submits to a form. The user's data imports into my sheet with a timestamp (column A).
Using the Arrayformula function, I'd like a column to autofill all the dates of a timestamp within that month. For example, if 1/5/2016 is entered as a timestamp, I'd like the formula to autofill in the dates 1/1/2016 - 1/31/2016.
Additionally, I'd like other months added in the Arrayformula column. For example, if both 1/5/2016 and 2/3/2016 are entered in column A, I'd like the formula to fill in the dates from 1/1/2016 - 2/29/2016.
I know I can manually write in the dates and drag them down the column, but I have a lot of sheets, and using an Arrayformula will save me a lot of time. I've tried a similar formula in column B, but it doesn't autofill in the date gaps. Is what I'm looking for possible?
Here's a copy of the editable spreadsheet I'm referring to: https://docs.google.com/a/flyingfx.com/spreadsheets/d/1Ka3cZfeXlIKfNzXwNCOWV15o74Bqp-4zaj_twC3v1KA/edit?usp=sharing
Short answer
Cell A1
1/1/2016
Cell A2
=ArrayFormula(ADD(A1,row(INDIRECT("A1:A"&30))))
Explanation
In Google Sheets dates are serialized numbers where integers are days and fractions are hours, minutes and so on. Once to have this in mind, the next is to find a useful construct.
INDIRECT(reference_string,use_A1_notation) is used to calculate a range of the desired size by given the height as a hardcoded constant, in this case 30. You should not worry about circular references in this construct.
ROW(reference) returns an array of consecutive numbers.
A1 is the starting date.
ADD(value1,value2). It's the same as using +. As the first argument is a scalar value and second argument is an array of values, it returns an array of the same size of the second argument.
ArrayFormula(array_formula) displays the values returned by array_formula
As A1 is a date, by default the returned values will be formatted as date too.
Increment by Month
If anyone wants to be able to increment by month, here's a way I've been able to accomplish that. Your solution #ptim got me on the right track, thanks.
Formula
Placed in B1
First_Month = 2020-11-01 [named range]
=ARRAYFORMULA(
IF(
ROW(A:A) = 1,
"Date",
IF(
LEN(A:A),
EDATE( First_Month, ROW( A:A ) -2 ),
""
)
)
)
Result
ID Month
1 2020-11-01
2 2020-12-01
3 2021-01-01
4 2021-02-01
5 2021-03-01
I have an alternative to the above, which allows you to edit only the first row, then add protection (as I like to do with the entire first row where I use this approach for other formulas):
=ARRAYFORMULA(
IF(
ROW(A1:A) = 1,
"Date",
IF(
ROW(A1:A) = 2,
DATE(2020, 1, 1),
DATE(2020, 1, 1) + (ROW(A1:A) - 2)
)
)
)
// pseudo code!
const START_DATE = 2020-01-01
if (currentRow == 1)
print "Date"
else if (currentRow == 2)
print START_DATE
else
print START_DATE + (currentRow - 2)
Notes:
the initial date is hard-coded (ensure that the two instances match!)
ROW(A1:1) returns the current row number, so the first if statement evaluates as "if this is Row 1, then render Date"
"if this is row 2, render the hard-coded date"
(nB: adding an integer to a date adds a day)
"else increment the date in A2 by the (adjusted) number of rows" (the minus two accounts for the two rows handled by the first two ifs (A1 and A2). Eg: in row 3, we want to add 1 to the date in row 2, so current:3 - 2 = 1.
Here's a live example (I added conditional formatting to even months to assist sanity checking that the last day of month is correct):
https://docs.google.com/spreadsheets/d/1seS00_w6kTazSNtrxTrGzuqzDpeG1VtFCKpiT_5C8QI/view#gid=0
Also - I find the following VScode extension handy for syntax highlighting Google Sheets formulas: https://github.com/leonidasIIV/vsc_sheets_formula_extension
The Row1 header trick is courtesy of Randy via https://www.tillerhq.com/what-are-your-favorite-google-spreadsheet-party-tricks/
nice. thanks.
To get the list length to adapt to the number of days in the selected month simply replace the static 30 by eomonth(A1;0)-A1. This accommodates for months with 31 days, and for February which can have either 28 or 29 days.
=ArrayFormula(ADD(A1,row(INDIRECT("A1:A"&eomonth(A1;0)-A1))))
Updated for 2022:
This can now be done pretty easily with the SEQUENCE function, it's also a bit more adaptable.
Below will list all of the days in columns but you can swap the first 2 values to place in rows instead:
=SEQUENCE(1,7,today()-7,1)
More specific to your example, below will take the date entered (via cell, formula, or named cell) and give you the full month in columns:
=SEQUENCE(1,day(EOMONTH("2016-1-5",0)),EOMONTH("2016-1-5",-1)+1,1)

Resources