sabato 8 ottobre 2022

[RISOLTO] Zoom non converte la registrazione facendo doppio click sul file .zoom

Oggi ho temuto di avere perso sei ore di webinar registrato in Zoom.

Lo metto anche in inglese in modo che qualcuno possa trovare il post. 

Zoom recorded files won't convert when double-clicked

Zoom non converte la registrazione 

In mattinata avevo fatto una breve prova di registrazione con destinazione il disco del computer e avevo cliccato sul file .zoom appena creato per convertirlo in mp4 e vederlo. Tutto bene.

Poi ho registrato durante il giorno, facendo tre pause e alla fine chiudendo la conferenza.

Di solito Zoom avrebbe iniziato la conversione ma non l'ha fatto, e questo mi è parso strano. Ho poi provato ad accedere alla cartella e fare doppio click sul file .zoom per avviare il convertitore, e sembrava non accadere nulla. 

Ho temuto che il file fosse corrotto o che Zoom non funszionasse. Provato a copiare la cartella su una chiavetta USB  e portare su un altro PC. Stesso comportamento.

Ho temuto che il file fosse corrotto e data per persa la registrazione.

Googlando ho trovatole indicazioni per andare a cercare il programma ztscoder.exe, che è poi quello che converte i file .zoom e che si trova in una in una cartella remota, ma nulla.

Più tardi con qualche ultimo barlume di speranza ho cercato su Youtube, trovato questo video: https://www.youtube.com/watch?v=1MdWRnr2UQg
e da un commento ho inteso che se c'è già un file mp4 nella cartella (quello della breve prova del mattino) altri file .zoom non vengono convertiti.

Un piccolo avviso da parte del convertitore mi sarebbe parso molto opportuno.

Comunque:

per convertire i file .zoom occorre che nella cartella non vi siano altri file mp4 altrimenti il file .zoom viene apparentemente ignorato.

Sappiatelo!

venerdì 18 marzo 2022

Unity Hub errore Javascript "Access is denied"

Nel mio Windows 10 Professional ho un account Amministratore (quello originario) e un account utente (con privilegi di amministratore).

Ho voluto provare a installare Unity, il software di editing 3D di Microsoft, che in genere richiede anche l'installazione di Unity Hub che è il "concentratore" per i progetti Unity e per le diverse versioni di Unity che possono essere installate sul computer.

Ho provato a installare Unity Hub nell'account utente e quando lo avviavo mi riportava una finestra di  errore. L'errore avveniva anche avviando Unity Hub come Amministratore e il messaggio era questo:

A JavaScript error occurred in the main process
Uncaught Exception:
Error: Access is denied.
\\?\C:\Users\Sergio\AppData\Local\Temp\f1101148-026b-......



Nel percorso indicato, "Sergio" è il nome dell'utente del computer.
Provando a lanciare Unity Hub dall'account amministratore, Unity Hub funzionava.

Unity può essere installato anche senza l'hub e ho provato. Lanciando direttamente Unity, ricevevo prima l'errore di Unity Hub, poi, ignorando l'errore, Unity si apriva, ma non era normale.

Cercando in internet ho solo inteso che potesse trattarsi di un problema di accesso alla cartella dei file temporanei 

La cartella C:\Users\Sergio\AppData è visibile solo abilitando la visualizzazione delle cartelle nascoste.
Visualizzando le Proprietà della cartella C:\Users\Sergio\AppData\Local\Temp\ appariva (dal tab "Sicurezza" che solo l'Amministratore poteva accedervi con i privilegi di "Controllo completo" e di "Modifica".

Ho provato dall'account "Sergio" a aggiungere l'utente Sergio e ad attribuirgli gli stessi privilegi ma quando ho cliccato su "Applica" ma ho ricevuto un messaggio di errore che mi lasciava intendere che l'operazione fosse stata solo in parte completata.
Allora ho acceduto dall'account Amministratore e rifatto l'operazione. L'applicazione della modifica è parsa essere andata a buon fine e Unity Hub ora non segnala più errore nell'utente Sergio.

Non sono pratico di cose come le impostazioni di sicurezza delle cartelle di sistema e ho il dubbio di avere reso il sistema meno sicuro con l'operazione che ho fatto, quindi riporto la cosa solo per dare una indicazione, dato che cercando in internet non sono riuscito a trovare soluzioni.

Forse bastava solo attribuire uno dei due privilegi, tra "Controllo completo" e "Modifica".

Se qualcuno ne sa di più, può lasciare un commento.

martedì 15 febbraio 2022

Google Sheets - Somma multiriga o multicolonna con ARRAYFORMULA()

Non l'ho capita ma me la annoto nel blog perché funziona.

Ho una tabella della quale voglio sommare per ogni riga, i valori delle colonne da B a - poniamo - H.
Inoltre li voglio sommare solo se la colonna A non è vuota.
E se aggiungo o rimuovo colonne tra B e H, la formula dovrebbe tenerne conto.

In riga 2 i titoli:

NOME | ORE1 | ORE2 | ORE3 | ... | OREn | TOT-ORE

Mettendo =SOMMA(B3:H3) in riga 3 e trascinandola su ogni riga sottostante è facile. Ma se volessi mettere la formula solo "nel titolo" e non pensarci più? Ci vorrebbe ARRAYFORMULA(), ma non funziona con SOMMA().

Con Google Sheets, la funzione SOMMA() richiede un intervallo o un elenco di celle o intervalli da sommare, ma poi riporta sempre un unico risultato.
Se si usa un intervallo (es. A2:E7), qualora nel foglio vengano aggiunte righe o colonne all'interno dell'intervallo, questo viene esteso automaticamente per comprendere anche quelle (e diventa per esempio A2:E7 o A2:D8),
Se nella formula si elencano solamente le celle o intervalli, Google non può sapere se quando aggiungiamo una colonna o riga nel foglio la vogliamo aggiungerne anche nella formula e non la aggiunge.

SOMMA() non funziona con ARRAYFORMULA() in quanto se scrivo SOMMA(A1:D7) questa espressione ha il significato di sommare tutte le celle dell'intervallo e dar un unico risultato mentre il mio intento era quello di avere un risultato per ogni riga. E se scrivo ARRAYFORMULA(SOMMA(A1:D7)) Google non può capire il mio intento e giustamente continua a darmi un unico risultato.

Cercando, ho trovato su stackoverflow (https://stackoverflow.com/questions/21797621/arrayformula-sum-in-google-spreadsheet) una formula che pare fare al caso mio.
Ho lievemente adattato la formula per verificare che in colonna A ci siano celle con contenuto e quindi mettere un risultato solo su righe valide e non in quelle vuote.
La formula, messa nella riga del titolo (riga 2),  mostrerà "Totale Ore" in riga 2 e sotto elenca le somme delle varie colonne a fianco.
Se aggiungo o tolgo colonne la formula si allarga o restringe da sé.

La formula ARRAYFORMULA() con SOMMA.SE()

={"Totale ore"; ARRAYFORMULA(SE(A3:A <> ""; (SOMMA.SE(SE(RIF.COLONNA(B:H); RIF.RIGA(A3:A)); RIF.RIGA(A3:A); B3:H));))}

In RIF.COLONNA() va indicato l'intervallo orizzontale ("B.H" in questo caso).
In RIF.RIGA() va indicato l'intervallo verticale. Indicando A3:A si va dalla riga 3 fino al fondo. La "A" non ha importanza, sarebbe lo stesso con B3:B o A3:Z perché RIF.RIGA() riporta solo il numero di riga.
Se si volesse limitare l'intervallo occorrerebbe indicare per esempio A3:A10.

Funziona anche se si vuole disporre in orizzontale una serie di somme dei valori presenti in colonne diverse, per esempio questa formula, messa in C9 somma i dati delle righe da 3 a 8 delle colonne da D in poi, se in riga 1 la cella non è vuota.
Notare il "\" che posto invece di ";" a separare il titolo da ARRAYFORMULA(), dispone le cose in orizzontale. 

={"Totale ore"\ ARRAYFORMULA(SE(D1:1 <> ""; (SOMMA.SE(SE(RIF.RIGA(3:8); RIF.COLONNA(D1:1)); RIF.COLONNA(D1:1); D3:8));))}

Credo di dover comprendere meglio il SOMMA.SE() per capire bene questa formula.

Nei due precedenti casi gli intervalli sono aperti ma la formula funziona anche se si limitano le colonne o le righe sulle quali operare.

venerdì 4 febbraio 2022

Webcam PTZ autotracking OSBOT Tiny 4k

Ho avuto modo di avere una OBSBOT Tiny 4k per la recensione, succede di rado di ottenere prodotti interessanti ma questo lo è.

Da un canto questa webcam potrebbe essere considerata un prodotto di nicchia perché avere una webcam che ti insegue il più delle volte non è necessario ma d'altra parte può essere considerata anche "solo" una webcam che rende una immagine molto buona e con una certa capacità di zoom (anche se solo digitale).

Va usata con un microfono separato, altrimenti la qualità del video viene rovinata da un audio povero.

Ho pubblicato un video su YouTube dove dimostro le cose scritte qua sotto:

https://www.youtube.com/watch?v=xURwOCXOSzI

La Tiny 4K viene dopo la Tiny Full HD. La si può definire una webcam, perché è fatta per essere connessa a un PC, via USB. Ma è anche una videocamera PTZ (Pan, Tilt, Zoom, anche se lo zoom è solo digitale) e si distingue sicuramente per il fatto di poter seguire un soggetto che si muove intorno ad essa fino a 150° a destra e 150° a sinistra e fino a 45° in alto o in basso.
La massima velocità di rotazione è di 120°/s
Funziona anche capovolta, l’immagine viene invertita automaticamente dalla webcam ma se si usa la app per controllare la direzione i comandi non sono altrettanto invertiti e diventa difficile controllare l’inquadratura.
Ha un campo visivo (diagonale) di 86° e uno zoom (solo digitale) che arriva a 4x, portando a circa 22° l’angolo di campo diagonale. Per questo ritengo che la Tiny 4K sia adatta a operare con soggetto non più vicino di 40cm e non più lontano di 3-4m.
La Tiny 4K ha la messa a fuoco automatica, che mette a fuoco fino a 10cm circa dalla lente. Se però si vuole mostrare da vicino un oggetto occorre disabilitare l’autotracking e tenere l’oggetto fermo per alcuni secondi di fronte alla webcam.
Arriva a una risoluzione 4K (3860 x 2160) con frequenza fino a 30 frame per secondo. Alle risoluzioni inferiori raggiunge i 60 frame per secondo.

Il sensore è Sony, da 1/2,8 pollici
Ha una modalità HDR (gamma dinamica allargata) per operare in condizioni di scene dove ci siano sia zone d’ombra sia zone molto luminose.
Tre tipi di inseguimento e inquadratura: il modo “normale”, un modo con inseguimento rapido per soggetti che si sostano velocemente e un modo che inquadra il soggetto lasciando maggiore spazio intorno, per non tagliare parti del soggetto.
Riconosce due gesti: uno che permette al soggetto di bloccare o riattivare l’inseguimento e l’altro che fa passare dalla massima larghezza di campo a un  livello di zoom preimpostato, tra 1x e 4x.
Ha in dotazione la custodia rigida che ospita la webcam, il supporto e i cavi.


Audio

Occorre dire qualcosa circa l’audio, che non è al livello del video. La Tiny ha un sistema di cancellazione del rumore che però lascia un suono che non mi pare molto buono. La Logitech Brio in questo caso dà risultati parecchio migliori. 


BOYA BY-WM3

In ogni caso per ottenere buoni risultati audio quando ci si muove intorno alla webcam occorrerà dotarsi di radiomicrofono. Sul sito OBSBOT viene indicato il microfono BOYA BY-WM3U, che è un microfono wireless digitale il cui ricevitore si può connettere sia a una porta USB C sia, in alternativa a un ingresso audio jack.

Per confronto, il modello precedente, la Tiny Full HD ha massima risoluzione Full HD (1920 x 1080) con frequenza di quadro di 30fps, alle risoluzioni inferiori arriva a 60fps. Il massimo zoom è di 2x, non ha la messa a fuoco automatica, la modalità HDR. H un campo visivo diagonale un po’ maggiore, di 90°. Non ha la custodia.


OBSBOT Tail

Sul sito OBSBOT viene presentata anche la OBSBOT Tail, che è una videocamera PTZ autonoma, alimentata da batteria ricaricabile, con due ore di autonomia, controllabile da cellulare via Wi-Fi e che registra su scheda SD. La Tail funziona sia come stabilizzatore (gimbal) sia come inseguitore del soggetto. È dotata anche di zoom ottico 3.5x (da 92° a 32°, equivalenti a una lunghezza focale di 21-74mm). Si arriva a 10x tra zoom ottico e digitale. La rotazione è a 360° senza arresto (può inseguire un soggetto che gira tutto intorno a sé), riconosce un paio di gesti in più rispetto alle Tiny, registra a risoluzione di 4K con 60fps e Full Hd fino a 240fps, per riprese slow motion. Consente scegliere in che zona della inquadratura tenere il soggetto, per una composizione della scena controllata. Può essere usata anche per streaming
Usabile attraverso un cellulare per streaming.

Una bella applicazione della OBSBOT Tail montata su un carrello a filo teso, che mostra cos può fare se c'è un solo soggetto da seguire:

https://youtu.be/yejdBcaNXh4?t=518


OBSBOT Me

Un altro dispositivo OBSBOT per chi intende usare un cellulare per la ripresa video, è l’OBSBOT Me, che è un supporto per cellulare dotato di videocamera che gli permette di inseguire il soggetto intorno al supporto. Regola solo l’angolo orizzontale, non muove il cellulare verso l’alto o il basso.

Controllo

Oltre al controllo con i gesti, la OBSBOT viene controllata attraverso la app, disponibile per PC e Mac. Come PC viene consigliato un I5 con 8GB di RAM, come Mac dei MacBook o iMac dal 2018 o successivi, con Intel I5. Una cosa che manca alla app è la possibilità di invertire il controllo del joistick virtuale quando si usa la Tiny capovolta e alto/basso o sinistra/destra si invertono.
Esiste un telecomando, che opera attraverso il PC, ma per ora non lo vedo su Amazon, che consente di muovere e zoomare, controllare più webcam Tiny e scegliere tra tre posizioni predefinite.

 

Alimentazione

Se connessa direttamente a una porta USB 3 funziona anche col solo cavo USB.
Nel caso venga collegata a un hub Dispone di una presa per ulteriore alimentazione e di un cavo da collegare a una porta o alimentatore USB aggiuntivo.


Applicazioni

È sicuramente pensata per circostanze quali la registrazione su computer o lo streaming da computer di lezioni alla lavagna o lezioni di Yoga, o altre situazioni in cui vi è un unico soggetto che si sposta e modifica la sua distanza dalla webcam, entro certi limiti.

A parte l’audio interno scarso e il fatto che non tutte le volte riconosce i comandi, mi sembra una eccellente webcam fissa per la qualità del video e una preziosa risorsa per chi necessita di una webcam autotracking.
Da quanto ho visto, la Tiny 4K a mio avviso vale decisamente i 60-70 euro in più rispetto alla Tiny Full HD Per l’autofocus e la qualità dell’immagine.

sabato 22 gennaio 2022

Google Sheets - Formula per trovare l'ultimo (o penultimo o primo, o secondo, etc) venerdì (o giovedì, o martedì etc.) di un dato mese

 Vorrei creare un "date picker" multiplo, che all'occorrenza possa anche accogliere delle indicazioni come "ogni ultimo mercoledì del mese", per inviare una email ad esempio.

Ho provato a cercare una formula per individuare l'ultimo giorno (lunedì, martedì, mercoledì, giovedì, venerdì, sabato o domenica) in un mese.

Non ho trovato nulla di interessante e ho prodotto qualcosa. La formula può valere per "l'ultimo venerdì del mese", "il secondo mercoledì del mese", il quarto giovedì ecc.

Per "primo", "secondo", "terzo" e "quarto" occorre indicare in una cella 1, 2, 3, 4; per "ultimo", "penultimo", "terzultimo", "quartultimo" occorre indicare -1, -2, -3, -4 

Il risultato si trova in un nuovo foglio "Weekdays" che ho aggiunto al calendario condiviso (che è in sola lettura ma può essere copiato):

https://docs.google.com/spreadsheets/d/1W1Z9JkUb58wDi8LRxS4XcrKcwWbTFWxmNxUq_cHx8BA/edit?usp=sharing

La formula ha bisogno di tre dati:
E2 un giorno di un certo mese (nel formato data di Google Sheet)

C5 il giorno della settimana desiderato (da 1 per domenica a 7 per sabato)

C6 un valore di 1, 2, 3 o 4 oppure di -1, -2, -3, -4 (a significare primo, secondo... penultimo, terzultimo...

Il valore riportato è la data richiesta.

La formula è:

=(FINE.MESE(E2; (-1 + (C6 < 0))) + 1) + RESTO((C5 - GIORNO.SETTIMANA((FINE.MESE(E2; (-1 + (C6 < 0))) + 1))); 7) + (C6 * 7)

I comandi in inglese sono:

=(EOMONTH(E2; (-1 + (C6 < 0))) + 1) +
  MOD((C5 - WEEKDAY((EOMONTH(E2; (-1 + (C6 < 0))) + 1))); 7) +
  (C6 * 7
 )

I seguenti esempi sono forse superflui ma li metto perché forse mi aiuteranno a ritrovare questo post quando mi servisse.

Con C5 = 1 e C6 = 1 si troverà la prima domenica del mese
Con C5 = 2 e C6 = 1 si troverà il primo lunedì del mese
Con C5 = 3 e C6 = 1 si troverà il primo martedì del mese
Con C5 = 4 e C6 = 1 si troverà il primo mercoledì del mese
Con C5 = 5 e C6 = 1 si troverà il primo giovedì del mese
Con C5 = 6 e C6 = 1 si troverà il primo venerdì del mese
Con C5 = 7 e C6 = 1 si troverà il primo sabato del mese
Con C5 = 1 e C6 = -1 si troverà l'ultima domenica del mese
Con C5 = 2 e C6 = -1 si troverà l'ultimo lunedì del mese
Con C5 = 3 e C6 = -1 si troverà l'ultimo martedì del mese
Con C5 = 4 e C6 = -1 si troverà l'ultimo mercoledì del mese
Con C5 = 5 e C6 = -1 si troverà l'ultimo giovedì del mese
Con C5 = 6 e C6 = -1 si troverà l'ultimo venerdì del mese
Con C5 = 7 e C6 = -1 si troverà l'ultimo sabato del mese

Altri esempi:
Con C5 = 3 e C6 = 4 si troverà il quarto martedì del mese
Con C5 = 5 e C6= -3 si troverà il terzultimo giovedì del mese

Per il funzionamento vedere il foglio.
Indicativamente:
Se la variabile che indica primo/ultimo etc. ha valore positivo (si cerca il primo, secondo terzo o quarto giorno nel mese) si parte dal primo giorno del mese, al quale si somma una correzione che cambia in base a che giorno è il primo del mese e quale giorno si sta cercando. Se il primo del mese è martedì e si sta cercando il giovedì, allora il giovedì sarà il 3 del mese. Determinato quello, si aggiunge un multiplo di 7 (0, 7, 14, 21) e si trova il primo o secondo etc. 
Se la variabile è negativa, si parte dal primo giorno del mese successivo e si trova il primo giorno del mese successivo, poi si sottrare un multiplo di 7 (7, 14, 21, 28).
Per trovare il primo giorno del mese si usa FINE.MESE() + 1. Quindi per il primo giorno del mese corrente occorre fare FINE.MESE(precedente)+1 e per il primo del mese successivo occorre fare FINE.MESE(corrente)+1.
Nella funzione FINE.MESE(), l'espressione (-1 + (C6 < 0))  genere -1 o 0. Vale -1 quando si cerca il primo, secondo... giorno del mese mentre vale 0 (mese corrente) quando si cerca l'ultimo, il penultimo... giorno del mese.



domenica 16 gennaio 2022

Google Sheets - Calendario mensile dinamico, in base a anno-mese o primo giorno del mese

Grazie a un video di Youtube ho trovato una formula intrigante che permette di formattare i giorni di un calendario mensile su 7 colonne in modo che la domenica sia sempre a sinistra.

Il video non spiega come funziona la formula e ho voluto cercare di capirlo, per impossessarmi dei "trucchi" usati. Il link del  video è questo. Immagino che la stessa formula sia stata presentata anche altrove.
https://www.youtube.com/watch?v=i3mPE7c-wt8

Ho utilizzato la formula per "dare vita" a un modello di calendario del 2019 che ho trovato tra i modelli di Google. Con questo link è possibile vedere il calendario e si può farne una copia da modificare: 
https://docs.google.com/spreadsheets/d/1W1Z9JkUb58wDi8LRxS4XcrKcwWbTFWxmNxUq_cHx8BA/edit?usp=sharing

La formula di Youtube si basa su un uso "creativo" della funzione QUERY() (ma si può usare anche JOIN()) per comporre una stringa con tutti i "vuoti" e i numeri utili a comporre la griglia del mese. La stringa ha un separatore per ogni elemento più un secondo separatore ogni sette elementi. La stringa, viene elaborata con la funzione SPLIT() per dare luogo alla matrice di 6 righe da 7 colonne.

Io l'ho riscritta utilizzando JOIN() invece di QUERY() perché mi pare abbia più senso.
In coda inserisco le due versioni, formattate in modo per me più leggibile della versione originale.
Al fondo c'è una versione a mio avviso migliorata.

Anticipazione: Dopo una prima stesura mi sono accorto che c'erano ancora dei casi che portavano a malfunzionamenti (il gennaio 2022 inizia da sabato e metteva in crisi la riga 5, o il febbraio 2015, che inizia da domenica e sta tutto nelle prime 4 righe). Ora mi pare tutto a posto e ho cercato di riportare tutto con precisione perché la minima svista può impedire il funzionamento.
Lo scopo è comunque quello di documentare le varie soluzioni che sono state messe insieme per ottenere il risultato.

La formula che presento è diversa da quella trovata (che riporto insieme a delle variazioni, in fondo).
Mentre studiavo il funzionamento della formula di Youtube ho voluto provare a fare delle variazioni e vedere se si poteva evitare la composizione e la divisione delle stringhe, generando direttamente le sei righe.
Sono riuscito, ma la formula ottenuta è più ingombrante.
Per certe parti ho sperimentato diverse soluzioni ed è stato interessante.
Direi che la soluzione con JOIN() e SPLIT() è più concisa e elegante, forse anche più flessibile se la si comprende bene.

La versione presentata è più lunga e si basa solo sulla composizione di array, non ci sono più JOIN() e SPLIT() e per ottenere il risultato ho dovuto aggirare diversi vincoli.

Il vincolo di non appoggiarsi a celle esterne fa sì che parti della formula siano ripetute molte volte quando in un normale linguaggio di programmazione si assegnerebbe il valore a una variabile e si utilizzerebbe quella.
Un altro vincolo è che generare la sequenza di giorni di un mese occorre usare una espressione tipo

ARRAYFORMULA( RIF.RIGA(INDIRETTO(inizioMese & ":" & fineMese)) )

(lo spiego dopo).
Parte della complessità è il vincolo di allineare i numeri dei giorni in modo da avere la domenica a sinistra, che implica che la prima riga può contenere da nessuna a 6 celle vuote (è difficile gestire il caso di "0" celle vuote).

Dopo tanto lavoro ho deciso di documentare il lavoro fatto, per mia futura memoria e per chi sarà curioso.

Formula monocella, indipendente

Una formula "monocella" e senza riferimenti per "stampare" un calendario mensile su una griglia di 6 righe per 7 colonne, facendo in modo di avere tutte le domeniche del mese sulla colonna di sinistra e gli altri giorni a seguire.

Nella formula, z1DataIni è la data del primo giorno del mese, supponendo di avere assegnato alla cella contenente la data, il nome z1DataIni (per farlo si seleziona la cella e nella finestrella dove compare la coordinata, si scrive il nome che si vuole assegnare).
Volendo utilizzare la coppia Anno-Mese, si può sostituire ogni occorrenza di z1DataIni con

 "DATA(Anno; Mese; 1)"

Se si volesse ottenere solo il mese corrente allora si potrebbe sostituire a z1DataIni l'espressione 

DATA(ANNO(OGGI()); MESE(OGGI()); 1)

come indicato più sotto.


La formula completa è la seguente. ho cercato di formattarla in modo che sia leggibile. Conviene forse incollarla in un differente editor di testo per evitare gli accapo.

=ARRAYFORMULA({ SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; MATR.TRASPOSTA(RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni + 6))); MATR.TRASPOSTA({ SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(z1DataIni) - 1; 1)) / 0; "-"); RIF.RIGA(INDIRETTO(z1DataIni & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) }) ); MATR.TRASPOSTA( {RIF.RIGA(INDIRETTO((z1DataIni + 7 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & (z1DataIni + 7 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 7 - 1))} ); MATR.TRASPOSTA( {RIF.RIGA(INDIRETTO((z1DataIni + 14 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & (z1DataIni + 14 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 7 - 1))} ); MATR.TRASPOSTA( {RIF.RIGA(INDIRETTO((z1DataIni + 21 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & (z1DataIni + 21 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 7 - 1))} ); ARRAY_CONSTRAIN( SE(((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1)))); MATR.TRASPOSTA(SPLIT(RIPETI("-,"; MAX(1; (7 - (FINE.MESE(z1DataIni; 0) - (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 1)))); ",")) }); MATR.TRASPOSTA({"-"; "-"; "-"; "-"; "-"; "-"; "-"}) ) ; 1; 7); SE(((z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1)))); MATR.TRASPOSTA(SPLIT(RIPETI("-,"; MAX(0; (7 - (FINE.MESE(z1DataIni; 0) - (z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 1)))); ",")) }); MATR.TRASPOSTA({"-"; "-"; "-"; "-"; "-"; "-"; "-"}) ) } 

)

Come funziona la formula in generale

Dal punto di vista generale, questa formula, messa in una cella, genera sei array orizzontali (di 7 elementi ciascuno) e li impila uno sopra l'altro, a formare la matrice di 7 colonne per 6 righe.
Dato che la colonna di sinistra corrisponde alla domenica, la prima riga può contenere da 7 giorni interi (se il mese inizia con una domenica) a un solo giorno (se il mese inizia con un sabato). 
Le righe 2, 3, 4 saranno comunque piene di numeri, poiché febbraio, che è il mese più corto, ha almeno 28 giorni.
La riga 5 potrà essere da totalmente vuota a totalmente piena.
La riga 6 potrà avere al massimo 2 numeri.
Le celle occupate dai giorni possono essere da 28 a 31 e l'ultima cella può essere la 28ma (se un febbraio inizia con una domenica) fino alla (31+6)ma, nel caso estremo in cui un mese di 31 giorni inizi con un sabato. Per questo occorre avere disponibili 6 righe.  
La formula è complessa perché si basa sul solo primo giorno del mese (indicato come z1DataIni) e non si appoggia a nessuna altra cella.

Come funzionano le righe 2, 3, 4, le più semplici

Le date, in Google Sheets sono indicate con "il numero di giorni dal 30 dicembre 1899". Per esempio, il 15 gennaio 2022 è rappresentato del numero 44562.

La formula deve mettere questi numeri nelle celle della griglia.
Ricordo che la formula che riempirà la griglia si trova in una singola cella, quella in alto a sinistra. Occorre pertanto che la formula crei, come risultato una matrice bidimensionale di 7x6 elementi.
I numeri dei giorni sono sequenziali e questo ci permette di usare un "trucco" per generare la sequenza che ci serve. Parlo di trucco perché usa delle funzioni solo per certi loro effetti collaterali, per di più combinati...

La funzione RIF.RIGA(), usata senza parametri riporta il numero della riga corrente ma se le si passa un intervallo, allora riporta il numero di riga. Per la precisione, se l'intervallo riguarda più righe, riporta la prima riga e basta. Sembra inutile ma...
La funzione ARRAYFORMULA() ha un effetto "esplosivo" su certe funzioni come RIF.RIGA(). Se si usa RIF.RIGA() all'interno di una funzione ARRAYFORMULA(), allora RIGA() diventa in grado di riportare "tutte le righe dell'intervallo dato", e le riporta sotto forma di un array, che sul foglio appare in forma di colonna di numeri successivi.
Se si volesse mostrare questa sequenza di numeri in orizzontale, anziché in verticale? Si può usare MATR.TRASPOSTA(), che "ruota di 90°... NO... che scambia righe e colonne... le ruota di 180° intorno alla bisettrice, direi. Comunque nel caso di una colonna, questa diventa una riga, e viceversa.
Ora potremmo avere una sequenza di numeri che viene mostrata in orizzontale:

La seguente formula rappresenta un array (statico). Gli elementi compaiono in verticale sul foglio: 

={"D"; "L"; "M"; "M"; "G"; "V"; "S"}

Se li si vuole mettere in orizzontale:

=MATR.TRASPOSTA({"D"; "L"; "M"; "M"; "G"; "V"; "S"})

Da quanto detto prima, se su vuole creare un array con dei numeri consecutivi (da 1 a 7, per esempio) , si può fare:

=ARRAYFORMULA(MATR.TRASPOSTA(RIF.RIGA(A1:A7)))

oppure

=MATR.TRASPOSTA(ARRAYFORMULA(RIF.RIGA(A1:A7)))

Invece di A1:A7 si può "risparmiare" usando 1:7, che rappresenta sempre un intervallo valido (tutte le colonne, dalla riga 1 alla riga 7):

=MATR.TRASPOSTA(ARRAYFORMULA(RIF.RIGA(1:7)))

Questo è ancora un intervallo "statico". A meno che non sostituiamo "1:7" con INDIRETTO(). Potremmo ottenere lo stesso intervallo con:

=MATR.TRASPOSTA(ARRAYFORMULA(RIF.RIGA(INDIRETTO("1:7"))))

La differenza è che questa volta l'intervallo è espresso da una stringa... Se abbiamo una cella chiamata "Inizio" e una cella chiamata "Fine", nulla vieta di indicare "1" in Inizio e "7" in Fine e poi scrivere:

=MATR.TRASPOSTA(ARRAYFORMULA(RIF.RIGA(INDIRETTO(Inizio & ":" & Fine))))

Se però scrivessimo

=MATR.TRASPOSTA(ARRAYFORMULA(RIF.RIGA(INDIRETTO(OGGI() & ":" & OGGI() + 6))))

otterremmo 7 "date" (quello che si vedrà saranno probabilmente 7 numeri di 5 cifre ma impostando il formato delle celle in un formato data si potrà ottenere le date complete, o il nome del giorno, o il solo anno, o il giorno del mese (che ci potrebbe servire per il calendario).

Occorre tenere conto dell'allineamento del giorno, che fa partire il primo giorno del mese da una delle sette caselle della prima riga, e la formula della riga (la seconda, in questo caso) diventa:

MATR.TRASPOSTA( {RIF.RIGA(INDIRETTO((z1DataIni + 7 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & (z1DataIni + 7 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 7 - 1))} );

Ho indicato "+ 7 -1" quando avrei potuto indicare "+ 6" ma è per chiarire l'intento della operazione.
la seconda riga parte da +7, la terza da +21, la quarta da +35, che significa che la seconda potrebbe iniziare dall'ottavo giorno del mese, o dal settimo, ... fino al secondo, in base all'allineamento.

Se per il calendario volessimo mostrare il mese corrente, potremmo partire da OGGI(), e dalla data odierna ricavare l'anno corrente (=ANNO(OGGI())) e il mese corrente (=MESE(OGGI())). A questo punto possiamo calcolare la data del primo giorno del mese corrente, con la funzione DATA(), con la forrmula:

=DATA(ANNO(OGGI()); MESE(OGGI()); 1)

Questa formula ci servirà varie volte, e se veramente vogliamo solo il calendario del mese corrente possiamo pensare di inserirla ovunque occorra avere il primo giorno del mese corrente.
Altrimenti possiamo mettere la formula in una cella "di servizio" e richiamare il contenuto di quella cella ogni volta che ci serve (diventa così la "variabile" z1DataIni che compare diverse volte nella formula completa.

Quanto abbiamo visto spiega abbastanza il modo in cui vengono generate le righe 2, 3, 4, che saranno sempre piene di numeri.

La riga 1

La riga 1 deve essere una riga di 7 elementi, dove il primo giorno del mese deve trovarsi allineato con la colonna del giorno corrispondente. Se il primo è una domenica, il primo giorno sarà nella prima colonna, se è un lunedì dovrà partire dalla seconda colonna e via di seguito, fino al sabato, che inizierà nell'ultima colonna.
Quello che serve è "riempire di vuoti" (da 0 a 6 vuoti) le prime celle, in base al giorno della settimana.
Un ausilio arriva dalla funzione GIORNO.SETTIMANA() che, in base a una data riporta un numero da 1 a 7 (1 per la domenica, 7 per il sabato, ma potrebbe cominciare anche dal lunedì). La formula

=(GIORNO.SETTIMANA(z1DataIni; 1) - 1)

ci darebbe esattamente il numero di caselle vuote da anteporre al primo giorno del mese. E il complemento di questo numero per arrivare a 7 ci darebbe quante caselle dobbiamo ancora riempire.
Il problema è che non sembra esserci modo di "gestire lo 0"... Ci arriviamo.

Al momento, come modo "dinamico" per riempire un array conosco solo l'uso di RIF.RIGA(), funzione che produce un numero. Ora ci occorrerebbe riempire un certo numero di celle con "il nulla"...
Quando occorre eseguire una funzione, e riportare un risultato anche se la funzione genera un errore si ricorre alla funzione SE.ERRORE() che ha la particolarità di valutare l'espressione data ma se questa per qualche motivo segnala un errore, allora può valutare una seconda espressione (che può anche essere un valore costante, e in genere lo è).
Immaginiamo di avere in A1 il valore, da 1 a 7 riportato da GIORNO.SETTIMANA().
Se A1 vale 3, vuol dire che il primo giorno del mese è martedì, e che nella prima riga del calendario ci occorrono due celle vuote all'inizio.
Un avvicinamento alla soluzione ci arriva dal considerare questa formula:

=ARRAYFORMULA(RIF.RIGA(INDIRETTO(1 & ":" & A1 - 1)))

che, almeno per i valori di A1 tra 2 e 7 ci permette di ottenere da 1 a 6 numeri. 
Un problema è che INDIRETTO() e RIF.RIGA() non accettano "numeri di riga inferiori a 1 e nemmeno intervalli di 0 celle. Ci dev'essere almeno una "cella", quindi quella formula ci darà almeno un risultato, oppure un errore (quando A1 vale 1, perché A1-1 diventa 0). 
Un secondo problema è che a noi serve riempire le celle col "nulla" non con un numero.
Si può rimediare applicando due soluzioni.
Soluzione 1: eseguire RIF.RIGA(INDIRETTO(...)) trasformando il numero in un'altra cosa:

=ARRAYFORMULA(SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & A1-1)) / 0: ""))

Con questa formula, ogni numero generato verrà diviso per 0, generando un errore, e in caso di errore, la funzione SE.ERRORE, invece di riportare l'errore, riporterà ciò che è stato indicato come secondo parametro, in questo caso una stringa vuota.

Abbiamo così ottenuto di poter generare da una a 6 celle vuote in base a quello che sarà il giorno della settimana. Resta il problema della domenica, che richiederebbe 0 celle vuote.
Una via è quella di non utilizzare la formula precedente se il primo giorno del mese è domenica.
Un modo sarebbe questo: se domenica prendo i primi 7 giorni, come si fa per le righe 2, 3 e 4, mentre se è un altro giorni prendo da uno a 6 "vuoti" e da 6 a 1 giorno del mese.
Ecco la parte relativa alla generazione della prima riga:

MATR.TRASPOSTA( SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni + 6)); { SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(z1DataIni) - 1; 1)) / 0; " "); RIF.RIGA(INDIRETTO(z1DataIni & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) } ) );


Se la si vuole provare separatamente occorre eliminare i ";" e includere il tutto in un ARRAYFORMULA(), così:

=ARRAYFORMULA(
MATR.TRASPOSTA( SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni + 6)); { SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(z1DataIni) - 1; 1)) / 0; " "); RIF.RIGA(INDIRETTO(z1DataIni & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) } ) )
)


Un secondo modo sarebbe questo: se il primo giorno del mese è domenica prendo il primo giorno del mese altrimenti prendo da uno a 6 vuoti; in seguito, se il primo giorno del mese è domenica (e ho già il primo giorno) parto dal secondo giorno, altrimenti parto dal primo giorno e riempio la riga.

MATR.TRASPOSTA({ SE(GIORNO.SETTIMANA(z1DataIni; 1) > 1; SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(z1DataIni) - 1; 1)) / 0; " "); RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni)) ); RIF.RIGA(INDIRETTO(SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; z1DataIni + 1; z1DataIni) & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) } );
La stessa sezione può essere scritta anche in questo modo. Se il primo giorno è domenica prende 7 giorni, se è uno degli altri giorni la formula compone alcuni "-" e i primi giorni del mese, per un totale di 7. In questo esempio, la funzione MATR.TRASPOSTA è applicata, ove occorre, "dentro" al SE() invece di essere esterna:

SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; MATR.TRASPOSTA(RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni + 6))); MATR.TRASPOSTA({ SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(z1DataIni) - 1; 1)) / 0; "-"); RIF.RIGA(INDIRETTO(z1DataIni & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) }) );

Un altro modo può essere questo, che usa FLATTEN() invece di {} e usa SPLIT() e RIPETI() per generare da 1 a 6 elementi vuoti "-" anziché il metodo della generazione di un errore. In questo caso viene gestita prima la condizione coln alcune celle vuote e poi quella con tutte le celle piene:

MATR.TRASPOSTA(FLATTEN( SE(GIORNO.SETTIMANA(z1DataIni; 1) > 1; MATR.TRASPOSTA(SPLIT(RIPETI("-,"; GIORNO.SETTIMANA(z1DataIni) - 1);",")); RIF.RIGA(INDIRETTO(z1DataIni & ":" & z1DataIni)) ); RIF.RIGA(INDIRETTO(SE(GIORNO.SETTIMANA(z1DataIni; 1) = 1; z1DataIni + 1; z1DataIni) & ":" & (z1DataIni + 6 - (GIORNO.SETTIMANA(z1DataIni; 1) - 1)))) ) );


E con questo abbiamo la prima riga, la seconda, la terza e la quarta.

La riga 5

La riga 5 è stata risolta in un modo "ridondante".
Quando si lavora con ARRAYFORMULA() è importante che tutti gli intervalli abbiano la stessa misura, altrimenti viene generato un errore. Ci servono tutte righe di 7 celle ciascuna, non basta riempire le celle necessarie e lasciare vuote le celle a destra, se non sono usate.
Una soluzione è però quella di creare un array più lungo del necessario e poi tagliarlo. Questo possiamo farlo con ARRAY_CONSTRAIN()), che limita l'estensione di un array a un massimo di righe e di colonne.
Oltre a questo abbiamo anche il caso speciale che quando un febbraio di 28 giorni inizia con una domenica, la riga 5 deve stare completamente vuota.
In un linguaggio di programmazione normale avremmo un ciclo con una variabile che rappresenta il giorno, e sarebbe facile incrementare il giorno a ogni ciclo e se diventa maggiore dell'ultimo giorno del mese indicare "" invece del numero. Qui non è possibile. 
Se usiamo RIF.RIGA(INDIRETTO() siamo vincolati a generare almeno un numero.
La riga 5 ha re casi da considerare:
- 1) se sono sufficienti le prime 28 celle, allora la riga 5 avrà 7 celle vuote;
- 2) se il calendario richiede più di 28 celle, ma meno di 35 allora ci sarà almeno un vuoto;
- 3) se il calendario richiede tutte le celle della riga 5, la riga avrà 7 celle piene e nessun vuoto.
Dato che non si può prendere "0 giorni" e non si possono creare "0 vuoti" occorrerebbe usare un SE() dentro a un SE e il test diventerebbe lunghissimo.
Per ora ho risolto distinguendo il caso "tutti vuoti" dal 1-7 pieni e in quest'ultimo caso, quando sono 7 pieni (che si verifica ad esempio col gennaio 2022) ho dovuto creare almeno un vuoto e utilizzare ARRAY_CONSTRAIN() per tagliare a 7 i risultati.
Nel primo caso, questa volta si generano tutti i giorni, dal 29esimo in poi e a questi elementi vengono accodate 7 stringhe nulle, quelle eccedenti verranno tagliate.

Nella espressione, estratta dalla formula generale, ARRAY_CONSTRAIN() "taglia" il risultato a 7 colonne. 
Il MAX(1; ...) nella formula doveva essere un MAX(0; ... ), per generare anche 0 vuoti ma ho dovuto evitare che accadesse


ARRAY_CONSTRAIN( SE(((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1)))); MATR.TRASPOSTA(SPLIT(RIPETI("-,"; MAX(1; (7 - (FINE.MESE(z1DataIni; 0) - (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 1)))); ",")) }); MATR.TRASPOSTA({"-"; "-"; "-"; "-"; "-"; "-"; "-"}) ) ; 1; 7);

Questa non funzionava col febbraio 2022 perché MAX(0; ...) riportava 0 e veniva generato un errore)

SE(((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1)))); MATR.TRASPOSTA(SPLIT(RIPETI("-,"; MAX(0; (7 - (FINE.MESE(z1DataIni; 0) - (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 1)))); ",")) }); MATR.TRASPOSTA({"-"; "-"; "-"; "-"; "-"; "-"; "-"}) );


Una versione diversa creava semplicemente un array più lungo del necessario accodando 6 elementi vuoti e lo troncava usando ARRAY_CONSTRAIN()


ARRAY_CONSTRAIN( SE(((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 28 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1))));
{""; ""; ""; ""; ""; ""} }); MATR.TRASPOSTA({""; ""; ""; ""; ""; ""; ""}) ); 1; 7);

La riga 6, ultima

Un po' come per la riga 5, ma in questo caso si verifica se il calendario richiede più di 35 celle. Questa compone un array di 7 elementi (Da tutti vuoti a due "pieni") usa SPLIT() e RIPETI() per generare da 1 a 6 "vuoti" e usa un array statico per il caso di 7 vuoti, ma anche lì potrebbe essere usato SPLIT/RIPETI.


SE(((z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) <= FINE.MESE(z1DataIni; 0)); MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) & ":" & MIN(FINE.MESE(z1DataIni; 0); (z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7) +7 - 1)))); MATR.TRASPOSTA(SPLIT(RIPETI("-,"; MAX(0; (7 - (FINE.MESE(z1DataIni; 0) - (z1DataIni + 35 - RESTO(GIORNO.SETTIMANA(z1DataIni) + 7 - 1; 7)) + 1)))); ",")) }); MATR.TRASPOSTA({"-"; "-"; "-"; "-"; "-"; "-"; "-"}) ) }

Questa era la versione con ARRAY_CONSTRAIN(), che accodava sempre 6 "vuoti" e tagliava l'eccesso (ricordo che per generare la matrice di 7 x 6, ARRAYFORMULA esige che tutte le righe siano di 7 elementi esatti):

ARRAY_CONSTRAIN( SE(((FINE.MESE(z1DataIni; 0) - z1DataIni +1) + (GIORNO.SETTIMANA(z1DataIni) - 1)) > 35; MATR.TRASPOSTA({RIF.RIGA(INDIRETTO((z1DataIni + 35 - (GIORNO.SETTIMANA(z1DataIni) - 1)) & ":" & FINE.MESE(z1DataIni; 0))); {""; ""; ""; ""; ""; ""; ""} }); MATR.TRASPOSTA({ {""; ""; ""; ""; ""; ""; ""} }) );1;7)


La formula "originale" trovata su Youtube e formattata in più righe

Purtroppo non riesco a commentarla adesso ma gli elementi dati prima possono servire a comprenderne una parte.
La funzione QUERY utilizzata qui è fuorviante, e può essere sostituita da JOIN, come nella versione che segue più sotto.

Quello che si ottiene dalla funzione QUERY() (o con JOIN()) è una stringa di questo tipo:

,,,,,44652,44653,|44654,44655,44656,44657,44658,44659,44660,|44661,44662,44663,

dove le virgole separano i singoli elementi e i caratteri "|" separano i gruppi di 7 elementi tra loro. 
Lo SPLIT() più interno separa i blocchi di 7 mentre quello più esterno separa gli elementi.
Ho capito che vengono generati elementi, fatti seguire da "," e, ogni 7 viene anche aggiunto il carattere "|"... Capisco che lo fa ma non ho ancora inteso bene secondo quale principio questo avviene. Sono abituato a ragionare secondo una logica sequenziale, invece quando si è dentro un ARRAYFORMULA() le cose mi pare accadano diversamente.

K3 è l'anno
K2 è il mese


=ArrayFormula( split( MATR.TRASPOSTA( split( QUERY( SE( GIORNO.SETTIMANA(DATA(K3; K2; 1); 1) = 1; RIF.RIGA(INDIRETTO(DATA(K3; K2; 1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))); { SE.ERRORE(RIF.RIGA(INDIRETTO("A1:A" & GIORNO.SETTIMANA(DATA($K$3; K2; 1)) - 1; 1)) / 0; " "); RIF.RIGA(INDIRETTO(DATA(K3;K2;1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))) } ) & "," & SE( RESTO(RIF.RIGA(INDIRETTO("A1:A" & (DATA.DIFF(DATA($K$3; K2; 1); FINE.MESE(DATA($K$3; K2; 1); 0); "D") + (GIORNO.SETTIMANA(DATA($K$3; K2; 1)))))); 7) = 0; "|"; ); ; 9^9 ); "|" ) ); "," ) )


La formula presentata su Youtube modificata con (JOIN() invece di QUERY():

K3 è l'anno
K2 è il mese


=ARRAYFORMULA( SPLIT( MATR.TRASPOSTA( SPLIT( JOIN( ""; SE(GIORNO.SETTIMANA(DATA(K3; K2; 1); 1) = 1; { RIF.RIGA(INDIRETTO(DATA(K3; K2; 1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))) }; { SE.ERRORE(RIF.RIGA(INDIRETTO(1 & ":" & GIORNO.SETTIMANA(DATA(K3; K2; 1)) - 1; 1)) / 0; " "); RIF.RIGA(INDIRETTO(DATA(K3; K2; 1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))) } ) & "," & SE( RESTO(RIF.RIGA(INDIRETTO(1 & ":" & DATA.DIFF(DATA(K3; K2; 1); FINE.MESE(DATA(K3; K2; 1); 0); "D") + (GIORNO.SETTIMANA(DATA(K3; K2; 1))))); 7) = 0; "|"; ) ); "|" ) ); "," ) )


Variazione nella generazione delle celle vuote

K3 è l'anno
K2 è il mese


Una variazione che usa MATR.TRASPOSTA(SPLIT(RIPETI())) per generare i "vuoti" iniziali, invece di SE.ERRORE(RIF.RIGA(...) /0; " ")

=ARRAYFORMULA( SPLIT( MATR.TRASPOSTA( SPLIT( JOIN( ""; SE(GIORNO.SETTIMANA(DATA(K3; K2; 1); 1) = 1; { RIF.RIGA(INDIRETTO(DATA(K3; K2; 1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))) }; { MATR.TRASPOSTA(SPLIT(RIPETI(" ,"; GIORNO.SETTIMANA(DATA(K3; K2; 1)) - 1); ",")); RIF.RIGA(INDIRETTO(DATA(K3; K2; 1) & ":" & FINE.MESE(DATA(K3; K2; 1); 0))) } ) & "," & SE( RESTO(RIF.RIGA(INDIRETTO(1 & ":" & DATA.DIFF(DATA(K3; K2; 1); FINE.MESE(DATA(K3; K2; 1); 0); "D") + (GIORNO.SETTIMANA(DATA(K3; K2; 1))))); 7) = 0; "|"; ) ); "|" ) ); "," ) )


Versione del 18 gennaio 2022. Ora mi piace

Ecco, a questa ci sono arrivato dopo essere passato per tutto il resto.
Meriterebbe una esplorazione.
La cosa centrale è che la parte SE.ERRORE(...) risolve il problema di generare zero o più "vuoti ("x", in questo caso). L'espressione parte dalla prima cella del calendario e, se non è un giorno di quel mese mette la x, altrimenti mette il giorno del mese. In particolare la parte (1 / RIF.RIGA...) >= C5) ha la funzione di valere 1 se è un giorno del mese oppure generare un errore se è ancora un giorno del mese precedente.
Questa si basa su una unica cella dove si trova il primo giorno del mese (ottenibile con  =DATA(anno, mese, 1)
Primo giorno del mese in C5


=ARRAYFORMULA( SPLIT( MATR.TRASPOSTA( SPLIT( JOIN(","; SE.ERRORE( ( RIF.RIGA(INDIRETTO(C5 - (GIORNO.SETTIMANA(C5) - 1) & ":" & FINE.MESE(C5; 0))) * (1 / (RIF.RIGA(INDIRETTO(C5 - (GIORNO.SETTIMANA(C5) - 1) & ":" & FINE.MESE(C5; 0))) >= C5)) ); "x" ) & SE(RESTO(RIF.RIGA(INDIRETTO(1 & ":" & (FINE.MESE(C5; 0) - C5 +1 + (GIORNO.SETTIMANA(C5) - 1)))); 7) = 0; "|"; ) ); "|" ) ); "," ) )


Questa si basa su anno e mese
Anno in C3
Mese in C4


=ARRAYFORMULA( SPLIT( MATR.TRASPOSTA( SPLIT( JOIN(","; SE.ERRORE( ( RIF.RIGA(INDIRETTO(DATA(C3; C4; 1) - (GIORNO.SETTIMANA(DATA(C3; C4; 1)) - 1) & ":" & FINE.MESE(DATA(C3; C4; 1); 0))) * (1 / (RIF.RIGA(INDIRETTO(DATA(C3; C4; 1) - (GIORNO.SETTIMANA(DATA(C3; C4; 1)) - 1) & ":" & FINE.MESE(DATA(C3; C4; 1); 0))) >= DATA(C3; C4; 1))) ); "x" ) & SE(RESTO(RIF.RIGA(INDIRETTO(1 & ":" & (FINE.MESE(DATA(C3; C4; 1); 0) - DATA(C3; C4; 1) + 1 + (GIORNO.SETTIMANA(DATA(C3; C4; 1)) - 1)))); 7) = 0; "|"; ) ); "|" ) ); "," ) )


Si trova nel file del calendario condiviso: