Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

calculate ismonday with 100% less eval #17052

Merged
merged 1 commit into from
Jun 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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