Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add concept exercise: dates / library-fees #412

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@
],
"status": "wip"
},
{
"uuid": "a35ef99d-41d1-42c9-8f14-ed98b8bd294b",
"name": "Library Fees",
"slug": "library-fees",
"concepts": [
"booleans-are-numbers",
"dates"
],
"prerequisites": [
"conditionals"
],
"status": "wip"
},
{
"uuid": "4b3e6780-b566-4315-a7b0-45500883f050",
"name": "Name Badge",
Expand Down
76 changes: 76 additions & 0 deletions exercises/concept/library-fees/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Instructions

Your librarian friend has asked you to extend her library software to automatically calculate late fees.
Her current system stores the exact date and time of a book checkout as an [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime string.
She runs a local library in a small town in Ghana, which uses the GMT timezone (UTC +0), doesn't use daylight saving time, and doesn't need to worry about other timezones.

## 1. Determine if a book was checked out before noon

If a book was checked out before noon, the reader has 28 days to return it.
If it was checked out at or after noon, it's 29 days.

Implement the `before_noon` function.
It should take a `DateTime` and return a boolean.

```julia
julia> before_noon(DateTime("2021-01-12T08:23:03"))
true
```

## 2. Calculate the return datetime

Based on the checkout datetime, calculate the return date.

Implement the `return_date` function.
It should take a `DateTime` and return a `Date`, either 28 or 29 days later.

```julia
julia> return_date(DateTime("2020-11-28T15:55:33"))
2020-12-27
```

## 3. Determine how late the return of the book was

The library has a flat rate for late returns.
To be able to calculate the fee, we need to know how many days after the return date the book was actually returned.

Implement the `days_late` function.
It should take two `DateTime`s, the checkout datetime and the datetime when the book was returned.
If the planned return datetime is on an earlier or the same day as the actual return datetime, the function should return 0 days.
Otherwise, the function should return the difference between those two datetimes in days.
If the difference between those two datetimes is not an exact number of days, e.g. 3.5 days, round down to the nearest number of full days.

```julia
julia> days_late(DateTime("2020-12-27T15:55:33"), DateTime("2021-01-03T09:23:36"))
6 days
```

## 4. Determine if the book was returned on a monday or tuesday

The library has a special offer for returning books on Mondays and Tuesdays.

Implement the `fee_discount` function.
It should take a `DateTime` and return a boolean.

```julia
julia> fee_discount(DateTime("2021-01-03T13:30:45"))
false
```

## 5. Calculate the late fee

Implement the `late_fee` function.
It should take three arguments: two ISO8601 datetime strings, checkout datetime and actual return datetime, and the late fee for one day.
It should return the total late fee according to how late the actual return of the book was.

Include the special Monday & Tuesday offer. If you return the book on Monday or Tuesday, your late fee is 50% off, rounded down to the nearest natural number.

```elixir
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong language

# Sunday, 7 days late
julia> late_fee("2020-11-28T15:55:33", "2021-01-03T13:30:45", 100)
700.0

# one day later, Monday, 8 days late
julia> late_fee("2020-11-28T15:55:33", "2021-01-04T09:02:11", 100)
400.0
```
23 changes: 23 additions & 0 deletions exercises/concept/library-fees/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"blurb": "Learn how to handle dates in Julia by calculating library fees.",
"language_versions": "≥1.0",
"files": {
"exemplar": [
".meta/exemplar.jl"
],
"test": [
"runtests.jl"
],
"solution": [
"library-fees.jl"
]
},
"contributors": [],
"authors": [
"SaschaMann",
"cmcaine"
],
"forked_from": [
"elixir/library-fees"
]
}
54 changes: 54 additions & 0 deletions exercises/concept/library-fees/.meta/exemplar.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Dates

"""
before_noon(dt)

Return true if the given DateTime is in the forenoon.
"""
before_noon(dt) = Time(dt) < Time(Hour(12))

"""
return_date(checkout_dt)

Return the return date for a given checkout DateTime.
"""
function return_date(checkout_dt)
# Make use of booleans being numbers.
# before_noon(checkout_dt) will be treated as 0 if it's false and as 1 if it's true.
Date(checkout_dt + Day(28) + Day(!before_noon(checkout_dt)))
end

"""
days_late(return_dt, actual_return_dt)

Return the number of days that the book was return too late.
"""
function days_late(return_dt, actual_return_dt)
# If the book was returned before the return date, return 0 days
actual_return_dt < return_dt && return Day(0)

ms_to_d = 1000 * 60 * 60 * 24
Δdt = actual_return_dt - return_dt
Day(Δdt - (Δdt % ms_to_d))
end

"""
fee_discount(return_dt)

Return true if the Monday & Tuesday discount should be applied on the day of the given DateTime.
"""
fee_discount(return_dt) = Dates.ismonday(return_dt) || Dates.istuesday(return_dt)

"""
late_fee(checkout_time, actual_return_time, daily_fee)

Return the total late fee.
"""
function late_fee(checkout_time, actual_return_time, daily_fee)
return_dt = DateTime(return_date(DateTime(checkout_time)))
actual_return_dt = DateTime(actual_return_time)

delay = days_late(return_dt, actual_return_dt)

floor(daily_fee * Dates.value(delay) * (1 - 0.5 * fee_discount(actual_return_dt)))
end
Empty file.
103 changes: 103 additions & 0 deletions exercises/concept/library-fees/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Test

include("library-fees.jl")

@testset "1. Determine if a book was checked out before noon" begin
@testset "Return true if the given DateTime is before 12:00" begin
@test before_noon(DateTime("2020-06-06T11:59:59"))
end

@testset "Return false if the given DateTime is after 12:00" begin
@test !before_noon(DateTime("2021-01-03T12:01:01"))
end

@testset "Return false if the given DateTime is exactly 12:00" begin
@test !before_noon(DateTime("2018-11-17T12:00:00"))
end
end

@testset "2. Calculate the return Date" begin
@testset "Add 28 days if the given DateTime is before 12:00" begin
@test return_date(DateTime("2020-02-14T11:59:59")) == Date("2020-03-13")
end

@testset "Add 29 days if the given DateTime is after 12:00" begin
@test return_date(DateTime("2021-01-03T12:01:01")) == Date("2021-02-01")
end

@testset "Add 29 days if the given DateTime is exactly at 12:00" begin
@test return_date(DateTime("2018-12-01T12:00:00")) == Date("2018-12-30")
end
end

@testset "3. Determine how late the return of the book was" begin
@testset "Return 0 days for identical datetimes" begin
@test days_late(DateTime("2021-02-01T12:00:00"), DateTime("2021-02-01T12:00:00")) == Day(0)
end

@testset "Return 0 days for identical dates, but different times" begin
@test days_late(DateTime("2019-03-11T13:50:00"), DateTime("2019-03-11T12:00:00")) == Day(0)
end

@testset "Return 0 when planned return date is later than actual return date" begin
@test days_late(DateTime("2020-12-03T09:50:00"), DateTime("2020-11-29T16:00:00")) == Day(0)
end

@testset "Return date difference in numbers of days when planned return date is earlier than actual return date" begin
@test days_late(DateTime("2020-06-12T09:50:00"), DateTime("2020-06-21T16:00:00")) == Day(9)
end
end

@testset "4. Determine if the book was returned on a monday or tuesday" begin
@testset "Mondays" begin
@test fee_discount(DateTime("2021-02-01T14:01:00"))
@test fee_discount(DateTime("2020-03-16T09:23:52"))
@test fee_discount(DateTime("2019-04-22T15:44:03"))
end

@testset "Tuesdays" begin
@test fee_discount(DateTime("2021-02-02T15:07:00"))
end

@testset "Other days" begin
# Friday
@test !fee_discount(DateTime("2020-03-14T08:54:51"))

# Sunday
@test !fee_discount(DateTime("2019-04-28T11:37:12"))
end
end

@testset "5. Calculate the late fee" begin
@testset "Return 0 if the book was returned less than 28 days after a morning checkout" begin
@test late_fee("2018-11-01T09:00:00", "2018-11-13T14:12:00", 123) == 0
end

@testset "Return 0 if the book was returned exactly 28 days after a morning checkout" begin
@test late_fee("2018-11-01T09:00:00", "2018-11-29T14:12:00", 123) == 0
end

@testset "Return the rate for one day if the book was returned exactly 29 days after a morning checkout" begin
@test late_fee("2018-11-01T09:00:00", "2018-11-30T14:12:00", 320) == 320
end

@testset "Return 0 if the book was returned less than 29 days after an afternoon checkout" begin
@test late_fee("2019-05-01T16:12:00", "2019-05-17T14:32:45", 400) == 0
end

@testset "Return 0 if the book was returned exactly 29 days after an afternoon checkout" begin
@test late_fee("2019-05-01T16:12:00", "2019-05-30T14:32:45", 313) == 0
end

@testset "Return the rate for one day if the book was returned exactly 30 days after an afternoon checkout" begin
@test late_fee("2019-05-01T16:12:00", "2019-05-31T14:32:45", 234) == 234
end

@testset "Multiply the number of days late by the rate for one day" begin
@test late_fee("2021-01-01T08:00:00", "2021-02-13T08:00:00", 111) == 111 * 15
end

@testset "Late fee is 50% off (rounded down) when the book is returned on a Monday" begin
@test late_fee("2021-01-01T08:00:00", "2021-02-15T08:00:00", 111) == floor(111 * 17 * 0.5)
end
end