diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aefc10325..d64761476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: echo "::set-output name=cache_key::$cache_key" - name: Retrieve Mix Dependencies Cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf id: mix-cache # id to use in retrieve action with: path: deps @@ -55,7 +55,7 @@ jobs: run: mix - name: Retrieve PLT Cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf id: plt-cache with: path: priv/plts @@ -106,7 +106,7 @@ jobs: echo "::set-output name=cache_key::$cache_key" - name: Retrieve Mix Dependencies Cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf id: mix-cache # id to use in retrieve action with: path: deps @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Retrieve Lychee Cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf id: lychee-cache # id to use in retrieve action with: path: .lycheecache diff --git a/config.json b/config.json index 1bf16b9f6..0ba0c0070 100644 --- a/config.json +++ b/config.json @@ -2505,6 +2505,23 @@ ], "difficulty": 6 }, + { + "slug": "swift-scheduling", + "name": "Swift Scheduling", + "uuid": "0eb21dfc-9666-475c-bc92-558f2663a598", + "practices": [ + "dates-and-time" + ], + "prerequisites": [ + "strings", + "regular-expressions", + "dates-and-time", + "cond", + "case", + "if" + ], + "difficulty": 6 + }, { "slug": "variable-length-quantity", "name": "Variable Length Quantity", diff --git a/exercises/practice/swift-scheduling/.docs/instructions.md b/exercises/practice/swift-scheduling/.docs/instructions.md new file mode 100644 index 000000000..267f7932d --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/instructions.md @@ -0,0 +1,43 @@ +# Instructions + +Your task is to convert delivery date descriptions to _actual_ delivery dates, based on when the meeting started. + +There are two types of delivery date descriptions: + +1. Fixed: a predefined set of words. +2. Variable: words that have a variable component, but follow a predefined set of patterns. + +## Fixed delivery date descriptions + +There are three fixed delivery date descriptions: + +- `"NOW"` +- `"ASAP"` (As Soon As Possible) +- `"EOW"` (End Of Week) + +The following table shows how to translate them: + +| Description | Meeting start | Delivery date | +| ----------- | ----------------------------- | ----------------------------------- | +| `"NOW"` | - | Two hours after the meeting started | +| `"ASAP"` | Before 13:00 | Today at 17:00 | +| `"ASAP"` | After or at 13:00 | Tomorrow at 13:00 | +| `"EOW"` | Monday, Tuesday, or Wednesday | Friday at 17:00 | +| `"EOW"` | Thursday or Friday | Sunday at 20:00 | + +## Variable delivery date descriptions + +There are two variable delivery date description patterns: + +- `"M"` (N-th month) +- `"Q"` (N-th quarter) + +| Description | Meeting start | Delivery date | +| ----------- | -------------------------- | ----------------------------------------------------------- | +| `"M"` | Before N-th month | At 8:00 on the _first_ workday¹ of this year's N-th month | +| `"M"` | After or in N-th month | At 8:00 on the _first_ workday¹ of next year's N-th month | +| `"Q"` | Before or in N-th quarter² | At 8:00 on the _last_ workday¹ of this year's N-th quarter² | +| `"Q"` | After N-th quarter² | At 8:00 on the _last_ workday¹ of next year's N-th quarter² | + +¹ A workday is a Monday, Tuesday, Wednesday, Thursday, or Friday. +² A year has four quarters, each with three months: January/February/March, April/May/June, July/August/September, and October/November/December. diff --git a/exercises/practice/swift-scheduling/.docs/introduction.md b/exercises/practice/swift-scheduling/.docs/introduction.md new file mode 100644 index 000000000..2322f813f --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +This week, it is your turn to take notes in the department's planning meeting. +In this meeting, your boss will set delivery dates for all open work items. +Annoyingly, instead of specifying the _actual_ delivery dates, your boss will only _describe them_ in an abbreviated format. +As many of your colleagues won't be familiar with this corporate lingo, you'll need to convert these delivery date descriptions to actual delivery dates. diff --git a/exercises/practice/swift-scheduling/.formatter.exs b/exercises/practice/swift-scheduling/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/exercises/practice/swift-scheduling/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/exercises/practice/swift-scheduling/.gitignore b/exercises/practice/swift-scheduling/.gitignore new file mode 100644 index 000000000..4febf1809 --- /dev/null +++ b/exercises/practice/swift-scheduling/.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"). +anagram-*.tar + diff --git a/exercises/practice/swift-scheduling/.meta/config.json b/exercises/practice/swift-scheduling/.meta/config.json new file mode 100644 index 000000000..c0b179431 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/swift_scheduling.ex" + ], + "test": [ + "test/swift_scheduling_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Convert delivery date descriptions to actual delivery dates." +} diff --git a/exercises/practice/swift-scheduling/.meta/example.ex b/exercises/practice/swift-scheduling/.meta/example.ex new file mode 100644 index 000000000..1e0f9f3e1 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/example.ex @@ -0,0 +1,134 @@ +defmodule SwiftScheduling do + @spec delivery_date(NaiveDateTime.t(), String.t()) :: NaiveDateTime.t() + def delivery_date(meeting_date, description) do + cond do + description == "NOW" -> now(meeting_date) + description == "ASAP" -> asap(meeting_date) + description == "EOW" -> eow(meeting_date) + match = Regex.run(~r[^(\d+)M$], description) -> nth_month(meeting_date, match) + match = Regex.run(~r[^Q(\d+)$], description) -> nth_quarter(meeting_date, match) + end + end + + defp now(meeting_date) do + NaiveDateTime.add(meeting_date, 2, :hour) + end + + defp asap(meeting_date) do + meet_today? = + meeting_date + |> NaiveDateTime.to_time() + |> Time.compare(~T[13:00:00]) + |> Kernel.==(:lt) + + [new_date, new_time] = + if meet_today? do + [meeting_date |> NaiveDateTime.to_date(), ~T[17:00:00]] + else + [ + meeting_date + |> NaiveDateTime.to_date() + |> Date.add(1), + ~T[13:00:00] + ] + end + + NaiveDateTime.new!(new_date, new_time) + end + + defp eow(meeting_date) do + before_thursday? = + meeting_date + |> NaiveDateTime.to_date() + |> Date.day_of_week() + |> Kernel.<(4) + + sunday = + meeting_date + |> NaiveDateTime.to_date() + |> Date.end_of_week() + + [new_date, new_time] = + if before_thursday? do + friday = Date.add(sunday, -2) + [friday, ~T[17:00:00]] + else + [sunday, ~T[20:00:00]] + end + + NaiveDateTime.new!(new_date, new_time) + end + + defp nth_month(meeting_date, [_, month]) do + month_number = String.to_integer(month) + schedule_for_this_year? = meeting_date.month < month_number + + new_time = ~T[08:00:00] + + nth_month_this_year = %{NaiveDateTime.to_date(meeting_date) | month: month_number} + + new_date = + if schedule_for_this_year? do + first_workday_of_month(nth_month_this_year) + else + nth_month_next_year = nth_month_this_year |> shift_year(1) + first_workday_of_month(nth_month_next_year) + end + + NaiveDateTime.new!(new_date, new_time) + end + + defp nth_quarter(meeting_date, [_, quarter]) do + quarter_number = String.to_integer(quarter) + schedule_for_this_year? = meeting_date.month <= quarter_number * 3 + + new_time = ~T[08:00:00] + + last_month_of_nth_quarter_this_year = %{ + NaiveDateTime.to_date(meeting_date) + | month: quarter_number * 3 + } + + new_date = + if schedule_for_this_year? do + last_workday_of_month(last_month_of_nth_quarter_this_year) + else + last_month_of_nth_quarter_next_year = + last_month_of_nth_quarter_this_year + |> shift_year(1) + + last_workday_of_month(last_month_of_nth_quarter_next_year) + end + + NaiveDateTime.new!(new_date, new_time) + end + + defp first_workday_of_month(date) do + first_day_of_month = + date + |> Date.beginning_of_month() + + case Date.day_of_week(first_day_of_month) do + n when n in 1..5 -> first_day_of_month + 6 -> Date.add(first_day_of_month, 2) + 7 -> Date.add(first_day_of_month, 1) + end + end + + defp last_workday_of_month(date) do + first_day_of_month = + date + |> Date.end_of_month() + + case Date.day_of_week(first_day_of_month) do + n when n in 1..5 -> first_day_of_month + 6 -> Date.add(first_day_of_month, -1) + 7 -> Date.add(first_day_of_month, -2) + end + end + + # Date.shift is only available from 1.17 + defp shift_year(date, n) do + %{date | year: date.year + n} + end +end diff --git a/exercises/practice/swift-scheduling/.meta/tests.toml b/exercises/practice/swift-scheduling/.meta/tests.toml new file mode 100644 index 000000000..7cc3e4158 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/tests.toml @@ -0,0 +1,58 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1d0e6e72-f370-408c-bc64-5dafa9c6da73] +description = "NOW translates to two hours later" + +[93325e7b-677d-4d96-b017-2582af879dc2] +description = "ASAP before one in the afternoon translates to today at five in the afternoon" + +[cb4252a3-c4c1-41f6-8b8c-e7269733cef8] +description = "ASAP at one in the afternoon translates to tomorrow at one in the afternoon" + +[6fddc1ea-2fe9-4c60-81f7-9220d2f45537] +description = "ASAP after one in the afternoon translates to tomorrow at one in the afternoon" + +[25f46bf9-6d2a-4e95-8edd-f62dd6bc8a6e] +description = "EOW on Monday translates to Friday at five in the afternoon" + +[0b375df5-d198-489e-acee-fd538a768616] +description = "EOW on Tuesday translates to Friday at five in the afternoon" + +[4afbb881-0b5c-46be-94e1-992cdc2a8ca4] +description = "EOW on Wednesday translates to Friday at five in the afternoon" + +[e1341c2b-5e1b-4702-a95c-a01e8e96e510] +description = "EOW on Thursday translates to Sunday at eight in the evening" + +[bbffccf7-97f7-4244-888d-bdd64348fa2e] +description = "EOW on Friday translates to Sunday at eight in the evening" + +[d651fcf4-290e-407c-8107-36b9076f39b2] +description = "EOW translates to leap day" + +[439bf09f-3a0e-44e7-bad5-b7b6d0c4505a] +description = "2M before the second month of this year translates to the first workday of the second month of this year" + +[86d82e83-c481-4fb4-9264-625de7521340] +description = "11M in the eleventh month translates to the first workday of the eleventh month of next year" + +[0d0b8f6a-1915-46f5-a630-1ff06af9da08] +description = "4M in the ninth month translates to the first workday of the fourth month of next year" + +[06d401e3-8461-438f-afae-8d26aa0289e0] +description = "Q1 in the first quarter translates to the last workday of the first quarter of this year" + +[eebd5f32-b16d-4ecd-91a0-584b0364b7ed] +description = "Q4 in the second quarter translates to the last workday of the fourth quarter of this year" + +[c920886c-44ad-4d34-a156-dc4176186581] +description = "Q3 in the fourth quarter translates to the last workday of the third quarter of next year" diff --git a/exercises/practice/swift-scheduling/lib/swift_scheduling.ex b/exercises/practice/swift-scheduling/lib/swift_scheduling.ex new file mode 100644 index 000000000..ea626fa82 --- /dev/null +++ b/exercises/practice/swift-scheduling/lib/swift_scheduling.ex @@ -0,0 +1,8 @@ +defmodule SwiftScheduling do + @doc """ + Convert delivery date descriptions to actual delivery dates, based on when the meeting started. + """ + @spec delivery_date(NaiveDateTime.t(), String.t()) :: NaiveDateTime.t() + def delivery_date(meeting_date, description) do + end +end diff --git a/exercises/practice/swift-scheduling/mix.exs b/exercises/practice/swift-scheduling/mix.exs new file mode 100644 index 000000000..67badac50 --- /dev/null +++ b/exercises/practice/swift-scheduling/mix.exs @@ -0,0 +1,28 @@ +defmodule SwiftScheduling.MixProject do + use Mix.Project + + def project do + [ + app: :swift_scheduling, + version: "0.1.0", + # elixir: "~> 1.8", + 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/exercises/practice/swift-scheduling/test/swift_scheduling_test.exs b/exercises/practice/swift-scheduling/test/swift_scheduling_test.exs new file mode 100644 index 000000000..855162c9b --- /dev/null +++ b/exercises/practice/swift-scheduling/test/swift_scheduling_test.exs @@ -0,0 +1,153 @@ +defmodule SwiftSchedulingTest do + use ExUnit.Case + + # @tag :pending + test "NOW translates to two hours later" do + meeting_date = ~N[2012-02-13T09:00:00] + description = "NOW" + expected_output = ~N[2012-02-13T11:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "ASAP before one in the afternoon translates to today at five in the afternoon" do + meeting_date = ~N[1999-06-03T09:45:00] + description = "ASAP" + expected_output = ~N[1999-06-03T17:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "ASAP at one in the afternoon translates to tomorrow at one in the afternoon" do + meeting_date = ~N[2008-12-21T13:00:00] + description = "ASAP" + expected_output = ~N[2008-12-22T13:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "ASAP after one in the afternoon translates to tomorrow at one in the afternoon" do + meeting_date = ~N[2008-12-21T14:50:00] + description = "ASAP" + expected_output = ~N[2008-12-22T13:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "EOW on Monday translates to Friday at five in the afternoon" do + meeting_date = ~N[2025-02-03T16:00:00] + description = "EOW" + expected_output = ~N[2025-02-07T17:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "EOW on Wednesday translates to Friday at five in the afternoon" do + meeting_date = ~N[2005-09-14T11:00:00] + description = "EOW" + expected_output = ~N[2005-09-16T17:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "EOW on Thursday translates to Sunday at eight in the evening" do + meeting_date = ~N[2011-05-19T08:30:00] + description = "EOW" + expected_output = ~N[2011-05-22T20:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "EOW on Friday translates to Sunday at eight in the evening" do + meeting_date = ~N[2022-08-05T14:00:00] + description = "EOW" + expected_output = ~N[2022-08-07T20:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "EOW translates to leap day" do + meeting_date = ~N[2008-02-25T10:30:00] + description = "EOW" + expected_output = ~N[2008-02-29T17:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "2M before the second month of this year translates to the first workday of the second month of this year" do + meeting_date = ~N[2007-01-02T14:15:00] + description = "2M" + expected_output = ~N[2007-02-01T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "11M in the eleventh month translates to the first workday of the eleventh month of next year" do + meeting_date = ~N[2013-11-21T15:30:00] + description = "11M" + expected_output = ~N[2014-11-03T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "4M in the ninth month translates to the first workday of the fourth month of next year" do + meeting_date = ~N[2019-11-18T15:15:00] + description = "4M" + expected_output = ~N[2020-04-01T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "Q1 in the first quarter translates to the last workday of the first quarter of this year" do + meeting_date = ~N[2003-01-01T10:45:00] + description = "Q1" + expected_output = ~N[2003-03-31T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "Q4 in the second quarter translates to the last workday of the fourth quarter of this year" do + meeting_date = ~N[2001-04-09T09:00:00] + description = "Q4" + expected_output = ~N[2001-12-31T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end + + @tag :pending + test "Q3 in the fourth quarter translates to the last workday of the third quarter of next year" do + meeting_date = ~N[2022-10-06T11:00:00] + description = "Q3" + expected_output = ~N[2023-09-29T08:00:00] + + output = SwiftScheduling.delivery_date(meeting_date, description) + assert output == expected_output + end +end diff --git a/exercises/practice/swift-scheduling/test/test_helper.exs b/exercises/practice/swift-scheduling/test/test_helper.exs new file mode 100644 index 000000000..35fc5bff8 --- /dev/null +++ b/exercises/practice/swift-scheduling/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)