Skip to content

Commit

Permalink
* Make DayOfWeek an Enum, deprecate dayofweek. (#19210)
Browse files Browse the repository at this point in the history
* Provide optional argument to `firsdayofweek`/`lastdayofweek` (#19208)
* A few misc fixes.
  • Loading branch information
simonbyrne committed Nov 16, 2016
1 parent 550f5d3 commit c9b8c2f
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 88 deletions.
2 changes: 1 addition & 1 deletion base/dates/Dates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export Period, DatePeriod, TimePeriod,
yearmonthday, yearmonth, monthday, year, month, week, day,
hour, minute, second, millisecond, dayofmonth,
# query.jl
dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
DayOfWeek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
dayofweekofmonth, daysofweekinmonth, monthname, monthabbr,
quarterofyear, dayofquarter,
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday,
Expand Down
7 changes: 7 additions & 0 deletions base/dates/accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,15 @@ end

# Accessor functions
value(dt::TimeType) = dt.instant.periods.value

"""
Dates.days(dt::TimeType)
The Rata Die day number: the number of days since `Date("0000-12-31")`.
"""
days(dt::Date) = value(dt)
days(dt::DateTime) = fld(value(dt),86400000)

year(dt::TimeType) = year(days(dt))
month(dt::TimeType) = month(days(dt))
week(dt::TimeType) = week(days(dt))
Expand Down
49 changes: 21 additions & 28 deletions base/dates/adjusters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,26 @@ Dates.trunc(::Dates.TimeType, ::Type{Dates.Period})

# Adjusters
"""
firstdayofweek(dt::TimeType) -> TimeType
firstdayofweek(dt::TimeType[, firstday::DayOfWeek=Monday]) -> TimeType
Adjusts `dt` to the Monday of its week.
Adjusts `dt` to the first day of its week. The optional second argument specifies the
`DayOfWeek` which starts the week: by default, this is `Monday`, as per ISO 8601.
"""
function firstdayofweek end

firstdayofweek(dt::Date) = Date(UTD(value(dt) - dayofweek(dt) + 1))
firstdayofweek(dt::DateTime) = DateTime(firstdayofweek(Date(dt)))
firstdayofweek(dt::Date, firstday::DayOfWeek=Monday) = Date(UTD(value(dt) - mod(Int(DayOfWeek(dt)) - Int(firstday),7)))
firstdayofweek(dt::DateTime, firstday::DayOfWeek=Monday) = DateTime(firstdayofweek(Date(dt),firstday))

"""
lastdayofweek(dt::TimeType) -> TimeType
lastdayofweek(dt::TimeType[, lastday::DayOfWeek=Sunday]) -> TimeType
Adjusts `dt` to the Sunday of its week.
Adjusts `dt` to the last day of its week. The optional second argument specifies the
`DayOfWeek` which ends the week: by default, this is `Sunday`, as per ISO 8601.
"""
function lastdayofweek end

lastdayofweek(dt::Date) = Date(UTD(value(dt) + (7 - dayofweek(dt))))
lastdayofweek(dt::DateTime) = DateTime(lastdayofweek(Date(dt)))
lastdayofweek(dt::Date, lastday::DayOfWeek=Sunday) = Date(UTD(value(dt) + mod(Int(lastday) - Int(DayOfWeek(dt)),7)))
lastdayofweek(dt::DateTime, lastday::DayOfWeek=Sunday) = DateTime(lastdayofweek(Date(dt),lastday))

"""
firstdayofmonth(dt::TimeType) -> TimeType
Expand All @@ -49,7 +51,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) - dayofmonth(dt) + 1))
firstdayofmonth(dt::DateTime) = DateTime(firstdayofmonth(Date(dt)))

"""
Expand Down Expand Up @@ -184,24 +186,15 @@ function DateTime(func::Function, y, m, d, h, mi, s; step::Period=Millisecond(1)
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)))

# "same" indicates whether the current date can be considered or not
"""
tonext(dt::TimeType,dow::Int;same::Bool=false) -> TimeType
tonext(dt::TimeType,dow::DayOfWeek;same::Bool=false) -> TimeType
Adjusts `dt` to the next day of week corresponding to `dow` with `1 = Monday, 2 = Tuesday,
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::DayOfWeek; same::Bool=false) = lastdayofweek(same ? dt : dt+Day(1), dow)

# Return the next TimeType where func evals true using step in incrementing
"""
Expand All @@ -217,13 +210,13 @@ function tonext(func::Function, dt::TimeType;step::Period=Day(1), negate::Bool=f
end

"""
toprev(dt::TimeType,dow::Int;same::Bool=false) -> TimeType
toprev(dt::TimeType,dow::DayOfWeek;same::Bool=false) -> TimeType
Adjusts `dt` to the previous day of week corresponding to `dow` with `1 = Monday, 2 =
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::DayOfWeek; same::Bool=false) = firstdayofweek(same ? dt : dt-Day(1), dow)

"""
toprev(func::Function,dt::TimeType;step=Day(-1),negate=false,limit=10000,same=false) -> TimeType
Expand All @@ -239,26 +232,26 @@ end

# Return the first TimeType that falls on dow in the Month or Year
"""
tofirst(dt::TimeType,dow::Int;of=Month) -> TimeType
tofirst(dt::TimeType, dow::DayOfWeek; of=Month) -> TimeType
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::DayOfWeek; of::Union{Type{Year}, Type{Month}}=Month)
dt = of <: Month ? firstdayofmonth(dt) : firstdayofyear(dt)
return adjust(ISDAYOFWEEK[dow], dt, Day(1), 366)
lastdayofweek(dt, dow)
end

# Return the last TimeType that falls on dow in the Month or Year
"""
tolast(dt::TimeType,dow::Int;of=Month) -> TimeType
tolast(dt::TimeType,dow::DayOfWeek;of=Month) -> TimeType
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::DayOfWeek; of::Union{Type{Year}, Type{Month}}=Month)
dt = of <: Month ? lastdayofmonth(dt) : lastdayofyear(dt)
return adjust(ISDAYOFWEEK[dow], dt, Day(-1), 366)
firstdayofweek(dt, dow)
end

function recur{T<:TimeType}(fun::Function, start::T, stop::T; step::Period=Day(1), negate::Bool=false, limit::Int=10000)
Expand Down
4 changes: 2 additions & 2 deletions base/dates/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@ function slotformat(slot::Slot{Month},dt,locale)
end
function slotformat(slot::Slot{DayOfWeekSlot},dt,locale)
if slot.letter == 'e'
return VALUETODAYOFWEEKABBR[locale][dayofweek(dt)]
return VALUETODAYOFWEEKABBR[locale][DayOfWeek(dt)]
else # == 'E'
return VALUETODAYOFWEEK[locale][dayofweek(dt)]
return VALUETODAYOFWEEK[locale][DayOfWeek(dt)]
end
end
slotformat(slot::Slot{Millisecond},dt,locale) = rpad(string(millisecond(dt)/1000.0)[3:end], slot.width, "0")
Expand Down
76 changes: 45 additions & 31 deletions base/dates/query.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

### Core query functions

# Monday = 1....Sunday = 7
dayofweek(days) = mod1(days,7)

# Number of days in year
"""
daysinyear(dt::TimeType) -> Int
Expand All @@ -20,49 +17,64 @@ const MONTHDAYS = [0,31,59,90,120,151,181,212,243,273,304,334]
dayofyear(y,m,d) = MONTHDAYS[m] + d + (m > 2 && isleapyear(y))

### Days of the Week
"""
dayofweek(dt::TimeType) -> Int64
@enum DayOfWeek Monday=1 Tuesday=2 Wednesday=3 Thursday=4 Friday=5 Saturday=6 Sunday=7

Returns the day of the week as an `Int64` with `1 = Monday, 2 = Tuesday, etc.`.
"""
dayofweek(dt::TimeType) = dayofweek(days(dt))
DayOfWeek(n::Integer)
const Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday = 1,2,3,4,5,6,7
const Mon,Tue,Wed,Thu,Fri,Sat,Sun = 1,2,3,4,5,6,7
const english_daysofweek = Dict(1=>"Monday",2=>"Tuesday",3=>"Wednesday",
4=>"Thursday",5=>"Friday",6=>"Saturday",7=>"Sunday")
const VALUETODAYOFWEEK = Dict{String,Dict{Int,String}}("english"=>english_daysofweek)
const english_daysofweekabbr = Dict(1=>"Mon",2=>"Tue",3=>"Wed",
4=>"Thu",5=>"Fri",6=>"Sat",7=>"Sun")
const VALUETODAYOFWEEKABBR = Dict{String,Dict{Int,String}}("english"=>english_daysofweekabbr)
dayname(dt::Integer;locale::AbstractString="english") = VALUETODAYOFWEEK[locale][dt]
An `Enum` with `DayOfWeek(1) == Monday`, `DayOfWeek(2) == Tuesday`, etc. Accepts an
arbitrary-sized integer, from which days will be computed modulo 7.
```jldoctest
julia> DayOfWeek(8) == Monday
true
```
"""
dayabbr(dt::TimeType; locale="english") -> AbstractString
DayOfWeek(n::Integer) = convert(DayOfWeek, mod1(n, 7))

Return the abbreviated name corresponding to the day of the week of the `Date` or `DateTime`
in the given `locale`.
"""
dayabbr(dt::Integer;locale::AbstractString="english") = VALUETODAYOFWEEKABBR[locale][dt]
DayOfWeek(dt::TimeType)
Returns the day of the week of `dt` as an element of the enum `DayOfWeek`.
"""
DayOfWeek(dt::TimeType) = DayOfWeek(days(dt))


const Mon,Tue,Wed,Thu,Fri,Sat,Sun = Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday

const english_daysofweek = Dict(k => string(k) for k in instances(DayOfWeek))
const VALUETODAYOFWEEK = Dict{String,Dict{DayOfWeek,String}}("english"=>english_daysofweek)

const english_daysofweekabbr = Dict(k => string(k)[1:3] for k in instances(DayOfWeek))
const VALUETODAYOFWEEKABBR = Dict{String,Dict{DayOfWeek,String}}("english"=>english_daysofweekabbr)

dayname(dow::DayOfWeek;locale::AbstractString="english") = VALUETODAYOFWEEK[locale][dow]
dayabbr(dow::DayOfWeek;locale::AbstractString="english") = VALUETODAYOFWEEKABBR[locale][dow]

"""
dayname(dt::TimeType; locale="english") -> AbstractString
Return the full day name corresponding to the day of the week of the `Date` or `DateTime` in
the given `locale`.
"""
dayname(dt::TimeType;locale::AbstractString="english") = VALUETODAYOFWEEK[locale][dayofweek(dt)]
dayname(dt::TimeType;locale::AbstractString="english") = VALUETODAYOFWEEK[locale][DayOfWeek(dt)]

"""
dayabbr(dt::TimeType; locale="english") -> AbstractString
dayabbr(dt::TimeType;locale::AbstractString="english") = VALUETODAYOFWEEKABBR[locale][dayofweek(dt)]
Return the abbreviated name corresponding to the day of the week of the `Date` or `DateTime`
in the given `locale`.
"""
dayabbr(dt::TimeType;locale::AbstractString="english") = VALUETODAYOFWEEKABBR[locale][DayOfWeek(dt)]

# Convenience methods for each day
ismonday(dt::TimeType) = dayofweek(dt) == Mon
istuesday(dt::TimeType) = dayofweek(dt) == Tue
iswednesday(dt::TimeType) = dayofweek(dt) == Wed
isthursday(dt::TimeType) = dayofweek(dt) == Thu
isfriday(dt::TimeType) = dayofweek(dt) == Fri
issaturday(dt::TimeType) = dayofweek(dt) == Sat
issunday(dt::TimeType) = dayofweek(dt) == Sun
ismonday(dt::TimeType) = DayOfWeek(dt) == Mon
istuesday(dt::TimeType) = DayOfWeek(dt) == Tue
iswednesday(dt::TimeType) = DayOfWeek(dt) == Wed
isthursday(dt::TimeType) = DayOfWeek(dt) == Thu
isfriday(dt::TimeType) = DayOfWeek(dt) == Fri
issaturday(dt::TimeType) = DayOfWeek(dt) == Sat
issunday(dt::TimeType) = DayOfWeek(dt) == Sun

# i.e. 1st Monday? 2nd Monday? 3rd Wednesday? 5th Sunday?
"""
Expand All @@ -71,9 +83,10 @@ issunday(dt::TimeType) = dayofweek(dt) == Sun
For the day of week of `dt`, returns which number it is in `dt`'s month. So if the day of
the week of `dt` is Monday, then `1 = First Monday of the month, 2 = Second Monday of the
month, etc.` In the range 1:5.
See also `daysofweekinmonth`.
"""
dayofweekofmonth(dt::TimeType) = (d = day(dt); return d < 8 ? 1 :
d < 15 ? 2 : d < 22 ? 3 : d < 29 ? 4 : 5)
dayofweekofmonth(dt::TimeType) = fld1(dayofmonth(dt), 7)

# Total number of a day of week in the month
# e.g. are there 4 or 5 Mondays in this month?
Expand All @@ -98,6 +111,7 @@ function daysofweekinmonth(dt::TimeType)
end

### Months

const January,February,March,April,May,June = 1,2,3,4,5,6
const July,August,September,October,November,December = 7,8,9,10,11,12
const Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec = 1,2,3,4,5,6,7,8,9,10,11,12
Expand Down
30 changes: 15 additions & 15 deletions test/dates/adjusters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ dt = Dates.Date(2014,5,21)
@test Dates.tonext(dt,Dates.Mon) == Dates.Date(2014,5,26)
@test Dates.tonext(dt,Dates.Tue) == Dates.Date(2014,5,27)
# No dayofweek function for out of range values
@test_throws KeyError Dates.tonext(dt,8)
#@test_throws KeyError Dates.tonext(dt,8)

@test Dates.tonext(Dates.Date(0),Dates.Mon) == Dates.Date(0,1,3)

Expand Down Expand Up @@ -263,7 +263,7 @@ dt = Dates.Date(2014,5,21)
@test Dates.toprev(dt,Dates.Mon) == Dates.Date(2014,5,19)
@test Dates.toprev(dt,Dates.Tue) == Dates.Date(2014,5,20)
# No dayofweek function for out of range values
@test_throws KeyError Dates.toprev(dt,8)
# @test_throws KeyError Dates.toprev(dt,8)

@test Dates.toprev(Dates.Date(0),Dates.Mon) == Dates.Date(-1,12,27)

Expand Down Expand Up @@ -339,7 +339,7 @@ Januarymondays2014 = [Dates.Date(2014,1,6),Dates.Date(2014,1,13),Dates.Date(2014
end) == 24

# Thanksgiving: 4th Thursday of November
thanksgiving = x->Dates.dayofweek(x) == Dates.Thu &&
thanksgiving = x->Dates.DayOfWeek(x) == Dates.Thu &&
Dates.month(x) == Dates.Nov &&
Dates.dayofweekofmonth(x) == 4

Expand All @@ -356,7 +356,7 @@ end == Dates.Date(2013,11,28)
# Pittsburgh street cleaning
dr = Dates.Date(2014):Dates.Date(2015)
@test length(Dates.recur(dr) do x
Dates.dayofweek(x) == Dates.Tue &&
Dates.DayOfWeek(x) == Dates.Tue &&
Dates.April < Dates.month(x) < Dates.Nov &&
Dates.dayofweekofmonth(x) == 2
end) == 6
Expand All @@ -371,19 +371,19 @@ isnewyears(dt) = Dates.yearmonthday(dt) == newyears(Dates.year(dt))
isindependenceday(dt) = Dates.yearmonthday(dt) == independenceday(Dates.year(dt))
isveteransday(dt) = Dates.yearmonthday(dt) == veteransday(Dates.year(dt))
ischristmas(dt) = Dates.yearmonthday(dt) == christmas(Dates.year(dt))
ismartinlutherking(dt) = Dates.dayofweek(dt) == Dates.Mon &&
ismartinlutherking(dt) = Dates.DayOfWeek(dt) == Dates.Mon &&
Dates.month(dt) == Dates.Jan && Dates.dayofweekofmonth(dt) == 3
ispresidentsday(dt) = Dates.dayofweek(dt) == Dates.Mon &&
ispresidentsday(dt) = Dates.DayOfWeek(dt) == Dates.Mon &&
Dates.month(dt) == Dates.Feb && Dates.dayofweekofmonth(dt) == 3
# Last Monday of May
ismemorialday(dt) = Dates.dayofweek(dt) == Dates.Mon &&
ismemorialday(dt) = Dates.DayOfWeek(dt) == Dates.Mon &&
Dates.month(dt) == Dates.May &&
Dates.dayofweekofmonth(dt) == Dates.daysofweekinmonth(dt)
islaborday(dt) = Dates.dayofweek(dt) == Dates.Mon &&
islaborday(dt) = Dates.DayOfWeek(dt) == Dates.Mon &&
Dates.month(dt) == Dates.Sep && Dates.dayofweekofmonth(dt) == 1
iscolumbusday(dt) = Dates.dayofweek(dt) == Dates.Mon &&
iscolumbusday(dt) = Dates.DayOfWeek(dt) == Dates.Mon &&
Dates.month(dt) == Dates.Oct && Dates.dayofweekofmonth(dt) == 2
isthanksgiving(dt) = Dates.dayofweek(dt) == Dates.Thu &&
isthanksgiving(dt) = Dates.DayOfWeek(dt) == Dates.Thu &&
Dates.month(dt) == Dates.Nov && Dates.dayofweekofmonth(dt) == 4

function easter(y)
Expand Down Expand Up @@ -416,13 +416,13 @@ const HOLIDAYS = x->isnewyears(x) || isindependenceday(x) ||

const OBSERVEDHOLIDAYS = x->begin
# If the holiday is on a weekday
if HOLIDAYS(x) && Dates.dayofweek(x) < Dates.Saturday
if HOLIDAYS(x) && Dates.DayOfWeek(x) < Dates.Sat
return true
# Holiday is observed Monday if falls on Sunday
elseif Dates.dayofweek(x) == 1 && HOLIDAYS(x-Dates.Day(1))
elseif Dates.DayOfWeek(x) == Dates.Mon && HOLIDAYS(x-Dates.Day(1))
return true
# Holiday is observed Friday if falls on Saturday
elseif Dates.dayofweek(x) == 5 && HOLIDAYS(x+Dates.Day(1))
elseif Dates.DayOfWeek(x) == Dates.Fri && HOLIDAYS(x+Dates.Day(1))
return true
else
return false
Expand All @@ -440,7 +440,7 @@ observed = Dates.recur(OBSERVEDHOLIDAYS,Dates.Date(1999):Dates.Date(2000))
# validate with http:https://www.workingdays.us/workingdays_holidays_2014.htm
@test length(Dates.recur(Dates.Date(2014):Dates.Date(2015);negate=true) do x
OBSERVEDHOLIDAYS(x) ||
Dates.dayofweek(x) > 5
Dates.DayOfWeek(x) > Dates.Fri
end) == 251

# First day of the next month for each day of 2014
Expand All @@ -453,7 +453,7 @@ end) == 251
@test length(Dates.recur(Date(2000):Dates.Month(1):Date(2016)) do dt
sum = 0
for i = 1:7
sum += Dates.dayofweek(dt) > 4 ? Dates.daysofweekinmonth(dt) : 0
sum += Dates.DayOfWeek(dt) > Dates.Thu ? Dates.daysofweekinmonth(dt) : 0
dt += Dates.Day(1)
end
return sum == 15
Expand Down
Loading

0 comments on commit c9b8c2f

Please sign in to comment.