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

Add Dates module to Base #7654

Merged
merged 30 commits into from
Aug 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6fb6330
Add Dates module to Base
quinnj Jul 18, 2014
bf16557
Merge branch 'master' of https://github.com/JuliaLang/julia into jq/d…
quinnj Aug 8, 2014
c1824f8
Add missing millisecond to month/year-TimeType arithmetic
quinnj Aug 8, 2014
9cb5922
Cleanup some definitions for Period types. Removes redundants and add…
quinnj Aug 8, 2014
142aad2
Add internal conversion methods to Day and Millisecond. This builds a…
quinnj Aug 8, 2014
0920ff6
Consolidate FixedPeriod-TimeType arithmetic by using new conversion m…
quinnj Aug 8, 2014
76344a5
Make now() return a DateTime corresponding to the user's timezone (se…
quinnj Aug 8, 2014
a899dce
Rename isleap to isleapyear and remove some definitions that will be …
quinnj Aug 8, 2014
aafe02f
Update comments and remove unneccesary inner constructors in Date/Dat…
quinnj Aug 8, 2014
533d9db
Move definitions of daysinmonth and isleapyear to types.jl from query…
quinnj Aug 8, 2014
760b7d3
Validate all inputs when building Date/DateTime by parts
quinnj Aug 8, 2014
07e2c42
Cleanup a few trait definitions and exports in types.jl
quinnj Aug 8, 2014
1a2a86b
Remove all uses of splatting to avoid related performance hits
quinnj Aug 8, 2014
a4b61d2
Add dayname/monthname definitions that take integer arguments for the…
quinnj Aug 8, 2014
e02baea
Allow the use of delimters at the beginning of a format string.
quinnj Aug 8, 2014
b616973
Make the DateFunction constructor smarter at detecting when the provi…
quinnj Aug 8, 2014
43729b3
Remove redundant method definitions for Date/DateTime
quinnj Aug 8, 2014
1c4c5f4
recur now handles 'empty ranges' and the code has been cleaned up
quinnj Aug 8, 2014
c92273b
Another round of updates on the docs
quinnj Aug 8, 2014
74dfe68
Big overhaul of date ranges.jl. Code is now much more concise and cor…
quinnj Aug 8, 2014
25db0d7
Another round of testing updates with all the recent commits.
quinnj Aug 8, 2014
491dfdf
Update range test with recent change in #7900.
quinnj Aug 8, 2014
3d6ecf0
Remove [at]time invocation from range tests
quinnj Aug 8, 2014
6ddef31
Merge branch 'master' of https://github.com/JuliaLang/julia into jq/d…
quinnj Aug 15, 2014
898b20e
Fix formatting of millisecond periods
quinnj Aug 15, 2014
5b17de8
Tighten up vectorized type for conversion functions
quinnj Aug 15, 2014
e2fd721
Don't importall .Dates in Base
quinnj Aug 15, 2014
cb6240d
Move all Dates exports to base/Dates.jl
quinnj Aug 15, 2014
642fcc4
Cleanup Dates export list
quinnj Aug 15, 2014
65e0d8d
Fix failing test in dates/query.jl
quinnj Aug 15, 2014
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
40 changes: 40 additions & 0 deletions base/Dates.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Dates

include("dates/types.jl")
include("dates/periods.jl")
include("dates/accessors.jl")
include("dates/query.jl")
include("dates/arithmetic.jl")
include("dates/conversions.jl")
include("dates/ranges.jl")
include("dates/adjusters.jl")
include("dates/io.jl")

export Period, DatePeriod, TimePeriod,
Year, Month, Week, Day, Hour, Minute, Second, Millisecond,
TimeType, DateTime, Date,
# accessors.jl
yearmonthday, yearmonth, monthday, year, month, week, day,
hour, minute, second, millisecond, dayofmonth,
# query.jl
dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr,
dayofweekofmonth, daysofweekinmonth, monthname, monthabbr,
quarterofyear, dayofquarter,
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday,
Mon, Tue, Wed, Thu, Fri, Sat, Sun,
January, February, March, April, May, June,
July, August, September, October, November, December,
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec,
# conversions.jl
unix2datetime, datetime2unix, now, today,
rata2datetime, datetime2rata, julian2datetime, datetime2julian,
# adjusters.jl
firstdayofweek, lastdayofweek,
firstdayofmonth, lastdayofmonth,
firstdayofyear, lastdayofyear,
firstdayofquarter, lastdayofquarter,
adjust, tonext, toprev, tofirst, tolast, recur,
# io.jl
ISODateTimeFormat, ISODateFormat, DateFormat

end # module
71 changes: 71 additions & 0 deletions base/dates/accessors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Convert # of Rata Die days to proleptic Gregorian calendar y,m,d,w
# Reference: http://mysite.verizon.net/aesir_research/date/date0.htm
function yearmonthday(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
d = c - div(153m-457,5); return m > 12 ? (y+1,m-12,d) : (y,m,d)
end
function year(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
return m > 12 ? y+1 : y
end
function yearmonth(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
return m > 12 ? (y+1,m-12) : (y,m)
end
function month(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
return m > 12 ? m-12 : m
end
function monthday(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
d = c - div(153m-457,5); return m > 12 ? (m-12,d) : (m,d)
end
function day(days)
z = days + 306; h = 100z - 25; a = fld(h,3652425); b = a - fld(a,4);
y = fld(100b+h,36525); c = b + z - 365y - fld(y,4); m = div(5c+456,153);
return c - div(153m-457,5)
end
# https://en.wikipedia.org/wiki/Talk:ISO_week_date#Algorithms
function week(days)
w = div(abs(days-1),7) % 20871
c,w = divrem((w + (w >= 10435)),5218)
w = (w*28+[15,23,3,11][c+1]) % 1461
return div(w,28) + 1
end

# Accessor functions
value(dt::TimeType) = dt.instant.periods.value
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))
day(dt::TimeType) = day(days(dt))
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)

dayofmonth(dt::TimeType) = day(dt)
yearmonth(dt::TimeType) = yearmonth(days(dt))
monthday(dt::TimeType) = monthday(days(dt))
yearmonthday(dt::TimeType) = yearmonthday(days(dt))

@vectorize_1arg TimeType year
@vectorize_1arg TimeType month
@vectorize_1arg TimeType day
@vectorize_1arg TimeType week
@vectorize_1arg DateTime hour
@vectorize_1arg DateTime minute
@vectorize_1arg DateTime second
@vectorize_1arg DateTime millisecond

@vectorize_1arg TimeType dayofmonth
@vectorize_1arg TimeType yearmonth
@vectorize_1arg TimeType monthday
@vectorize_1arg TimeType yearmonthday
157 changes: 157 additions & 0 deletions base/dates/adjusters.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
### 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::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

# Adjusters
firstdayofweek(dt::Date) = Date(UTD(value(dt) - dayofweek(dt) + 1))
firstdayofweek(dt::DateTime) = DateTime(firstdayofweek(Date(dt)))
lastdayofweek(dt::Date) = Date(UTD(value(dt) + (7-dayofweek(dt))))
lastdayofweek(dt::DateTime) = DateTime(lastdayofweek(Date(dt)))

@vectorize_1arg TimeType firstdayofweek
@vectorize_1arg TimeType lastdayofweek

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

@vectorize_1arg TimeType firstdayofmonth
@vectorize_1arg TimeType lastdayofmonth

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

@vectorize_1arg TimeType firstdayofyear
@vectorize_1arg TimeType lastdayofyear

function firstdayofquarter(dt::Date)
y,m = yearmonth(dt)
mm = m < 4 ? 1 : m < 7 ? 4 : m < 10 ? 7 : 10
return Date(y,mm,1)
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)
end
firstdayofquarter(dt::DateTime) = DateTime(firstdayofquarter(Date(dt)))
lastdayofquarter(dt::DateTime) = DateTime(lastdayofquarter(Date(dt)))
@vectorize_1arg TimeType firstdayofquarter
@vectorize_1arg TimeType lastdayofquarter

# Temporal Adjusters
immutable DateFunction
f::Function
# validate boolean, single-arg inner constructor
function DateFunction(f::Function,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)))
end
end
Base.show(io::IO,df::DateFunction) = println(df.f.code)

# Core adjuster
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)
end

# Constructors using DateFunctions
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

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)
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)
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)
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)
end

# Return the next TimeType that falls on dow
ISDAYOFWEEK = [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) = adjust(ISDAYOFWEEK[dow],same ? dt : dt+Day(1),Day(1),7)
# Return the next TimeType where func evals true using step in incrementing
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

toprev(dt::TimeType,dow::Int;same::Bool=false) = adjust(ISDAYOFWEEK[dow],same ? dt : dt+Day(-1),Day(-1),7)
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
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)
end

# Return the last TimeType that falls on dow in the Month or Year
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)
end

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)
while true
next = Dates.adjust(df,start,step,limit)
cmp(next,stop) == check && break
push!(a,next)
start = next + step
end
return a
end
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
76 changes: 76 additions & 0 deletions base/dates/arithmetic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Instant arithmetic
for op in (:+,:*,:%,:/)
@eval ($op)(x::Instant,y::Instant) = throw(ArgumentError("Operation not defined for Instants"))
end
(+)(x::Instant) = x
(-){T<:Instant}(x::T,y::T) = x.periods - y.periods

# TimeType arithmetic
for op in (:+,:*,:%,:/)
@eval ($op)(x::TimeType,y::TimeType) = throw(ArgumentError("Operation not defined for TimeTypes"))
end
(+)(x::TimeType) = x
(-){T<:TimeType}(x::T,y::T) = x.instant - y.instant

# TimeType-Year arithmetic
function (+)(dt::DateTime,y::Year)
oy,m,d = yearmonthday(dt); ny = oy+value(y); ld = daysinmonth(ny,m)
return DateTime(ny,m,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
end
function (+)(dt::Date,y::Year)
oy,m,d = yearmonthday(dt); ny = oy+value(y); ld = daysinmonth(ny,m)
return Date(ny,m,d <= ld ? d : ld)
end
function (-)(dt::DateTime,y::Year)
oy,m,d = yearmonthday(dt); ny = oy-value(y); ld = daysinmonth(ny,m)
return DateTime(ny,m,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
end
function (-)(dt::Date,y::Year)
oy,m,d = yearmonthday(dt); ny = oy-value(y); ld = daysinmonth(ny,m)
return Date(ny,m,d <= ld ? d : ld)
end

# TimeType-Month arithmetic
# monthwrap adds two months with wraparound behavior (i.e. 12 + 1 == 1)
monthwrap(m1,m2) = (v = mod1(m1+m2,12); return v < 0 ? 12 + v : v)
# yearwrap takes a starting year/month and a month to add and returns
# the resulting year with wraparound behavior (i.e. 2000-12 + 1 == 2001)
yearwrap(y,m1,m2) = y + fld(m1 + m2 - 1,12)

function (+)(dt::DateTime,z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,value(z))
mm = monthwrap(m,value(z)); ld = daysinmonth(ny,mm)
return DateTime(ny,mm,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
end
function (+)(dt::Date,z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,value(z))
mm = monthwrap(m,value(z)); ld = daysinmonth(ny,mm)
return Date(ny,mm,d <= ld ? d : ld)
end
function (-)(dt::DateTime,z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,-value(z))
mm = monthwrap(m,-value(z)); ld = daysinmonth(ny,mm)
return DateTime(ny,mm,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
end
function (-)(dt::Date,z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,-value(z))
mm = monthwrap(m,-value(z)); ld = daysinmonth(ny,mm)
return Date(ny,mm,d <= ld ? d : ld)
end
(+)(x::Date,y::Week) = return Date(UTD(value(x) + 7*value(y)))
(-)(x::Date,y::Week) = return Date(UTD(value(x) - 7*value(y)))
(+)(x::Date,y::Day) = return Date(UTD(value(x) + y))
(-)(x::Date,y::Day) = return Date(UTD(value(x) - y))
(+)(x::DateTime,y::Period) = return DateTime(UTM(value(x)+toms(y)))
(-)(x::DateTime,y::Period) = return DateTime(UTM(value(x)-toms(y)))
(+)(y::Period,x::TimeType) = x + y
(-)(y::Period,x::TimeType) = x - y

(.+){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape(T[i + y for i in x], size(x))
(.-){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape(T[i - y for i in x], size(x))
(.+){T<:TimeType}(y::Period, x::AbstractArray{T}) = x .+ y
(.-){T<:TimeType}(y::Period, x::AbstractArray{T}) = x .- y
43 changes: 43 additions & 0 deletions base/dates/conversions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Conversion/Promotion
Date(dt::TimeType) = convert(Date,dt)
DateTime(dt::TimeType) = convert(DateTime,dt)
Base.convert(::Type{DateTime},dt::Date) = DateTime(UTM(value(dt)*86400000))
Base.convert(::Type{Date},dt::DateTime) = Date(UTD(days(dt)))
Base.convert{R<:Real}(::Type{R},x::DateTime) = convert(R,value(x))
Base.convert{R<:Real}(::Type{R},x::Date) = convert(R,value(x))

@vectorize_1arg DateTime Date
@vectorize_1arg Date DateTime

### External Conversions
const UNIXEPOCH = value(DateTime(1970)) #Rata Die milliseconds for 1970-01-01T00:00:00
function unix2datetime(x)
rata = UNIXEPOCH + int64(1000*x)
return DateTime(UTM(rata))
end
# Returns unix seconds since 1970-01-01T00:00:00
datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH)/1000.0
function now()
tm = TmStruct(time())
return DateTime(tm.year+1900,tm.month+1,tm.mday,tm.hour,tm.min,tm.sec)
end
today() = Date(now())

rata2datetime(days) = DateTime(yearmonthday(days)...)
datetime2rata(dt::DateTime) = days(dt)

# Julian conversions
const JULIANEPOCH = value(DateTime(-4713,11,24,12))
function julian2datetime(f)
rata = JULIANEPOCH + int64(86400000*f)
return DateTime(UTM(rata))
end
# Returns # of julian days since -4713-11-24T12:00:00
datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0

@vectorize_1arg Real unix2datetime
@vectorize_1arg DateTime datetime2unix
@vectorize_1arg Real rata2datetime
@vectorize_1arg DateTime datetime2rata
@vectorize_1arg Real julian2datetime
@vectorize_1arg DateTime datetime2julian
Loading