Introducing Julia/Working with dates and times

Previous page
Working with text files
Introducing Julia Next page
Plotting
Working with dates and times

Working with dates and times

edit

Functions for working with dates and times are provided in the standard package Dates. To use any of the time and date functions, you must do one of the following:

  • using Dates
  • import Dates

If you use import Dates functions, you’ll need to prefix every function with an explicit Dates., e.g. Dates.dayofweek(dt), as shown in this chapter. However, if you add the line using Dates to your code, this brings all exported Dates functions into Main, and they can be used without the Dates. prefix.

Types

edit

This diagram shows the relationship between the various types used to store Times, Dates, and DateTimes.

 
Shows the hierarchy of date and date-time types in Julia

Date, Time, and DateTimes

edit

There are three main datatypes available:

  • A Dates.Time object represents a precise moment of time in a day. It doesn't say anything about the day of the week, or the year, though. It's accurate to a nanosecond.
  • A Dates.Date object represents just a date: no time zones, no daylight saving issues, etc... It's accurate to, well, a day.
  • A Dates.DateTime object is a combination of a date and a time of day, and so it specifies an exact moment in time. It's accurate to a millisecond or so.

Use one of these constructors to make the type of object you want:

julia> rightnow = Dates.Time(Dates.now()) # a Dates.Time object
16:51:56.374
julia> birthday = Dates.Date(1997,3,15)   # a Dates.Date object
1997-03-15

julia> armistice = Dates.DateTime(1918,11,11,11,11,11) # a Dates.DateTime object
1918-11-11T11:11:11

The Dates.today() function returns a Date object for the current date:

julia> datetoday = Dates.today()
2014-09-02

The Dates.now() function returns a DateTime object for the current instant in time:

julia> datetimenow = Dates.now()
2014-09-02T08:20:07.437

(We used Dates.now() earlier to define rightnow, then converted it to a Dates.Time using Dates.Time().)

Sometimes you want UTC (the reference time for the world, without local adjustments for daylight savings):

julia> Dates.now(Dates.UTC)
2014-09-02T08:27:54.14

To create an object from a formatted string, use the DateTime() function in Dates, and supply a suitable format string that matches the formatting:

julia> Dates.DateTime("20140529 120000", "yyyymmdd HHMMSS")
2014-05-29T12:00:00

julia> Dates.DateTime("18/05/2009 16:12", "dd/mm/yyyy HH:MM")
2009-05-18T16:12:00

julia> vacation = Dates.DateTime("2014-09-02T08:20:07") # defaults to expecting ISO8601 format
2014-09-02T08:20:07

See Date Formatting below for more examples.

Date and time queries

edit

Once you have a date/time or date object, you can extract information from it with the following functions. For both date and datetime objects, you can obtain the year, month, day, and so on:

julia> Dates.year(birthday)
1997

julia> Dates.year(datetoday)
2014

julia> Dates.month(birthday)
3

julia> Dates.month(datetoday)
9

julia> Dates.day(birthday)
15

julia> Dates.day(datetoday)
2

and, for date/time objects:

julia> Dates.minute(now())
37

julia> Dates.hour(now())
16

julia> Dates.second(now())
8
julia> Dates.minute(rightnow)
37

julia> Dates.hour(rightnow)
16

julia> Dates.second(rightnow)
8

There's also a bunch of other useful ones:

julia> Dates.dayofweek(birthday)
6

julia> Dates.dayname(birthday)
"Saturday"

julia> Dates.yearmonth(now())
(2014,9)

julia> Dates.yearmonthday(birthday)
(1997,3,15)

julia> Dates.isleapyear(birthday)
false

julia> Dates.daysofweekinmonth(datetoday)
5

julia> Dates.monthname(birthday)
"March"

julia> Dates.monthday(now())
(9,2)

julia> Dates.dayofweekofmonth(birthday)
3 

Two of those functions are very similarly named: the Dates.daysofweekinmonth() (days of week in month) function tells you how many days there are in the month with the same day name as the specified day — there are five Tuesdays in the current month (at the time of writing). The last function, dayofweekofmonth(birthday) (day of week of month), tells us that the 15th of March, 1997, was the third Saturday of the month.

You can also find days relative to a date, such as the first day of the week containing that day, using the adjusting functions, described below.

Date arithmetic

edit

You can do arithmetic on dates and date/time objects. Subtracting two dates or datetimes to find the difference is the most obvious one:

julia> datetoday - birthday
6380 days

julia> datetimenow - armistice
3023472252000 milliseconds

which you can convert to Dates.Days or Dates.Milliseconds or some other unit:

julia> Dates.Period(datetoday - birthday)
7357 days

julia> Dates.canonicalize(Dates.CompoundPeriod(datetimenow - armistice))
5138 weeks, 5 days, 5 hours, 46 minutes, 1 second, 541 milliseconds

julia> convert(Dates.Day, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1)))
491 days

julia> convert(Dates.Millisecond, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1)))
42422400000 milliseconds

To add and subtract periods of time to date and date/time objects, use the Dates. constructor functions to specify the period. For example, Dates.Year(20) defines a period of 20 years, and Dates.Month(6) defines a period of 6 months. So, to add 20 years and 6 months to the birthday date:

julia> birthday + Dates.Year(20) + Dates.Month(6)
2017-09-15

Here's 6 months ago from now:

julia> Dates.now() - Dates.Month(6)
2014-03-02T16:43:08

and similarly for months, weeks:

julia> Dates.now() - Dates.Year(2) - Dates.Month(6)
2012-03-02T16:44:03

and similarly for weeks and hours. Here's the date and time for two weeks and 12 hours from now:

julia> Dates.now() + Dates.Week(2) + Dates.Hour(12)
2015-09-18T20:49:16

and there are

julia> daystoxmas = Dates.Date(Dates.year(Dates.now()), 12, 25) - Dates.today()
148 days

or 148 (shopping) days till Christmas (at the time this was written).

To retrieve the value as a number, use the function Dates.value():

julia> Dates.value(daystoxmas)
148

This works with different types of date/time objects too:

julia> lastchristmas = Dates.now() - Dates.DateTime(2017, 12, 25, 0, 0, 0)
25464746504 milliseconds

julia> Dates.value(lastchristmas)
25464746504

Range of dates

edit

You can make iterable range objects that define a range of dates:

julia>  d = Dates.Date(1980,1,1):Dates.Month(3):Dates.Date(2019,1,1)
1980-01-01:3 months:2019-01-01

This iterator yields the first day of every third month. To find out which of these fall on weekdays, you can provide an anonymous function to filter() that tests the day name against the given day names:

julia> weekdays = filter(dy -> Dates.dayname(dy) != "Saturday" && Dates.dayname(dy) != "Sunday" , d)

104-element Array{Date,1}:

1980-01-01
1980-04-01
1980-07-01
⋮         
2014-07-01
2014-10-01
2016-04-01
2016-07-01
2018-01-01
2018-10-01
2019-01-01

Similarly, here's a range of times 3 hours apart from now, for a year hence:

julia> d = collect(Dates.DateTime(Dates.now()):Dates.Hour(3):Dates.DateTime(Dates.now() + Dates.Year(1)))
2929-element Array{DateTime,1}:
2015-09-04T08:30:59
2015-09-04T11:30:59
2015-09-04T14:30:59
 ⋮                  
2016-09-03T20:30:59
2016-09-03T23:30:59
2016-09-04T02:30:59
2016-09-04T05:30:59
2016-09-04T08:30:59

If you have to pay a bill every 30 days, starting on the 1st of January 2018, the following code shows how the due date creeps forward every month:

julia> foreach(d -> println(Dates.format(d, "d u yyyy")), Dates.Date("2018-01-01"):Dates.Day(30):Dates.Date("2019-01-01"))
1 Jan 2018
31 Jan 2018
2 Mar 2018
1 Apr 2018
1 May 2018
31 May 2018
30 Jun 2018
30 Jul 2018
29 Aug 2018
28 Sep 2018
28 Oct 2018
27 Nov 2018
27 Dec 2018

Date formatting

edit

To specify date formats, you use date formatting codes in a formatting string. Each character refers to a date/time element:

y  Year digit eg yyyy => 2015, yy => 15
m  Month digit eg m => 3 or 03
u  Month name eg Jan
U  Month name eg January
e  Day of week eg Tue
E  Day of week eg Tuesday
d  Day eg 3 or 03
H  Hour digit eg HH => 00
M  Minute digit eg MM => 00
S  Second digit eg S => 00
s  Millisecond digit eg .000

You can use these formatting strings with functions such as DateTime() and Dates.format(). For example, you create a DateTime object from a string by identifying the different elements in the incoming string:

julia> Dates.Date("Fri, 15 Jun 2018", "e, d u y")
2018-06-15
julia> Dates.DateTime("Fri, 15 Jun 2018 11:43:14", "e, d u y H:M:S")
2018-06-15T11:43:14

Other characters are used literally. In the second example, the formatting characters matched up as follows:

Fri, 15 Jun 2018 11:43:14
e  ,  d   u    y  H: M: S

You can supply a format string to Dates.format to format a date object. In the formatting string, you repeat the characters to control how years and days, for example, are output:

julia> timenow = Dates.now()
2015-07-28T11:43:14
julia> Dates.format(timenow, "e, dd u yyyy HH:MM:SS")
"Tue, 28 Jul 2015 11:43:14"

When you're creating a formatted date, you can double some of the components of the format string to produce a leading zero for single digit date elements:

julia> anothertime = Dates.DateTime("Tue, 8 Jul 2015 2:3:7", "e, d u y H:M:S")
2015-07-08T02:03:07

julia> Dates.format(anothertime, "e: dd u yy, HH.MM.SS") # with leading zeros
"Wed: 08 Jul 15, 02.03.07"

julia> Dates.format(anothertime, "e: d u yy, H.M.S")
"Wed: 8 Jul 15, 2.3.7"

To convert a date string from one format to another, you can use DateTime() and a format string to convert the string to a DateTime object, then DateFormat() to output the object in a different format:

julia> formatted_date = "Tue, 28 Jul 2015 11:43:14"
"Tue, 28 Jul 2015 11:43:14"

julia> temp = Dates.DateTime(formatted_date, "e, dd u yyyy HH:MM:SS")
2015-07-28T11:43:14

julia> Dates.format(temp, "dd, U, yyyy HH:MM, e")
"28, July, 2015 11:43, Tue"

If you're doing a lot of date formatting (you can apply date functions to an array of strings), it's a good idea to pre-define a DateFormat object and then use that for bulk conversions (this is quicker):

julia> dformat = Dates.DateFormat("y-m-d");

julia> Dates.Date.([   # broadcast
      "2010-01-01", 
      "2011-03-23", 
      "2012-11-3", 
      "2013-4-13", 
      "2014-9-20", 
      "2015-3-1"
      ], dformat)
6-element Array{Date,1}:
 2010-01-01
 2011-03-23
 2012-11-03
 2013-04-13
 2014-09-20
 2015-03-01

There are some built-in formats that you can use. For example, there's Dates.ISODateTimeFormat to give you the ISO8601 format:

julia> Dates.DateTime.([  
          "2010-01-01", 
          "2011-03-23", 
          "2012-11-3", 
          "2013-4-13", 
          "2014-9-20", 
          "2015-3-1" 
          ], Dates.ISODateTimeFormat) 
6-element Array{DateTime,1}:
2010-01-01T00:00:00
2011-03-23T00:00:00
2012-11-03T00:00:00
2013-04-13T00:00:00
2014-09-20T00:00:00
2015-03-01T00:00:00

and here's good old RFC1123:

julia> Dates.format(Dates.now(), Dates.RFC1123Format)
"Sat, 30 Jul 2016 16:36:09"

Date adjustments

edit

Sometimes you want to find a date nearest to another - for example, the first day of that week, or the last day of the month that contains that date. You can do this with the functions like Dates.firstdayofweek() and Dates.lastdayofmonth(). So, if we're currently in the middle of the week:

julia> Dates.dayname(now())
"Wednesday"

the first day of the week is returned by this:

julia> Dates.firstdayofweek(now())
2014-09-01T00:00:00

which you could also write using the function chain operator:

julia> Dates.now() |> Dates.firstdayofweek |> Dates.dayname 
"Monday"

A more general solution is provided by the tofirst(), tolast(), tonext(), and toprev() methods.

With tonext() and toprev(), you can provide a (possibly anonymous) function that returns true when a date has been correctly adjusted. For example, the function:

d->Dates.dayofweek(d) == Dates.Tuesday

returns true if the day d is a Tuesday. Use this with the tonext() method:

julia> Dates.tonext(d->Dates.dayofweek(d) == Dates.Tuesday, birthday)
1997-03-18 # the first Tuesday after the birthday

Or you can find the next Sunday following the birthday date:

julia> Dates.tonext(d->Dates.dayname(d) == "Sunday", birthday)
1997-03-16 # the first Sunday after the birthday

With tofirst() and tolast(), you can find the first Sunday, or Thursday, or whatever, of a month. Monday is 1, Tuesday 2, etc.

julia> Dates.tofirst(birthday, 1) # the first Monday (1) of that month
1997-03-03

Supply the keyword argument of=Year to get the first matching weekday of the year.

julia> Dates.tofirst(birthday, 1, of=Year) # the first Monday (1) of 1997
1997-01-06

Rounding dates and times

edit

You can use round(), floor(), and ceil(), usually used to round numbers up or down to the nearest preferred values, to adjust dates forward or backwards in time so that they have 'rounder' values.

julia> Dates.now()
2016-09-12T17:55:11.378

julia> Dates.format(round(Dates.DateTime(Dates.now()), Dates.Minute(15)), Dates.RFC1123Format)
"Mon, 12 Sep 2016 18:00:00"

The ceil() adjusts dates or times forward in time:

julia> ceil(birthday, Dates.Month)
1997-04-01

julia> ceil(birthday, Dates.Year)
1998-01-01

julia> ceil(birthday, Dates.Week)
1997-03-17

Recurring dates

edit

It's useful to be able to find all dates in a range of dates that satisfy some particular criteria. For example, you can work out the second Sunday in a month by using the Dates.dayofweekofmonth() and Dates.dayname() functions.

For example, let's create a range of dates from the first of September 2014 until Christmas Day, 2014:

julia> dr = Dates.Date(2014,9,1):Dates.Day(1):Dates.Date(2014,12,25)
2014-09-01:1 day:2014-12-25

Now an anonymous function similar to the ones we used in tonext() earlier finds a selection of those dates in that range that satisfy that function:

julia> filter(d -> Dates.dayname(d) == "Sunday", dr)
16-element Array{Date,1}:
 2014-09-07
 2014-09-14
 2014-09-21
 2014-09-28
 2014-10-05
 2014-10-12
 2014-10-19
 2014-10-26
 2014-11-02
 2014-11-09
 2014-11-16
 2014-11-23
 2014-11-30
 2014-12-07
 2014-12-14
 2014-12-21

These are the dates of every Sunday between September 1st 2014 until Christmas Day, 2014.

By combining criteria in the anonymous function, you can build up more complicated recurring events. Here's a list of all the Tuesdays in that period which are on days that are odd numbered and greater than 20:

julia> filter(d->Dates.dayname(d) == "Tuesday" && isodd(Dates.day(d)) && Dates.day(d) > 20, dr)
4-element Array{Date,1}:
2014-09-23
2014-10-21
2014-11-25
2014-12-23

and here's every second Tuesday in 2016 between April and November:

dr = Dates.Date(2015):Dates.Day(1):Dates.Date(2016);
filter(dr) do x
    Dates.dayofweek(x) == Dates.Tue &&
    Dates.April <= Dates.month(x) <= Dates.Nov &&
    Dates.dayofweekofmonth(x) == 2
end
8-element Array{Base.Dates.Date,1}:
 2015-04-14
 2015-05-12
 2015-06-09
 2015-07-14
 2015-08-11
 2015-09-08
 2015-10-13
 2015-11-10

Unix time

edit

You sometimes have to deal with another type of timekeeping: Unix time. Unix time is a count of the number of seconds that have elapsed since the beginning of the year 1970 (the birth of Unix). In Julia the count is stored in a 64 bit integer, and we'll never see the end of Unix time. (The universe will have ended long before 64 bit Unix time reaches the maximum possible value, which will be in approximately 292 billion years from now, at 15:30:08 on Sunday, 4 December 292,277,026,596.)

In Julia, the time() function, used without arguments, returns the Unix time value of the current second:

julia> time()
1.414141581230945e9

The strftime() ("string format time") function, which lives in the Libc module, converts a number of seconds in Unix time to a more readable form:

julia> Libc.strftime(86400 * 365.25 * 4) # 4 years worth of Unix seconds
"Tue  1 Jan 00:00:00 1974"

You can choose a different format by supplying a format string, with the different components of the date and time defined by '%' letter codes:

julia> Libc.strftime("%A, %B %e at %T, %Y", 86400 * 365.25 * 4)
"Tuesday, January  1 at 00:00:00, 1974"

The strptime() function takes a format string and a date string, and returns a TmStruct expression. This can then be converted to a Unix time value by passing it to time():

julia> Libc.strptime("%A, %B %e at %T, %Y", "Tuesday, January  1 at 00:00:00, 1974")
Base.Libc.TmStruct(0,0,0,1,0,74,2,0,0,0,0,0,0,0)

julia> time(ans)
1.262304e8

julia> time(Libc.strptime("%Y-%m-%d","2014-10-1"))
1.4121216e9

The Dates module also offers a unix2datetime() function, which converts a Unix time value to a date/time object:

julia> Dates.unix2datetime(time())
2014-10-24T09:26:29.305

Moments in time

edit

DateTimes are stored as milliseconds, in the field instant. Use Dates.value to obtain the value.

julia> moment=Dates.now()
2017-02-01T12:45:46.326
  
julia> Dates.value(moment)
63621636346326
julia> moment.instant
Base.Dates.UTInstant{Base.Dates.Millisecond}(63621636346326 milliseconds)

If you use the more precise Dates.Time type, you can access nanoseconds.

julia> moment = Dates.Time(Dates.now())
17:38:44.33
julia> Dates.value(moment)
63524330000000
 
julia> moment.instant
63524330000000 nanoseconds

Timing and monitoring

edit

The @elapsed macro returns the number of seconds an expression took to evaluate:

function test(n)
     for i in 1:n
        x = sin(rand())
     end
end
julia> @elapsed test(100000000)
1.309819509

The @time macro tells you how long an expression took to evaluate, and how memory was allocated.

julia> @time test(100000000)
2.532941 seconds (4 allocations: 160 bytes)