Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.9", override: true},
{:timex, "~> 3.7.11"},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Upon completing this lesson, a student should be able to answer the following questions.
- How do we represent dates and times using Date, Time, and DateTime structs?
- How do we manipulate dates and times using Date, Time, and DateTime module functions?
- How can we format a date/time for display purposes?
- How do we get the current time in a particular timezone?
UTC (Coordinated Universal Time) is a standardized timezone. Typically we store times in UTC and then convert UTC to a desired timezone when necessary. This ensures consistency.
The Date module contains functionality related to calendar dates. Date structs store the year
, month
, and day
and some other related fields.
{:ok, date} = Date.new(2000, 10, 1)
IO.inspect(date.year, label: "year")
IO.inspect(date.month, label: "month")
IO.inspect(date.day, label: "day")
The Time module contains functionality related to times. Time structs store hour
, minute
, second
.
{:ok, time} = Time.new(12, 30, 10)
IO.inspect(time.hour, label: "hour")
IO.inspect(time.minute, label: "minute")
IO.inspect(time.second, label: "second")
DateTime is a hybrid of Date and Time, so it can represent both a calendar day, and a time of day. The DateTime module contains functionality related to datetimes. The DateTime struct stores fields to represent both date values, and time values.
{:ok, datetime} = DateTime.new(date, time)
IO.inspect(datetime.year, label: "year")
IO.inspect(datetime.month, label: "month")
IO.inspect(datetime.day, label: "day")
IO.inspect(datetime.hour, label: "hour")
IO.inspect(datetime.minute, label: "minute")
IO.inspect(datetime.second, label: "second")
Timex is a library that makes it easier to manipulate dates/times. It also has timezone support, which Elixir does not provide out of the box.
If you're planning on frequently manipulating dates and times, or need to use multiple timezones, then Timex might be a useful library to include in future projects.
We can create the Date, Time, and DateTime structs through an alternative sigil syntax using ~D[]
, ~T[]
, and ~U
.
date = ~D[2000-10-01]
time = ~T[12:30:10]
# The Z Offset Specifies The Timezone Offset. Z Is Zero For UTC.
datetime = ~U[2000-10-01 12:30:10Z]
Time and timezones can get very complicated. It's not within the scope of this course to delve deeply into that complexity.
This reading is intended as a primer to working with dates, times, and timezones. You will need to consult the documentation and do your own research to go deeper.
YouTube.new("https://www.youtube.com/watch?v=-5wpm-gesOY")
Generally, we deal with UTC (Coordinated Universal Time) which is a standardized timezone, and then convert UTC time to the client's timezone only when necessary.
If you work the other way around, constantly dealing with many different timezones, it's easy to make mistakes converting them from one to another.
There are many built-in modules for dealing with time, in addition to common external libraries such as Timex.
Generally, you will most commonly work with the DateTime struct.
A DateTime struct is a snapshot of a date and time in a given timezone.
You can retrieve the current DateTime with DateTime.utc_now/0
.
DateTime.utc_now()
You'll notice a string output similar to ~U[2022-04-27 02:13:29.306614Z]
. This is a sigil.
Sigils are a textual representation of data. You can find a full explanation of sigils on the Elixir Lang Sigil Getting Started Guide
Sigils use a tilda ~
and a character for the type of data they represent. For example,
the date above uses the ~U
sigil to represent a UTC datetime.
Under the hood, DateTime is a struct, we can see the full data contained in the struct below.
DateTime contains date information such as :day
, :month
, and :year
. DateTime also contains time information such as :hour
, :minute
, :second
and even :microsecond
.
Map.from_struct(DateTime.utc_now())
In order to understand DateTime, we also need to consider two other structs Date and Time.
A DateTime struct is built using a both a Date and a Time struct. Date represents the calendar
date with :year
, :month
, and :day
. Time represents the time of day with :hour
, :minute
, and :second
.
{:ok, date} = Date.new(2022, 6, 24)
{:ok, time} = Time.new(12, 30, 0)
{:ok, datetime} = DateTime.new(date, time)
Here we can see the DateTime struct contains all of the information from the time
and date
we created above, as
well as utc timezone information.
Map.from_struct(datetime)
DateTime is also timezone aware unlike it's counterpart NaiveDateTime. You can
see that NaiveDateTime is missing timezone information such as :time_zone
and :zone_abbr
.
{:ok, naive_datetime} = NaiveDateTime.new(date, time)
Map.from_struct(naive_datetime)
As a shorter syntax, it's common to use the ~T
sigil for the Time struct and the ~D
sigil for the Date struct.
{:ok, datetime} = DateTime.new(~D[2022-06-24], ~T[12:30:00])
You could also create the DateTime directly using the ~U
sigil.
datetime = ~U[2022-06-24 12:30:00Z]
Under the hood, this is still the same DateTime struct.
Map.from_struct(datetime)
Like any struct or map, you can access all of these properties directly using dot notation.
datetime.year
When you want to display time information you can use the Calendar module's strftime/3
function.
Calendar.strftime(DateTime.utc_now(), "%y-%m-%d %I:%M:%S %p")
The strftime/3
function accepts a Date, Time, DateTime, or NaiveDateTime and uses the
information in the struct to display a formatted string.
You can use a percent %
symbol and then one of the accepted format options
to display information from the given struct.
For example, you could display the current month with %B
.
Calendar.strftime(DateTime.utc_now(), "%B")
By default, Elixir does not have any timezone data. You'll notice that the current DateTime is for utc, not your local timezone, so the time displayed likely doesn't match your own.
Calendar.strftime(DateTime.utc_now(), "%c")
Elixir can be configured with timezone data, however it is beyond the scope of this current lesson.
For more information, you can check out the publicly available Elixir School's lesson on Working with timezones.
The DateTime module contains functions for timezone aware dates and times.
Here are a few common functions to get you started.
add/4
add time to an existing DateTime struct.compare/2
compare two DateTime structs to see if one is before, after, or the same as another.diff/3
determine the time between two DateTime structs.new/4
create a new DateTime struct and return an{:ok, datetime}
tuple.new!/4
create a new DateTime struct or raise an error.utc_now/2
get the current utc DateTime.
Versions of these functions also exist for the Date module if you do not need to consider the time, but are only concerned about the calendar date.
Create a DateTime for April 19, 1938 at noon (12pm).
Example Solution
Using sigils.
~U[1938-04-19 12:00:00Z]
Using functions.
DateTime.new!(Date.new!(1938, 4, 19), Time.new!(12, 0, 0))
Enter your solution below.
If you solved the challenge above using sigils, complete it again using functions. If you solved the challenge using functions, then complete it again using sigils.
Enter your new solution below.
DateTime.new!(~D[1938-04-18])
Create a DateTime for today at the current time in universal time.
Example Solution
DateTime.utc_now()
Enter your solution below.
Use DateTime.add/4 to add one day to the current DateTime. This creates a new DateTime for tomorrow at the current time.
Example Solution
day_in_seconds = 60 * 60 * 24
DateTime.add(DateTime.utc_now(), day_in_seconds)
Enter your solution below
Use DateTime.compare/2 to compare the DateTime below with the current DateTime. The date
below should be :lt
(less than) the current DateTime.
Example Solution
date = DateTime.new!(~D[2020-01-01], ~T[12:00:00])
DateTime.compare(date, DateTime.utc_now())
Enter your solution below.
date = DateTime.new!(~D[2020-01-01], ~T[12:00:00])
Use DateTime.diff/3 to determine how many seconds are between 2000-01-01
and 2010-01-01
.
Example Solution
start = DateTime.new!(~D[2000-01-01], ~T[12:00:00])
finish = DateTime.new!(~D[2010-01-01], ~T[12:00:00])
DateTime.diff(finish, start)
Enter your solution below.
The Timex external library provides a number of useful features for working with dates in Elixir. It is not built-in to Elixir and needs to be installed in a project.
Timex is not currently within the scope of this course, however we have installed Timex specifically in this livebook file only. You will not normally have access to Timex as the purpose of this section is to build familiarity with DateTime.
Timex uses the tzdata timezone database for Elixir. You can create a timezone aware DateTime using Timex.now/1 and the name of a timezone.
Timex.now("Asia/Kabul")
You can find the full list of available timezones using Tzdata.zone_list/0
.
Tzdata.zone_list()
Many websites host the full list of timezones such as timezonedb.com
Use Timex.now/1 to create a datetime for your current timezone. Compare it with your local time to ensure it is correct.
Consider the following resource(s) to deepen your understanding of the topic.
DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.
Run git status
to ensure there are no undesirable changes.
Then run the following in your command line from the curriculum
folder to commit your progress.
$ git add .
$ git commit -m "finish Dates And Times reading"
$ git push
We're proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.
We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.