"Οι ημερομηνίες και οι ώρες είναι κάτι που το διδάσκουμε σε μικρά παιδιά. Πόσο δύσκολο μπορεί να είναι;"
Πολλοί προγραμματιστές έχουν κάνει αυτό το λάθος, και η εμπειρία που ακολουθεί έχει την τάση να έχει άσχημη επίδραση στην υγεία και την ευτυχία τους.
Όποιος κάνει μη τετριμμένο προγραμματισμό με ημερομηνίες και ώρες θα πρέπει τουλάχιστον να είναι προετοιμασμένος να κατανοήσει και να μετριάσει πιθανά προβλήματα.
Αυτό το κείμενο θα επιχειρήσει να δώσει μια επισκόπηση του πώς η R χειρίζεται ημερομηνίες και ώρες, αλλά για περισσότερες λεπτομέρειες θα χρειαστεί να συμβουλευτείς την πλήρη τεκμηρίωση.
Ως γλώσσα επιστήμης δεδομένων, η R δίνει ιδιαίτερη σημασία στις ημερομηνίες και τις ώρες. Μεγάλο μέρος των δεδομένων φτάνει με τη μορφή χρονοσειρών: καθημερινός αριθμός ασθενών κατά τη διάρκεια μιας επιδημίας, μηνιαία στοιχεία απασχόλησης που αναφέρουν οι κυβερνήσεις, πληθυσμοί πουλιών σε μια αποικία φωλιάσματος, ετήσιο ΑΕΠ για μεγάλες οικονομίες, και ατελείωτα ακόμη παραδείγματα.
Μήπως αυτό σημαίνει ότι η R κάνει απλή τη διαχείριση ημερομηνιών και ώρας;
Δυστυχώς, όχι!
Η λειτουργικότητα ημερομηνίας και ώρας στη Base R έχει κακή φήμη και το μεγαλύτερο μέρος της χρησιμοποιείται πλέον σπάνια.
Το πακέτο lubridate είναι μια μεγάλη βελτίωση, αλλά να είσαι προετοιμασμένος για μια μεγάλη και περίπλοκη βιβλιοθήκη, σχεδιασμένη να χειρίζεται ένα μεγάλο και περίπλοκο φάσμα προβλημάτων.
Αρχικά ανεξάρτητο, το lubridate πλέον διανέμεται μαζί με τη συλλογή tidyverse.
Φόρτωσέ το είτε με library(lubridate) είτε με library(tidyverse).
Πόροι που θα σε βοηθήσουν να δουλέψεις με το lubridate περιλαμβάνουν:
Μια date είναι απλώς μια μέρα, χωρίς πληροφορία ώρας.
Εσωτερικά, οι ημερομηνίες αποθηκεύονται ως ο (προσημασμένος) αριθμός ημερών από το 1970-01-01.
today()
#> [1] "2026-04-22"
Ένα datetime συνδυάζει πληροφορία ημερομηνίας, ώρας και ζώνης ώρας.
Εσωτερικά, αποθηκεύεται ως ο αριθμός δευτερολέπτων από την αρχή του 1970 στη ζώνη ώρας UTC.
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
Ίσως παραδόξως, η R δεν έχει εσωτερική αναπαράσταση για τιμές μόνο ώρας.
date και datetime
Ίσως οι πιο συχνές ανάγκες με ημερομηνίες και ώρες είναι:
today()) ή/και datetime (με now()).Υπάρχουν πολλοί τρόποι να γράψεις ημερομηνίες και ώρες, και συνήθως εξαρτώνται από τον πολιτισμό. Οι ημερομηνίες με μόνο αριθμούς, όπως "7/6/23", είναι διφορούμενες, μπερδεύουν, και έχουν οδηγήσει σε πολλά ακριβά λάθη σε πολυεθνικούς οργανισμούς (ο συγγραφέας το λέει από πικρή προσωπική εμπειρία).
Το διεθνές πρότυπο (y-m-d) ορίζεται στο ISO 8601, με δύο βασικά πλεονεκτήματα:
Ωστόσο, η επιστήμη δεδομένων αφορά τη δουλειά με όποια δεδομένα μπορείς να βρεις, και συχνά είναι άσκοπο να παραπονιέσαι για το πώς τα μορφοποιούν οι άλλοι.
Η Base R έχει συναρτήσεις μετατροπής που δέχονται μια συμβολοσειρά μορφής (με προεπιλογή το 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"
Οι συμβολοσειρές μορφής μοιάζουν με τη συνάρτηση strptime(), που υπάρχει εδώ και δεκαετίες και περιλαμβάνεται σε πολλές γλώσσες.
Οι προγραμματιστές που είναι εξοικειωμένοι με αυτή τη σύνταξη μπορούν προαιρετικά να χρησιμοποιήσουν τη συνάρτηση fast_strptime() του lubridate.
Ακόμη πιο χρήσιμα, το lubridate παρέχει ένα ευρύ φάσμα βοηθητικών συναρτήσεων, με ονόματα που αντιπροσωπεύουν τη σειρά των πεδίων.
Μόλις οι συναρτήσεις ξέρουν ποιες τιμές να ψάξουν και με ποια σειρά, η ανάλυση είναι εντυπωσιακά ευέλικτη.
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
Σε περιπτώσεις όπου το πρόγραμμά σου έχει τα συστατικά μιας ημερομηνίας ή ενός datetime ως ξεχωριστούς αριθμούς, μπορούν να συναρμολογηθούν με τις make_date() ή make_datetime().
Αυτά είναι τα συστατικά που μπορούν να δοθούν, με τη σειρά και τις προεπιλεγμένες τιμές τους:
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"
Φυσικά, η make_date() δέχεται μόνο ορίσματα έτους, μήνα και ημέρας.
Η κονσόλα της R εμφανίζει αυτόματα τις ημερομηνίες και τα datetime ως αναπαράσταση συμβολοσειράς. Αυτό είναι συνήθως βολικό, αλλά μερικές φορές παραπλανητικό.
Εσωτερικά, οι ημερομηνίες και τα datetime είναι αριθμοί (συν μερικά χαρακτηριστικά), όχι συμβολοσειρές. Το να επιστρέφεις μια date(time) όταν αναμένεται συμβολοσειρά θα προκαλέσει προβλήματα, οπότε πρέπει να ξέρουμε πώς γίνεται η μετατροπή.
Μια απλή προσέγγιση είναι να χρησιμοποιήσεις τη as.character() της Base R, που βγάζει μια συμβολοσειρά σε μορφή ISO 8601.
Υπάρχει επίσης μια συνάρτηση format_ISO8601() για datetime, με επιλογές για στρογγυλοποίηση σε επιθυμητή ακρίβεια.
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"
Λίγοι μη προγραμματιστές θέλουν να βλέπουν έξοδο ISO 8601, οπότε ο χειρισμός μορφών ημερομηνίας και ώρας που εξαρτώνται από τον πολιτισμό είναι απαραίτητος.
Η Base R παρέχει τη συνάρτηση format(), που χρησιμοποιεί συμβολοσειρές μορφής παρόμοιες με τις σεβάσμιες συναρτήσεις strftime() που περιλαμβάνονται σε πολλές άλλες γλώσσες.
format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"
Αυτή η προσέγγιση της Base R είναι άσχημη αλλά αξιόπιστη. Αυτοί είναι μερικοί από τους πιο συνηθισμένους κωδικούς:
| Code | Result |
|---|---|
| %d | Αριθμός ημέρας του μήνα (5, 17, 28, κ.λπ.) |
| %a | Συντομευμένη ημέρα εβδομάδας (Mon, Tue, Wed, κ.λπ.) |
| %A | Πλήρης ημέρα εβδομάδας (Monday, Tuesday, κ.λπ.) |
| %w | Αριθμός ημέρας εβδομάδας (0-6, η Κυριακή είναι 0) |
| %u | Αριθμός ημέρας εβδομάδας (1-7, η Δευτέρα είναι 1) |
| %b | Συντομευμένος μήνας (Jan, Feb, κ.λπ.) |
| %B | Πλήρης μήνας (January, February, κ.λπ.) |
| %y | Διψήφιο έτος (π.χ. 89) |
| %Y | Τετραψήφιο έτος (π.χ. 1989) |
| %h | ώρες (ρολόι 24ώρου) |
| %m | λεπτά |
| %s | δευτερόλεπτα |
| %z | μετατόπιση από το UTC |
| %Z | Ζώνη ώρας (χαρακτήρες) |
Το lubridate στοχεύει να παρέχει μια πιο φιλική προς τον άνθρωπο εναλλακτική με τη συνάρτηση stamp(), και τις πιο περιορισμένες stamp_date() και stamp_time().
Αυτές δέχονται μια συμβολοσειρά προτύπου που αποτελεί παράδειγμα της επιθυμητής εξόδου, και δημιουργούν μια προσαρμοσμένη συνάρτηση που παράγει αυτή τη μορφή.
Το lubridate προσπαθεί να μαντέψει τη μορφή, αλλά συχνά πέφτει σε ασάφειες και παράγει λάθος αποτέλεσμα.
Να είσαι έτοιμος να δώσεις συμβολοσειρές μορφής (όπως "dmy") για να άρεις την ασάφεια.
Για το Exercism, μπορεί επίσης να είναι χρήσιμο να ορίσεις locale = "C", για να προσπεράσεις το τοπικό σου περιβάλλον (locale) και να δουλέψεις στα αγγλικά (όπως περιμένουν τα tests).
Τα locales είναι περίπλοκα!
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 παρέχει πολλές συναρτήσεις για να πάρεις ή να ορίσεις στοιχεία μιας ημερομηνίας ή ενός datetime: προφανείς όπως οι year() και second(), αλλά και πιο εξειδικευμένες όπως οι quarter() και epiweek().
Όλες αυτές οι συναρτήσεις μπορούν να χρησιμοποιηθούν για να πάρεις μια τιμή, και οι περισσότερες μπορούν επίσης να χρησιμοποιηθούν για να ορίσεις την τιμή.
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"
Για τις wday() (ημέρα εβδομάδας) και month(), η προεπιλογή είναι ακέραια έξοδος, αλλά:
labels = TRUE επιστρέφει μια συντομευμένη συμβολοσειρά χαρακτήρων που αντιστοιχεί στο τρέχον locale.abbr = FALSE, επιστρέφεται το πλήρες όνομα.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
Οι ετικέτες είναι categorical variables, κάτι που θα είναι το θέμα μιας άλλης Έννοιας.
Οι τιμές περιορίζονται στα καταγεγραμμένα Levels, που δείχνουν και τη σειρά ταξινόμησης.
Η εβδομάδα ξεκινά την Κυριακή από προεπιλογή, αλλά αυτό μπορεί να αλλάξει με το όρισμα week_start (χρησιμοποίησε 1 για τη Δευτέρα).
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
Μερικές βοηθητικές συναρτήσεις επιστρέφουν τιμές TRUE/FALSE, όπως οι leap_year() και dst() (είναι ενεργή η θερινή ώρα;).
Οι μήνες έχουν διαφορετικό μήκος, οπότε η days_in_month() δίνει την ακέραια τιμή.
Τόσο οι τιμές date όσο και οι τιμές datetime αντιπροσωπεύουν μια στιγμή πάνω στη χρονογραμμή.
Πώς χειριζόμαστε έννοιες όπως "αυτή την ώρα χθες" ή "σε μια εβδομάδα από τώρα";
Όσοι είναι εξοικειωμένοι με άλλες γλώσσες προσανατολισμένες στην επιστήμη μπορεί να γνωρίζουν το datetime.timedelta στην Python ή τον τύπο Period στη Julia.
Αντίθετα, η R έχει όχι λιγότερους από τρεις τρόπους να αναπαραστήσει χρονικά διαστήματα, με διαφορετικά πλεονεκτήματα και περιορισμούς. Όπως πάντα, διάλεξε αυτόν που ταιριάζει καλύτερα στο πλαίσιο σου.
Τα διάφορα διαστήματα μπορούν εύκολα να μετατραπούν μεταξύ τους, με τις as.period(), as_duration() και as.interval().
Οι τύποι διαστημάτων μπορούν να ελεγχθούν με την is.period(), κ.λπ.
Μια period, όπως η hours(3), μπορεί να χρησιμοποιηθεί για να αυξήσεις μια ώρα ρολογιού κατά 3 ώρες.
Ανωμαλίες όπως η θερινή ώρα (DST) αγνοούνται σε μεγάλο βαθμό.
Όλες οι περίοδοι είναι το όνομα μιας μονάδας σε πληθυντικό: years, weeks, κ.λπ.
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"
Μια duration, όπως η dhours(3), αυξάνει τον φυσικό χρόνο κατά 3 ώρες, σεβόμενη ανωμαλίες όπως η θερινή ώρα που κάνουν την ώρα ρολογιού να αποκλίνει από τον φυσικό χρόνο.
Όλες οι διάρκειες προσθέτουν το πρόθεμα d στην αντίστοιχη περίοδο: dyears, dweeks, κ.λπ.
dhours(3)
#> [1] "10800s (~3 hours)"
Ένα interval είναι συνδυασμός χρόνου έναρξης και λήξης, που δημιουργείται είτε με τη συνάρτηση interval() είτε με τον τελεστή ένθεσης %--%.
# 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
Ο χειρισμός των ζωνών ώρας μπορεί να είναι δύσκολος. Το παρακάτω απόσπασμα είναι από την τεκμηρίωση της Python, αλλά είναι εξίσου σχετικό με την R:
"Οι κανόνες για την προσαρμογή της ώρας σε όλο τον κόσμο είναι περισσότερο πολιτικοί παρά ορθολογικοί, αλλάζουν συχνά, και δεν υπάρχει πρότυπο κατάλληλο για κάθε εφαρμογή εκτός από το UTC."
Μερικά γενικά σημεία για τις ζώνες ώρας:
datetime περιλαμβάνει πάντα μια ζώνη ώρας (tz); μια date δεν έχει επίγνωση ζώνης ώρας.now() αποτελεί εξαίρεση: παίρνει την τοπική σου ώρα από το ρολόι του συστήματος, μαζί με το tz.
Το τοπικό tz χρησιμοποιείται από προεπιλογή, αλλά μπορείς να ορίσεις άλλα, όπως 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
Η αριθμητική που σχετίζεται με ζώνες ώρας είναι πιο αξιόπιστη με διάρκειες παρά με περιόδους.
Η εργασία με ημερομηνίες και datetime είναι συνήθως πολύ πιο εύκολη με το lubridate παρά με τη Base R.
Αυτή η Έννοια προσπάθησε να δώσει μια επισκόπηση, αλλά αναπόφευκτα παραλείπει πάρα πολλές λεπτομέρειες.
Με κίνδυνο να επαναληφθώ: η τεκμηρίωση είναι φίλη σου, οπότε χρησιμοποίησέ την.
Σε αυτή την άσκηση θα δουλέψεις σε μια εφαρμογή προγραμματισμού ραντεβού για ένα σαλόνι ομορφιάς στη Νέα Υόρκη, το οποίο άνοιξε στις 15 Σεπτεμβρίου του 2012.
Έχεις πέντε εργασίες, οι οποίες όλες θα έχουν να κάνουν με ημερομηνίες ραντεβού. Οι ώρες είναι σε μορφή 24ώρου (χωρίς π.μ./μ.μ.). Για να κρατήσουμε τα ζητήματα ζώνης ώρας όσο το δυνατόν πιο απλά, υπόθεσε ότι τα πάντα χρησιμοποιούν UTC.
Υλοποίησε τη συνάρτηση schedule_appointment() ώστε να μετατρέπει μια κειμενική αναπαράσταση μιας ημερομηνίας ραντεβού στην αντίστοιχη μορφή DateTime.
Σημείωσε ότι η είσοδος είναι σε αμερικανική σειρά μήνας/ημέρα/έτος.
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
Υλοποίησε τη συνάρτηση has_passed(), η οποία παίρνει μια ημερομηνία ραντεβού και ελέγχει αν το ραντεβού ήταν κάπου στο παρελθόν:
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
Υλοποίησε τη συνάρτηση is_afternoon_appointment(), η οποία παίρνει μια ημερομηνία ραντεβού και ελέγχει αν το ραντεβού είναι το απόγευμα (>= 12:00 και < 18:00):
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
Υλοποίησε τη συνάρτηση day_of_week(), η οποία επιστρέφει την αριθμητική ημέρα της εβδομάδας.
Το σαλόνι είναι κλειστό τις Κυριακές, οπότε η Δευτέρα είναι η ημέρα 1 και το Σάββατο η ημέρα 6.
# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5
Όταν ένας πελάτης χρειάζεται να μεταθέσει ένα ραντεβού, αυτό γίνεται αυτόματα για την επόμενη Παρασκευή την ίδια ώρα της ημέρας (καθώς αυτή είναι η πιο ήσυχη μέρα της εβδομάδας).
Υλοποίησε τη συνάρτηση reschedule(), η οποία παίρνει το αρχικό ραντεβού (ως συμβολοσειρά κειμένου σε σειρά μήνας/ημέρα/έτος) και επιστρέφει ένα ραντεβού που έχει μετατεθεί.
# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"
Αν το αρχικό ραντεβού ήταν για Δευτέρα έως Πέμπτη, μετάθεσέ το στην Παρασκευή αυτής της εβδομάδας. Για ραντεβού Παρασκευής ή Σαββάτου, μετάθεσέ το στην Παρασκευή της επόμενης εβδομάδας.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις R με 21 έννοιες111 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.