Skip to content

Commit

Permalink
Add support for DateTime and NaiveDateTime (#87)
Browse files Browse the repository at this point in the history
* Added DateTime support

* Standardized Faker.Date with Faker.DateTime

* Updated CHANGELOG

* Updated USAGE.md

* Don't test against now() for DateTime.t

* Fixed test for Faker.DateTime.forward/1

* Removed private now() function

* Cleaned up Faker.DateTime.between\2 implementation; fixed tests

* Added Faker.DateTime.between(Date.T, Date.t) :: DateTime.t implementation

* Fixed comments

* Added NaiveDateTime support for Faker.DateTime.between/2

* Fixed comment for NaiveDateTime version of Faker.DateTime.between/2

* Removed unused variable

* Added NaiveDateTime support

* Updated CHANGELOG.md and USAGE.md

* Use NaiveDateTime in USAGE

* Added copy and paste examples for Faker.DateTime.between/2 and Faker.NaiveDateTime.between/2
  • Loading branch information
anthonator authored and Igor Kapkov committed Apr 25, 2017
1 parent 8d8dd3e commit 5d685da
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Change log itself follows [Keep a CHANGELOG](http://keepachangelog.com) format.
* `Faker.Code.iban` [@tobyhinloopen][]
* `Faker.Beer` [@orieken][]
* `Faker.Date` [@tobyhinloopen][]
* `Faker.Date.between` [@anthonator][]
* `Faker.DateTime` [@anthonator][]
* `Faker.NaiveDateTime` [@anthonator][]
* `Faker.Nato` [@petehamilton][]
* `Faker.Pokemon` [@orieken][]
* `Faker.App.semver` [@wojtekmach][]
Expand Down
40 changes: 40 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- [Faker.Commerce](#fakercommerce)
- [Faker.Company](#fakercompany)
- [Faker.Date](#fakerdate)
- [Faker.DateTime](#fakerdatetime)
- [Faker.NaiveDateTime](#fakernaivedatetime)
- [Faker.File](#fakerfile)
- [Faker.Internet](#fakerinternet)
- [Faker.Internet.UserAgent](#fakerinternetuseragent)
Expand Down Expand Up @@ -177,6 +179,44 @@ Faker.Date.date_of_birth(10..19) #=> ~D[2004-05-15]
Faker.Date.forward(4) #=> ~D[2016-12-25]

Faker.Date.backward(4) #=> ~D[2016-12-20]

Faker.Date.between(~D[2016-12-20], ~D[2016-12-25]) #=> ~D[2016-12-23]
```

### Faker.DateTime

```elixir
Faker.DateTime.forward(4)
#=> %DateTime{calendar: Calendar.ISO, day: 25, hour: 6,
#=> microsecond: {922180, 6}, minute: 2, month: 12, second: 17,
#=> std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0, year: 2016,
#=> zone_abbr: "UTC"}

Faker.DateTime.backward(4)
#=> %DateTime{calendar: Calendar.ISO, day: 20, hour: 6,
#=> microsecond: {922180, 6}, minute: 2, month: 12, second: 17,
#=> std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0, year: 2016,
#=> zone_abbr: "UTC"}

from = DateTime.utc_now()
to = DateTime.utc_now() |> DateTime.to_unix
to = to + 86400 * 10 |> DateTime.from_unix!

Faker.DateTime.between(from, to)
#=> %DateTime{calendar: Calendar.ISO, day: 23, hour: 6,
#=> microsecond: {922180, 6}, minute: 2, month: 12, second: 17,
#=> std_offset: 0, time_zone: "Etc/UTC", utc_offset: 0, year: 2016,
#=> zone_abbr: "UTC"}
```

### Faker.NaiveDateTime

```elixir
Faker.NaiveDateTime.forward(4) #=> ~N[2016-12-25 06:02:17.922180]

Faker.NaiveDateTime.backward(4) #=> ~N[2016-12-20 06:02:17.922180]

Faker.NaiveDateTime.between(~N[2016-12-20 00:00:00], ~N[2016-12-25 00:00:00]) #=> ~N[2016-12-23 06:02:17.922180]
```

### Faker.File
Expand Down
20 changes: 10 additions & 10 deletions lib/faker/date.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ if Version.match?(System.version(), ">= 1.3.0") do
Functions for generating dates
"""

@seconds_per_day 86400

@doc """
Returns a random date of birth for a person with an age specified by a number or range
Expand Down Expand Up @@ -47,15 +45,17 @@ if Version.match?(System.version(), ">= 1.3.0") do
"""
@spec forward(integer) :: Date.t
def forward(days) do
unix_now =
DateTime.utc_now()
|> DateTime.to_unix()

sign = if days < 0, do: -1, else: 1
Faker.DateTime.forward(days)
|> DateTime.to_date
end

unix_now + sign * @seconds_per_day * :crypto.rand_uniform(1, abs(days))
|> DateTime.from_unix!()
|> DateTime.to_date()
@doc """
Returns a random date between two dates
"""
@spec between(Date.t, Date.t) :: Date.t
def between(from, to) do
Faker.DateTime.between(from, to)
|> DateTime.to_date
end
end
end
79 changes: 79 additions & 0 deletions lib/faker/datetime.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
if Version.match?(System.version(), ">= 1.3.0") do
defmodule Faker.DateTime do
@microseconds_per_day 86400000000

@doc """
Returns a random date in the past up to N days, today not included
"""
@spec backward(integer) :: DateTime.t
def backward(days) do
forward(-days)
end

@doc """
Returns a random date in the future up to N days, today not included
"""
@spec forward(integer) :: DateTime.t
def forward(days) do
sign = if days < 0, do: -1, else: 1

today = DateTime.utc_now() |> to_timestamp
from = today + sign * @microseconds_per_day # add or subtract extra day to avoid returning today
to = from + @microseconds_per_day * days

unix_between(from, to)
end

@doc """
Returns a random date & time between two dates
"""
@spec between(Date.t, Date.t) :: DateTime.t
def between(%Date{} = from, %Date{} = to) do
between(date_to_datetime(from), date_to_datetime(to))
end

@doc """
Returns a random `DateTime.t` between two `NaiveDateTime.t`'s
"""
@spec between(NaiveDateTime.t, NaiveDateTime.t) :: DateTime.t
def between(%NaiveDateTime{} = from, %NaiveDateTime{} = to) do
between(naivedatetime_to_datetime(from), naivedatetime_to_datetime(to))
end

@doc """
Returns a random `DateTime.t` between two `DateTime.t`'s
"""
@spec between(DateTime.t, DateTime.t) :: DateTime.t
def between(from, to) do
unix_between(to_timestamp(from), to_timestamp(to))
end

# private

defp date_to_datetime(date) do
%DateTime{calendar: Calendar.ISO, day: date.day, hour: 0, minute: 0,
month: date.month, second: 0, time_zone: "Etc/UTC",
utc_offset: 0, std_offset: 0, year: date.year, zone_abbr: "UTC"}
end

defp naivedatetime_to_datetime(naivedatetime) do
%DateTime{calendar: naivedatetime.calendar, day: naivedatetime.day,
hour: naivedatetime.hour, minute: naivedatetime.minute,
month: naivedatetime.month, second: naivedatetime.second,
time_zone: "Etc/UTC", utc_offset: 0, std_offset: 0,
year: naivedatetime.year, zone_abbr: "UTC"}
end

defp to_timestamp(datetime) do
DateTime.to_unix(datetime, :microseconds)
end

defp unix_between(from, to) do
diff = to - from
sign = if diff < 0, do: -1, else: 1

from + sign * :crypto.rand_uniform(0, abs(diff))
|> DateTime.from_unix!(:microseconds)
end
end
end
29 changes: 29 additions & 0 deletions lib/faker/naivedatetime.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
if Version.match?(System.version(), ">= 1.3.0") do
defmodule Faker.NaiveDateTime do
@doc """
Returns a random date in the past up to N days, today not included
"""
@spec backward(integer) :: NaiveDateTime.t
def backward(days) do
forward(-days)
end

@doc """
Returns a random date in the future up to N days, today not included
"""
@spec forward(integer) :: NaiveDateTime.t
def forward(days) do
Faker.DateTime.forward(days)
|> DateTime.to_naive
end

@doc """
Returns a random `NaiveDateTime.t` between two `NaiveDateTime.t`'s
"""
@spec between(NaiveDateTime.t, NaiveDateTime.t) :: NaiveDateTime.t
def between(from, to) do
Faker.DateTime.between(from, to)
|> DateTime.to_naive
end
end
end
9 changes: 9 additions & 0 deletions test/faker/date_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ if Version.match?(System.version(), ">= 1.3.0") do
assert now().year > year || now().month > month || now().day > day
end

test "between/2" do
from_date = ~D[2017-01-01]
to_date = ~D[2017-01-10]
between_date = Faker.Date.between(from_date, to_date)
assert %Date{year: year, month: month, day: day} = between_date
assert from_date.year <= year || from_date.month <= month || from_date.day <= day
assert to_date.year >= year || from_date.month >= month || to_date.day >= day
end

defp age(%Date{year: year, month: month, day: day}) do
%Date{ year: current_year, month: current_month, day: current_day } = now()
already_aged_this_year = current_month > month || current_month == month && day >= current_day
Expand Down
87 changes: 87 additions & 0 deletions test/faker/datetime_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
if Version.match?(System.version(), ">= 1.3.0") do
defmodule DateTimeTest do
use ExUnit.Case, async: true

test "forward/1" do
now = DateTime.utc_now()
forwarded_date = Faker.DateTime.forward(10)
assert %DateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = forwarded_date
assert now.year < year || now.month < month
|| now.hour < hour || now.minute < minute
|| now.day < day || now.second < second
|| now.microsecond < microsecond
end

test "backward/1" do
now = DateTime.utc_now()
backward_date = Faker.DateTime.backward(10)
assert %DateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = backward_date
assert now.year > year || now.month > month
|| now.hour > hour || now.minute > minute
|| now.day > day || now.second > second
|| now.microsecond > microsecond
end

test "between/2 for Date.t" do
from_date = DateTime.utc_now() |> DateTime.to_date
to_date = Faker.DateTime.forward(50) |> DateTime.to_date
between_date = Faker.DateTime.between(from_date, to_date)
assert %DateTime{year: year, month: month, day: day}
= between_date
assert from_date.year <= year || from_date.month <= month
|| from_date.day <= day
assert to_date.year >= year || to_date.month >= month
|| to_date.day >= day
end

test "between/2 for NaiveDateTime.t" do
from_datetime = DateTime.utc_now()
from_date = from_datetime |> DateTime.to_date
from_time = from_datetime |> DateTime.to_time
{:ok, from_naivedatetime} = NaiveDateTime.new(from_date, from_time)

to_datetime = Faker.DateTime.forward(50)
to_date = to_datetime |> DateTime.to_date
to_time = to_datetime |> DateTime.to_time
{:ok, to_naivedatetime} = NaiveDateTime.new(to_date, to_time)

between_date = Faker.DateTime.between(from_naivedatetime, to_naivedatetime)
assert %DateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = between_date
assert from_naivedatetime.year <= year || from_naivedatetime.month <= month
|| from_naivedatetime.hour <= hour || from_naivedatetime.minute <= minute
|| from_naivedatetime.day <= day || from_naivedatetime.second <= second
|| from_naivedatetime.microsecond <= microsecond
assert to_naivedatetime.year >= year || to_naivedatetime.month >= month
|| to_naivedatetime.hour >= hour || to_naivedatetime.minute >= minute
|| to_naivedatetime.day >= day || to_naivedatetime.second >= second
|| to_naivedatetime.microsecond >= microsecond
end

test "between/2 for DateTime.t" do
from_date = DateTime.utc_now()
to_date = Faker.DateTime.forward(50)
between_date = Faker.DateTime.between(from_date, to_date)
assert %DateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = between_date
assert from_date.year <= year || from_date.month <= month
|| from_date.hour <= hour || from_date.minute <= minute
|| from_date.day <= day || from_date.second <= second
|| from_date.microsecond <= microsecond
assert to_date.year >= year || to_date.month >= month
|| to_date.hour >= hour || to_date.minute >= minute
|| to_date.day >= day || to_date.second >= second
|| to_date.microsecond >= microsecond
end
end
end
65 changes: 65 additions & 0 deletions test/faker/naivedatetime_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
if Version.match?(System.version(), ">= 1.3.0") do
defmodule NaiveDateTimeTest do
use ExUnit.Case, async: true

test "forward/1" do
now_datetime = DateTime.utc_now()
now_date = now_datetime |> DateTime.to_date
now_time = now_datetime |> DateTime.to_time
{:ok, now} = NaiveDateTime.new(now_date, now_time)

forwarded_date = Faker.NaiveDateTime.forward(10)
assert %NaiveDateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = forwarded_date
assert now.year < year || now.month < month
|| now.hour < hour || now.minute < minute
|| now.day < day || now.second < second
|| now.microsecond < microsecond
end

test "backward/1" do
now_datetime = DateTime.utc_now()
now_date = now_datetime |> DateTime.to_date
now_time = now_datetime |> DateTime.to_time
{:ok, now} = NaiveDateTime.new(now_date, now_time)

backward_date = Faker.NaiveDateTime.backward(10)
assert %NaiveDateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = backward_date
assert now.year > year || now.month > month
|| now.hour > hour || now.minute > minute
|| now.day > day || now.second > second
|| now.microsecond > microsecond
end

test "between/2" do
from_datetime = DateTime.utc_now()
from_date = from_datetime |> DateTime.to_date
from_time = from_datetime |> DateTime.to_time
{:ok, from_naivedatetime} = NaiveDateTime.new(from_date, from_time)

to_datetime = Faker.DateTime.forward(50)
to_date = to_datetime |> DateTime.to_date
to_time = to_datetime |> DateTime.to_time
{:ok, to_naivedatetime} = NaiveDateTime.new(to_date, to_time)

between_date = Faker.DateTime.between(from_naivedatetime, to_naivedatetime)
assert %DateTime{
year: year, month: month, day: day, hour: hour, minute: minute,
second: second, microsecond: microsecond
} = between_date
assert from_naivedatetime.year <= year || from_naivedatetime.month <= month
|| from_naivedatetime.hour <= hour || from_naivedatetime.minute <= minute
|| from_naivedatetime.day <= day || from_naivedatetime.second <= second
|| from_naivedatetime.microsecond <= microsecond
assert to_naivedatetime.year >= year || to_naivedatetime.month >= month
|| to_naivedatetime.hour >= hour || to_naivedatetime.minute >= minute
|| to_naivedatetime.day >= day || to_naivedatetime.second >= second
|| to_naivedatetime.microsecond >= microsecond
end
end
end

0 comments on commit 5d685da

Please sign in to comment.