Dates and Times
Mix.install([
{:kino, github: "livebook-dev/kino", override: true},
{:kino_lab, "~> 0.1.0-dev", github: "jonatanklosko/kino_lab"},
{:vega_lite, "~> 0.1.4"},
{:kino_vega_lite, "~> 0.1.1"},
{:benchee, "~> 0.1"},
{:ecto, "~> 3.7"},
{:math, "~> 0.7.0"},
{:faker, "~> 0.17.0"},
{:utils, path: "#{__DIR__}/../utils"},
{:timex, "~> 3.0"}
])
Navigation
Overview
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 you own research to go deeper.
Kino.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())
DateTime
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)
And like any struct, you can access all of these properties directly.
datetime.year
Display the Current Date
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.
DateTime Module Functions
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 existingDateTime
struct. -
compare/2
compare twoDateTime
structs to see if one is before, after, or the same as another. -
diff/3
determine the time between twoDateTime
structs. -
new/4
create a newDateTime
struct and return an{:ok, datetime}
tuple. -
new!/4
create a newDateTime
struct or raise an error. -
utc_now/2
get the current utcDateTime
.
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.
Your Turn
Use DateTime.new!/4
to create a DateTime
for April 18, 1938 at noon.
Replace nil
with your answer.
date = nil
Utils.feedback(:datetime_new, date)
Create a DateTime
for today at the current time.
Replace nil
with your answer.
today = nil
Utils.feedback(:datetime_today, today)
Create a DateTime
for tomorrow at the current time. You may wish to use DateTime.add/4
.
tomorrow = nil
Utils.feedback(:datetime_tomorrow, tomorrow)
Use DateTime.compare/2
to check if the current time is greater than the datetime
below. comparison
should be either :lt
or :gt
.
Replace nil
with your answer.
date = DateTime.new!(~D[1938-04-18], ~T[12:00:00])
comparison = nil
Utils.feedback(:datetime_compare, comparison)
Use DateTime.diff/3
to find the time difference from the first date to the second date. Your answer should
be a positive integer. Keep in mind that DateTime.diff/3
subtracts the first argument by the second argument.
Replace nil
with your answer.
date1 = DateTime.new!(~D[2000-01-01], ~T[12:00:00])
date2 = DateTime.new!(~D[2010-02-02], ~T[12:00:00])
difference = nil
Utils.feedback(:datetime_diff, difference)
Timex
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
Your Turn
Use Timex.now/1
to create a datetime for your current timezone. Compare it with your local time
to ensure it is correct.
Commit Your Progress
Run the following in your command line from the project folder to track and save your progress in a Git commit.
$ git add .
$ git commit -m "finish datetime section"