Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Dates and Times

datetime.livemd

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

Return Home Report An Issue

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 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.

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"