Tracks
/
R
R
/
Übungen
/
Termine für die Schönheit
Termine für die Schönheit

Termine für die Schönheit

Lernübung

Einführung

„Datum und Uhrzeit bringen wir schon kleinen Kindern bei. Wie schwer kann das schon sein?"

Viele Programmierer haben diesen Fehler gemacht, und die darauffolgende Erfahrung wirkt sich meist schlecht auf ihre Gesundheit und ihr Wohlbefinden aus.

Wer nicht trivial mit Datum und Uhrzeit programmiert, sollte zumindest bereit sein, mögliche Probleme zu verstehen und zu entschärfen.

Dieses Dokument versucht einen Überblick darüber zu geben, wie R mit Datum und Uhrzeit umgeht. Für weitere Details wirst du aber die vollständige Dokumentation lesen müssen.

Als Sprache für Data Science haben Datum und Uhrzeit in R eine besondere Bedeutung. Ein Großteil der Daten kommt in Form von Zeitreihen: tägliche Patientenzahlen während eines Krankheitsausbruchs, monatliche Beschäftigungszahlen von Regierungen, Vogelpopulationen in einer Brutkolonie, jährliches BIP großer Volkswirtschaften und unzählige weitere Beispiele.

Heißt das, dass R den Umgang mit Datum und Uhrzeit einfach macht?

Leider nein!

Die Funktionalität für Datum und Uhrzeit in Base R hat einen schlechten Ruf, und das meiste davon wird heute kaum noch genutzt.

Das Paket lubridate ist eine große Verbesserung, aber mach dich auf eine umfangreiche und komplizierte Bibliothek gefasst, die für eine ebenso umfangreiche wie komplizierte Bandbreite von Problemen ausgelegt ist.

Ursprünglich eigenständig, gehört lubridate heute zur tidyverse-Sammlung. Lade es entweder mit library(lubridate) oder mit library(tidyverse).

Zu den Hilfsmitteln für die Arbeit mit lubridate gehören:

Datum und Datetime

Ein date ist einfach ein Tag, ohne Uhrzeit.

Intern werden Datumsangaben als (vorzeichenbehaftete) Anzahl von Tagen seit dem 1970-01-01 gespeichert.

today()
#> [1] "2026-04-22"

Ein datetime kombiniert Datum, Uhrzeit und Zeitzone.

Intern wird es als Anzahl der Sekunden seit Beginn des Jahres 1970 in der Zeitzone UTC gespeichert.

now()
#> [1] "2026-04-22 15:37:11 MST"  # mid-afternoon in Arizona

Vielleicht überraschend: R hat keine interne Darstellung für reine Uhrzeitwerte.

Mit date- und datetime-Werten arbeiten

Die häufigsten Anliegen rund um Datum und Uhrzeit sind vermutlich:

  • Ein passendes Eingabeformat parsen, um ein Datum oder einen Datetime-Wert zu erzeugen.
  • Aus einem Datum oder Datetime-Wert das benötigte numerische oder String-Format gewinnen.
  • Einen Offset auf ein Datum oder einen Datetime-Wert anwenden.
  • Die Zeitspanne zwischen zwei solchen Werten berechnen.
  • Das aktuelle Datum (mit today()) und/oder den aktuellen Datetime-Wert (mit now()) ermitteln.

Aus Strings parsen

Es gibt viele Arten, Datum und Uhrzeit zu schreiben, und sie sind meist kulturspezifisch. Rein numerische Datumsangaben wie „7/6/23" sind mehrdeutig, verwirrend und haben in multinationalen Unternehmen schon zu vielen teuren Fehlern geführt (der Autor spricht da aus leidvoller eigener Erfahrung).

Der internationale Standard (y-m-d) ist in ISO 8601 definiert und hat zwei wesentliche Vorteile:

  • Das Parsen ist schnell und eindeutig.
  • Das Sortieren ist einfach, weil der Datetime-Wert wie normaler Text behandelt und alphabetisch sortiert werden kann.

Bei Data Science geht es aber darum, mit den Daten zu arbeiten, die du findest, und es ist meist sinnlos, sich darüber zu beschweren, wie andere sie formatieren.

Base R hat Konvertierungsfunktionen, die einen Format-String entgegennehmen (standardmäßig 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"

Die Format-Strings ähneln der schon jahrzehntealten Funktion strptime(), die in vielen Sprachen enthalten ist. Wenn du diese Syntax kennst, kannst du alternativ die Funktion fast_strptime() aus lubridate verwenden.

Noch praktischer: lubridate bietet eine breite Palette von Hilfsfunktionen, deren Namen die Reihenfolge der Felder wiedergeben. Wenn die Funktionen erst einmal wissen, nach welchen Werten sie in welcher Reihenfolge suchen sollen, ist das Parsen erstaunlich flexibel.

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

Aus numerischen Werten konstruieren

Wenn dein Programm die Bestandteile eines Datums oder Datetime-Werts als einzelne Zahlen vorliegen hat, kannst du sie mit make_date() oder make_datetime() zusammensetzen.

Das sind die Bestandteile, die angegeben werden können, mit ihrer Reihenfolge und ihren Standardwerten:

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() nimmt natürlich nur die Argumente Jahr, Monat und Tag entgegen.

In menschenlesbarer Form ausgeben

Die R-Konsole zeigt Datums- und Datetime-Werte automatisch als String-Darstellung an. Das ist meist praktisch, manchmal aber irreführend.

Intern sind Datums- und Datetime-Werte Zahlen (plus ein paar Attribute), keine Strings. Wenn du ein Datum (oder einen Datetime-Wert) zurückgibst, wo ein String erwartet wird, führt das zu Problemen. Wir müssen also wissen, wie die Umwandlung funktioniert.

Ein einfacher Weg ist as.character() aus Base R, das einen String im ISO-8601-Format ausgibt.

Für Datetime-Werte gibt es außerdem die Funktion format_ISO8601(), die Optionen zum Runden auf eine gewünschte Genauigkeit bietet.

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"

Nur wenige Nicht-Programmierer wollen eine Ausgabe nach ISO 8601 sehen, deshalb ist der Umgang mit kulturspezifischen Datums- und Uhrzeitformaten unerlässlich.

Base R bietet die Funktion format(), die Format-Strings verwendet, ähnlich wie die ehrwürdigen strftime()-Funktionen in vielen anderen Sprachen.

format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"

Dieser Base-R-Ansatz ist hässlich, aber zuverlässig. Hier sind einige der gebräuchlicheren Codes:

Code Ergebnis
%d Tag des Monats (5, 17, 28 usw.)
%a Abgekürzter Wochentag (Mon, Tue, Wed usw.)
%A Voller Wochentag (Monday, Tuesday usw.)
%w Wochentagsnummer (0-6, Sunday ist 0)
%u Wochentagsnummer (1-7, Monday ist 1)
%b Abgekürzter Monat (Jan, Feb usw.)
%B Voller Monat (January, February usw.)
%y Zweistellige Jahreszahl (z. B. 89)
%Y Vierstellige Jahreszahl (z. B. 1989)
%h Stunden (24-Stunden-Format)
%m Minuten
%s Sekunden
%z Abweichung von UTC
%Z Zeitzone (String)

Lubridate will mit der Funktion stamp() eine menschenfreundlichere Alternative bieten, dazu die spezielleren stamp_date() und stamp_time().

Sie nehmen einen Template-String, der ein Beispiel für die gewünschte Ausgabe ist, und erzeugen daraus eine eigene Funktion für dieses Format.

Lubridate versucht, das Format zu erraten, stößt aber oft auf Mehrdeutigkeiten und liefert das falsche Ergebnis. Sei darauf vorbereitet, Format-Strings (etwa "dmy") anzugeben, um Mehrdeutigkeiten aufzulösen.

Bei Exercism kann es außerdem nützlich sein, locale = "C" anzugeben, um die System-Locale zu umgehen und auf Englisch zu arbeiten (wie es die Tests erwarten). Locales sind kompliziert!

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"

Bestandteile setzen oder auslesen

Lubridate bietet viele Funktionen, um Elemente eines Datums oder Datetime-Werts zu lesen oder zu setzen: naheliegende wie year() und second(), aber auch speziellere wie quarter() und epiweek().

Alle diese Funktionen kannst du zum Lesen eines Werts verwenden, und die meisten auch zum Setzen.

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"

Bei wday() (Wochentag) und month() ist die Ausgabe standardmäßig eine Ganzzahl, aber:

  • Mit labels = TRUE wird ein abgekürzter String entsprechend der aktuellen Locale zurückgegeben.
  • Mit zusätzlich abbr = FALSE wird der vollständige Name zurückgegeben.
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

Die Labels sind categorical variables, die Thema eines anderen Konzepts sein werden. Die Werte sind auf die aufgelisteten Levels beschränkt, die auch die Sortierreihenfolge anzeigen.

Die Woche beginnt standardmäßig am Sonntag, aber das lässt sich mit dem Argument week_start ändern (nutze 1 für Montag).

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

Ein paar Hilfsfunktionen geben TRUE/FALSE-Werte zurück, zum Beispiel leap_year() und dst() (ist die Sommerzeit aktiv?).

Monate sind unterschiedlich lang, deshalb liefert days_in_month() die Ganzzahl.

Zeitspannen

Sowohl date- als auch datetime-Werte repräsentieren einen Zeitpunkt auf der Zeitachse.

Wie gehen wir mit Vorstellungen um wie „gestern um diese Zeit" oder „in einer Woche"?

Wer andere wissenschaftsorientierte Sprachen kennt, hat vielleicht schon von datetime.timedelta in Python oder dem Typ Period in Julia gehört.

R kennt dagegen nicht weniger als drei Arten, Zeitspannen darzustellen, mit jeweils unterschiedlichen Stärken und Grenzen. Wähle wie immer die, die am besten zu deinem Anwendungsfall passt.

Die verschiedenen Spannen lassen sich leicht ineinander umwandeln, mit as.period(), as_duration() und as.interval().

Span-Typen lassen sich mit is.period() usw. abfragen.

Perioden

Ein period wie hours(3) kann eine Uhrzeit um 3 Stunden erhöhen.

Unregelmäßigkeiten wie die Sommerzeit (DST) werden weitgehend ignoriert.

Alle Perioden sind der Plural eines Einheitennamens: years, weeks usw.

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"

Durations

Eine duration wie dhours(3) erhöht die physikalische Zeit um 3 Stunden und berücksichtigt dabei Unregelmäßigkeiten wie die Sommerzeit, durch die die Uhrzeit von der physikalischen Zeit abweicht.

Alle Durations stellen dem Namen der entsprechenden Periode ein d voran: dyears, dweeks usw.

dhours(3)
#> [1] "10800s (~3 hours)"

Intervalle

Ein interval kombiniert eine Start- und eine Endzeit und wird entweder mit der Funktion interval() oder dem Infix-Operator %--% erstellt.

# 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

Zeitzonen

Der Umgang mit Zeitzonen kann herausfordernd sein. Das folgende Zitat stammt aus der Python-Dokumentation, gilt aber genauso für R:

„Die Regeln für die Zeitanpassung auf der Welt sind eher politisch als rational, ändern sich häufig, und außer UTC gibt es keinen Standard, der für jede Anwendung geeignet ist."

Ein paar allgemeine Punkte zu Zeitzonen:

  • Ein datetime enthält immer eine Zeitzone (tz); ein date ist nicht zeitzonenbewusst.
  • Konstruktoren für Datetime-Werte verwenden standardmäßig UTC.
  • Die Funktion now() ist eine Ausnahme: Sie holt deine lokale Zeit samt tz aus der Systemuhr. Standardmäßig wird der lokale tz verwendet, aber du kannst auch andere angeben, etwa 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

Rechnungen mit Zeitzonen sind mit Durations zuverlässiger als mit Perioden.

Fazit

Die Arbeit mit Datums- und Datetime-Werten ist mit lubridate meist viel einfacher als mit Base R.

Dieses Konzept hat versucht, einen Überblick zu geben, lässt aber zwangsläufig sehr viele Details aus.

Auf die Gefahr hin, mich zu wiederholen: Die Dokumentation ist dein Freund, also nutze sie bitte.

Anleitung

In dieser Übung arbeitest du an einem Terminplaner für einen Schönheitssalon in New York, der am 15. September 2012 eröffnet hat.

Du hast fünf Aufgaben, bei denen es jeweils um Termindaten geht. Die Uhrzeiten sind im 24-Stunden-Format angegeben (kein AM/PM). Damit Zeitzonenprobleme so einfach wie möglich bleiben, geh davon aus, dass alles UTC verwendet.

1. Terminangabe im rein numerischen Format parsen

Implementiere die Funktion schedule_appointment(), um eine textuelle Darstellung eines Termins in das entsprechende DateTime-Format zu parsen.

Beachte, dass die Eingabe im US-Format Monat/Tag/Jahr vorliegt.

julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00

2. Prüfen, ob ein Termin bereits vorbei ist

Implementiere die Funktion has_passed(), die einen Termin entgegennimmt und prüft, ob der Termin irgendwann in der Vergangenheit lag:

julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true

3. Prüfen, ob ein Termin am Nachmittag liegt

Implementiere die Funktion is_afternoon_appointment(), die einen Termin entgegennimmt und prüft, ob er am Nachmittag liegt (>= 12:00 und < 18:00):

julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true

4. Den Wochentag eines Termins prüfen

Implementiere die Funktion day_of_week(), die den numerischen Wochentag zurückgibt. Der Salon ist sonntags geschlossen, daher ist Montag Tag 1 und Samstag Tag 6.

# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5

5. Einen Termin verschieben

Wenn ein Kunde einen Termin verschieben muss, wird er automatisch auf den nächsten Freitag zur gleichen Uhrzeit gelegt (denn das ist der langsamste Tag der Woche).

Implementiere die Funktion reschedule(), die den ursprünglichen Termin (als String im Format Monat/Tag/Jahr) entgegennimmt und einen verschobenen Termin zurückgibt.

# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"

Wenn der ursprüngliche Termin auf Montag bis Donnerstag fiel, verschiebe ihn auf Freitag dieser Woche. Bei Terminen am Freitag oder Samstag verschiebst du ihn auf Freitag der nächsten Woche.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
R Exercism

Bereit, mit Termine für die Schönheit zu starten?

Melde dich bei Exercism an, um R mit 21 Konzepte111 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.