From 0ab1fcaa3ed80b572c3d2aa866ca7aec3176bcfc Mon Sep 17 00:00:00 2001 From: Ruidy Date: Thu, 3 Feb 2022 15:18:36 -0400 Subject: [PATCH] date parser --- date-parser/.exercism/config.json | 22 ++ date-parser/.exercism/metadata.json | 1 + date-parser/.formatter.exs | 4 + date-parser/.gitignore | 24 ++ date-parser/HELP.md | 75 +++++ date-parser/HINTS.md | 52 ++++ date-parser/README.md | 196 ++++++++++++ date-parser/lib/date_parser.ex | 34 +++ date-parser/mix.exs | 28 ++ date-parser/test/date_parser_test.exs | 423 ++++++++++++++++++++++++++ date-parser/test/test_helper.exs | 2 + 11 files changed, 861 insertions(+) create mode 100644 date-parser/.exercism/config.json create mode 100644 date-parser/.exercism/metadata.json create mode 100644 date-parser/.formatter.exs create mode 100644 date-parser/.gitignore create mode 100644 date-parser/HELP.md create mode 100644 date-parser/HINTS.md create mode 100644 date-parser/README.md create mode 100644 date-parser/lib/date_parser.ex create mode 100644 date-parser/mix.exs create mode 100644 date-parser/test/date_parser_test.exs create mode 100644 date-parser/test/test_helper.exs diff --git a/date-parser/.exercism/config.json b/date-parser/.exercism/config.json new file mode 100644 index 0000000..98a9377 --- /dev/null +++ b/date-parser/.exercism/config.json @@ -0,0 +1,22 @@ +{ + "blurb": "Learn about regular expressions by parsing dates.", + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska", + "Cohen-Carlisle" + ], + "files": { + "solution": [ + "lib/date_parser.ex" + ], + "test": [ + "test/date_parser_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10" +} diff --git a/date-parser/.exercism/metadata.json b/date-parser/.exercism/metadata.json new file mode 100644 index 0000000..6b6c548 --- /dev/null +++ b/date-parser/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"date-parser","id":"3f650e7a4dd641cc8e2c6abdbea38850","url":"https://exercism.org/tracks/elixir/exercises/date-parser","handle":"rjNemo","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/date-parser/.formatter.exs b/date-parser/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/date-parser/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/date-parser/.gitignore b/date-parser/.gitignore new file mode 100644 index 0000000..737e559 --- /dev/null +++ b/date-parser/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +regular_expressions-*.tar + diff --git a/date-parser/HELP.md b/date-parser/HELP.md new file mode 100644 index 0000000..ac36871 --- /dev/null +++ b/date-parser/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be skipped. + +Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/date_parser.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- [Exercism's support channel on gitter](https://gitter.im/exercism/support) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. +If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang). \ No newline at end of file diff --git a/date-parser/HINTS.md b/date-parser/HINTS.md new file mode 100644 index 0000000..ee339ec --- /dev/null +++ b/date-parser/HINTS.md @@ -0,0 +1,52 @@ +# Hints + +## General + +- Review regular expression patterns from the introduction. Remember, when creating the pattern a string, you must escape some characters. +- Read about the [`Regex` module][regex-docs] in the documentation. +- Read about the [regular expression sigil][sigils-regex] in the Getting Started guide. +- Check out this website about regular expressions: [Regular-Expressions.info][website-regex-info]. +- Check out this website about regular expressions: [Rex Egg -The world's most tyrannosauical regex tutorial][website-rexegg]. +- Check out this website about regular expressions: [RegexOne - Learn Regular Expressions with simple, interactive exercises.][website-regexone]. +- Check out this website about regular expressions: [Regular Expressions 101 - an online regex sandbox][website-regex-101]. +- Check out this website about regular expressions: [RegExr - an online regex sandbox][website-regexr]. + +## 1. Match the day, month, and year from a date + +- Remember to return a string representing the regular expression pattern. +- Review how to create _character classes_ or use _shorthand character classes_. +- Review _quantifiers_. +- A day is one or two digits. +- A month is one or two digits. +- A year is four digits. + +## 2. Match the day of the week and the month of the year + +- Review how to write a pattern to match _string literals_. +- Review _alternations_. +- Wrap the whole expression in a _group_. + +## 3. Capture the day, month, and year + +- Review how to write patterns for captures and named captures. +- Reuse the `day/0`, `month/0`, `year/0`, `day_names/0`, and `month_names/0` functions that you already implemented. + +## 4. Combine the captures to capture the whole date + +- Remember, string interpolation may be used to join strings. +- Reuse the `capture_day/0`, `capture_month/0`, `capture_year/0`, `capture_day_name/0`, and `capture_month_name/0` functions that you already implemented. + +## 5. Narrow the capture to match only on the date + +- Remembers, _anchors_ help to match the pattern to the **whole line**. +- String interpolation may be used in the regular expression sigil syntax. +- Reuse the `capture_numeric_date/0`, `capture_month_name_date/0`, and `capture_day_month_name_date/0` functions that you already implemented. + +[regex-docs]: https://hexdocs.pm/elixir/Regex.html +[sigils-regex]: https://elixir-lang.org/getting-started/sigils.html#regular-expressions +[website-regex-info]: https://www.regular-expressions.info +[website-rexegg]: https://www.rexegg.com/ +[website-regexone]: https://regexone.com/ +[website-regex-101]: https://regex101.com/ +[website-regexr]: https://regexr.com/ +[website-regex-crossword]: https://regexcrossword.com/ \ No newline at end of file diff --git a/date-parser/README.md b/date-parser/README.md new file mode 100644 index 0000000..310fab2 --- /dev/null +++ b/date-parser/README.md @@ -0,0 +1,196 @@ +# Date Parser + +Welcome to Date Parser on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Regular Expressions + +Regular expressions (regex) are a powerful tool for working with strings in Elixir. Regular expressions in Elixir follow the **PCRE** specification (**P**erl **C**ompatible **R**egular **E**xpressions). String patterns representing the regular expression's meaning are first compiled then used for matching all or part of a string. + +In Elixir, the most common way to create regular expressions is using the `~r` sigil. Sigils provide _syntactic sugar_ shortcuts for common tasks in Elixir. To match a _string literal_, we can use the string itself as a pattern following the sigil. + +```elixir +~r/test/ +``` + +The `=~/2` operator is useful to perform a regex match on a string to return a `boolean` result. + +```elixir +"this is a test" =~ ~r/test/ +# => true +``` + +Two notes about using sigils: + +- many different delimiters may be used depending on your requirements rather than `/` +- string patterns are already _escaped_, when writing the pattern as a string not using a regex, you will have to _escape_ backslashes (`\`) + +### Character classes + +Matching a range of characters using square brackets `[]` defines a _character class_. This will match any one character to the characters in the class. You can also specify a range of characters like `a-z`, as long as the start and end represent a contiguous range of code points. + +```elixir +regex = ~r/[a-z][ADKZ][0-9][!?]/ +"jZ5!" =~ regex +# => true +"jB5?" =~ regex +# => false +``` + +_Shorthand character classes_ make the pattern more concise. For example: + +- `\d` short for `[0-9]` (any digit) +- `\w` short for `[A-Za-z0-9_]` (any 'word' character) +- `\s` short for `[ \t\r\n\f]` (any whitespace character) + +When a _shorthand character class_ is used outside of a sigil, it must be escaped: `"\\d"` + +### Alternations + +_Alternations_ use `|` as a special character to denote matching one _or_ another + +```elixir +regex = ~r/cat|bat/ +"bat" =~ regex +# => true +"cat" =~ regex +# => true +``` + +### Quantifiers + +_Quantifiers_ allow for a repeating pattern in the regex. They affect the group preceding the quantifier. + +- `{N, M}` where `N` is the minimum number of repetitions, and `M` is the maximum +- `{N,}` match `N` or more repetitions + - `{0,}` may also be written as `*`: match zero-or-more repetitions + - `{1,}` may also be written as `+`: match one-or-more repetitions +- `{,N}` match up to `N` repetitions + +### Groups + +Round brackets `()` are used to denote _groups_ and _captures_. The group may also be _captured_ in some instances to be returned for use. In Elixir, these may be named or un-named. Captures are named by appending `?` after the opening parenthesis. Groups function as a single unit, like when followed by _quantifiers_. + +```elixir +regex = ~r/(h)at/ +Regex.replace(regex, "hat", "\\1op") +# => "hop" + +regex = ~r/(?b)/ +Regex.scan(regex, "blueberry", capture: :all_names) +# => [["b"], ["b"]] +``` + +### Anchors + +_Anchors_ are used to tie the regular expression to the beginning or end of the string to be matched: + +- `^` anchors to the beginning of the string +- `$` anchors to the end of the string + +### Interpolation + +Because the `~r` is a shortcut for `"pattern" |> Regex.escape() |> Regex.compile!()`, you may also use string interpolation to dynamically build a regular expression pattern: + +```elixir +anchor = "$" +regex = ~r/end of the line#{anchor}/ +"end of the line?" =~ regex +# => false +"end of the line" =~ regex +# => true +``` + +## Instructions + +You have been tasked to write a service which ingests events. Each event has a date associated with it, but you notice that 3 different formats are being submitted to your service's endpoint: + +- `"01/01/1970"` +- `"January 1, 1970"` +- `"Thursday, January 1, 1970"` + +You can see there are some similarities between each of them, and decide to write some composable regular expression patterns. + +## 1. Match the day, month, and year from a date + +Implement `day/0`, `month/0`, and `year/0` to return a string pattern which, when compiled, would match the numeric components in `"01/01/1970"` (`dd/mm/yyyy`). The day and month may appear as `1` or `01` (left padded with zeroes). + +```elixir +"31" =~ DateParser.day() |> Regex.compile!() +# => true +"12" =~ DateParser.month() |> Regex.compile!() +# => true +"1970" =~ DateParser.year() |> Regex.compile!() +# => true +``` + +## 2. Match the day of the week and the month of the year + +Implement `day_names/0` and `month_names/0` to return a string pattern which, when compiled, would match the named day of the week and the named month of the year respectively. + +```elixir +"Tuesday" =~ DateParser.day_names() |> Regex.compile!() +# => true +"June" =~ DateParser.month_names() |> Regex.compile!() +# => true +``` + +## 3. Capture the day, month, and year + +Implement `capture_day/0`, `capture_month/0`, `capture_year/0`, `capture_day_name/0`, `capture_month_name/0` to return a string pattern which captures the respective components to the names: `"day"`, `"month"`, `"year"`, `"day_name"`, `"month_name"` + +```elixir +DateParser.capture_month_name() +|> Regex.compile!() +|> Regex.named_captures("December") +# => %{"month_name" => "December"} +``` + +## 4. Combine the captures to capture the whole date + +Implement `capture_numeric_date/0`, `capture_month_name_date()`, and `capture_day_month_name_date/0` to return a string pattern which captures the components from part 3 using the respective date format: + +- numeric date - `"01/01/1970"` +- month name date - `"January 1, 1970"` +- day month name date - `"Thursday, January 1, 1970"` + +```elixir +DateParser.capture_numeric_date() +|> Regex.compile!() +|> Regex.named_captures("01/01/1970") +# => %{"day" => "01", "month" => "01", "year" => "1970"} +``` + +## 5. Narrow the capture to match only on the date + +Implement `match_numeric_date/0`, `match_month_name_date/0`, and `match_day_month_name_date/0` to return a compiled regular expression that only matches the date, and which can also capture the components. + +```elixir +"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" +# } +``` + +## Source + +### Created by + +- @neenjaw + +### Contributed to by + +- @angelikatyborska +- @Cohen-Carlisle \ No newline at end of file diff --git a/date-parser/lib/date_parser.ex b/date-parser/lib/date_parser.ex new file mode 100644 index 0000000..499f0da --- /dev/null +++ b/date-parser/lib/date_parser.ex @@ -0,0 +1,34 @@ +defmodule DateParser do + def day(), do: "\\d{1,2}" + + def month(), do: "\\d{1,2}" + + def year(), do: "\\d{4}" + + def day_names(), do: "(Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)" + + def month_names(), + do: "(January|February|March|April|May|June|July|August|September|October|November|December)" + + def capture_day(), do: "(?#{day()})" + + def capture_month(), do: "(?#{month()})" + + def capture_year(), do: "(?#{year()})" + + def capture_day_name(), do: "(?#{day_names()})" + + def capture_month_name(), do: "(?#{month_names()})" + + def capture_numeric_date(), do: "#{capture_day()}/#{capture_month()}/#{capture_year()}" + + def capture_month_name_date(), do: "#{capture_month_name()} #{capture_day()}, #{capture_year()}" + + def capture_day_month_name_date(), do: "#{capture_day_name()}, #{capture_month_name_date()}" + + def match_numeric_date(), do: ~r/^#{capture_numeric_date()}$/ + + def match_month_name_date(), do: ~r/^#{capture_month_name_date()}$/ + + def match_day_month_name_date(), do: ~r/^#{capture_day_month_name_date()}$/ +end diff --git a/date-parser/mix.exs b/date-parser/mix.exs new file mode 100644 index 0000000..a1c2747 --- /dev/null +++ b/date-parser/mix.exs @@ -0,0 +1,28 @@ +defmodule DateParser.MixProject do + use Mix.Project + + def project do + [ + app: :date_parser, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/date-parser/test/date_parser_test.exs b/date-parser/test/date_parser_test.exs new file mode 100644 index 0000000..2095451 --- /dev/null +++ b/date-parser/test/date_parser_test.exs @@ -0,0 +1,423 @@ +defmodule DateParserTest do + use ExUnit.Case + + @tag task_id: 1 + test "numeric pattern for day is a string" do + assert DateParser.day() |> is_binary() + end + + describe "numeric pattern for day matches" do + @tag task_id: 1 + test "un-padded days" do + assert "1" =~ Regex.compile!(DateParser.day()) + assert "2" =~ Regex.compile!(DateParser.day()) + assert "3" =~ Regex.compile!(DateParser.day()) + assert "4" =~ Regex.compile!(DateParser.day()) + assert "5" =~ Regex.compile!(DateParser.day()) + assert "6" =~ Regex.compile!(DateParser.day()) + assert "7" =~ Regex.compile!(DateParser.day()) + assert "8" =~ Regex.compile!(DateParser.day()) + assert "9" =~ Regex.compile!(DateParser.day()) + assert "10" =~ Regex.compile!(DateParser.day()) + assert "11" =~ Regex.compile!(DateParser.day()) + assert "12" =~ Regex.compile!(DateParser.day()) + assert "13" =~ Regex.compile!(DateParser.day()) + assert "14" =~ Regex.compile!(DateParser.day()) + assert "15" =~ Regex.compile!(DateParser.day()) + assert "16" =~ Regex.compile!(DateParser.day()) + assert "17" =~ Regex.compile!(DateParser.day()) + assert "18" =~ Regex.compile!(DateParser.day()) + assert "19" =~ Regex.compile!(DateParser.day()) + assert "20" =~ Regex.compile!(DateParser.day()) + assert "21" =~ Regex.compile!(DateParser.day()) + assert "22" =~ Regex.compile!(DateParser.day()) + assert "23" =~ Regex.compile!(DateParser.day()) + assert "24" =~ Regex.compile!(DateParser.day()) + assert "25" =~ Regex.compile!(DateParser.day()) + assert "26" =~ Regex.compile!(DateParser.day()) + assert "27" =~ Regex.compile!(DateParser.day()) + assert "28" =~ Regex.compile!(DateParser.day()) + assert "29" =~ Regex.compile!(DateParser.day()) + assert "30" =~ Regex.compile!(DateParser.day()) + assert "31" =~ Regex.compile!(DateParser.day()) + end + + @tag task_id: 1 + test "padded days" do + assert "01" =~ Regex.compile!(DateParser.day()) + assert "02" =~ Regex.compile!(DateParser.day()) + assert "03" =~ Regex.compile!(DateParser.day()) + assert "04" =~ Regex.compile!(DateParser.day()) + assert "05" =~ Regex.compile!(DateParser.day()) + assert "06" =~ Regex.compile!(DateParser.day()) + assert "07" =~ Regex.compile!(DateParser.day()) + assert "08" =~ Regex.compile!(DateParser.day()) + assert "09" =~ Regex.compile!(DateParser.day()) + end + end + + describe "numeric pattern for day doesn't match" do + @tag task_id: 1 + test "too few digits", do: refute("" =~ Regex.compile!("^#{DateParser.day()}$")) + @tag task_id: 1 + test "too many digits", do: refute("111" =~ Regex.compile!("^#{DateParser.day()}$")) + @tag task_id: 1 + test "one letter", do: refute("a" =~ Regex.compile!(DateParser.day())) + @tag task_id: 1 + test "two letters", do: refute("bb" =~ Regex.compile!(DateParser.day())) + end + + @tag task_id: 1 + test "numeric pattern for month is a string" do + assert DateParser.month() |> is_binary() + end + + describe "numeric pattern for month matches" do + @tag task_id: 1 + test "un-padded months" do + assert "1" =~ Regex.compile!(DateParser.month()) + assert "2" =~ Regex.compile!(DateParser.month()) + assert "3" =~ Regex.compile!(DateParser.month()) + assert "4" =~ Regex.compile!(DateParser.month()) + assert "5" =~ Regex.compile!(DateParser.month()) + assert "6" =~ Regex.compile!(DateParser.month()) + assert "7" =~ Regex.compile!(DateParser.month()) + assert "8" =~ Regex.compile!(DateParser.month()) + assert "9" =~ Regex.compile!(DateParser.month()) + assert "10" =~ Regex.compile!(DateParser.month()) + assert "11" =~ Regex.compile!(DateParser.month()) + assert "12" =~ Regex.compile!(DateParser.month()) + end + + @tag task_id: 1 + test "padded months" do + assert "01" =~ Regex.compile!(DateParser.month()) + assert "02" =~ Regex.compile!(DateParser.month()) + assert "03" =~ Regex.compile!(DateParser.month()) + assert "04" =~ Regex.compile!(DateParser.month()) + assert "05" =~ Regex.compile!(DateParser.month()) + assert "06" =~ Regex.compile!(DateParser.month()) + assert "07" =~ Regex.compile!(DateParser.month()) + assert "08" =~ Regex.compile!(DateParser.month()) + assert "09" =~ Regex.compile!(DateParser.month()) + end + end + + describe "numeric pattern for month doesn't match" do + @tag task_id: 1 + test "too few digits", do: refute("" =~ Regex.compile!("^#{DateParser.month()}$")) + @tag task_id: 1 + test "too many digits", do: refute("111" =~ Regex.compile!("^#{DateParser.month()}$")) + @tag task_id: 1 + test "one letter", do: refute("a" =~ Regex.compile!(DateParser.month())) + @tag task_id: 1 + test "two letters", do: refute("bb" =~ Regex.compile!(DateParser.month())) + @tag task_id: 1 + test "short month name", do: refute("Jan" =~ Regex.compile!(DateParser.month())) + @tag task_id: 1 + test "long month name", do: refute("January" =~ Regex.compile!(DateParser.month())) + end + + @tag task_id: 1 + test "numeric pattern for year is a string" do + assert DateParser.year() |> is_binary() + end + + describe "numeric pattern for year" do + @tag task_id: 1 + test "matches 4 digits", do: assert("1970" =~ Regex.compile!("^#{DateParser.year()}$")) + @tag task_id: 1 + test "doesn't match short year", do: refute("84" =~ Regex.compile!("^#{DateParser.year()}$")) + @tag task_id: 1 + test "doesn't match letters", do: refute("198A" =~ Regex.compile!("^#{DateParser.year()}$")) + @tag task_id: 1 + test "doesn't match too few", do: refute("198" =~ Regex.compile!("^#{DateParser.year()}$")) + @tag task_id: 1 + test "doesn't match too many", do: refute("19701" =~ Regex.compile!("^#{DateParser.year()}$")) + end + + @tag task_id: 2 + test "pattern for day names is a string" do + assert DateParser.day_names() |> is_binary() + end + + @tag task_id: 2 + test "day names match" do + assert "Sunday" =~ Regex.compile!(DateParser.day_names()) + assert "Monday" =~ Regex.compile!(DateParser.day_names()) + assert "Tuesday" =~ Regex.compile!(DateParser.day_names()) + assert "Wednesday" =~ Regex.compile!(DateParser.day_names()) + assert "Thursday" =~ Regex.compile!(DateParser.day_names()) + assert "Friday" =~ Regex.compile!(DateParser.day_names()) + assert "Saturday" =~ Regex.compile!(DateParser.day_names()) + end + + @tag task_id: 2 + test "day names don't match with trailing or leading whitespace" do + refute " Sunday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Monday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Tuesday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Wednesday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Thursday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Friday " =~ Regex.compile!("^#{DateParser.day_names()}$") + refute " Saturday " =~ Regex.compile!("^#{DateParser.day_names()}$") + end + + describe "day names don't match" do + @tag task_id: 2 + test "combined" do + refute "TuesdayWednesday" =~ Regex.compile!("^#{DateParser.day_names()}$") + end + + @tag task_id: 2 + test "short name" do + refute "Sun" =~ Regex.compile!("^#{DateParser.day_names()}$") + end + + @tag task_id: 2 + test "numeric day of the week (0-indexed)" do + refute "0" =~ Regex.compile!("^#{DateParser.day_names()}$") + end + + @tag task_id: 2 + test "numeric day of the week (1-indexed)" do + refute "1" =~ Regex.compile!("^#{DateParser.day_names()}$") + end + end + + @tag task_id: 2 + test "pattern for month names is a string" do + assert DateParser.month_names() |> is_binary() + end + + @tag task_id: 2 + test "month names match" do + assert "January" =~ Regex.compile!(DateParser.month_names()) + assert "February" =~ Regex.compile!(DateParser.month_names()) + assert "March" =~ Regex.compile!(DateParser.month_names()) + assert "April" =~ Regex.compile!(DateParser.month_names()) + assert "May" =~ Regex.compile!(DateParser.month_names()) + assert "June" =~ Regex.compile!(DateParser.month_names()) + assert "July" =~ Regex.compile!(DateParser.month_names()) + assert "August" =~ Regex.compile!(DateParser.month_names()) + assert "September" =~ Regex.compile!(DateParser.month_names()) + assert "October" =~ Regex.compile!(DateParser.month_names()) + assert "November" =~ Regex.compile!(DateParser.month_names()) + assert "December" =~ Regex.compile!(DateParser.month_names()) + end + + @tag task_id: 2 + test "month names don't match with trailing or leading whitespace" do + refute " January " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " February " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " March " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " April " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " May " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " June " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " July " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " August " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " September " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " October " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " November " =~ Regex.compile!("^#{DateParser.month_names()}$") + refute " December " =~ Regex.compile!("^#{DateParser.month_names()}$") + end + + describe "month names don't match" do + @tag task_id: 2 + test "combined" do + refute "JanuaryFebruary" =~ Regex.compile!("^#{DateParser.month_names()}$") + end + + @tag task_id: 2 + test "short name" do + refute "Jan" =~ Regex.compile!("^#{DateParser.month_names()}$") + end + + @tag task_id: 2 + test "numeric month of the year (0-indexed)" do + refute "0" =~ Regex.compile!("^#{DateParser.month_names()}$") + end + + @tag task_id: 2 + test "numeric month of the year (1-indexed)" do + refute "1" =~ Regex.compile!("^#{DateParser.month_names()}$") + end + end + + describe "capture" do + @tag task_id: 3 + test "numeric month" do + assert DateParser.capture_month() |> is_binary() + + assert %{"month" => "01"} = + DateParser.capture_month() + |> Regex.compile!() + |> Regex.named_captures("01") + end + + @tag task_id: 3 + test "numeric day" do + assert DateParser.capture_day() |> is_binary() + + assert %{"day" => "01"} = + DateParser.capture_day() + |> Regex.compile!() + |> Regex.named_captures("01") + end + + @tag task_id: 3 + test "numeric year" do + assert DateParser.capture_year() |> is_binary() + + assert %{"year" => "1970"} = + DateParser.capture_year() + |> Regex.compile!() + |> Regex.named_captures("1970") + end + + @tag task_id: 3 + test "capture day name" do + assert DateParser.capture_day_name() |> is_binary() + + assert %{"day_name" => "Monday"} = + DateParser.capture_day_name() + |> Regex.compile!() + |> Regex.named_captures("Monday") + end + + @tag task_id: 3 + test "capture month name" do + assert DateParser.capture_month_name() |> is_binary() + + assert %{"month_name" => "February"} = + DateParser.capture_month_name() + |> Regex.compile!() + |> Regex.named_captures("February") + end + + @tag task_id: 4 + test "numeric date" do + assert DateParser.capture_numeric_date() |> is_binary() + + assert %{"year" => "1970", "month" => "02", "day" => "01"} = + DateParser.capture_numeric_date() + |> Regex.compile!() + |> Regex.named_captures("01/02/1970") + end + + @tag task_id: 4 + test "month named date" do + assert DateParser.capture_month_name_date() |> is_binary() + + assert %{"year" => "1970", "month_name" => "January", "day" => "1"} = + DateParser.capture_month_name_date() + |> Regex.compile!() + |> Regex.named_captures("January 1, 1970") + end + + @tag task_id: 4 + test "day and month named date" do + assert DateParser.capture_day_month_name_date() |> is_binary() + + assert %{ + "year" => "1970", + "month_name" => "January", + "day" => "1", + "day_name" => "Thursday" + } = + DateParser.capture_day_month_name_date() + |> Regex.compile!() + |> Regex.named_captures("Thursday, January 1, 1970") + end + end + + describe "regex match" do + @tag task_id: 5 + test "pattern to match numeric date is a regex" do + assert match?(%Regex{}, DateParser.match_numeric_date()) + end + + @tag task_id: 5 + test "numeric date matches" do + assert DateParser.match_numeric_date() |> Regex.match?("01/02/1970") + end + + @tag task_id: 5 + test "numeric date has named captures" do + assert %{"year" => "1970", "month" => "02", "day" => "01"} = + DateParser.match_numeric_date() + |> Regex.named_captures("01/02/1970") + end + + @tag task_id: 5 + test "numeric date with a prefix doesn't match" do + refute DateParser.match_numeric_date() |> Regex.match?("The day was 01/02/1970") + end + + @tag task_id: 5 + test "numeric date with a suffix doesn't match" do + refute DateParser.match_numeric_date() |> Regex.match?("01/02/1970 was the day") + end + + @tag task_id: 5 + test "pattern to match month name date is a regex" do + assert match?(%Regex{}, DateParser.match_month_name_date()) + end + + @tag task_id: 5 + test "month named date matches" do + assert DateParser.match_month_name_date() |> Regex.match?("January 1, 1970") + end + + @tag task_id: 5 + test "month named date has named captures" do + assert %{"year" => "1970", "month_name" => "January", "day" => "1"} = + DateParser.match_month_name_date() + |> Regex.named_captures("January 1, 1970") + end + + @tag task_id: 5 + test "month named date with a prefix doesn't match" do + refute DateParser.match_month_name_date() |> Regex.match?("The day was January 1, 1970") + end + + @tag task_id: 5 + test "month named date with a suffix doesn't match" do + refute DateParser.match_month_name_date() |> Regex.match?("January 1, 1970 was the day") + end + + @tag task_id: 5 + test "pattern to match day month name date is a regex" do + assert match?(%Regex{}, DateParser.match_day_month_name_date()) + end + + @tag task_id: 5 + test "day and month names date matches" do + assert DateParser.match_day_month_name_date() |> Regex.match?("Thursday, January 1, 1970") + end + + @tag task_id: 5 + test "day and month names date has named captures" do + assert %{ + "year" => "1970", + "month_name" => "January", + "day" => "1", + "day_name" => "Thursday" + } = + DateParser.match_day_month_name_date() + |> Regex.named_captures("Thursday, January 1, 1970") + end + + @tag task_id: 5 + test "day and month names date with a prefix doesn't match" do + refute DateParser.match_day_month_name_date() + |> Regex.match?("The day way Thursday, January 1, 1970") + end + + @tag task_id: 5 + test "day and month names date with a suffix doesn't match" do + refute DateParser.match_day_month_name_date() + |> Regex.match?("Thursday, January 1, 1970 was the day") + end + end +end diff --git a/date-parser/test/test_helper.exs b/date-parser/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/date-parser/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0)