-
Notifications
You must be signed in to change notification settings - Fork 15
Datetime Manual
From the beginning, Julia has had a problem with being greedy. This same greed has driven the development of the Datetime package. With roots in Joda/JS-310, numpy datetime64, lubridate, zoo, and runt, the Datetime package aims to greedily provide a DateTime implementation that is not only top in efficiency and performance, but that also contains a full range of types and methods for working with dates, times, periods, and time series of any flavor. Dates and times can sometimes be tricky to work with, so Datetime strives for accuracy, clarity, and simplicity.
We also realize we're far from perfect, so tire-kicking, feature requests, and bug reports are heartily welcomed and appreciated!
- Calendars/Type Parameters
- Period Types
- TimeTypes
- Date
- DateTime
- TimeType Ranges
Following Joda-time's idea of pluggable chronologies, Datetime defines Calendar, an abstract type that can be subtyped for specific calendar implementations and that all Period/TimeTypes take as a parameter. Datetime defines a default Calendar with abstract ISOCalendar <: Calendar which follows ISO 8601 standards. Using a Calendar type as a type and method parameter is a core principle of the Datetime package; it not only leverages Julia's multiple dispatch for Calendar-specific methods, but also allows for extremely efficient Date, DateTime, and Period types that don't need to store certain metadata like a Calendar value/pointer. Indeed, these types simply wrap 32 and 64-bit integers; yep, just 4 and 8 [bytes!][1]. The same style is used to specify an offset for DateTime; thus, PST (Pacific Standard Time) is an Offsets type that DateTime can take as a parameter upon creation and is used in calculating the appropriate offset when showing its value. Let's see some of these parameters in action:
julia> y = year(1)
1 year
julia> typeof(y)
Year{ISOCalendar}
julia> dt = today()
2013-07-26
julia> typeof(dt)
Date{ISOCalendar}
julia> calendar(dt)
ISOCalendar
julia> typeof(calendar(dt))
DataType
julia> super(calendar(dt))
Calendar
julia> dt2 = now(CST)
2013-07-25T23:08:12 CDT
julia> typeof(dt2)
DateTime{ISOCalendar,Zone382}
julia> super(CST)
TimeZone
julia> super(TimeZone)
OffsetsZone382 represents the "America/Chicago" or Central Standard Time timezone (for those interested 382 comes directly from the universally used Olson timezone database zone IDs).
[1]: Compare to other languages: R = 256 bytes, Python date, datetime classes = 32, 48 bytes, Joda-time = 24-40 bytes
Period types (i.e. Year, Month, Week, Day, etc) have always been a bit controversial in Datetime implementations. The core problem is that Periods represent a human view of time that aren't fixed durations of time. Consider 1 month; it could represent, in days, a value of 28, 29, 30, or 31 depending on the year and month context. Or 1 year could represent 365 or 366 days in the case of a leap year. In any case, the real trickiness comes out when Date/DateTime-Period arithmetic is considered (which is discussed for each type in its section below). Thanks to the power and expressiveness of Julia's type and dispatch systems, Datetime provides Period implementations that aim to be simple and predictable. Currently Year, Month, Week, Day, Hour, Minute, and Second are 32-bit Period types that can be used for Date/DateTime arithmetic, as well as some limited inter-Period arithmetic. We say "limited" because inter-Period operations aren't always clear (e.g. 1 month + 1 day = ? days). The current approach, which could possibly change, is to allow Year, Week, Day, Hour, Minute, and Second inter-ops, but for months, only Year-Month operations are allowed (it's the only conversion that's consistent). We also follow a "promote downwards" approach to not lose precision (e.g. 1 week + 7 days == 14 days). The motivation for inter-Period operations was to help Periods feel like real types that have their own behaviors and are not severely limited or awkward to work with. Constructing Period types is done through the lowercase single or plural versions of their names:
julia> y = year(1)
1 year
julia> m = months(1)
1 month
julia> w = weeks(52)
52 weeks
julia> d = days(10)
10 days
julia> h = hour(24)
24 hours
julia> mi = minute(1)
1 minute
julia> s = second(1)
1 secondConversion between types occur with same functions applied to already created Periods:
julia> month(year(1))
12 months
julia> weeks(year(1))
52 weeks
julia> hours(day(1))
24 hoursArithmetic and comparison operations follow the promotion rules discussed earlier.
julia> year(1) + month(1)
13 months
julia> year(1) + week(1)
53 weeks
julia> year(1) + day(1)
366 days
julia> month(1) + week(1)
ERROR: no method convert(Type{Week{ISOCalendar}},Month{ISOCalendar})
in + at no file
julia> month(1) + day(1)
ERROR: no method convert(Type{Day{ISOCalendar}},Month{ISOCalendar})
in + at no file
julia> week(1) == day(7)
true
julia> day(1) == seconds(86400)
true
julia> year(1) > second(1)
truePeriod types also support PeriodRange, which allows a Range type to be constructed similar to the numeric Ranges in Julia.
julia> startyear = year(1)
1 year
julia> endyear = year(101)
101 years
julia> stepyear = year(25)
25 years
julia> r = startyear:stepyear:endyear
1 year:25 years:101 years
julia> first(r)
1 year
julia> last(r)
101 years
julia> length(r)
5
julia> step(r)
25 years
julia> [r]
5-element Period Array:
1 year
26 years
51 years
76 years
101 yearsDatetime currently provides two TimeTypes: Date and DateTime, representing different levels of "precision". The motivation for distinct types is simple, some operations are much simpler, both in terms of code and mental reasoning, when the complexities of greater precision don't have to be dealt with. For example, the Date type has a "day-precision" (i.e. no hours, minutes, or seconds), which means that normal consideration for time zones, daylight savings/summer time, and leap seconds is unneccesary. Users should take care to think about the level precision they will actually require and choose a TimeType appropriately, remembering that the simpler the date type, the faster, more efficient, and less complex the code will be.
As mentioned above, the Date implementation has "day" precision. It is represented in the format "YYYY-MM-DD". A Date type is 8 bytes (64 bits), is immutable, and takes a Calendar type as a parameter.
julia> dt = today()
2013-07-26
julia> typeof(dt)
Date{ISOCalendar}
julia> sizeof(dt)
8
julia> isbits(dt)
true
julia> isimmutable(dt)
trueThe default ISOCalendar type used by the Datetime package follows ISO 8601 standards. This means dates follow the proleptic Gregorian calendar, which means that the normal calendar we think of toady, which was implemented as it currently stands in 1582, is applied retroactively back through time. So even though the folks in 1582 fast-forwarded 10 days and didn't experience October 5-14, these are valid dates for the ISOCalendar (non-ISO implementations usually switch to the Julian calendar before Oct 14, 1582).
The ISO 8601 standard is also particular about BC/BCE dates. In common use, the date 1-12-31 BC/BCE was followed by 1-1-1 AD/CE, thus no year 0 exists. The ISO standard however states that 1 BC/BCE is year 0, so 0000-12-31 is the day before -001-01-01, and year -0001 (yes, negative 1 for the year) is 2 BC/BCE, year -0002 is 3 BC/BCE, etc. Clear as mud? Just take note that if you happen to be working with BC/BCE dates and using the default ISOCalendar that the year might be off by 1 than what you initially think.
Ok, on to more important stuff.
Dates can be constructed by parts or there is preliminary support for parsing.
julia> date(2013,7,1)
2013-07-01
julia> date(2013)
2013-01-01
julia> date(2013,7)
2013-07-01
julia> today()
2013-07-26
julia> a = "2005-01-31"
b = "2005/31/01"
c = "01.31.2005"
d = "31 01 2005"
aa = "2005,1,31"
bb = "2005-31-1"
cc = "1-31-2005"
dd = "31-1-2005"
aaa = "05-01-31"
bbb = "05/31/01"
ccc = "01.31.05"
ddd = "31 01 05"
e = "20050131"
ee = "2005131"
julia> for i in (a,b,c,d,aa,bb,cc,dd,aaa,bbb,ccc,ddd,ee,ee)
try
dt = date(i)
println(i," ",dt)
catch
println(i," ","Can't parse")
continue
end
end
2005-01-31 2005-01-31
2005/31/01 2005-01-31
01.31.2005 2005-01-31
31 01 2005 2005-01-31
2005,1,31 2005-01-31
2005-31-1 2005-01-31
1-31-2005 2005-01-31
31-1-2005 2005-01-31
05-01-31 2031-05-01
05/31/01 2001-05-31
01.31.05 2005-01-31
31 01 05 2005-01-31
2005131 Can't parse
2005131 Can't parseNote that parsing accepts a few separators (-,/,.,,,and space), in a few different formats, but care should be taken because the algorithm is really just guessing what it thinks you mean, while always defaulting to the standard ISO format YYYY-MM-DD. A planned parsedate function will take a format string as input to help specify the year, month, and day parts.
Accessor functions return the year, month, and day parts of a Date, and other generic date functions are provided for convenience in working with dates.
julia> dt = date(2013,7,1)
2013-07-01
julia> year(dt)
2013
julia> month(dt)
7
julia> day(dt)
1
julia> string(dt)
"2013-07-01"
julia> week(dt)
27
julia> isdate(dt)
true
julia> calendar(dt)
ISOCalendar
julia> typemax(Date)
252522163911149-12-31
julia> typemin(Date)
-252522163911150-01-01
julia> isleap(dt)
false
julia> lastdayofmonth(dt)
31
julia> dayofweek(dt)
1
julia> dayofweekinmonth(dt)
1
julia> daysofweekinmonth(dt)
5
julia> firstdayofweek(dt)
2013-07-01
julia> lastdayofweek(dt)
2013-07-07
julia> dayofyear(dt)
182As has been noted, the Date type has a day precision, so subtracting two dates results in the number of days between them.
julia> dt = date(2012,2,29)
2012-02-29
julia> dt2 = date(2000,2,1)
2000-02-01
julia> dt > dt2
true
julia> dt != dt2
true
julia> year(-dt) == -2012
true
julia> +dt == dt
true
julia> dt + dt2
ERROR: + not defined for Date/DateTime
in error at error.jl:22
julia> dt * dt2
ERROR: * not defined for Date/DateTime
in error at error.jl:22
julia> dt / dt2
ERROR: / not defined for Date/DateTime
in error at error.jl:22
julia> dt - dt2
4411 daysDatetime's approach to Period arithmetic tries to be very simple and clear while still giving the user flexibility. It's good practice when using any language/date framework to be familiar with how period arihtmetic is handled as there is no strong consensus and some tricky issues to deal with (though much less so for a day-precision type than a datetime type, more on that later). **A key thing to note is that Period arithmetic in Datetime is non-associative. As we explained earlier, inter-Period conversion is allowed, so year(1) + day(1) == days(366). That means if you try the following you may trip up trying to combine the periods before doing the date operations.
julia> dt2 = date(2000,2,1)
2000-02-01
julia> dt2 - year(4) + days(366)
1997-02-01
julia> dt2 - (year(4) - days(366))
1997-02-02Fortunately, this is only something to remember with Years, as any operations with Months throw exceptions, and other inter-Period conversions are associative.
Also to note is that Datetime implements calendrical arithmetic, which is a slightly vague term for what we normally think of when doing date-period arithmetic. What this means is that the Periods will be added/subtracted from the date similar to a 7-7-7 slot machine, trying to only change the slot it needs to. Only when it needs to? Yes, because sometimes when you try to add 1 month to 2013-01-31, it's not apparently clear what you're expecting. 2013-02-28 because it's the last day of February? 2013-03-02 because you wanted to add 30 days? Datetime takes the approach of only changing when necessary, which means if you add/subtract a month and the new day of month happens to be "out of range" for the new month, then the new day of month becomes the last day of the new month. So for our example above date(2013,1,31) + month(1) == date(2013,2,28). Got it? It's not too difficult, but just be aware. Here's an example.
julia> dt = date(2013,1,31)
for i = 1:12
println(dt)
dt += month(1)
end
2013-01-31
julia> 2013-01-31
2013-02-28
2013-03-28
2013-04-28
2013-05-28
2013-06-28
2013-07-28
2013-08-28
2013-09-28
2013-10-28
2013-11-28
2013-12-28Some more examples of general period arithmetic for your enjoyment.
julia> dt = date(2012,2,29)
2012-02-29
julia> dt2 = date(2000,2,1)
2000-02-01
julia> dt2 - year(3) == date(1997,2,1)
true
julia> dt2 - year(3)
1997-02-01
julia> dt + year(1)
2013-02-28
julia> year(3) - dt2
1997-02-01
julia> dt2 - months(3)
1999-11-01
julia> months(11) + dt
2013-01-29
julia> months(8) + dt
2012-10-29
julia> dt2 + days(4411)
2012-02-29
julia> dt2 + days(4412)
2012-03-01
julia> dt2 + weeks(52)
2001-01-30
julia> dt2 + weeks(104)
2002-01-29Dates can be constructed by parts or there is preliminary support for parsing.