Introducing Julia/Working with dates and times

From Wikibooks, open books for an open world
Jump to: navigation, search
« Introducing Julia
Working with dates and times
»
Working with text files Plotting

Working with dates and times[edit]

Functions for working with dates and times are provided in the package Dates. This is built-in to the base Julia system.

Prefixes[edit]

Only the Date(), DateTime(), and now() functions are exported; to use all other Dates functions, you’ll need to prefix each function call with an explicit Dates., e.g. Dates.dayofweek(dt), as shown in this chapter. However, you could add the line using Dates to your code to bring all exported functions into Main, so that 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 v0.6.

Date, Time, and DateTimes[edit]

There are three main datatypes available:

  • A 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 Date object represents just a date: no time zones, no daylight saving issues, etc... It's accurate to, well, a day.
  • A 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(now())
16:51:56.374
julia> birthday = Dates.Date(1997,3,15)
1997-03-15
julia> armistice = Dates.DateTime(1918,11,11,11,11,11)
1918-11-11T11:11:11

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

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

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

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

(We used now() to make a Dates.DateTime, 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> theday = 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 particular information from it with the following functions. For both date and datetime objects:

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() (daysof weekinmonth) 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) (dayofweekofmonth), tells us that the 15th of March, 1997, was the third Saturday of the month.

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> now() - Dates.Month(6)
2014-03-02T16:43:08

and similarly for months, weeks:

julia> 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> now() + Dates.Week(2) + Dates.Hour(12)
2015-09-18T20:49:16

and there are

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

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

Range of dates[edit]

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

julia> d = Date(1980,1,1):Dates.Month(3):Date(2015,1,1)
1980-01-01:3 months:2015-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
 2015-01-01

julia> length(ans) / length(d)
0.7375886524822695

or about 74%.

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

julia> d = collect(DateTime(now()):Dates.Hour(3):DateTime(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 2017, the following code shows how the due date creeps forward every month:

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

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> Date("Tue, 28 Jul 2015", "e, d u y")
 2015-07-28
 julia> DateTime("Tue, 28 Jul 2015 11:43:14", "e, d u y H:M:S")
 2015-07-28T11:43:14

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

Tue, 28 Jul 2015 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 = 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 = 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> Date([
       "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> 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(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

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 supply the date, and the first day of that month is returned. Supply the keyword argument of=Year to get the first day of that year.

julia> Dates.tofirst(birthday, 1)
1997-03-03
julia> Dates.tofirst(birthday, 1, of=Year)
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> now()
2016-09-12T17:55:11.378

julia> Dates.format(round(DateTime(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.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.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

Julia 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. You can access the field instant to see the actual value.

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

 julia> Dates.value(moment)
 63621636346326

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

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

Timing and monitoring[edit]

If you want to tell how long functions take to run, you can use tic() and toc(), which provide the start and finish buttons of a virtual stopwatch:

function test(n)
    tic()
    for i in 1:n
       x = sin(rand())
    end
    toc()
end

julia> test(100000000)
elapsed time: 1.313614113 seconds
1.313614113

toc() prints the time since the most recent tic() and returns the value as well.

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)