Percursos
/
R
R
/
Exercícios
/
Marcações de beleza
Marcações de beleza

Marcações de beleza

Exercício de aprendizagem

Introdução

"As datas e as horas são coisas que ensinamos a crianças pequenas. Que dificuldade pode haver?"

Muitos programadores cometeram esse erro, e a experiência que se segue costuma ter um efeito negativo na sua saúde e na sua felicidade.

Quem faz programação não trivial com datas e horas deve, no mínimo, estar preparado para compreender e mitigar potenciais problemas.

Este documento vai tentar dar uma visão geral de como o R trata as datas e as horas, mas será necessário consultar a documentação completa para mais detalhes.

Enquanto linguagem de ciência de dados, as datas e as horas têm uma importância particular no R. Grande parte dos dados chega na forma de séries temporais: o número diário de doentes durante um surto de doença, os números mensais do emprego reportados pelos governos, populações de aves numa colónia de nidificação, o PIB anual das principais economias, e infindáveis exemplos mais.

Isto significa que o R torna o tratamento de datas e horas simples?

Infelizmente, não!

A funcionalidade de datas e horas no Base R tem má reputação e, atualmente, grande parte já é raramente usada.

O pacote lubridate é uma grande melhoria, mas prepara-te para uma biblioteca grande e complicada, concebida para lidar com uma grande e complicada variedade de problemas.

Originalmente independente, o lubridate está agora incluído na coleção tidyverse. Carrega-o com library(lubridate) ou library(tidyverse).

Alguns recursos para te ajudar a trabalhar com o lubridate:

Datas e datetimes

Uma date é apenas um dia, sem informação de horas.

Internamente, as datas são armazenadas como o número (com sinal) de dias desde 1970-01-01.

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

Um datetime combina informação de data, hora e fuso horário.

Internamente, é armazenado como o número de segundos desde o início de 1970, no fuso horário UTC.

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

Talvez surpreendentemente, o R não tem nenhuma representação interna para valores apenas de hora.

Trabalhar com valores date e datetime

As necessidades mais frequentes com datas e horas são, provavelmente:

  • Interpretar um formato de entrada adequado para construir uma data ou um datetime.
  • Obter o formato numérico ou de string pretendido a partir de uma data ou de um datetime.
  • Aplicar um desvio a um valor de data ou datetime.
  • Calcular o intervalo de tempo entre dois desses valores.
  • Obter a data atual (com today()) e/ou o datetime atual (com now()).

Interpretar a partir de strings

Há muitas formas de escrever datas e horas, que tendem a ser específicas de cada cultura. As datas só com números, como "7/6/23", são ambíguas, confusas, e já levaram a muitos erros dispendiosos em organizações multinacionais (o autor fala por amarga experiência pessoal).

O padrão internacional (ano-mês-dia) está definido na ISO 8601, com duas vantagens principais:

  • A interpretação é rápida e inequívoca.
  • A ordenação é fácil, pois o datetime pode ser tratado como texto normal e ordenado alfabeticamente.

No entanto, a ciência de dados consiste em trabalhar com os dados que consegues encontrar, e muitas vezes não vale a pena queixarmo-nos de como as outras pessoas os formatam.

O Base R tem funções de conversão que recebem uma string de formato (com ISO 8601 como valor predefinido).

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"

As strings de formato são semelhantes à função strptime(), com décadas de existência, incluída em muitas linguagens. Os programadores familiarizados com esta sintaxe podem, opcionalmente, usar a função fast_strptime() do lubridate.

De forma mais útil, o lubridate disponibiliza uma vasta gama de funções auxiliares, com nomes que representam a ordem dos campos. Assim que as funções sabem que valores procurar, e em que ordem, a interpretação é impressionantemente flexível.

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

Construir a partir de valores numéricos

Quando o teu programa tem os componentes de uma data ou de um datetime como números separados, podem ser reunidos com make_date() ou make_datetime().

Estes são os componentes que podem ser fornecidos, mostrando a sua ordem e os valores predefinidos:

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() recebe apenas os argumentos year, month e day.

Apresentar num formato legível para humanos

A consola do R apresenta automaticamente as datas e os datetimes como uma representação em string. Isto é habitualmente prático, mas por vezes enganador.

Internamente, as datas e os datetimes são números (mais alguns atributos), não strings. Devolver uma data ou um datetime quando se espera uma string vai causar problemas, por isso precisamos de saber como fazer a conversão.

Uma abordagem simples é usar as.character() do Base R, que devolve uma string no formato ISO 8601.

Existe também uma função format_ISO8601() para datetimes, que tem opções para arredondar para a precisão pretendida.

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"

Poucos não-programadores querem ver resultados em ISO 8601, por isso é essencial lidar com formatos de data e hora específicos de cada cultura.

O Base R disponibiliza a função format(), que usa strings de formato semelhantes às venerandas funções strftime() incluídas em muitas outras linguagens.

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

Essa abordagem do Base R é feia, mas fiável. Estes são alguns dos códigos mais comuns:

Code Result
%d Número do dia do mês (5, 17, 28, etc.)
%a Dia da semana abreviado (Mon, Tue, Wed, etc.)
%A Dia da semana completo (Monday, Tuesday, etc.)
%w Número do dia da semana (0-6, o domingo é 0)
%u Número do dia da semana (1-7, a segunda-feira é 1)
%b Mês abreviado (Jan, Feb, etc.)
%B Mês completo (January, February, etc.)
%y Ano com 2 algarismos (por exemplo, 89)
%Y Ano com 4 algarismos (por exemplo, 1989)
%h horas (relógio de 24 horas)
%m minutos
%s segundos
%z desvio em relação ao UTC
%Z Fuso horário (caráter)

O lubridate procura oferecer uma alternativa mais amiga do utilizador com a função stamp(), e com as mais específicas stamp_date() e stamp_time().

Estas recebem uma string de modelo que é um exemplo do resultado pretendido, e criam uma função personalizada para gerar esse formato.

O lubridate tenta adivinhar o formato, mas depara-se frequentemente com ambiguidades e produz o resultado errado. Prepara-te para fornecer strings de formato (como "dmy") para desfazer ambiguidades.

No Exercism, também pode ser útil especificar locale = "C", para ignorar o locale do teu sistema e trabalhar em inglês (como os testes esperam). Os locales são complicados!

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"

Definir ou extrair componentes

O lubridate disponibiliza muitas funções para obter ou definir elementos de uma data ou datetime: umas óbvias, como year() e second(), mas também outras mais especializadas, como quarter() e epiweek().

Todas estas funções podem ser usadas para obter um valor, e a maioria também pode ser usada para definir o valor.

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"

Para wday() (dia da semana) e month(), o resultado predefinido é um número inteiro, mas:

  • Definir labels = TRUE devolve uma string de carateres abreviada correspondente ao locale atual.
  • Definir também abbr = FALSE devolve o 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

Os rótulos são categorical variables, que serão o tema de outro Conceito. Os valores estão restritos aos Levels listados, que também mostram a ordem de ordenação.

A semana começa ao domingo por predefinição, mas isto pode ser alterado com o argumento week_start (usa 1 para segunda-feira).

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

Algumas funções utilitárias devolvem valores TRUE/FALSE, como leap_year() e dst() (a hora de verão está ativa?).

Os meses têm durações diferentes, por isso days_in_month() dá o valor inteiro.

Intervalos de tempo

Tanto os valores date como os datetime representam um instante ao longo da linha do tempo.

Como lidamos com conceitos como "a esta hora ontem" ou "daqui a uma semana"?

Os alunos familiarizados com outras linguagens orientadas para a ciência podem conhecer o datetime.timedelta do Python ou o tipo Period do Julia.

Em contrapartida, o R tem nada menos do que três formas de representar intervalos de tempo, com diferentes pontos fortes e limitações. Como sempre, escolhe a que melhor se adapta ao teu contexto.

Os vários tipos de intervalo podem ser facilmente convertidos entre si, com as.period(), as_duration() e as.interval().

Os tipos de intervalo podem ser verificados com is.period(), etc.

Períodos

Um period, como hours(3), pode ser usado para incrementar uma hora de relógio em 3 horas.

As irregularidades, como a hora de verão (DST), são em grande parte ignoradas.

Todos os períodos são o nome de uma unidade no plural: years, weeks, etc.

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"

Durações

Uma duration, como dhours(3), incrementa o tempo físico em 3 horas, respeitando irregularidades como a hora de verão que fazem a hora de relógio divergir do tempo físico.

Todas as durações acrescentam o prefixo d ao período correspondente: dyears, dweeks, etc.

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

Intervalos

Um interval é uma combinação de uma hora de início e de fim, criado com a função interval() ou com o operador infixo %--%.

# 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

Fusos horários

Lidar com fusos horários pode ser um desafio. A citação seguinte é da documentação do Python, mas é igualmente relevante para o R:

"As regras de ajuste da hora em todo o mundo são mais políticas do que racionais, mudam com frequência, e não há nenhum padrão adequado a todas as aplicações além do UTC."

Alguns pontos gerais sobre fusos horários:

  • Um datetime inclui sempre um fuso horário (tz); uma date não tem noção de fuso horário.
  • Os construtores de datetime usam geralmente UTC por predefinição.
  • A função now() é uma exceção: obtém a tua hora local a partir do relógio do sistema, incluindo o tz. O tz local é usado por predefinição, mas podem especificar-se outros, como em 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

A aritmética com fusos horários é mais fiável com durações do que com períodos.

Conclusão

Trabalhar com datas e datetimes é habitualmente muito mais fácil com o lubridate do que com o Base R.

Este Conceito tentou dar uma visão geral, mas deixa inevitavelmente de fora muitíssimos detalhes.

Correndo o risco de repetir: a documentação é tua amiga, por isso usa-a.

Instruções

Neste exercício vais trabalhar num agendador de marcações para um salão de beleza em Nova Iorque que abriu a 15 de setembro de 2012.

Tens cinco tarefas, todas relacionadas com datas de marcações. As horas estão em formato de 24 horas (sem AM/PM). Para manter as questões de fuso horário o mais simples possível, assume que tudo usa UTC.

1. Interpretar uma data de marcação só com números

Implementa a função schedule_appointment() para interpretar uma representação textual de uma data de marcação para o formato DateTime correspondente.

Repara que o valor de entrada está na ordem mês/dia/ano, ao estilo dos Estados Unidos.

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

2. Verificar se uma marcação já passou

Implementa a função has_passed(), que recebe uma data de marcação e verifica se a marcação se situa algures no passado:

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

3. Verificar se a marcação é à tarde

Implementa a função is_afternoon_appointment(), que recebe uma data de marcação e verifica se a marcação é à tarde (>= 12:00 e < 18:00):

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

4. Verificar o dia da semana de uma marcação

Implementa a função day_of_week(), que devolve o dia da semana numérico. O salão está fechado aos domingos, por isso segunda-feira é o dia 1 e sábado é o dia 6.

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

5. Reagendar uma marcação

Quando um cliente precisa de reagendar uma marcação, esta é automaticamente marcada para a sexta-feira seguinte, à mesma hora do dia (por ser o dia mais calmo da semana).

Implementa a função reschedule(), que recebe a marcação original (como string de texto na ordem mês/dia/ano) e devolve uma marcação reagendada.

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

Se a marcação original era de segunda a quinta-feira, reagenda para a sexta-feira desta semana. Para marcações de sexta-feira ou sábado, reagenda para a sexta-feira da semana seguinte.

Editar via GitHub A ligação abre numa nova janela ou separador
R Exercism

Estás pronto para começar Marcações de beleza?

Inscreve-te no Exercism para aprenderes e dominares R com 21 conceitos111 exercícios, e mentoria humana real, tudo grátis.