Skip to content

Commit 06fa32c

Browse files
authored
Initial work to add a Time type to Base.Dates (#12274)
* Add a Time type to the Base.Dates module
1 parent 75302ae commit 06fa32c

18 files changed

+467
-94
lines changed

base/dates/Dates.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ include("io.jl")
2020

2121
export Period, DatePeriod, TimePeriod,
2222
Year, Month, Week, Day, Hour, Minute, Second, Millisecond,
23-
TimeZone, UTC, TimeType, DateTime, Date,
23+
Microsecond, Nanosecond,
24+
TimeZone, UTC, TimeType, DateTime, Date, Time,
2425
# periods.jl
2526
canonicalize,
2627
# accessors.jl
2728
yearmonthday, yearmonth, monthday, year, month, week, day,
2829
hour, minute, second, millisecond, dayofmonth,
30+
microsecond, nanosecond,
2931
# query.jl
3032
dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
3133
dayofweekofmonth, daysofweekinmonth, monthname, monthabbr,

base/dates/accessors.jl

Lines changed: 18 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),Int64(24))
57+
minute(t::Time) = mod(fld(value(t),60000000000),Int64(60))
58+
second(t::Time) = mod(fld(value(t),1000000000),Int64(60))
59+
millisecond(t::Time) = mod(fld(value(t),Int64(1000000)),Int64(1000))
60+
microsecond(t::Time) = mod(fld(value(t),Int64(1000)),Int64(1000))
61+
nanosecond(t::Time) = mod(value(t),Int64(1000))
5562

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

@@ -122,3 +129,14 @@ for parts in (["year", "month"], ["month", "day"], ["year", "month", "day"])
122129
""" $func(dt::TimeType)
123130
end
124131
end
132+
133+
for func in (:hour, :minute, :second, :millisecond, :microsecond, :nanosecond)
134+
name = string(func)
135+
@eval begin
136+
@doc """
137+
$($name)(t::Time) -> Int64
138+
139+
The $($name) of a `Time` as an `Int64`.
140+
""" $func(t::Time)
141+
end
142+
end

base/dates/adjusters.jl

Lines changed: 34 additions & 0 deletions
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
@@ -184,6 +191,33 @@ function DateTime(func::Function, y, m, d, h, mi, s; step::Period=Millisecond(1)
184191
return adjust(DateFunction(func, negate, DateTime(y)), DateTime(y, m, d, h, mi, s), step, limit)
185192
end
186193

194+
"""
195+
Time(f::Function, h[, mi, s, ms, us]; step=Second(1), negate=false, limit=10000) -> Time
196+
197+
Create a `Time` through the adjuster API. The starting point will be constructed from the
198+
provided `h, mi, s, ms, us` arguments, and will be adjusted until `f::Function` returns `true`. The step
199+
size in adjusting can be provided manually through the `step` keyword. If `negate=true`,
200+
then the adjusting will stop when `f::Function` returns `false` instead of `true`. `limit`
201+
provides a limit to the max number of iterations the adjustment API will pursue before
202+
throwing an error (in the case that `f::Function` is never satisfied). Note that the default step
203+
will adjust to allow for greater precision for the given arguments; i.e. if hour, minute, and second
204+
arguments are provided, the default step will be `Millisecond(1)` instead of `Second(1)`.
205+
"""
206+
Time(::Function, args...)
207+
208+
function Time(func::Function, h, mi=0; step::Period=Second(1), negate::Bool=false, limit::Int=10000)
209+
return adjust(DateFunction(func, negate, Time(h, mi)), Time(h, mi), step, limit)
210+
end
211+
function Time(func::Function, h, mi, s; step::Period=Millisecond(1), negate::Bool=false, limit::Int=10000)
212+
return adjust(DateFunction(func, negate, Time(h, mi, s)), Time(h, mi, s), step, limit)
213+
end
214+
function Time(func::Function, h, mi, s, ms; step::Period=Microsecond(1), negate::Bool=false, limit::Int=10000)
215+
return adjust(DateFunction(func, negate,Time(h, mi, s, ms)),Time(h, mi, s, ms), step, limit)
216+
end
217+
function Time(func::Function, h, mi, s, ms, us; step::Period=Nanosecond(1), negate::Bool=false, limit::Int=10000)
218+
return adjust(DateFunction(func, negate, Time(h, mi, s, ms, us)), Time(h, mi, s, ms, us), step, limit)
219+
end
220+
187221
# Return the next TimeType that falls on dow
188222
ISDAYOFWEEK = Dict(Mon => DateFunction(ismonday, false, Date(0)),
189223
Tue => DateFunction(istuesday, false, Date(0)),

base/dates/arithmetic.jl

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

11+
# Date-Time arithmetic
12+
"""
13+
dt::Date + t::Time -> DateTime
14+
15+
The addition of a `Date` with a `Time` produces a `DateTime`. The hour, minute, second, and millisecond parts of
16+
the `Time` are used along with the year, month, and day of the `Date` to create the new `DateTime`.
17+
Non-zero microseconds or nanoseconds in the `Time` type will result in an `InexactError` being thrown.
18+
"""
19+
function (+)(dt::Date, t::Time)
20+
(microsecond(t) > 0 || nanosecond(t) > 0) && throw(InexactError())
21+
y, m, d = yearmonthday(dt)
22+
return DateTime(y, m, d, hour(t), minute(t), second(t), millisecond(t))
23+
end
24+
(+)(t::Time, dt::Date) = dt + t
25+
1126
# TimeType-Year arithmetic
1227
function (+)(dt::DateTime,y::Year)
1328
oy,m,d = yearmonthday(dt); ny = oy+value(y); ld = daysinmonth(ny,m)
@@ -57,14 +72,16 @@ function (-)(dt::Date,z::Month)
5772
mm = monthwrap(m,-value(z)); ld = daysinmonth(ny,mm)
5873
return Date(ny,mm,d <= ld ? d : ld)
5974
end
60-
(+)(x::Date,y::Week) = return Date(UTD(value(x) + 7*value(y)))
61-
(-)(x::Date,y::Week) = return Date(UTD(value(x) - 7*value(y)))
62-
(+)(x::Date,y::Day) = return Date(UTD(value(x) + value(y)))
63-
(-)(x::Date,y::Day) = return Date(UTD(value(x) - value(y)))
64-
(+)(x::DateTime,y::Period) = return DateTime(UTM(value(x)+toms(y)))
65-
(-)(x::DateTime,y::Period) = return DateTime(UTM(value(x)-toms(y)))
66-
(+)(y::Period,x::TimeType) = x + y
67-
(-)(y::Period,x::TimeType) = x - y
75+
(+)(x::Date, y::Week) = return Date(UTD(value(x) + 7*value(y)))
76+
(-)(x::Date, y::Week) = return Date(UTD(value(x) - 7*value(y)))
77+
(+)(x::Date, y::Day) = return Date(UTD(value(x) + value(y)))
78+
(-)(x::Date, y::Day) = return Date(UTD(value(x) - value(y)))
79+
(+)(x::DateTime, y::Period) = return DateTime(UTM(value(x) + toms(y)))
80+
(-)(x::DateTime, y::Period) = return DateTime(UTM(value(x) - toms(y)))
81+
(+)(x::Time, y::TimePeriod) = return Time(Nanosecond(value(x) + tons(y)))
82+
(-)(x::Time, y::TimePeriod) = return Time(Nanosecond(value(x) - tons(y)))
83+
(+)(y::Period, x::TimeType) = x + y
84+
(-)(y::Period, x::TimeType) = x - y
6885

6986
for op in (:+, :-)
7087
@eval begin

base/dates/conversions.jl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,18 @@ the new `DateTime` are assumed to be zero.
1919
"""
2020
DateTime(dt::TimeType) = convert(DateTime,dt)
2121

22-
Base.convert(::Type{DateTime},dt::Date) = DateTime(UTM(value(dt)*86400000))
23-
Base.convert(::Type{Date},dt::DateTime) = Date(UTD(days(dt)))
22+
"""
23+
Time(dt::DateTime) -> Time
24+
25+
Converts a `DateTime` to a `Time`. The hour, minute, second, and millisecond parts of
26+
the `DateTime` are used to create the new `Time`. Microsecond and nanoseconds are zero by default.
27+
"""
28+
Time(dt::DateTime) = convert(Time, dt)
29+
30+
Base.convert(::Type{DateTime}, dt::Date) = DateTime(UTM(value(dt)*86400000))
31+
Base.convert(::Type{Date}, dt::DateTime) = Date(UTD(days(dt)))
32+
Base.convert(::Type{Time}, dt::DateTime) = Time(Nanosecond((value(dt) % 86400000) * 1000000))
33+
2434
"""
2535
convert{T<:Real}(::Type{T}, dt::DateTime) -> T
2636
Converts a DateTime value `dt` to a number of type `T`. The returned value corresponds to the number of Rata Die milliseconds since epoch.

base/dates/io.jl

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,41 @@
22

33
# TODO: optimize this
44
function Base.string(dt::DateTime)
5-
y,m,d = yearmonthday(days(dt))
6-
h,mi,s = hour(dt),minute(dt),second(dt)
7-
yy = y < 0 ? @sprintf("%05i",y) : lpad(y,4,"0")
8-
mm = lpad(m,2,"0")
9-
dd = lpad(d,2,"0")
10-
hh = lpad(h,2,"0")
11-
mii = lpad(mi,2,"0")
12-
ss = lpad(s,2,"0")
13-
ms = millisecond(dt) == 0 ? "" : string(millisecond(dt)/1000.0)[2:end]
14-
return "$yy-$mm-$(dd)T$hh:$mii:$ss$(ms)"
5+
y, m, d = yearmonthday(days(dt))
6+
h, mi, s = hour(dt), minute(dt), second(dt)
7+
yy = y < 0 ? @sprintf("%05i", y) : lpad(y, 4, "0")
8+
mm = lpad(m, 2, "0")
9+
dd = lpad(d, 2, "0")
10+
hh = lpad(h, 2, "0")
11+
mii = lpad(mi, 2, "0")
12+
ss = lpad(s, 2, "0")
13+
ms = millisecond(dt) == 0 ? "" : string(millisecond(dt) / 1000.0)[2:end]
14+
return "$yy-$mm-$(dd)T$hh:$mii:$ss$ms"
1515
end
16-
Base.show(io::IO,x::DateTime) = print(io,string(x))
16+
17+
Base.show(io::IO, x::DateTime) = print(io, string(x))
18+
1719
function Base.string(dt::Date)
18-
y,m,d = yearmonthday(value(dt))
19-
yy = y < 0 ? @sprintf("%05i",y) : lpad(y,4,"0")
20-
mm = lpad(m,2,"0")
21-
dd = lpad(d,2,"0")
20+
y, m, d = yearmonthday(value(dt))
21+
yy = y < 0 ? @sprintf("%05i", y) : lpad(y, 4, "0")
22+
mm = lpad(m, 2, "0")
23+
dd = lpad(d, 2, "0")
2224
return "$yy-$mm-$dd"
2325
end
24-
Base.show(io::IO,x::Date) = print(io,string(x))
26+
27+
Base.show(io::IO, x::Date) = print(io, string(x))
28+
29+
function Base.string(t::Time)
30+
h, mi, s = hour(t), minute(t), second(t)
31+
hh = lpad(h, 2, "0")
32+
mii = lpad(mi, 2, "0")
33+
ss = lpad(s, 2, "0")
34+
nss = tons(Millisecond(t)) + tons(Microsecond(t)) + tons(Nanosecond(t))
35+
ns = nss == 0 ? "" : rstrip(@sprintf("%.9f", nss / 1e+9)[2:end], '0')
36+
return "$hh:$mii:$ss$ns"
37+
end
38+
39+
Base.show(io::IO, x::Time) = print(io, string(x))
2540

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

base/dates/periods.jl

Lines changed: 38 additions & 15 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,19 @@ 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)`](@ref)." : ""
19+
typs = period in (:Microsecond, :Nanosecond) ? ["Time"] :
20+
period in (:Hour, :Minute, :Second, :Millisecond) ? ["Time", "DateTime"] : ["Date","DateTime"]
21+
reference = period == :Week ? " For details see [`$accessor_str(::Union{Date, DateTime})`](@ref)." : ""
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 $($typ_str) as a `$($period_str)`.$($reference)
28+
""" $period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
29+
end
30+
end
2231
@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-
3032
@doc """
3133
$($period_str)(v)
3234
@@ -117,15 +119,28 @@ periodisless(::Period,::Hour) = false
117119
periodisless(::Minute,::Hour) = true
118120
periodisless(::Second,::Hour) = true
119121
periodisless(::Millisecond,::Hour) = true
122+
periodisless(::Microsecond,::Hour) = true
123+
periodisless(::Nanosecond,::Hour) = true
120124
periodisless(::Period,::Minute) = false
121125
periodisless(::Second,::Minute) = true
122126
periodisless(::Millisecond,::Minute) = true
127+
periodisless(::Microsecond,::Minute) = true
128+
periodisless(::Nanosecond,::Minute) = true
123129
periodisless(::Period,::Second) = false
124130
periodisless(::Millisecond,::Second) = true
131+
periodisless(::Microsecond,::Second) = true
132+
periodisless(::Nanosecond,::Second) = true
125133
periodisless(::Period,::Millisecond) = false
134+
periodisless(::Microsecond,::Millisecond) = true
135+
periodisless(::Nanosecond,::Millisecond) = true
136+
periodisless(::Period,::Microsecond) = false
137+
periodisless(::Nanosecond,::Microsecond) = true
138+
periodisless(::Period,::Nanosecond) = false
126139

127140
# return (next coarser period, conversion factor):
128141
coarserperiod{P<:Period}(::Type{P}) = (P,1)
142+
coarserperiod(::Type{Nanosecond}) = (Microsecond,1000)
143+
coarserperiod(::Type{Microsecond}) = (Millisecond,1000)
129144
coarserperiod(::Type{Millisecond}) = (Second,1000)
130145
coarserperiod(::Type{Second}) = (Minute,60)
131146
coarserperiod(::Type{Minute}) = (Hour,60)
@@ -202,6 +217,9 @@ julia> Dates.CompoundPeriod(Dates.Minute(50000))
202217
"""
203218
CompoundPeriod{P<:Period}(p::Vector{P}) = CompoundPeriod(Array{Period}(p))
204219

220+
CompoundPeriod(t::Time) = CompoundPeriod(Period[Hour(t), Minute(t), Second(t), Millisecond(t),
221+
Microsecond(t), Nanosecond(t)])
222+
205223
CompoundPeriod(p::Period...) = CompoundPeriod(Period[p...])
206224

207225

@@ -382,7 +400,7 @@ end
382400

383401
# Fixed-value Periods (periods corresponding to a well-defined time interval,
384402
# as opposed to variable calendar intervals like Year).
385-
typealias FixedPeriod Union{Week,Day,Hour,Minute,Second,Millisecond}
403+
typealias FixedPeriod Union{Week,Day,Hour,Minute,Second,Millisecond,Microsecond,Nanosecond}
386404

387405
# like div but throw an error if remainder is nonzero
388406
function divexact(x,y)
@@ -392,10 +410,10 @@ function divexact(x,y)
392410
end
393411

394412
# FixedPeriod conversions and promotion rules
395-
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1)]
413+
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1000),(Microsecond,1000),(Nanosecond,1)]
396414
for i = 1:length(fixedperiod_conversions)
397415
(T,n) = fixedperiod_conversions[i]
398-
N = 1
416+
N = Int64(1)
399417
for j = i-1:-1:1 # less-precise periods
400418
(Tc,nc) = fixedperiod_conversions[j]
401419
N *= nc
@@ -432,6 +450,8 @@ Base.promote_rule(::Type{Year}, ::Type{Month}) = Month
432450
Base.isless{T<:OtherPeriod,S<:OtherPeriod}(x::T,y::S) = isless(promote(x,y)...)
433451

434452
# truncating conversions to milliseconds and days:
453+
toms(c::Nanosecond) = div(value(c), 1000000)
454+
toms(c::Microsecond) = div(value(c), 1000)
435455
toms(c::Millisecond) = value(c)
436456
toms(c::Second) = 1000*value(c)
437457
toms(c::Minute) = 60000*value(c)
@@ -440,7 +460,10 @@ toms(c::Day) = 86400000*value(c)
440460
toms(c::Week) = 604800000*value(c)
441461
toms(c::Month) = 86400000.0*30.436875*value(c)
442462
toms(c::Year) = 86400000.0*365.2425*value(c)
443-
toms(c::CompoundPeriod) = isempty(c.periods)?0.0 : Float64(sum(toms,c.periods))
463+
toms(c::CompoundPeriod) = isempty(c.periods)? 0.0 : Float64(sum(toms, c.periods))
464+
tons(x) = toms(x) * 1000000
465+
tons(x::Microsecond) = value(x) * 1000
466+
tons(x::Nanosecond) = value(x)
444467
days(c::Millisecond) = div(value(c),86400000)
445468
days(c::Second) = div(value(c),86400)
446469
days(c::Minute) = div(value(c),1440)

base/dates/ranges.jl

Lines changed: 4 additions & 2 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
9-
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(b) - Int128(a))/toms(c))
10-
guess(a::Date,b::Date,c) = Int64(div(Int64(b - a),days(c)))
10+
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(b) - Int128(a)) / toms(c))
11+
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

0 commit comments

Comments
 (0)