«Le date e gli orari sono cose che insegniamo ai bambini piccoli. Quanto può essere difficile?»
Molti programmatori hanno commesso questo errore, e l'esperienza che ne segue tende ad avere un effetto negativo sulla loro salute e felicità.
Chiunque faccia programmazione non banale con date e orari dovrebbe almeno essere preparato a comprendere e mitigare i potenziali problemi.
Questo documento tenterà di dare una panoramica di come R gestisce date e orari, ma per ulteriori dettagli sarà necessario consultare la documentazione completa.
In quanto linguaggio di data science, in R date e orari hanno un'importanza particolare. Gran parte dei dati arriva sotto forma di serie temporali: numero giornaliero di pazienti durante un'epidemia, dati mensili sull'occupazione riportati dai governi, popolazioni di uccelli in una colonia nidificante, PIL annuale delle principali economie, e infiniti altri esempi.
«Questo significa che R rende semplice la gestione di date e orari?»
Purtroppo no!
La funzionalità per date e orari in Base R ha una cattiva reputazione e oggi la maggior parte è usata raramente.
Il pacchetto lubridate è un grande miglioramento, ma preparati a una libreria grande e complicata, progettata per gestire una gamma ampia e complicata di problemi.
Originariamente indipendente, lubridate è ora incluso nella raccolta tidyverse.
Caricalo con library(lubridate) o library(tidyverse).
Tra le risorse che ti aiutano a lavorare con lubridate ci sono:
Una date è semplicemente un giorno, senza informazioni sull'orario.
Internamente, le date sono memorizzate come il numero (con segno) di giorni trascorsi da 1970-01-01.
today()
#> [1] "2026-04-22"
Un datetime combina informazioni su data, ora e fuso orario.
Internamente, è memorizzato come il numero di secondi dall'inizio del 1970 nel fuso orario UTC.
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
Forse sorprendentemente, R non ha una rappresentazione interna per valori di sola ora.
date e datetime
Forse le esigenze più frequenti relative a date e orari sono:
today()) e/o il datetime corrente (con now()).Ci sono molti modi per scrivere date e orari, che tendono a essere specifici di una cultura. Le date tutte numeriche come «7/6/23» sono ambigue, confusionarie e hanno portato a molti errori costosi in organizzazioni multinazionali (l'autore lo dice per amara esperienza personale).
Lo standard internazionale (a-m-g) è definito in ISO 8601, con due vantaggi principali:
Tuttavia, la data science significa lavorare con qualunque dato tu riesca a trovare. Spesso è inutile lamentarsi di come gli altri lo formattano.
Base R ha funzioni di conversione che accettano una stringa di formato (predefinita a ISO 8601).
as.Date("2026-04-22")
#> [1] "2026-04-22"
as.Date("4/22/26", format = "%m/%d/%y")
#> [1] "2026-04-22"
as.POSIXct("2026-04-22 15:30:09 MST")
#> [1] "2026-04-22 15:30:09 MST"
Le stringhe di formato somigliano alla funzione strptime(), che esiste da decenni ed è inclusa in molti linguaggi.
Chi conosce questa sintassi può, se vuole, usare la funzione fast_strptime() di lubridate.
Più utile ancora, lubridate fornisce una vasta gamma di funzioni ausiliarie, con nomi che rappresentano l'ordine dei campi.
Una volta che le funzioni sanno quali valori cercare e in quale ordine, l'analisi è sorprendentemente flessibile.
mdy("4/22/26")
#> [1] "2026-04-22"
mdy("April 22nd 26")
#> [1] "2026-04-22"
mdy("Wednesday, April 22nd 2026")
#> [1] "2026-04-22"
mdy_hm("Wednesday, April 22nd 2026, 4:14 pm")
#> [1] "2026-04-22 16:14:00 UTC" # defaults to your OS timezone
Nelle situazioni in cui il tuo programma ha i componenti di una data o di un datetime come numeri separati, è possibile assemblarli con make_date() o make_datetime().
Questi sono i componenti che si possono fornire, con il loro ordine e i valori predefiniti:
make_datetime(
year = 1970L,
month = 1L,
day = 1L,
hour = 0L,
min = 0L,
sec = 0,
tz = "UTC"
)
#> [1] "1970-01-01 UTC"
make_datetime(2026, 4, 22, 17)
#> [1] "2026-04-22 17:00:00 UTC"
Naturalmente, make_date() accetta solo gli argomenti year, month e day.
La console di R visualizza automaticamente date e datetime come rappresentazione di stringa. Di solito è comodo, ma a volte fuorviante.
Internamente, date e datetime sono numeri (più qualche attributo), non stringhe. Restituire una data o un datetime quando ci si aspetta una stringa causerà problemi, quindi dobbiamo sapere come fare la conversione.
Un approccio semplice è usare as.character() di Base R, che produce una stringa in formato ISO 8601.
Esiste anche una funzione format_ISO8601() per i datetime, che ha opzioni per arrotondare a una precisione desiderata.
today() |> as.character() # a date
#> [1] "2026-04-23"
now() |> as.character() # a datetime
#> [1] "2026-04-23 09:07:09.779319"
now() |> format_ISO8601() # defaults to nearest second
#> [1] "2026-04-23T09:07:38"
Pochi non programmatori vogliono vedere un output in ISO 8601, quindi gestire formati di date e orari specifici di una cultura è essenziale.
Base R fornisce la funzione format(), che usa stringhe di formato simili alle venerabili funzioni strftime() incluse in molti altri linguaggi.
format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"
Quell'approccio di Base R è brutto ma affidabile. Questi sono alcuni dei codici più comuni:
| Codice | Risultato |
|---|---|
| %d | Numero del giorno del mese (5, 17, 28, ecc.) |
| %a | Giorno della settimana abbreviato (Mon, Tue, Wed, ecc.) |
| %A | Giorno della settimana completo (Monday, Tuesday, ecc.) |
| %w | Numero del giorno della settimana (0-6, domenica è 0) |
| %u | Numero del giorno della settimana (1-7, lunedì è 1) |
| %b | Mese abbreviato (Jan, Feb, ecc.) |
| %B | Mese completo (January, February, ecc.) |
| %y | Anno a 2 cifre (ad es. 89) |
| %Y | Anno a 4 cifre (ad es. 1989) |
| %h | ore (orologio a 24 ore) |
| %m | minuti |
| %s | secondi |
| %z | offset da UTC |
| %Z | Fuso orario (carattere) |
Lubridate mira a fornire un'alternativa più adatta agli esseri umani con la funzione stamp(), e le più specifiche stamp_date() e stamp_time().
Queste accettano una stringa modello che è un esempio dell'output desiderato, e creano una funzione personalizzata per generare quel formato.
Lubridate tenta di indovinare il formato, ma spesso incontra ambiguità e produce il risultato sbagliato.
Preparati a fornire stringhe di formato (come "dmy") per risolvere le ambiguità.
Per Exercism, può anche essere utile specificare locale = "C", per aggirare la locale di sistema e lavorare in inglese (come si aspettano i test).
Le locale sono complicate!
sf <- stamp("Saturday, June 27, 2026 at 14:45", locale = "C")
#> Multiple formats matched: "%A, %B %d, %Y at %H:%M"(1), "Saturday, %Om %d, %Y at %H:%M"(1), "Saturday, %B %d, %Y at %H:%M"(1), "%A, %Om %d, %Y at %H:%M"(0)
#> Using: "%A, %B %d, %Y at %H:%M"
sf(make_datetime(2023, 3, 29, 15, 0, 0))
#> [1] "Wednesday, March 29, 2023 at 15:00"
Lubridate fornisce molte funzioni per ottenere o impostare elementi di una data o di un datetime: quelle ovvie come year() e second(), ma anche altre più specifiche come quarter() e epiweek().
Tutte queste funzioni possono essere usate per ottenere un valore, e la maggior parte può anche essere usata per impostarlo.
dt <- now()
dt
#> [1] "2026-04-23 15:47:11 MST"
# get
month(dt)
#> [1] 4
# set
month(dt) <- 11
dt
#> [1] "2026-11-23 15:47:11 MST"
Per wday() (giorno della settimana) e month(), l'output predefinito è un numero intero, ma:
labels = TRUE si ottiene una stringa di caratteri abbreviata corrispondente alla locale corrente.abbr = FALSE si ottiene il nome completo.d <- today()
d
#> [1] "2026-04-23"
wday(d)
#> [1] 5
wday(d, label = TRUE)
#> [1] Thu
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
wday(d, label = TRUE, abbr = FALSE)
#> [1] Thursday
#> Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < Friday < Saturday
Le etichette sono categorical variables, che saranno l'argomento di un altro Concetto.
I valori sono limitati ai Levels elencati, che mostrano anche l'ordine di ordinamento.
La settimana inizia di domenica per impostazione predefinita, ma questo si può cambiare con l'argomento week_start (usa 1 per lunedì).
dt <- make_datetime(2026, 4, 23)
# default behavior
wday(dt)
#> [1] 5
wday(dt, label = T)
#> [1] Thu
# set week_start to Monday
wday(dt, week_start = 1)
#> [1] 4
Alcune funzioni di utilità restituiscono valori TRUE/FALSE, come leap_year() e dst() (l'ora legale è attiva?).
I mesi hanno lunghezze diverse, quindi days_in_month() fornisce il valore intero.
Sia i valori date che i valori datetime rappresentano un istante lungo la linea temporale.
Come gestiamo concetti come «questa stessa ora di ieri» o «tra una settimana»?
Chi ha familiarità con altri linguaggi orientati alla scienza potrebbe conoscere datetime.timedelta in Python o il tipo Period in Julia.
Al contrario, R ha nientemeno che tre modi per rappresentare gli intervalli di tempo, con punti di forza e limiti diversi. Come sempre, scegli quello più adatto al tuo contesto.
I vari intervalli possono essere convertiti facilmente l'uno nell'altro, con as.period(), as_duration() e as.interval().
I tipi di intervallo si possono interrogare con is.period(), ecc.
Un period, come hours(3), può essere usato per aumentare di 3 ore un orario.
Le irregolarità come l'ora legale (DST) sono in gran parte ignorate.
Tutti i periodi sono il nome al plurale di un'unità: years, weeks, ecc.
d <- today()
d
#> [1] "2026-04-25"
d + weeks(2)
#> [1] "2026-05-09"
# dates are coerced to datetimes at midnight if necessary
d - hours(20)
#> [1] "2026-04-24 04:00:00 UTC"
hours(3)
#> [1] "3H 0M 0S"
Una duration, come dhours(3), aumenta di 3 ore il tempo fisico, rispettando irregolarità come l'ora legale che fanno divergere l'orario dal tempo fisico.
Tutte le durate aggiungono un prefisso d al periodo corrispondente: dyears, dweeks, ecc.
dhours(3)
#> [1] "10800s (~3 hours)"
Un interval è una combinazione di un'ora di inizio e una di fine, creato con la funzione interval() oppure con l'operatore infisso %--%.
# the next 2 lines were run about 33s apart, so now() is different
dt1 <- now()
dt2 <- now() + hours(1)
# interval(0 function)
ivl <- interval(dt1, dt2)
ivl
#> [1] 2026-04-24 16:24:54 MST--2026-04-24 17:25:27 MST
# infix operator
iv2 <- dt1 %--% dt2
iv2 == iv1
#> [1] TRUE
Avere a che fare con i fusi orari può essere difficile. La citazione che segue proviene dalla documentazione di Python, ma è altrettanto pertinente per R:
«Le regole per l'adeguamento degli orari nel mondo sono più politiche che razionali, cambiano spesso, e non esiste uno standard adatto a ogni applicazione all'infuori dell'UTC.»
Alcuni punti generali sui fusi orari:
datetime include sempre un fuso orario (tz); una date non è sensibile al fuso orario.now() è un'eccezione: prende l'ora locale dall'orologio di sistema, incluso tz.
Per impostazione predefinita si usa il tz locale, ma se ne possono specificare altri, come now("UTC").# timezone defaults to UTC
local <- ymd_hm("2026-04-25 10:32")
local
#> [1] "2026-04-25 10:32:00 UTC"
# timezone can be specified
AZ <- ymd_hm("2026-04-25 10:32", tz = "America/Phoenix")
AZ
#> [1] "2026-04-25 10:32:00 MST"
# same clocktime, different tz
local - AZ
#> Time difference of -7 hours
L'aritmetica legata ai fusi orari è più affidabile con le durate che con i periodi.
Lavorare con date e datetime di solito è molto più semplice con lubridate che con Base R.
Questo Concetto ha cercato di dare una panoramica, ma inevitabilmente tralascia moltissimi dettagli.
A rischio di ripetermi: la documentazione è tua amica, quindi usala.
In questo esercizio lavorerai a un sistema di pianificazione degli appuntamenti per un salone di bellezza a New York, aperto il 15 settembre 2012.
Hai cinque attività, che riguarderanno tutte le date degli appuntamenti. Gli orari sono nel formato a 24 ore (senza AM/PM). Per semplificare il più possibile le questioni legate al fuso orario, supponi che tutto usi UTC.
Implementa la funzione schedule_appointment() per interpretare una rappresentazione testuale di una data di appuntamento nel formato DateTime corrispondente.
Nota che l'input è nell'ordine mese/giorno/anno in stile statunitense.
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
Implementa la funzione has_passed() che prende in input una data di appuntamento e verifica se l'appuntamento è nel passato:
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
Implementa la funzione is_afternoon_appointment() che prende in input una data di appuntamento e verifica se l'appuntamento è nel pomeriggio (>= 12:00 e < 18:00):
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
Implementa la funzione day_of_week() che restituisce il giorno della settimana numerico.
Il salone è chiuso la domenica, quindi il lunedì è il giorno 1 e il sabato è il giorno 6.
# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5
Quando un cliente ha bisogno di riprogrammare un appuntamento, questo viene automaticamente spostato al venerdì successivo alla stessa ora del giorno (visto che è il giorno più tranquillo della settimana).
Implementa la funzione reschedule() che prende in input l'appuntamento originale (come stringa di testo nell'ordine mese/giorno/anno) e restituisce un appuntamento riprogrammato.
# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"
Se l'appuntamento originale era da lunedì a giovedì, riprogrammalo al venerdì di questa settimana. Per gli appuntamenti di venerdì o sabato, riprogramma al venerdì della settimana successiva.
Iscriviti a Exercism per imparare e padroneggiare R con 21 concetti111 esercizi e il mentoring di persone reali, tutto gratis.