Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

Visualizzazione post con etichetta strftime. Mostra tutti i post
Visualizzazione post con etichetta strftime. Mostra tutti i post

mercoledì 27 aprile 2022

Riders of Strptime
come si processano data e ora con la strptime in C - pt.2

Membro del consiglio: Lascia che ti fermi qui, Otto.
Otto: Si.
Membro del consiglio: Quanto tempo lei e il suo team avete passato su questo algoritmo?
Otto: Oh ragazzi, è difficile da dire esattamente... Quarantasei settimane. Ma l'abbiamo fatto principalmente di notte.
Membro del consiglio: Quindi abbiamo speso un anno e una fortuna per un algoritmo che può capire che i poveri guidano Kia e i ricchi Mercedes?

Ebbene si, sono un perfezionista. È un difetto? È un pregio? Boh, non lo so. Anche Otto e Lars, i due coprotagonisti del bellissimo Riders of Justice sono dei perfezionisti, tanto da dedicare ben quarantasei settimane, alla scrittura di un algoritmo in grado di scoprire che i poveri guidano Kia e i ricchi guidano Mercedes (e li hanno licenziati: non c'è giustizia in questo mondo). Io, ad esempio, ho sempre tentato scrivere codice buono, a costo di sforare le tempistiche di realizzazione, piuttosto che scrivere in fretta codice che "basta che stia in piedi". E se c'è qualche altro perfezionista tra i lettori (è un difetto frequente nei programmatori) mi capirà. Bravi Otto e Lars, io non vi avrei mai licenziato.

...siamo qui per difendere i programmatori-perfezionisti. Qualcosa in contrario?... 

E cosa centra il discorso iniziale con questo articolo? Centra, centra... il problema è che pochi mesi fa ho scritto un post intitolato Strptime: No Way Home in cui proponevo una (spero) interessante versione "custom" della strptime(3), che è una ottima funzione della libc, ma ha qualche criticità d'uso e di funzionamento. In particolare mi ero soffermato su un problema reale che ho affrontato in un progetto, un problema sulla gestione di data/ora (da qui in avanti datetime) in formato ISO 8601 "ma con i millisecondi", e proprio i millisecondi non piacciono molto alla strptime(3). Ora non mi sembra il caso di ripetere tutta la presentazione e gli esempi del vecchio articolo (basta rileggerlo, no?), credo che basti dire che è tutt'ora valido nei primi tre quarti (descrizione del tema, esempi vari e codice esplicativo) ma nell'ultimo quarto... c'e una funzione che avevo scritto ad-hoc, la myStrptime(), che già` al tempo della pubblicazione non mi  convinceva più di tanto (non mi dava una buona impressione neanche "esteticamente", sapete com'è: sesto senso del programmatore).

(...e, tra l'altro, la myStrptime() contiene anche un piccolo bug, non grave direi, ma pur sempre un bug. Chissà se qualcuno se n'è accorto...)

Che fare allora? Correggere o modificare l'articolo originale per migliorarlo facendo finta di niente? Oppure pubblicare un remake? (un remake dopo tre mesi? Nel cinema di solito si aspetta un po' di più...). Ok, alla fine ho optato per fare una "seconda parte" (questa che state leggendo) in cui vi invito a rileggere l'articolo "prima parte", ricordandovi, però, di sorvolare sulla funzione finale myStrptime() prendendo per buona quella nuova che vi mostrerò tra poco.

Ok, si può fare!

Allora, riepiloghiamo: la strptime(3) funziona bene ed è ben adattabile a moltissimi tipi di datetime. Noi vogliamo farne una versione specializzata, che tratta qualche tipo in meno ma accetta i millisecondi, e già che ci siamo, approfittiamo per renderla un po' più user-friendly (cambiando la sintassi d'uso: si perde in genericità ma si guadagna in facilità di sviluppo). E per quanto riguarda i tipi accettati bisogna coprire più casi  rispetto alla myStrptime() originale: la famiglia di datetime accettata, sarà la seguente:

DATETIME UTC
------------
2022-04-23T09:30:01Z
2022-04-23T09:30:01.278Z
2022-04-23T11:30:01+0200
2022-04-23T11:30:01+02.00
2022-04-23T11:30:01.278+0200
2022-04-23T11:30:01.278+02.00
20220423T09:30:01Z
20220423T09:30:01.278Z
20220423T11:30:01+0200
20220423T11:30:01+02.00
20220423T11:30:01.278+0200
20220423T11:30:01.278+02.00
2022-04-23T093001Z
2022-04-23T093001.278Z
2022-04-23T113001+0200
2022-04-23T113001+02.00
2022-04-23T113001.278+0200
2022-04-23T113001.278+02.00
20220423T093001Z
20220423T093001.278Z
20220423T113001+0200
20220423T113001+02.00
20220423T113001.278+0200
20220423T113001.278+02.00
DATETIME NON UTC
----------------
2022-04-23T11:30:01
2022-04-23T11:30:01.278
20220423T11:30:01
20220423T11:30:01.278
2022-04-23T113001
2022-04-23T113001.278
20220423T113001
20220423T113001.278

Se qualcuno ha familiarità con il formato ISO 8601 noterà che l'obiettivo (ambizioso, direi) è coprire tutti i formati previsti del datetime UTC, il che non è poco! (nella versione del vecchio articolo si coprivano solo i primi 6 casi). E tratteremo anche i millisecondi! E, ciliegina sulla torta, copriremo anche alcuni formati non UTC. Sono ben 32 formati diversi, e scusate se è poco!

Allora, bando alle ciance: la nuova funzione l'ho ribattezzata isoStrptime() ed è questa (con relativo main() di esempio):

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// prototipi locali
int isoStrptime(const char *s, struct tm *tm);

// main() - funzione main
int main(void)
{
char buf[256];
char datetime[32];
struct tm tm;

strcpy(datetime, "2022-04-23T09:30:01Z");
if (isoStrptime(datetime, &tm) != -1) {
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);
}
else
printf("ERROR!\n");

strcpy(datetime, "20220423T113001.278+0200");
if (isoStrptime(datetime, &tm) != -1) {
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);
}
else
printf("ERROR!\n");

exit(EXIT_SUCCESS);
}

// isoStrptime() - funzione wrapper per strptime(3)
int isoStrptime(
const char *s, // datetime sorgente
struct tm *tm) // struct tm destinazione
{
const char *format; // formato del datetime sorgente
size_t len; // lunghezza della parte data+ora+secondi

// analizzo la stringa sorgente
if (strchr(s, '-') && strchr(s, ':')) {
// appartiene al type/subtype: 2022-04-23T09:30:01Z
format = "%Y-%m-%dT%H:%M:%S%z";
len = 19;
}
else if (strchr(s, '-') == NULL && strchr(s, ':')) {
// appartiene al type/subtype: 20220423T09:30:01Z
format = "%Y%m%dT%H:%M:%S%z";
len = 17;
}
else if (strchr(s, '-') && strchr(s, ':') == NULL) {
// appartiene al type/subtype: 2022-04-23T093001Z
format = "%Y-%m-%dT%H%M%S%z";
len = 17;
}
else if (strchr(s, '-') == NULL && strchr(s, ':') == NULL) {
// appartiene al type/subtype: 20220423T093001Z
format = "%Y%m%dT%H%M%S%z";
len = 15;
}

// controllo se la lunghezza della stringa sorgente è Ok
char part_one[32];
if (strlen(s) >= len && strlen(s) <= 29) {
// reset del tm prima di usarlo (questo è importante)
memset(tm, 0, sizeof(struct tm));

// estraggo la parte data+ora+secondi
memcpy(part_one, s, len);
part_one[len] = 0;

// eventualmente estraggo la seconda parte (il timezone)
char mys[32];
char part_two[32];
if (strlen(s) > strlen(part_one) && (strlen(s) - strlen(part_one)) != 4) {
// case con timezone
snprintf(part_two, sizeof(part_two), "%s", &s[strlen(part_one)]);

// ricompongo le due parti e applico strptime(3)
char *mytimezone;
if ((mytimezone = strchr(part_two, 'Z')) != NULL) {
// caso speciale con Z finale
snprintf(mys, sizeof(mys), "%s%s", part_one, mytimezone);
}
else if ( (mytimezone = strchr(part_two, '+')) != NULL ||
(mytimezone = strchr(part_two, '-')) != NULL ) {

// caso con timezone esplicito
snprintf(mys, sizeof(mys), "%s%s", part_one, mytimezone);
}

// uso strptime(3) per processare la nuova stringa
if (strptime(mys, format, tm) != NULL)
return 0; // strptime(3) Ok
}
else {
// caso senza timezone (tolgo il %z dal format)
char format_noz[32];
snprintf(format_noz, sizeof(format_noz), "%s", format);
format_noz[strlen(format) - 2] = 0;

// uso strptime(3) per processare la nuova stringa
if (strptime(part_one, format_noz, tm) != NULL)
return 0; // strptime(3) Ok
}
}

// lunghezza non Ok oppure errore della strptime(3): ritorno errore
return -1;
}

Che ve ne pare? Questa mi dà già una buona impressione estetica (e di solito mi fido di questa impressione), ed è, evidentemente, molto più facile da usare: solo 2 parametri invece di 4! E non c'è bisogno di passare il format (che viene auto-costruito internamente), basta passare solo la stringa del datetime e la struct tm destinazione. Mi piace, e non contiene neanche il (piccolo) bug della myStrptime() citato sopra. Nel main() ho messo solo 2 esempi di uso (per renderlo meno pesante da leggere), ma sulla falsariga dei 2 esempi potete aggiungere gli altri 30, e magari anche qualche caso di data erronea (io l'ho fatto e funzionano tutti e 32, e anche gli errori sono ben rilevati).

Ebbene si, ora mi sento meglio, il peso da "funzione venuta male" si è affievolito, finalmente. E quindi per oggi può bastare, così potrò dedicare le prossime 46 settimane a creare qualche geniale algoritmo...

Ciao, e al prossimo post! 

domenica 23 gennaio 2022

Strptime: No Way Home
come si processano data e ora con la strptime in C (e C++)

Stephen Strange: Peter, a cosa devo il piacere?
Peter Parker: Mi dispiace disturbarla, signore.
Stephen Strange: Abbiamo salvato mezzo universo insieme, puoi evitare di chiamarmi signore.
Peter Parker: Stephen...
Stephen StrangeSuona strano ma te lo concedo.

Per la serie "non fare promesse se non puoi mantenerle", rinvio al prossimo articolo (ma davvero?) la conclusione dell'argomento in corso, quello delle nuove keyword del C. Il fatto è che a volte mi trovo con dei problemi reali da risolvere, problemi che sono sempre un buon spunto per la scrittura. Ad esempio, ultimamente mi sono scontrato con uno dei rompicapo "classici" della programmazione, la gestione di data/ora (da qui in avanti datetime) di una applicazione vera, con tutti gli annessi e connessi, come il formato e il timezone (uh, quest'ultimo ha provocato nel corso degli anni dei mostruosi mal di testa a innumerevoli colleghi, incluso il sottoscritto... maledetto timezone!).

Ebbene si, mi sono sentito un po' come il Peter Parker dell'ottimo Spider-Man: No Way Home, alle prese con i suoi soliti problemi esistenziali, di identità, di grandi responsabilità... perché una applicazione deve funzionare sempre bene (sono un perfezionista) e, quindi, deve girare bene a casa tua, nel tuo ufficio e, soprattutto, anche nel resto del mondo, e questo è vero solo quando i datetime sono stati gestiti come si deve. Per questo ci sto sempre attento, e non aspetto che si presenti l'errore per risolverlo: "prevenire è meglio che curare" (e "non ci sono più le mezze stagioni", e "si stava meglio quando si stava peggio", e..., non facciamoci mai mancare i luoghi comuni).

...preparati a non sapere più neanche che giorno è oggi...

E veniamo al dunque: capita molto frequentemente di dover processare un datetime che ti è arrivato, in formato "human readable",  attraverso i più svariati canali (dall'interfaccia utente, dalla rete, ecc.). Fortunatamente i comitati ISO si sono occupati anche dei formati delle date (se ne occupano, normalmente, tra una nuova versione del C++ e la successiva, ah ah ah), e quindi possiamo usare, ad esempio, lo standard ISO 8601, per essere sicuri di riuscire a interpretare (quasi) qualsiasi formato (ma non vi preoccupate: ci sarà sempre qualcuno che non ama gli standard e vi proporrà qualche formato "custom" super-complicato, tanto per complicare anche la vostra vita). Ovviamente, quando ci arriva un datetime dobbiamo trasformarlo da "human readable" a "machine readable", per poterlo poi trattare nel codice.

Un metodo abbastanza classico e di uso quasi universale (per fortuna) è trasformarlo in una struttura dati di tipo struct tm che contiene tutti i campi che ci necessitano per processare il datetime in una applicazione (e/o usare direttamente il tempo di riferimento dei sistemi POSIX, il numero di secondi passati da Epoch, ma questa è un altra storia).

Un modo tipico di processare il datetime è quello di usare la funzione standard della libc strptime(3) (nonché la sua versione speculare, la strftime(3)). E, a questo punto, direi di vedere direttamente un esempio per chiarire meglio il meccanismo: vai col codice!

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// main() - funzione main
int main(void)
{
// decodifico il datetime con strptime(3)
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
strptime("2022-01-21 14:29:44", "%Y-%m-%d %H:%M:%S", &tm);
printf("il datetime sorgente è: 2022-01-21 14:29:44\n");

// mostro i campi della struct tm
printf("\nla struttura tm corrispondente è:\n");
printf("tm_sec = %d\n", tm.tm_sec); /* Seconds (0-60) */
printf("tm_min = %d\n", tm.tm_min); /* Minutes (0-59) */
printf("tm_hour = %d\n", tm.tm_hour); /* Hours (0-23) */
printf("tm_mday = %d\n", tm.tm_mday); /* Day of the month (1-31) */
printf("tm_mon = %d\n", tm.tm_mon); /* Month (0-11) */
printf("tm_year = %d\n", tm.tm_year); /* Year - 1900 */
printf("tm_wday = %d\n", tm.tm_wday); /* Day of the week (0-6, Sunday = 0) */
printf("tm_yday = %d\n", tm.tm_yday); /* Day in the year (0-365, 1 Jan = 0) */
printf("tm_isdst = %d\n", tm.tm_isdst); /* Daylight saving time */

// ricostruisco il datetime con strftime(3)
char buf[256];
strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
printf("\nil datetime ricostruito è: %s\n", buf);

exit(EXIT_SUCCESS);
}

E questo codice, una volta compilato ed eseguito, da il seguente risultato:

il datetime sorgente è: 2022-01-21 14:29:44

la struttura tm corrispondente è:
tm_sec = 44
tm_min = 29
tm_hour = 14
tm_mday = 21
tm_mon = 0
tm_year = 122
tm_wday = 5
tm_yday = 20
tm_isdst = 0

il datetime ricostruito è: 21 Jan 2022 14:29

Tutto questo è molto semplice: si processa la data "human readable" in formato ISO 8601 con strptime(3), si ottiene una struct tm corrispondente e, passando questa struttura a strftime(3), si può riformattare il datetime (anche con un formato diverso, se necessario). Inutile ricordare che strptime(3) appartiene a una famiglia di funzioni che fanno capo a time(2), con cui si possono fare un sacco di cose interessanti (ma, magari, ne parleremo un altra volta).

E adesso veniamo al problema reale che mi ha ispirato questo articolo: la strptime(3) è una notevole funzione, ma alcuni formati non li digerisce benissimo, e lo standard ISO 8601 (ahimè) ne prevede un sacco. Il caso con cui mi sono scontrato è un datetime come il seguente che mi arrivava contenuto in un messaggio da processare:

2022-01-21T14:29:44.278Z

questo datetime è corretto da un punto di vista formale, anche se è poco usuale visto che contiene i millisecondi e, a causa di questo, il trattamento con strptime(3) a volte funziona e a volte no. Questo non è, ovviamente, un comportamento accettabile, e una possibile soluzione (sempre che proprio non vi servano anche i millisecondi) potrebbe essere quella di non usare strptime(3) e di usare, invece, scanf(3). Vediamo come:

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// prototipi locali
void myStrptimeScanf(const char *s, const char *format, struct tm *tm);

// main() - funzione main
int main(void)
{
const char *datetime;
struct tm tm;
char buf[256];

// decodifico e ricostruisco il datetime con strptime(3) e strftime(3)
//

datetime = "2022-01-21T14:29:44.278Z";
myStrptimeScanf(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

exit(EXIT_SUCCESS);
}

// myStrptimeScanf() - funzione wrapper per strptime(3)
void myStrptimeScanf(
const char *s, // datetime sorgente
const char *format, // formato del datetime sorgente
struct tm *tm) // struct tm destinazione
{
memset(tm, 0, sizeof(struct tm));
int year, mon, mday, hour, min;
float sec;
sscanf(s, "%d-%d-%dT%d:%d:%fZ", &year, &mon, &mday, &hour, &min, &sec);
tm->tm_year = year - 1900; // anno da 1900
tm->tm_mon = mon - 1; // 0-11
tm->tm_mday = mday; // 1-31
tm->tm_hour = hour; // 0-23
tm->tm_min = min; // 0-59
tm->tm_sec = (int)sec; // 0-60
}

E questo codice, una volta compilato ed eseguito, da il seguente risultato:

datetime: 2022-01-21T14:29:44.278Z - datetime ricostruito: 21 Jan 2022 14:29:44+0000

Questo metodo funziona ma non è molto flessibile, visto che il tipo di data accettato è solo quello dell'esempio, quindi dovremmo riscrivere la funzione per ogni tipo di datetime che ci può arrivare (e lo standard, come detto sopra, ne prevede molti). Tra l'altro il datetime proposto pur essendo abbastanza tipico (a parte i millisecondi) potrebbe a sua volta avere della varianti come queste:

2022-01-21T14:29:44Z
2022-01-21T14:29:44.278Z
2022-01-21T14:29:44+0100
2022-01-21T14:29:44+01.00
2022-01-21T14:29:44.278+0100
2022-01-21T14:29:44.278+01.00

dove ha un ruolo importante anche il maledetto timezone, che in questo caso è UTC (e ricordate: usare UTC è sempre una buona idea, può risparmiarvi un sacco di problemi).

Che fare, allora? La soluzione migliore è, direi, continuare a usare la strptime(3), che è molto flessibile (basta giocare con l'argomento <format>) e poi gestisce bene gli errori (cosa che non fa il codice con la scanf(3) mostrato qui sopra): "ma chi ce lo fa fare di riscrivere tutta la gestione degli errori per formati e/o valori sbagliati se lo fa già benissimo la strptime(3)? ". Il trucco da usare è molto semplice: si può aggiustare  il datetime prima di passarlo alla strptime(3). E quindi, ripeto, se il nostro problema è solo quello dei millisecondi (che, dopo una rapida ricerca in rete, ho scoperto che è un problema non infrequente) possiamo operare nella seguente maniera:

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// prototipi locali
char *myStrptime(const char *s, const char *format, struct tm *tm, size_t len);

// main() - funzione main
int main(void)
{
const char *datetime;
struct tm tm;
char buf[256];

// decodifico e ricostruisco il datetime con strptime(3) e strftime(3)
//

datetime = "2022-01-21T14:29:44.278Z";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

datetime = "2022-01-21T14:29:44+0100";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

datetime = "2022-01-21T14:29:44+01.00";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

datetime = "2022-01-21T14:29:44.278Z";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

datetime = "2022-01-21T14:29:44.278+0100";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

datetime = "2022-01-21T14:29:44.278+01.00";
myStrptime(datetime, "%Y-%m-%dT%H:%M:%S%z", &tm, 19);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S%z", &tm);
printf("datetime: %-29s - datetime ricostruito: %s\n", datetime, buf);

exit(EXIT_SUCCESS);
}

// myStrptime() - funzione wrapper per strptime(3)
char *myStrptime(
const char *s, // datetime sorgente
const char *format, // formato del datetime sorgente
struct tm *tm, // struct tm destinazione
size_t len) // lunchezza della parte data+ora+secondi
{
// estraggo la parte data+ora+secondi
char part_one[32];
memcpy(part_one, s, len);
part_one[len] = 0;

// estraggo la seconda parte
char part_two[32];
snprintf(part_two, sizeof(part_two), "%s", &s[strlen(part_one)]);

// ricompongo le due parti e applico strptime(3)
memset(tm, 0, sizeof(struct tm));
char mys[32];
char *mytimezone;
if ((mytimezone = strchr(part_two, 'Z')) != NULL) {
// caso speciale con Z finale
snprintf(mys, sizeof(mys), "%s%s", part_one, mytimezone);
return strptime(mys, format, tm);
}
else if ( (mytimezone = strchr(part_two, '+')) != NULL ||
(mytimezone = strchr(part_two, '-')) != NULL ) {

// caso con timezone esplicito
snprintf(mys, sizeof(mys), "%s%s", part_one, mytimezone);
return strptime(mys, format, tm);
}

// caso non riconosciuto: ritorna errore
return NULL;
}

E questo codice, una volta compilato ed eseguito, da il seguente risultato:

datetime: 2022-01-21T14:29:44.278Z - datetime ricostruito: 21 Jan 2022 14:29:44+0000
datetime: 2022-01-21T14:29:44+0100 - datetime ricostruito: 21 Jan 2022 14:29:44+0100
datetime: 2022-01-21T14:29:44+01.00 - datetime ricostruito: 21 Jan 2022 14:29:44+0100
datetime: 2022-01-21T14:29:44.278Z - datetime ricostruito: 21 Jan 2022 14:29:44+0000
datetime: 2022-01-21T14:29:44.278+0100 - datetime ricostruito: 21 Jan 2022 14:29:44+0100
datetime: 2022-01-21T14:29:44.278+01.00 - datetime ricostruito: 21 Jan 2022 14:29:44+0100

Come avrete notato ho spezzato il datetime sorgente in due parti: la prima contiene data+ora+secondi, e può avere qualsiasi formato, tanto poi useremo la strptime(3) giocando con l'argomento <format>. Per rendere più generica la funzione ho aggiunto un nuovo argomento con la lunghezza della prima parte, così si può accettare (quasi) qualsiasi datetime. La seconda parte può contenere o non contenere i millisecondi (e se li contiene li tagliamo via, tanto nella struct tm non si possono mettere), e il timezone si processa con l'apposito formato "%z" che va bene per tutte le varianti mostrate sopra. Una volta ricomposta la stringa datetime sorgente possiamo chiamare la strptime(3) come se nulla fosse: una soluzione semplice per un problema solo apparentemente complesso (e ricordate: la soluzione più semplice è sempre la migliore, o non conoscete il Rasoio di Occam?).

Ok, per oggi può bastare, e prometto che ritorneremo (nel prossimo articolo) con l'argomento che avevamo lasciato pendente, quello delle nuove keyword del C (e se andate a controllare ho copiato/incollato esattamente la stessa promessa dello scorso articolo... la manterrò questa volta? ah ah ah).

Ciao, e al prossimo post!

P.S.

Questo articolo contiene (spero) argomenti e spiegazioni interessanti. Ma la funzione finale proposta, la myStrptime(), già` al tempo della pubblicazione non mi  convinceva più di tanto (non mi dava una buona impressione neanche “esteticamente”, sapete com'è: sesto senso del programmatore). Che fare allora? Correggere o modificare questo articolo per migliorarlo facendo finta di niente? Oppure pubblicare un remake? (un remake dopo tre mesi? Nel cinema di solito si aspetta un po’ di più…). Ok, alla fine ho optato per fare una “seconda parte”, che potete trovare qui. Ciao di nuovo!