Trilhas
/
Elixir
Elixir
/
Exercícios
/
Parser de datas
Parser de datas

Parser de datas

Exercício de aprendizagem

Introdução

Expressões regulares

As expressões regulares (regex) são uma ferramenta poderosa para trabalhar com strings em Elixir. As expressões regulares em Elixir seguem a especificação PCRE (Perl Compatible Regular Expressions). Os padrões de string que representam o significado da expressão regular são primeiro compilados e depois usados para fazer a correspondência de toda a string ou de parte dela.

Em Elixir, a forma mais comum de criar expressões regulares é usando o sigilo ~r. Os sigilos oferecem atalhos de açúcar sintático para tarefas comuns em Elixir. Para corresponder a um literal de string, podemos usar a própria string como padrão logo após o sigilo.

~r/test/

O operador =~/2 é útil para fazer uma correspondência de regex em uma string e retornar um resultado boolean.

"this is a test" =~ ~r/test/
# => true

Duas observações sobre o uso de sigilos:

  • você pode usar vários delimitadores diferentes, dependendo da sua necessidade, em vez de /
  • os padrões de string já vêm escapados; ao escrever o padrão como string sem usar uma regex, você vai precisar escapar as barras invertidas (\)

Classes de caracteres

Corresponder a um intervalo de caracteres usando colchetes [] define uma classe de caracteres. Isso corresponde a qualquer um dos caracteres da classe. Você também pode especificar um intervalo de caracteres como a-z, desde que o início e o fim representem um intervalo contíguo de pontos de código.

regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false

Classes de caracteres abreviadas deixam o padrão mais conciso. Por exemplo:

  • \d, abreviação de [0-9] (qualquer dígito)
  • \w, abreviação de [A-Za-z0-9_] (qualquer caractere de 'palavra')
  • \s, abreviação de [ \t\r\n\f] (qualquer caractere de espaço em branco)

Quando uma classe de caracteres abreviada é usada fora de um sigilo, ela precisa ser escapada: "\\d"

Alternâncias

As alternâncias usam | como caractere especial para indicar a correspondência de um ou outro

regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true

Quantificadores

Os quantificadores permitem um padrão repetido na regex. Eles afetam o grupo que vem antes do quantificador.

  • {N, M}, em que N é o número mínimo de repetições e M é o máximo
  • {N,} corresponde a N repetições ou mais
    • {0,} também pode ser escrito como *: corresponde a zero ou mais repetições
    • {1,} também pode ser escrito como +: corresponde a uma ou mais repetições
  • {,N} corresponde a até N repetições

Grupos

Parênteses () são usados para denotar grupos e capturas. Em alguns casos, o grupo também pode ser capturado para ser retornado e usado. Em Elixir, eles podem ser nomeados ou não nomeados. As capturas são nomeadas acrescentando ?<name> depois do parêntese de abertura. Os grupos funcionam como uma unidade única, como quando são seguidos por quantificadores.

regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"

regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]

Âncoras

As âncoras são usadas para prender a expressão regular ao início ou ao fim da string que será correspondida:

  • ^ ancora no início da string
  • $ ancora no fim da string

Interpolação

Como o ~r é um atalho para "pattern" |> Regex.escape() |> Regex.compile!(), você também pode usar interpolação de string para montar dinamicamente um padrão de expressão regular:

anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true

Instruções

Você tem a tarefa de escrever um serviço que recebe eventos. Cada evento tem uma data associada, mas você percebe que 3 formatos diferentes estão sendo enviados ao endpoint do seu serviço:

  • "01/01/1970"
  • "January 1, 1970"
  • "Thursday, January 1, 1970"

Você consegue ver que existem algumas semelhanças entre cada um deles e decide escrever alguns padrões de expressão regular componíveis.

1. Faça a correspondência do dia, do mês e do ano de uma data

Implemente day/0, month/0 e year/0 para retornar um padrão de string que, quando compilado, corresponda aos componentes numéricos em "01/01/1970" (dd/mm/yyyy). O dia e o mês podem aparecer como 1 ou 01 (preenchidos com zeros à esquerda).

"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true

2. Faça a correspondência do dia da semana e do mês do ano

Implemente day_names/0 e month_names/0 para retornar um padrão de string que, quando compilado, corresponda ao nome do dia da semana e ao nome do mês do ano, respectivamente.

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. Capture o dia, o mês e o ano

Implemente capture_day/0, capture_month/0, capture_year/0, capture_day_name/0, capture_month_name/0 para retornar um padrão de string que captura os respectivos componentes com os nomes: "day", "month", "year", "day_name", "month_name"

DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")

# => %{"month_name" => "December"}

4. Combine as capturas para capturar a data inteira

Implemente capture_numeric_date/0, capture_month_name_date(), e capture_day_month_name_date/0 para retornar um padrão de string que captura os componentes da parte 3 usando o respectivo formato de data:

  • data numérica - "01/01/1970"
  • data com nome do mês - "January 1, 1970"
  • data com dia e nome do mês - "Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")

# => %{"day" => "01", "month" => "01", "year" => "1970"}

5. Restrinja a captura para corresponder apenas à data

Implemente match_numeric_date/0, match_month_name_date/0, e match_day_month_name_date/0 para retornar uma expressão regular compilada que corresponda apenas à data, e que também possa capturar os componentes.

"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true

DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")

# => %{
#     "day" => "1",
#     "day_name" => "Thursday",
#     "month_name" => "January",
#     "year" => "1970"
#   }
Editar via GitHub O link abre em uma nova janela ou aba
Elixir Exercism

Tudo pronto para começar Parser de datas?

Crie sua conta no Exercism para aprender e dominar Elixir com 58 conceitos168 exercícios e mentoria humana de verdade, tudo de graça.