Skip to content

Commit 8bd39af

Browse files
committed
Add support for a Time TimeType in the Dates module
1 parent 906e06b commit 8bd39af

File tree

15 files changed

+414
-23
lines changed

15 files changed

+414
-23
lines changed

base/Dates.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ include("dates/io.jl")
1616

1717
export Period, DatePeriod, TimePeriod,
1818
Year, Month, Week, Day, Hour, Minute, Second, Millisecond,
19-
TimeZone, UTC, TimeType, DateTime, Date,
19+
Microsecond, Nanosecond,
20+
TimeZone, UTC, TimeType, DateTime, Date, Time,
2021
# accessors.jl
2122
yearmonthday, yearmonth, monthday, year, month, week, day,
2223
hour, minute, second, millisecond, dayofmonth,
24+
microsecond, nanosecond,
2325
# query.jl
2426
dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
2527
dayofweekofmonth, daysofweekinmonth, monthname, monthabbr,

base/dates/accessors.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ end
4242

4343
# Accessor functions
4444
value(dt::TimeType) = dt.instant.periods.value
45+
value(t::Time) = t.instant.value
4546
days(dt::Date) = value(dt)
4647
days(dt::DateTime) = fld(value(dt),86400000)
4748
year(dt::TimeType) = year(days(dt))
@@ -52,6 +53,12 @@ hour(dt::DateTime) = mod(fld(value(dt),3600000),24)
5253
minute(dt::DateTime) = mod(fld(value(dt),60000),60)
5354
second(dt::DateTime) = mod(fld(value(dt),1000),60)
5455
millisecond(dt::DateTime) = mod(value(dt),1000)
56+
hour(t::Time) = mod(fld(value(t),3600000000000),24)
57+
minute(t::Time) = mod(fld(value(t),60000000000),60)
58+
second(t::Time) = mod(fld(value(t),1000000000),60)
59+
millisecond(t::Time) = mod(fld(value(t),1000000),1000)
60+
microsecond(t::Time) = mod(fld(value(t),1000),1000)
61+
nanosecond(t::Time) = mod(value(t),1000)
5562

5663
dayofmonth(dt::TimeType) = day(dt)
5764

@@ -67,6 +74,12 @@ yearmonthday(dt::TimeType) = yearmonthday(days(dt))
6774
@vectorize_1arg DateTime minute
6875
@vectorize_1arg DateTime second
6976
@vectorize_1arg DateTime millisecond
77+
@vectorize_1arg Time hour
78+
@vectorize_1arg Time minute
79+
@vectorize_1arg Time second
80+
@vectorize_1arg Time millisecond
81+
@vectorize_1arg Time microsecond
82+
@vectorize_1arg Time nanosecond
7083

7184
@vectorize_1arg TimeType dayofmonth
7285
@vectorize_1arg TimeType yearmonth
@@ -137,3 +150,14 @@ for parts in (["year", "month"], ["month", "day"], ["year", "month", "day"])
137150
""" $func(dt::TimeType)
138151
end
139152
end
153+
154+
for func in (:hour, :minute, :second, :millisecond, :microsecond, :nanosecond)
155+
name = string(func)
156+
@eval begin
157+
@doc """
158+
$($name)(t::Time) -> Int64
159+
160+
The $($name) of a `Time` as an `Int64`.
161+
""" $func(t::Time)
162+
end
163+
end

base/dates/adjusters.jl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ Base.trunc(dt::DateTime,p::Type{Minute}) = dt - Second(dt) - Millisecond(dt)
1313
Base.trunc(dt::DateTime,p::Type{Second}) = dt - Millisecond(dt)
1414
Base.trunc(dt::DateTime,p::Type{Millisecond}) = dt
1515

16+
Base.trunc(t::Time,p::Type{Hour}) = Time(Hour(t))
17+
Base.trunc(t::Time,p::Type{Minute}) = Time(Hour(t),Minute(t))
18+
Base.trunc(t::Time,p::Type{Second}) = Time(Hour(t),Minute(t),Second(t))
19+
Base.trunc(t::Time,p::Type{Millisecond}) = t - Microsecond(t) - Nanosecond(t)
20+
Base.trunc(t::Time,p::Type{Microsecond}) = t - Nanosecond(t)
21+
Base.trunc(t::Time,p::Type{Nanosecond}) = t
22+
1623
"""
1724
trunc(dt::TimeType, ::Type{Period}) -> TimeType
1825
@@ -167,12 +174,37 @@ provided `y, m` arguments, and will be adjusted until `f::Function` returns `tru
167174
size in adjusting can be provided manually through the `step` keyword. If `negate=true`,
168175
then the adjusting will stop when `f::Function` returns `false` instead of `true`. `limit`
169176
provides a limit to the max number of iterations the adjustment API will pursue before
170-
throwing an error (given that `f::Function` is never satisfied).
177+
throwing an error (in the case that `f::Function` is never satisfied).
171178
"""
172179
function Date(func::Function,y,m=1,d=1;step::Period=Day(1),negate::Bool=false,limit::Int=10000)
173180
return adjust(DateFunction(func,negate,Date(y,m,d)),Date(y,m,d),step,limit)
174181
end
175182

183+
"""
184+
Time(f::Function, h[, mi, s, ms, us]; step=Second(1), negate=false, limit=10000) -> Time
185+
186+
Create a `Time` through the adjuster API. The starting point will be constructed from the
187+
provided `h, mi, s, ms, us` arguments, and will be adjusted until `f::Function` returns `true`. The step
188+
size in adjusting can be provided manually through the `step` keyword. If `negate=true`,
189+
then the adjusting will stop when `f::Function` returns `false` instead of `true`. `limit`
190+
provides a limit to the max number of iterations the adjustment API will pursue before
191+
throwing an error (in the case that `f::Function` is never satisfied).
192+
"""
193+
Time(::Function, args...)
194+
195+
function Time(func::Function,h,mi=0;step::Dates.Period=Dates.Second(1),negate::Bool=false,limit::Int=10000)
196+
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi)),Dates.Time(h,mi),step,limit)
197+
end
198+
function Time(func::Function,h,mi,s;step::Dates.Period=Dates.Millisecond(1),negate::Bool=false,limit::Int=10000)
199+
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s)),Dates.Time(h,mi,s),step,limit)
200+
end
201+
function Time(func::Function,h,mi,s,ms;step::Dates.Period=Dates.Microsecond(1),negate::Bool=false,limit::Int=10000)
202+
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s,ms)),Dates.Time(h,mi,s,ms),step,limit)
203+
end
204+
function Time(func::Function,h,mi,s,ms,us;step::Dates.Period=Dates.Nanosecond(1),negate::Bool=false,limit::Int=10000)
205+
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s,ms,us)),Dates.Time(h,mi,s,ms,us),step,limit)
206+
end
207+
176208
"""
177209
DateTime(f::Function, y[, m, d, h, mi, s]; step=Day(1), negate=false, limit=10000) -> DateTime
178210

base/dates/arithmetic.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
(+)(x::TimeType) = x
99
(-){T<:TimeType}(x::T,y::T) = x.instant - y.instant
1010

11+
# Time arithmetic
12+
(+)(x::Time,y::Time) = Time(Nanosecond(+(value(x),value(y))))
13+
1114
# TimeType-Year arithmetic
1215
function (+)(dt::DateTime,y::Year)
1316
oy,m,d = yearmonthday(dt); ny = oy+value(y); ld = daysinmonth(ny,m)
@@ -63,6 +66,8 @@ end
6366
(-)(x::Date,y::Day) = return Date(UTD(value(x) - value(y)))
6467
(+)(x::DateTime,y::Period) = return DateTime(UTM(value(x)+toms(y)))
6568
(-)(x::DateTime,y::Period) = return DateTime(UTM(value(x)-toms(y)))
69+
(+)(x::Time,y::TimePeriod) = return Time(Nanosecond(value(x)+tons(y)))
70+
(-)(x::Time,y::TimePeriod) = return Time(Nanosecond(value(x)-tons(y)))
6671
(+)(y::Period,x::TimeType) = x + y
6772
(-)(y::Period,x::TimeType) = x - y
6873

base/dates/io.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ function Base.string(dt::Date)
2222
return "$yy-$mm-$dd"
2323
end
2424
Base.show(io::IO,x::Date) = print(io,string(x))
25+
function Base.string(t::Time)
26+
h,mi,s = hour(t),minute(t),second(t)
27+
hh = lpad(h,2,"0")
28+
mii = lpad(mi,2,"0")
29+
ss = lpad(s,2,"0")
30+
nss = tons(Millisecond(t)) + tons(Microsecond(t)) + tons(Nanosecond(t))
31+
ns = nss == 0 ? "" : @sprintf("%.9f", nss/1e+9)[2:end]
32+
return "$hh:$mii:$ss$(ns)"
33+
end
34+
Base.show(io::IO,x::Time) = print(io,string(x))
2535

2636
### Parsing
2737
const english = Dict{String,Int}("january"=>1,"february"=>2,"march"=>3,"april"=>4,

base/dates/periods.jl

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ value(x::Period) = x.value
66
# The default constructors for Periods work well in almost all cases
77
# P(x) = new((convert(Int64,x))
88
# The following definitions are for Period-specific safety
9-
for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond)
9+
for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
1010
period_str = string(period)
1111
accessor_str = lowercase(period_str)
1212
# Convenience method for show()
@@ -16,17 +16,20 @@ for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond
1616
# AbstractString parsing (mainly for IO code)
1717
@eval $period(x::AbstractString) = $period(Base.parse(Int64,x))
1818
# Period accessors
19-
typ_str = period in (:Hour, :Minute, :Second, :Millisecond) ? "DateTime" : "TimeType"
20-
description = typ_str == "TimeType" ? "`Date` or `DateTime`" : "`$typ_str`"
21-
reference = period == :Week ? " For details see [`$accessor_str(::$typ_str)`](:func:`$accessor_str`)." : ""
19+
typs = period in (:Microsecond, :Nanosecond) ? ["Time"] : (period in (:Hour, :Minute, :Second, :Millisecond) ? ["DateTime","Time"] : ["TimeType"])
20+
description = typs == ["TimeType"] ? "`Date` or `DateTime`" : (typs == ["Time"] ? "`Time`" : "`Time` or `DateTime`")
21+
reference = period == :Week ? " For details see [`$accessor_str(::TimeType)`](:func:`$accessor_str`)." : ""
22+
for typ_str in typs
23+
@eval begin
24+
@doc """
25+
$($period_str)(dt::$($typ_str)) -> $($period_str)
26+
27+
The $($accessor_str) part of a $($description) as a `$($period_str)`.$($reference)
28+
""" ->
29+
$period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
30+
end
31+
end
2232
@eval begin
23-
@doc """
24-
$($period_str)(dt::$($typ_str)) -> $($period_str)
25-
26-
The $($accessor_str) part of a $($description) as a `$($period_str)`.$($reference)
27-
""" ->
28-
$period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
29-
3033
@doc """
3134
$($period_str)(v)
3235
@@ -125,15 +128,28 @@ periodisless(::Period,::Hour) = false
125128
periodisless(::Minute,::Hour) = true
126129
periodisless(::Second,::Hour) = true
127130
periodisless(::Millisecond,::Hour) = true
131+
periodisless(::Microsecond,::Hour) = true
132+
periodisless(::Nanosecond,::Hour) = true
128133
periodisless(::Period,::Minute) = false
129134
periodisless(::Second,::Minute) = true
130135
periodisless(::Millisecond,::Minute) = true
136+
periodisless(::Microsecond,::Minute) = true
137+
periodisless(::Nanosecond,::Minute) = true
131138
periodisless(::Period,::Second) = false
132139
periodisless(::Millisecond,::Second) = true
140+
periodisless(::Microsecond,::Second) = true
141+
periodisless(::Nanosecond,::Second) = true
133142
periodisless(::Period,::Millisecond) = false
143+
periodisless(::Microsecond,::Millisecond) = true
144+
periodisless(::Nanosecond,::Millisecond) = true
145+
periodisless(::Period,::Microsecond) = false
146+
periodisless(::Nanosecond,::Microsecond) = true
147+
periodisless(::Period,::Nanosecond) = false
134148

135149
# return (next coarser period, conversion factor):
136150
coarserperiod{P<:Period}(::Type{P}) = (P,1)
151+
coarserperiod(::Type{Nanosecond}) = (Microsecond,1000)
152+
coarserperiod(::Type{Microsecond}) = (Millisecond,1000)
137153
coarserperiod(::Type{Millisecond}) = (Second,1000)
138154
coarserperiod(::Type{Second}) = (Minute,60)
139155
coarserperiod(::Type{Minute}) = (Hour,60)
@@ -368,7 +384,7 @@ end
368384

369385
# Fixed-value Periods (periods corresponding to a well-defined time interval,
370386
# as opposed to variable calendar intervals like Year).
371-
typealias FixedPeriod Union{Week,Day,Hour,Minute,Second,Millisecond}
387+
typealias FixedPeriod Union{Week,Day,Hour,Minute,Second,Millisecond,Microsecond,Nanosecond}
372388

373389
# like div but throw an error if remainder is nonzero
374390
function divexact(x,y)
@@ -378,7 +394,7 @@ function divexact(x,y)
378394
end
379395

380396
# FixedPeriod conversions and promotion rules
381-
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1)]
397+
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1000),(Microsecond,1000),(Nanosecond,1)]
382398
for i = 1:length(fixedperiod_conversions)
383399
(T,n) = fixedperiod_conversions[i]
384400
N = 1
@@ -426,7 +442,10 @@ toms(c::Day) = 86400000*value(c)
426442
toms(c::Week) = 604800000*value(c)
427443
toms(c::Month) = 86400000.0*30.436875*value(c)
428444
toms(c::Year) = 86400000.0*365.2425*value(c)
429-
toms(c::CompoundPeriod) = isempty(c.periods)?0.0 : Float64(sum(toms,c.periods))
445+
toms(c::CompoundPeriod) = isempty(c.periods)? 0.0 : Float64(sum(toms,c.periods))
446+
tons(x) = Dates.toms(x) * 1000000
447+
tons(x::Microsecond) = value(x) * 1000
448+
tons(x::Nanosecond) = value(x)
430449
days(c::Millisecond) = div(value(c),86400000)
431450
days(c::Second) = div(value(c),86400)
432451
days(c::Minute) = div(value(c),1440)

base/dates/ranges.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
# Override default step; otherwise it would be Millisecond(1)
66
Base.colon{T<:DateTime}(start::T, stop::T) = StepRange(start, Day(1), stop)
7+
Base.colon{T<:Time}(start::T, stop::T) = StepRange(start, Second(1), stop)
78

89
# Given a start and end date, how many steps/periods are in between
910
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(b) - Int128(a))/toms(c))
1011
guess(a::Date,b::Date,c) = Int64(div(Int64(b - a),days(c)))
12+
len(a::Time,b::Time,c) = Int64(div(Int64(b - a),tons(c)))
1113
function len(a,b,c)
1214
lo, hi, st = min(a,b), max(a,b), abs(c)
1315
i = guess(a,b,c)-1

base/dates/types.jl

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ for T in (:Year,:Month,:Week,:Day)
1212
$T(v::Number) = new(v)
1313
end
1414
end
15-
for T in (:Hour,:Minute,:Second,:Millisecond)
15+
for T in (:Hour,:Minute,:Second,:Millisecond,:Microsecond,:Nanosecond)
1616
@eval immutable $T <: TimePeriod
1717
value::Int64
1818
$T(v::Number) = new(v)
@@ -58,10 +58,11 @@ immutable Date <: TimeType
5858
Date(instant::UTInstant{Day}) = new(instant)
5959
end
6060

61-
# Fallback constructors
62-
_c(x) = convert(Int64,x)
63-
DateTime(y,m=1,d=1,h=0,mi=0,s=0,ms=0) = DateTime(_c(y),_c(m),_c(d),_c(h),_c(mi),_c(s),_c(ms))
64-
Date(y,m=1,d=1) = Date(_c(y),_c(m),_c(d))
61+
# Time is a nanosecond precision representation of hours:minutes:seconds.fractional
62+
immutable Time <: TimeType
63+
instant::Nanosecond
64+
Time(instant::Nanosecond) = new(instant)
65+
end
6566

6667
# Convert y,m,d to # of Rata Die days
6768
# Works by shifting the beginning of the year to March 1,
@@ -112,6 +113,21 @@ function Date(y::Int64,m::Int64=1,d::Int64=1)
112113
return Date(UTD(totaldays(y,m,d)))
113114
end
114115

116+
"""
117+
Time(h, [mi, s, ms, us, ns]) -> Time
118+
119+
Construct a `Time` type by parts. Arguments must be convertible to `Int64`.
120+
"""
121+
function Time(h::Int64,mi::Int64=0,s::Int64=0,ms::Int64=0,us::Int64=0,ns::Int64=0)
122+
-1 < h < 24 || throw(ArgumentError("Hour: $h out of range (0:23)"))
123+
-1 < mi < 60 || throw(ArgumentError("Minute: $mi out of range (0:59)"))
124+
-1 < s < 60 || throw(ArgumentError("Second: $s out of range (0:59)"))
125+
-1 < ms < 1000 || throw(ArgumentError("Millisecond: $ms out of range (0:999)"))
126+
-1 < us < 1000 || throw(ArgumentError("Microsecond: $us out of range (0:999)"))
127+
-1 < ns < 1000 || throw(ArgumentError("Nanosecond: $ns out of range (0:999)"))
128+
return Time(Nanosecond(ns + 1000us + 1000000ms + 1000000000s + 60000000000mi + 3600000000000h))
129+
end
130+
115131
# Convenience constructors from Periods
116132
function DateTime(y::Year,m::Month=Month(1),d::Day=Day(1),
117133
h::Hour=Hour(0),mi::Minute=Minute(0),
@@ -121,6 +137,11 @@ function DateTime(y::Year,m::Month=Month(1),d::Day=Day(1),
121137
end
122138

123139
Date(y::Year,m::Month=Month(1),d::Day=Day(1)) = Date(value(y),value(m),value(d))
140+
function Time(h::Hour,mi::Minute=Minute(0),s::Second=Second(0),
141+
ms::Millisecond=Millisecond(0),
142+
us::Microsecond=Microsecond(0),ns::Nanosecond=Nanosecond(0))
143+
return Time(value(h),value(mi),value(s),value(ms),value(us),value(ns))
144+
end
124145

125146
# To allow any order/combination of Periods
126147

@@ -161,6 +182,31 @@ function Date(periods::Period...)
161182
return Date(y,m,d)
162183
end
163184

185+
"""
186+
Time(period::Period...) -> Time
187+
188+
Construct a `Time` type by `Period` type parts. Arguments may be in any order. `Time` parts
189+
not provided will default to the value of `Dates.default(period)`.
190+
"""
191+
function Time(periods::Period...)
192+
h = Hour(0); mi = Minute(0); s = Second(0)
193+
ms = Millisecond(0); us = Microsecond(0); ns = Nanosecond(0)
194+
for p in periods
195+
isa(p, Hour) && (h = p::Hour)
196+
isa(p, Minute) && (mi = p::Minute)
197+
isa(p, Second) && (s = p::Second)
198+
isa(p, Millisecond) && (ms = p::Millisecond)
199+
isa(p, Microsecond) && (us = p::Microsecond)
200+
isa(p, Nanosecond) && (ns = p::Nanosecond)
201+
end
202+
return Time(h,mi,s,ms,us,ns)
203+
end
204+
205+
# Fallback constructors
206+
DateTime(y,m=1,d=1,h=0,mi=0,s=0,ms=0) = DateTime(Int64(y),Int64(m),Int64(d),Int64(h),Int64(mi),Int64(s),Int64(ms))
207+
Date(y,m=1,d=1) = Date(Int64(y),Int64(m),Int64(d))
208+
Time(h,mi=0,s=0,ms=0,us=0,ns=0) = Time(Int64(h),Int64(mi),Int64(s),Int64(ms),Int64(us),Int64(ns))
209+
164210
# Traits, Equality
165211
Base.isfinite{T<:TimeType}(::Union{Type{T},T}) = true
166212
calendar(dt::DateTime) = ISOCalendar
@@ -169,21 +215,32 @@ calendar(dt::Date) = ISOCalendar
169215
"""
170216
eps(::DateTime) -> Millisecond
171217
eps(::Date) -> Day
218+
eps(::Time) -> Nanosecond
172219
173-
Returns `Millisecond(1)` for `DateTime` values and `Day(1)` for `Date` values.
220+
Returns `Millisecond(1)` for `DateTime` values, `Day(1)` for `Date` values, and `Nanosecond(1)` for `Time` values.
174221
"""
175222
Base.eps
176223

177224
Base.eps(dt::DateTime) = Millisecond(1)
178225
Base.eps(dt::Date) = Day(1)
226+
Base.eps(t::Time) = Nanosecond(1)
179227

180228
Base.typemax(::Union{DateTime,Type{DateTime}}) = DateTime(146138512,12,31,23,59,59)
181229
Base.typemin(::Union{DateTime,Type{DateTime}}) = DateTime(-146138511,1,1,0,0,0)
182230
Base.typemax(::Union{Date,Type{Date}}) = Date(252522163911149,12,31)
183231
Base.typemin(::Union{Date,Type{Date}}) = Date(-252522163911150,1,1)
232+
Base.typemax(::Union{Time,Type{Time}}) = Time(23,59,59,999,999,999)
233+
Base.typemin(::Union{Time,Type{Time}}) = Time(0)
234+
184235
# Date-DateTime promotion, isless, ==
185236
Base.promote_rule(::Type{Date},x::Type{DateTime}) = DateTime
186-
Base.isless(x::Date,y::Date) = isless(value(x),value(y))
187237
Base.isless(x::DateTime,y::DateTime) = isless(value(x),value(y))
238+
Base.isless(x::Date,y::Date) = isless(value(x),value(y))
239+
Base.isless(x::Time,y::Time) = isless(value(x),value(y))
188240
Base.isless(x::TimeType,y::TimeType) = isless(promote(x,y)...)
189241
==(x::TimeType,y::TimeType) = ===(promote(x,y)...)
242+
function ==(a::Time,b::Time)
243+
return hour(a) == hour(b) && minute(a) == minute(b) &&
244+
second(a) == second(b) && millisecond(a) == millisecond(b) &&
245+
microsecond(a) == microsecond(b) && nanosecond(a) == nanosecond(b)
246+
end

0 commit comments

Comments
 (0)