Skip to content

Commit

Permalink
Merge pull request #17052 from JuliaLang/jn/dates-noevil
Browse files Browse the repository at this point in the history
calculate ismonday with 100% less eval
  • Loading branch information
vtjnash authored Jun 21, 2016
2 parents f9dc84f + a6e9ae3 commit 5ed294d
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 70 deletions.
131 changes: 63 additions & 68 deletions base/dates/adjusters.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# This file is a part of Julia. License is MIT: http://julialang.org/license

### truncation
Base.trunc(dt::Date,p::Type{Year}) = Date(UTD(totaldays(year(dt),1,1)))
Base.trunc(dt::Date,p::Type{Month}) = firstdayofmonth(dt)
Base.trunc(dt::Date,p::Type{Day}) = dt
Base.trunc(dt::Date, p::Type{Year}) = Date(UTD(totaldays(year(dt), 1, 1)))
Base.trunc(dt::Date, p::Type{Month}) = firstdayofmonth(dt)
Base.trunc(dt::Date, p::Type{Day}) = dt

Base.trunc(dt::DateTime,p::Type{Year}) = DateTime(trunc(Date(dt),Year))
Base.trunc(dt::DateTime,p::Type{Month}) = DateTime(trunc(Date(dt),Month))
Base.trunc(dt::DateTime,p::Type{Day}) = DateTime(Date(dt))
Base.trunc(dt::DateTime,p::Type{Hour}) = dt - Minute(dt) - Second(dt) - Millisecond(dt)
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(dt::DateTime, p::Type{Year}) = DateTime(trunc(Date(dt), Year))
Base.trunc(dt::DateTime, p::Type{Month}) = DateTime(trunc(Date(dt), Month))
Base.trunc(dt::DateTime, p::Type{Day}) = DateTime(Date(dt))
Base.trunc(dt::DateTime, p::Type{Hour}) = dt - Minute(dt) - Second(dt) - Millisecond(dt)
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

"""
trunc(dt::TimeType, ::Type{Period}) -> TimeType
Expand Down Expand Up @@ -39,7 +39,7 @@ Adjusts `dt` to the Sunday of its week.
"""
function lastdayofweek end

lastdayofweek(dt::Date) = Date(UTD(value(dt) + (7-dayofweek(dt))))
lastdayofweek(dt::Date) = Date(UTD(value(dt) + (7 - dayofweek(dt))))
lastdayofweek(dt::DateTime) = DateTime(lastdayofweek(Date(dt)))

@vectorize_1arg TimeType firstdayofweek
Expand All @@ -52,7 +52,7 @@ Adjusts `dt` to the first day of its month.
"""
function firstdayofmonth end

firstdayofmonth(dt::Date) = Date(UTD(value(dt)-day(dt)+1))
firstdayofmonth(dt::Date) = Date(UTD(value(dt) - day(dt) + 1))
firstdayofmonth(dt::DateTime) = DateTime(firstdayofmonth(Date(dt)))

"""
Expand All @@ -63,8 +63,8 @@ Adjusts `dt` to the last day of its month.
function lastdayofmonth end

function lastdayofmonth(dt::Date)
y,m,d = yearmonthday(dt)
return Date(UTD(value(dt)+daysinmonth(y,m)-d))
y, m, d = yearmonthday(dt)
return Date(UTD(value(dt) + daysinmonth(y, m) - d))
end
lastdayofmonth(dt::DateTime) = DateTime(lastdayofmonth(Date(dt)))

Expand All @@ -78,7 +78,7 @@ Adjusts `dt` to the first day of its year.
"""
function firstdayofyear end

firstdayofyear(dt::Date) = Date(UTD(value(dt)-dayofyear(dt)+1))
firstdayofyear(dt::Date) = Date(UTD(value(dt) - dayofyear(dt) + 1))
firstdayofyear(dt::DateTime) = DateTime(firstdayofyear(Date(dt)))

"""
Expand All @@ -89,8 +89,8 @@ Adjusts `dt` to the last day of its year.
function lastdayofyear end

function lastdayofyear(dt::Date)
y,m,d = yearmonthday(dt)
return Date(UTD(value(dt)+daysinyear(y)-dayofyear(y,m,d)))
y, m, d = yearmonthday(dt)
return Date(UTD(value(dt) + daysinyear(y) - dayofyear(y, m, d)))
end
lastdayofyear(dt::DateTime) = DateTime(lastdayofyear(Date(dt)))

Expand All @@ -107,7 +107,7 @@ function firstdayofquarter end
function firstdayofquarter(dt::Date)
y,m = yearmonth(dt)
mm = m < 4 ? 1 : m < 7 ? 4 : m < 10 ? 7 : 10
return Date(y,mm,1)
return Date(y, mm, 1)
end
firstdayofquarter(dt::DateTime) = DateTime(firstdayofquarter(Date(dt)))

Expand All @@ -120,8 +120,8 @@ function lastdayofquarter end

function lastdayofquarter(dt::Date)
y,m = yearmonth(dt)
mm,d = m < 4 ? (3,31) : m < 7 ? (6,30) : m < 10 ? (9,30) : (12,31)
return Date(y,mm,d)
mm, d = m < 4 ? (3, 31) : m < 7 ? (6, 30) : m < 10 ? (9, 30) : (12, 31)
return Date(y, mm, d)
end
lastdayofquarter(dt::DateTime) = DateTime(lastdayofquarter(Date(dt)))

Expand All @@ -132,29 +132,24 @@ lastdayofquarter(dt::DateTime) = DateTime(lastdayofquarter(Date(dt)))
immutable DateFunction
f::Function
# validate boolean, single-arg inner constructor
function DateFunction(f::ANY,negate::Bool,dt::TimeType)
try
f(dt) in (true,false) || throw(ArgumentError("Provided function must take a single TimeType argument and return true or false"))
catch e
throw(ArgumentError("Provided function must take a single TimeType argument"))
end
n = negate ? (!) : identity
return new(@eval x->$n($f(x)))
function DateFunction(f::ANY, negate::Bool, dt::TimeType)
isa(f(dt), Bool) || throw(ArgumentError("Provided function must take a single TimeType argument and return true or false"))
return new(negate ? x -> !f(x)::Bool : f)
end
end
Base.show(io::IO,df::DateFunction) = println(io, df.f)
Base.show(io::IO, df::DateFunction) = println(io, df.f)

# Core adjuster
function adjust(df::DateFunction,start,step,limit)
function adjust(df::DateFunction, start, step, limit)
for i = 1:limit
df.f(start) && return start
start += step
end
throw(ArgumentError("Adjustment limit reached: $limit iterations"))
end

function adjust(func::Function,start;step::Period=Day(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,start),start,step,limit)
function adjust(func::Function, start; step::Period=Day(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, start), start, step, limit)
end

# Constructors using DateFunctions
Expand All @@ -169,8 +164,8 @@ step size in adjusting can be provided manually through the `step` keyword. If
`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).
"""
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)
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

"""
Expand All @@ -185,30 +180,30 @@ pursue before throwing an error (in the case that `f::Function` is never satisfi
"""
DateTime(::Function, args...)

function DateTime(func::Function,y,m=1;step::Period=Day(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,DateTime(y,m)),DateTime(y,m),step,limit)
function DateTime(func::Function, y, m=1; step::Period=Day(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, DateTime(y, m)), DateTime(y, m), step, limit)
end
function DateTime(func::Function,y,m,d;step::Period=Hour(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y,m,d),step,limit)
function DateTime(func::Function, y, m, d; step::Period=Hour(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, DateTime(y)), DateTime(y, m, d), step, limit)
end
function DateTime(func::Function,y,m,d,h;step::Period=Minute(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y,m,d,h),step,limit)
function DateTime(func::Function, y, m, d, h; step::Period=Minute(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, DateTime(y)), DateTime(y, m, d, h), step, limit)
end
function DateTime(func::Function,y,m,d,h,mi;step::Period=Second(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y,m,d,h,mi),step,limit)
function DateTime(func::Function, y, m, d, h, mi; step::Period=Second(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, DateTime(y)), DateTime(y, m, d, h, mi), step, limit)
end
function DateTime(func::Function,y,m,d,h,mi,s;step::Period=Millisecond(1),negate::Bool=false,limit::Int=10000)
return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y,m,d,h,mi,s),step,limit)
function DateTime(func::Function, y, m, d, h, mi, s; step::Period=Millisecond(1), negate::Bool=false, limit::Int=10000)
return adjust(DateFunction(func, negate, DateTime(y)), DateTime(y, m, d, h, mi, s), step, limit)
end

# Return the next TimeType that falls on dow
ISDAYOFWEEK = Dict(Mon=>DateFunction(ismonday,false,Date(0)),
Tue=>DateFunction(istuesday,false,Date(0)),
Wed=>DateFunction(iswednesday,false,Date(0)),
Thu=>DateFunction(isthursday,false,Date(0)),
Fri=>DateFunction(isfriday,false,Date(0)),
Sat=>DateFunction(issaturday,false,Date(0)),
Sun=>DateFunction(issunday,false,Date(0)))
ISDAYOFWEEK = Dict(Mon => DateFunction(ismonday, false, Date(0)),
Tue => DateFunction(istuesday, false, Date(0)),
Wed => DateFunction(iswednesday, false, Date(0)),
Thu => DateFunction(isthursday, false, Date(0)),
Fri => DateFunction(isfriday, false, Date(0)),
Sat => DateFunction(issaturday, false, Date(0)),
Sun => DateFunction(issunday, false, Date(0)))

# "same" indicates whether the current date can be considered or not
"""
Expand All @@ -218,7 +213,7 @@ Adjusts `dt` to the next day of week corresponding to `dow` with `1 = Monday, 2
etc`. Setting `same=true` allows the current `dt` to be considered as the next `dow`,
allowing for no adjustment to occur.
"""
tonext(dt::TimeType,dow::Int;same::Bool=false) = adjust(ISDAYOFWEEK[dow],same ? dt : dt+Day(1),Day(1),7)
tonext(dt::TimeType, dow::Int; same::Bool=false) = adjust(ISDAYOFWEEK[dow], same ? dt : dt+Day(1), Day(1), 7)

# Return the next TimeType where func evals true using step in incrementing
"""
Expand All @@ -229,8 +224,8 @@ returns `true`. `func` must take a single `TimeType` argument and return a `Bool
allows `dt` to be considered in satisfying `func`. `negate` will make the adjustment process
terminate when `func` returns `false` instead of `true`.
"""
function tonext(func::Function,dt::TimeType;step::Period=Day(1),negate::Bool=false,limit::Int=10000,same::Bool=false)
return adjust(DateFunction(func,negate,dt),same ? dt : dt+step,step,limit)
function tonext(func::Function, dt::TimeType;step::Period=Day(1), negate::Bool=false, limit::Int=10000, same::Bool=false)
return adjust(DateFunction(func, negate, dt), same ? dt : dt+step, step, limit)
end

"""
Expand All @@ -240,7 +235,7 @@ Adjusts `dt` to the previous day of week corresponding to `dow` with `1 = Monday
Tuesday, etc`. Setting `same=true` allows the current `dt` to be considered as the previous
`dow`, allowing for no adjustment to occur.
"""
toprev(dt::TimeType,dow::Int;same::Bool=false) = adjust(ISDAYOFWEEK[dow],same ? dt : dt+Day(-1),Day(-1),7)
toprev(dt::TimeType, dow::Int; same::Bool=false) = adjust(ISDAYOFWEEK[dow], same ? dt : dt+Day(-1), Day(-1), 7)

"""
toprev(func::Function,dt::TimeType;step=Day(-1),negate=false,limit=10000,same=false) -> TimeType
Expand All @@ -250,8 +245,8 @@ returns `true`. `func` must take a single `TimeType` argument and return a `Bool
allows `dt` to be considered in satisfying `func`. `negate` will make the adjustment process
terminate when `func` returns `false` instead of `true`.
"""
function toprev(func::Function,dt::TimeType;step::Period=Day(-1),negate::Bool=false,limit::Int=10000,same::Bool=false)
return adjust(DateFunction(func,negate,dt),same ? dt : dt+step,step,limit)
function toprev(func::Function, dt::TimeType; step::Period=Day(-1), negate::Bool=false, limit::Int=10000, same::Bool=false)
return adjust(DateFunction(func, negate, dt), same ? dt : dt+step, step, limit)
end

# Return the first TimeType that falls on dow in the Month or Year
Expand All @@ -261,9 +256,9 @@ end
Adjusts `dt` to the first `dow` of its month. Alternatively, `of=Year` will adjust to the
first `dow` of the year.
"""
function tofirst(dt::TimeType,dow::Int;of::Union{Type{Year},Type{Month}}=Month)
function tofirst(dt::TimeType, dow::Int; of::Union{Type{Year}, Type{Month}}=Month)
dt = of <: Month ? firstdayofmonth(dt) : firstdayofyear(dt)
return adjust(ISDAYOFWEEK[dow],dt,Day(1),366)
return adjust(ISDAYOFWEEK[dow], dt, Day(1), 366)
end

# Return the last TimeType that falls on dow in the Month or Year
Expand All @@ -273,20 +268,20 @@ end
Adjusts `dt` to the last `dow` of its month. Alternatively, `of=Year` will adjust to the
last `dow` of the year.
"""
function tolast(dt::TimeType,dow::Int;of::Union{Type{Year},Type{Month}}=Month)
function tolast(dt::TimeType, dow::Int; of::Union{Type{Year}, Type{Month}}=Month)
dt = of <: Month ? lastdayofmonth(dt) : lastdayofyear(dt)
return adjust(ISDAYOFWEEK[dow],dt,Day(-1),366)
return adjust(ISDAYOFWEEK[dow], dt, Day(-1), 366)
end

function recur{T<:TimeType}(fun::Function,start::T,stop::T;step::Period=Day(1),negate::Bool=false,limit::Int=10000)
function recur{T<:TimeType}(fun::Function, start::T, stop::T; step::Period=Day(1), negate::Bool=false, limit::Int=10000)
((start != stop) & ((step > zero(step)) != (stop > start))) && return T[]
a = T[]
check = start <= stop ? 1 : -1
df = Dates.DateFunction(fun,negate,start)
df = Dates.DateFunction(fun, negate, start)
while true
next = Dates.adjust(df,start,step,limit)
cmp(next,stop) == check && break
push!(a,next)
next = Dates.adjust(df, start, step, limit)
cmp(next, stop) == check && break
push!(a, next)
start = next + step
end
return a
Expand All @@ -300,6 +295,6 @@ should be "included" in the final set. `recur` applies `func` over each element
of `dr`, including those elements for which `func` returns `true` in the resulting Array,
unless `negate=true`, then only elements where `func` returns `false` are included.
"""
function recur{T<:TimeType}(fun::Function,dr::StepRange{T};negate::Bool=false,limit::Int=10000)
return recur(fun,first(dr),last(dr);step=step(dr),negate=negate,limit=limit)
function recur{T<:TimeType}(fun::Function, dr::StepRange{T};negate::Bool=false, limit::Int=10000)
return recur(fun, first(dr), last(dr); step=step(dr), negate=negate, limit=limit)
end
4 changes: 2 additions & 2 deletions test/dates/adjusters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ januarymondays2014 = [Dates.Date(2014,1,6),Dates.Date(2014,1,13),Dates.Date(2014
@test Dates.recur(Dates.ismonday,startdate:stopdate) == januarymondays2014
@test Dates.recur(x->!Dates.ismonday(x),startdate,stopdate;negate=true) == januarymondays2014

@test_throws ArgumentError Dates.recur((x,y)->x+y,Dates.Date(2013):Dates.Date(2014))
@test_throws ArgumentError Dates.DateFunction((x,y)->x+y, false, Date(0))
@test_throws MethodError Dates.recur((x,y)->x+y,Dates.Date(2013):Dates.Date(2014))
@test_throws MethodError Dates.DateFunction((x,y)->x+y, false, Date(0))
@test_throws ArgumentError Dates.DateFunction((dt)->2, false, Date(0))
@test length(Dates.recur(x->true,Dates.Date(2013):Dates.Date(2013,2))) == 32
@test length(Dates.recur(x->true,Dates.Date(2013):Dates.Date(2013,1,1))) == 1
Expand Down

0 comments on commit 5ed294d

Please sign in to comment.