Skip to content

Commit

Permalink
Add support for a Time TimeType in the Dates module
Browse files Browse the repository at this point in the history
  • Loading branch information
quinnj committed May 13, 2016
1 parent 906e06b commit 8bd39af
Show file tree
Hide file tree
Showing 15 changed files with 414 additions and 23 deletions.
4 changes: 3 additions & 1 deletion base/Dates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ include("dates/io.jl")

export Period, DatePeriod, TimePeriod,
Year, Month, Week, Day, Hour, Minute, Second, Millisecond,
TimeZone, UTC, TimeType, DateTime, Date,
Microsecond, Nanosecond,
TimeZone, UTC, TimeType, DateTime, Date, Time,
# accessors.jl
yearmonthday, yearmonth, monthday, year, month, week, day,
hour, minute, second, millisecond, dayofmonth,
microsecond, nanosecond,
# query.jl
dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
dayofweekofmonth, daysofweekinmonth, monthname, monthabbr,
Expand Down
24 changes: 24 additions & 0 deletions base/dates/accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ end

# Accessor functions
value(dt::TimeType) = dt.instant.periods.value
value(t::Time) = t.instant.value
days(dt::Date) = value(dt)
days(dt::DateTime) = fld(value(dt),86400000)
year(dt::TimeType) = year(days(dt))
Expand All @@ -52,6 +53,12 @@ hour(dt::DateTime) = mod(fld(value(dt),3600000),24)
minute(dt::DateTime) = mod(fld(value(dt),60000),60)
second(dt::DateTime) = mod(fld(value(dt),1000),60)
millisecond(dt::DateTime) = mod(value(dt),1000)
hour(t::Time) = mod(fld(value(t),3600000000000),24)
minute(t::Time) = mod(fld(value(t),60000000000),60)
second(t::Time) = mod(fld(value(t),1000000000),60)
millisecond(t::Time) = mod(fld(value(t),1000000),1000)
microsecond(t::Time) = mod(fld(value(t),1000),1000)
nanosecond(t::Time) = mod(value(t),1000)

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

Expand All @@ -67,6 +74,12 @@ yearmonthday(dt::TimeType) = yearmonthday(days(dt))
@vectorize_1arg DateTime minute
@vectorize_1arg DateTime second
@vectorize_1arg DateTime millisecond
@vectorize_1arg Time hour
@vectorize_1arg Time minute
@vectorize_1arg Time second
@vectorize_1arg Time millisecond
@vectorize_1arg Time microsecond
@vectorize_1arg Time nanosecond

@vectorize_1arg TimeType dayofmonth
@vectorize_1arg TimeType yearmonth
Expand Down Expand Up @@ -137,3 +150,14 @@ for parts in (["year", "month"], ["month", "day"], ["year", "month", "day"])
""" $func(dt::TimeType)
end
end

for func in (:hour, :minute, :second, :millisecond, :microsecond, :nanosecond)
name = string(func)
@eval begin
@doc """
$($name)(t::Time) -> Int64
The $($name) of a `Time` as an `Int64`.
""" $func(t::Time)
end
end
34 changes: 33 additions & 1 deletion base/dates/adjusters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Base.trunc(dt::DateTime,p::Type{Minute}) = dt - Second(dt) - Millisecond(dt)
Base.trunc(dt::DateTime,p::Type{Second}) = dt - Millisecond(dt)
Base.trunc(dt::DateTime,p::Type{Millisecond}) = dt

Base.trunc(t::Time,p::Type{Hour}) = Time(Hour(t))
Base.trunc(t::Time,p::Type{Minute}) = Time(Hour(t),Minute(t))
Base.trunc(t::Time,p::Type{Second}) = Time(Hour(t),Minute(t),Second(t))
Base.trunc(t::Time,p::Type{Millisecond}) = t - Microsecond(t) - Nanosecond(t)
Base.trunc(t::Time,p::Type{Microsecond}) = t - Nanosecond(t)
Base.trunc(t::Time,p::Type{Nanosecond}) = t

"""
trunc(dt::TimeType, ::Type{Period}) -> TimeType
Expand Down Expand Up @@ -167,12 +174,37 @@ provided `y, m` arguments, and will be adjusted until `f::Function` returns `tru
size in adjusting can be provided manually through the `step` keyword. If `negate=true`,
then the adjusting will stop when `f::Function` returns `false` instead of `true`. `limit`
provides a limit to the max number of iterations the adjustment API will pursue before
throwing an error (given that `f::Function` is never satisfied).
throwing an error (in the case that `f::Function` is never satisfied).
"""
function Date(func::Function,y,m=1,d=1;step::Period=Day(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,Date(y,m,d)),Date(y,m,d),step,limit)
end

"""
Time(f::Function, h[, mi, s, ms, us]; step=Second(1), negate=false, limit=10000) -> Time
Create a `Time` through the adjuster API. The starting point will be constructed from the
provided `h, mi, s, ms, us` arguments, and will be adjusted until `f::Function` returns `true`. The step
size in adjusting can be provided manually through the `step` keyword. If `negate=true`,
then the adjusting will stop when `f::Function` returns `false` instead of `true`. `limit`
provides a limit to the max number of iterations the adjustment API will pursue before
throwing an error (in the case that `f::Function` is never satisfied).
"""
Time(::Function, args...)

function Time(func::Function,h,mi=0;step::Dates.Period=Dates.Second(1),negate::Bool=false,limit::Int=10000)
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi)),Dates.Time(h,mi),step,limit)
end
function Time(func::Function,h,mi,s;step::Dates.Period=Dates.Millisecond(1),negate::Bool=false,limit::Int=10000)
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s)),Dates.Time(h,mi,s),step,limit)
end
function Time(func::Function,h,mi,s,ms;step::Dates.Period=Dates.Microsecond(1),negate::Bool=false,limit::Int=10000)
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s,ms)),Dates.Time(h,mi,s,ms),step,limit)
end
function Time(func::Function,h,mi,s,ms,us;step::Dates.Period=Dates.Nanosecond(1),negate::Bool=false,limit::Int=10000)
return Dates.adjust(Dates.DateFunction(func,negate,Dates.Time(h,mi,s,ms,us)),Dates.Time(h,mi,s,ms,us),step,limit)
end

"""
DateTime(f::Function, y[, m, d, h, mi, s]; step=Day(1), negate=false, limit=10000) -> DateTime
Expand Down
5 changes: 5 additions & 0 deletions base/dates/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
(+)(x::TimeType) = x
(-){T<:TimeType}(x::T,y::T) = x.instant - y.instant

# Time arithmetic
(+)(x::Time,y::Time) = Time(Nanosecond(+(value(x),value(y))))

# TimeType-Year arithmetic
function (+)(dt::DateTime,y::Year)
oy,m,d = yearmonthday(dt); ny = oy+value(y); ld = daysinmonth(ny,m)
Expand Down Expand Up @@ -63,6 +66,8 @@ end
(-)(x::Date,y::Day) = return Date(UTD(value(x) - value(y)))
(+)(x::DateTime,y::Period) = return DateTime(UTM(value(x)+toms(y)))
(-)(x::DateTime,y::Period) = return DateTime(UTM(value(x)-toms(y)))
(+)(x::Time,y::TimePeriod) = return Time(Nanosecond(value(x)+tons(y)))
(-)(x::Time,y::TimePeriod) = return Time(Nanosecond(value(x)-tons(y)))
(+)(y::Period,x::TimeType) = x + y
(-)(y::Period,x::TimeType) = x - y

Expand Down
10 changes: 10 additions & 0 deletions base/dates/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ function Base.string(dt::Date)
return "$yy-$mm-$dd"
end
Base.show(io::IO,x::Date) = print(io,string(x))
function Base.string(t::Time)
h,mi,s = hour(t),minute(t),second(t)
hh = lpad(h,2,"0")
mii = lpad(mi,2,"0")
ss = lpad(s,2,"0")
nss = tons(Millisecond(t)) + tons(Microsecond(t)) + tons(Nanosecond(t))
ns = nss == 0 ? "" : @sprintf("%.9f", nss/1e+9)[2:end]
return "$hh:$mii:$ss$(ns)"
end
Base.show(io::IO,x::Time) = print(io,string(x))

### Parsing
const english = Dict{String,Int}("january"=>1,"february"=>2,"march"=>3,"april"=>4,
Expand Down
47 changes: 33 additions & 14 deletions base/dates/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ value(x::Period) = x.value
# The default constructors for Periods work well in almost all cases
# P(x) = new((convert(Int64,x))
# The following definitions are for Period-specific safety
for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond)
for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
period_str = string(period)
accessor_str = lowercase(period_str)
# Convenience method for show()
Expand All @@ -16,17 +16,20 @@ for period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond
# AbstractString parsing (mainly for IO code)
@eval $period(x::AbstractString) = $period(Base.parse(Int64,x))
# Period accessors
typ_str = period in (:Hour, :Minute, :Second, :Millisecond) ? "DateTime" : "TimeType"
description = typ_str == "TimeType" ? "`Date` or `DateTime`" : "`$typ_str`"
reference = period == :Week ? " For details see [`$accessor_str(::$typ_str)`](:func:`$accessor_str`)." : ""
typs = period in (:Microsecond, :Nanosecond) ? ["Time"] : (period in (:Hour, :Minute, :Second, :Millisecond) ? ["DateTime","Time"] : ["TimeType"])
description = typs == ["TimeType"] ? "`Date` or `DateTime`" : (typs == ["Time"] ? "`Time`" : "`Time` or `DateTime`")
reference = period == :Week ? " For details see [`$accessor_str(::TimeType)`](:func:`$accessor_str`)." : ""
for typ_str in typs
@eval begin
@doc """
$($period_str)(dt::$($typ_str)) -> $($period_str)
The $($accessor_str) part of a $($description) as a `$($period_str)`.$($reference)
""" ->
$period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
end
end
@eval begin
@doc """
$($period_str)(dt::$($typ_str)) -> $($period_str)
The $($accessor_str) part of a $($description) as a `$($period_str)`.$($reference)
""" ->
$period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))

@doc """
$($period_str)(v)
Expand Down Expand Up @@ -125,15 +128,28 @@ periodisless(::Period,::Hour) = false
periodisless(::Minute,::Hour) = true
periodisless(::Second,::Hour) = true
periodisless(::Millisecond,::Hour) = true
periodisless(::Microsecond,::Hour) = true
periodisless(::Nanosecond,::Hour) = true
periodisless(::Period,::Minute) = false
periodisless(::Second,::Minute) = true
periodisless(::Millisecond,::Minute) = true
periodisless(::Microsecond,::Minute) = true
periodisless(::Nanosecond,::Minute) = true
periodisless(::Period,::Second) = false
periodisless(::Millisecond,::Second) = true
periodisless(::Microsecond,::Second) = true
periodisless(::Nanosecond,::Second) = true
periodisless(::Period,::Millisecond) = false
periodisless(::Microsecond,::Millisecond) = true
periodisless(::Nanosecond,::Millisecond) = true
periodisless(::Period,::Microsecond) = false
periodisless(::Nanosecond,::Microsecond) = true
periodisless(::Period,::Nanosecond) = false

# return (next coarser period, conversion factor):
coarserperiod{P<:Period}(::Type{P}) = (P,1)
coarserperiod(::Type{Nanosecond}) = (Microsecond,1000)
coarserperiod(::Type{Microsecond}) = (Millisecond,1000)
coarserperiod(::Type{Millisecond}) = (Second,1000)
coarserperiod(::Type{Second}) = (Minute,60)
coarserperiod(::Type{Minute}) = (Hour,60)
Expand Down Expand Up @@ -368,7 +384,7 @@ end

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

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

# FixedPeriod conversions and promotion rules
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1)]
const fixedperiod_conversions = [(Week,7),(Day,24),(Hour,60),(Minute,60),(Second,1000),(Millisecond,1000),(Microsecond,1000),(Nanosecond,1)]
for i = 1:length(fixedperiod_conversions)
(T,n) = fixedperiod_conversions[i]
N = 1
Expand Down Expand Up @@ -426,7 +442,10 @@ toms(c::Day) = 86400000*value(c)
toms(c::Week) = 604800000*value(c)
toms(c::Month) = 86400000.0*30.436875*value(c)
toms(c::Year) = 86400000.0*365.2425*value(c)
toms(c::CompoundPeriod) = isempty(c.periods)?0.0 : Float64(sum(toms,c.periods))
toms(c::CompoundPeriod) = isempty(c.periods)? 0.0 : Float64(sum(toms,c.periods))
tons(x) = Dates.toms(x) * 1000000
tons(x::Microsecond) = value(x) * 1000
tons(x::Nanosecond) = value(x)
days(c::Millisecond) = div(value(c),86400000)
days(c::Second) = div(value(c),86400)
days(c::Minute) = div(value(c),1440)
Expand Down
2 changes: 2 additions & 0 deletions base/dates/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

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

# Given a start and end date, how many steps/periods are in between
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(b) - Int128(a))/toms(c))
guess(a::Date,b::Date,c) = Int64(div(Int64(b - a),days(c)))
len(a::Time,b::Time,c) = Int64(div(Int64(b - a),tons(c)))
function len(a,b,c)
lo, hi, st = min(a,b), max(a,b), abs(c)
i = guess(a,b,c)-1
Expand Down
71 changes: 64 additions & 7 deletions base/dates/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ for T in (:Year,:Month,:Week,:Day)
$T(v::Number) = new(v)
end
end
for T in (:Hour,:Minute,:Second,:Millisecond)
for T in (:Hour,:Minute,:Second,:Millisecond,:Microsecond,:Nanosecond)
@eval immutable $T <: TimePeriod
value::Int64
$T(v::Number) = new(v)
Expand Down Expand Up @@ -58,10 +58,11 @@ immutable Date <: TimeType
Date(instant::UTInstant{Day}) = new(instant)
end

# Fallback constructors
_c(x) = convert(Int64,x)
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))
Date(y,m=1,d=1) = Date(_c(y),_c(m),_c(d))
# Time is a nanosecond precision representation of hours:minutes:seconds.fractional
immutable Time <: TimeType
instant::Nanosecond
Time(instant::Nanosecond) = new(instant)
end

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

"""
Time(h, [mi, s, ms, us, ns]) -> Time
Construct a `Time` type by parts. Arguments must be convertible to `Int64`.
"""
function Time(h::Int64,mi::Int64=0,s::Int64=0,ms::Int64=0,us::Int64=0,ns::Int64=0)
-1 < h < 24 || throw(ArgumentError("Hour: $h out of range (0:23)"))
-1 < mi < 60 || throw(ArgumentError("Minute: $mi out of range (0:59)"))
-1 < s < 60 || throw(ArgumentError("Second: $s out of range (0:59)"))
-1 < ms < 1000 || throw(ArgumentError("Millisecond: $ms out of range (0:999)"))
-1 < us < 1000 || throw(ArgumentError("Microsecond: $us out of range (0:999)"))
-1 < ns < 1000 || throw(ArgumentError("Nanosecond: $ns out of range (0:999)"))
return Time(Nanosecond(ns + 1000us + 1000000ms + 1000000000s + 60000000000mi + 3600000000000h))
end

# Convenience constructors from Periods
function DateTime(y::Year,m::Month=Month(1),d::Day=Day(1),
h::Hour=Hour(0),mi::Minute=Minute(0),
Expand All @@ -121,6 +137,11 @@ function DateTime(y::Year,m::Month=Month(1),d::Day=Day(1),
end

Date(y::Year,m::Month=Month(1),d::Day=Day(1)) = Date(value(y),value(m),value(d))
function Time(h::Hour,mi::Minute=Minute(0),s::Second=Second(0),
ms::Millisecond=Millisecond(0),
us::Microsecond=Microsecond(0),ns::Nanosecond=Nanosecond(0))
return Time(value(h),value(mi),value(s),value(ms),value(us),value(ns))
end

# To allow any order/combination of Periods

Expand Down Expand Up @@ -161,6 +182,31 @@ function Date(periods::Period...)
return Date(y,m,d)
end

"""
Time(period::Period...) -> Time
Construct a `Time` type by `Period` type parts. Arguments may be in any order. `Time` parts
not provided will default to the value of `Dates.default(period)`.
"""
function Time(periods::Period...)
h = Hour(0); mi = Minute(0); s = Second(0)
ms = Millisecond(0); us = Microsecond(0); ns = Nanosecond(0)
for p in periods
isa(p, Hour) && (h = p::Hour)
isa(p, Minute) && (mi = p::Minute)
isa(p, Second) && (s = p::Second)
isa(p, Millisecond) && (ms = p::Millisecond)
isa(p, Microsecond) && (us = p::Microsecond)
isa(p, Nanosecond) && (ns = p::Nanosecond)
end
return Time(h,mi,s,ms,us,ns)
end

# Fallback constructors
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))
Date(y,m=1,d=1) = Date(Int64(y),Int64(m),Int64(d))
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))

# Traits, Equality
Base.isfinite{T<:TimeType}(::Union{Type{T},T}) = true
calendar(dt::DateTime) = ISOCalendar
Expand All @@ -169,21 +215,32 @@ calendar(dt::Date) = ISOCalendar
"""
eps(::DateTime) -> Millisecond
eps(::Date) -> Day
eps(::Time) -> Nanosecond
Returns `Millisecond(1)` for `DateTime` values and `Day(1)` for `Date` values.
Returns `Millisecond(1)` for `DateTime` values, `Day(1)` for `Date` values, and `Nanosecond(1)` for `Time` values.
"""
Base.eps

Base.eps(dt::DateTime) = Millisecond(1)
Base.eps(dt::Date) = Day(1)
Base.eps(t::Time) = Nanosecond(1)

Base.typemax(::Union{DateTime,Type{DateTime}}) = DateTime(146138512,12,31,23,59,59)
Base.typemin(::Union{DateTime,Type{DateTime}}) = DateTime(-146138511,1,1,0,0,0)
Base.typemax(::Union{Date,Type{Date}}) = Date(252522163911149,12,31)
Base.typemin(::Union{Date,Type{Date}}) = Date(-252522163911150,1,1)
Base.typemax(::Union{Time,Type{Time}}) = Time(23,59,59,999,999,999)
Base.typemin(::Union{Time,Type{Time}}) = Time(0)

# Date-DateTime promotion, isless, ==
Base.promote_rule(::Type{Date},x::Type{DateTime}) = DateTime
Base.isless(x::Date,y::Date) = isless(value(x),value(y))
Base.isless(x::DateTime,y::DateTime) = isless(value(x),value(y))
Base.isless(x::Date,y::Date) = isless(value(x),value(y))
Base.isless(x::Time,y::Time) = isless(value(x),value(y))
Base.isless(x::TimeType,y::TimeType) = isless(promote(x,y)...)
==(x::TimeType,y::TimeType) = ===(promote(x,y)...)
function ==(a::Time,b::Time)
return hour(a) == hour(b) && minute(a) == minute(b) &&
second(a) == second(b) && millisecond(a) == millisecond(b) &&
microsecond(a) == microsecond(b) && nanosecond(a) == nanosecond(b)
end
Loading

0 comments on commit 8bd39af

Please sign in to comment.