From 6fb633083f4d01a2b7f1d8d4db8bfb22258c889f Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 17 Jul 2014 22:30:12 -0400 Subject: [PATCH 01/28] Add Dates module to Base --- base/Dates.jl | 13 ++ base/dates/accessors.jl | 75 +++++++ base/dates/adjusters.jl | 167 ++++++++++++++++ base/dates/arithmetic.jl | 85 ++++++++ base/dates/conversions.jl | 43 ++++ base/dates/io.jl | 171 ++++++++++++++++ base/dates/periods.jl | 135 +++++++++++++ base/dates/query.jl | 120 +++++++++++ base/dates/ranges.jl | 81 ++++++++ base/dates/types.jl | 154 ++++++++++++++ base/exports.jl | 6 + base/sysimg.jl | 4 + doc/manual/dates.rst | 343 ++++++++++++++++++++++++++++++++ doc/stdlib/dates.rst | 409 ++++++++++++++++++++++++++++++++++++++ test/dates.jl | 11 + test/dates/accessors.jl | 194 ++++++++++++++++++ test/dates/adjusters.jl | 325 ++++++++++++++++++++++++++++++ test/dates/arithmetic.jl | 347 ++++++++++++++++++++++++++++++++ test/dates/conversions.jl | 42 ++++ test/dates/io.jl | 254 +++++++++++++++++++++++ test/dates/periods.jl | 299 ++++++++++++++++++++++++++++ test/dates/query.jl | 215 ++++++++++++++++++++ test/dates/ranges.jl | 115 +++++++++++ test/dates/types.jl | 143 +++++++++++++ test/runtests.jl | 2 +- 25 files changed, 3752 insertions(+), 1 deletion(-) create mode 100644 base/Dates.jl create mode 100644 base/dates/accessors.jl create mode 100644 base/dates/adjusters.jl create mode 100644 base/dates/arithmetic.jl create mode 100644 base/dates/conversions.jl create mode 100644 base/dates/io.jl create mode 100644 base/dates/periods.jl create mode 100644 base/dates/query.jl create mode 100644 base/dates/ranges.jl create mode 100644 base/dates/types.jl create mode 100644 doc/manual/dates.rst create mode 100644 doc/stdlib/dates.rst create mode 100644 test/dates.jl create mode 100644 test/dates/accessors.jl create mode 100644 test/dates/adjusters.jl create mode 100644 test/dates/arithmetic.jl create mode 100644 test/dates/conversions.jl create mode 100644 test/dates/io.jl create mode 100644 test/dates/periods.jl create mode 100644 test/dates/query.jl create mode 100644 test/dates/ranges.jl create mode 100644 test/dates/types.jl diff --git a/base/Dates.jl b/base/Dates.jl new file mode 100644 index 0000000000000..7689e8304d107 --- /dev/null +++ b/base/Dates.jl @@ -0,0 +1,13 @@ +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") + +end # module \ No newline at end of file diff --git a/base/dates/accessors.jl b/base/dates/accessors.jl new file mode 100644 index 0000000000000..d91718355537a --- /dev/null +++ b/base/dates/accessors.jl @@ -0,0 +1,75 @@ +# 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)) +#TODO: add hourminute, minutesecond, hourminutesecond + +@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 + +export yearmonthday, yearmonth, monthday, year, month, week, day, + hour, minute, second, millisecond, dayofmonth \ No newline at end of file diff --git a/base/dates/adjusters.jl b/base/dates/adjusters.jl new file mode 100644 index 0000000000000..1119f3d507ee4 --- /dev/null +++ b/base/dates/adjusters.jl @@ -0,0 +1,167 @@ +### 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::Dates.TimeType) + f(dt) in (true,false) || throw(ArgumentError("Provided function must take a single TimeType argument and return true or false")) + 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;step::Period=Day(1),negate::Bool=false,limit::Int=10000) + return adjust(DateFunction(func,negate,Date(y)),Date(y),step,limit) +end +function Date(func::Function,y,m;step::Period=Day(1),negate::Bool=false,limit::Int=10000) + return adjust(DateFunction(func,negate,Date(y,m)),Date(y,m),step,limit) +end +function Date(func::Function,y,m,d;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;step::Period=Day(1),negate::Bool=false,limit::Int=10000) + return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y),step,limit) +end +function DateTime(func::Function,y,m;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) + a = T[] + #sizehint(a,) + df = DateFunction(fun,negate,start) + while true + next = Dates.adjust(df,start,step,limit) + next > stop && break + push!(a,next) + start = start == next ? start+step : 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 + +export firstdayofweek, lastdayofweek, + firstdayofmonth, lastdayofmonth, + firstdayofyear, lastdayofyear, + firstdayofquarter, lastdayofquarter, + adjust, tonext, toprev, tofirst, tolast, recur diff --git a/base/dates/arithmetic.jl b/base/dates/arithmetic.jl new file mode 100644 index 0000000000000..b7db1ab5315af --- /dev/null +++ b/base/dates/arithmetic.jl @@ -0,0 +1,85 @@ +# 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 + +# DateTime 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 + +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)) +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)) +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 + +# Date/DateTime-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)) +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)) +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::Week) = return DateTime(UTM(value(x)+604800000*value(y))) +(-)(x::DateTime,y::Week) = return DateTime(UTM(value(x)-604800000*value(y))) +(+)(x::DateTime,y::Day) = return DateTime(UTM(value(x)+86400000 *value(y))) +(-)(x::DateTime,y::Day) = return DateTime(UTM(value(x)-86400000 *value(y))) +(+)(x::DateTime,y::Hour) = return DateTime(UTM(value(x)+3600000 *value(y))) +(-)(x::DateTime,y::Hour) = return DateTime(UTM(value(x)-3600000 *value(y))) +(+)(x::DateTime,y::Minute) = return DateTime(UTM(value(x)+60000 *value(y))) +(-)(x::DateTime,y::Minute) = return DateTime(UTM(value(x)-60000 *value(y))) +(+)(x::DateTime,y::Second) = return DateTime(UTM(value(x)+1000*value(y))) +(-)(x::DateTime,y::Second) = return DateTime(UTM(value(x)-1000*value(y))) +(+)(x::DateTime,y::Millisecond) = return DateTime(UTM(value(x)+value(y))) +(-)(x::DateTime,y::Millisecond) = return DateTime(UTM(value(x)-value(y))) +(+)(y::Period,x::TimeType) = x + y +(-)(y::Period,x::TimeType) = x - y + +(.+){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape([i + y for i in x], size(x)) +(.-){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape([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 \ No newline at end of file diff --git a/base/dates/conversions.jl b/base/dates/conversions.jl new file mode 100644 index 0000000000000..649f6f5aa5cf4 --- /dev/null +++ b/base/dates/conversions.jl @@ -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:00Z +function unix2datetime(x) + rata = UNIXEPOCH + int64(1000*x) + return DateTime(UTM(rata)) +end +# Returns unix seconds since 1970-01-01T00:00:00Z +datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH)/1000.0 +now() = unix2datetime(time()) +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:00Z +datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0 + +@vectorize_1arg Any unix2datetime +@vectorize_1arg DateTime datetime2unix +@vectorize_1arg Any rata2datetime +@vectorize_1arg DateTime datetime2rata +@vectorize_1arg Any julian2datetime +@vectorize_1arg DateTime datetime2julian + +export unix2datetime, datetime2unix, now, today, + rata2datetime, datetime2rata, julian2datetime, datetime2julian diff --git a/base/dates/io.jl b/base/dates/io.jl new file mode 100644 index 0000000000000..686ac8dc7506c --- /dev/null +++ b/base/dates/io.jl @@ -0,0 +1,171 @@ +# TODO: optimize this +function Base.string(dt::DateTime) + y,m,d = yearmonthday(days(dt)) + h,mi,s = hour(dt),minute(dt),second(dt) + yy = y < 0 ? @sprintf("%05i",y) : lpad(y,4,"0") + mm = lpad(m,2,"0") + dd = lpad(d,2,"0") + hh = lpad(h,2,"0") + mii = lpad(mi,2,"0") + ss = lpad(s,2,"0") + ms = millisecond(dt) == 0 ? "" : string(millisecond(dt)/1000.0)[2:end] + return "$yy-$mm-$(dd)T$hh:$mii:$ss$(ms)" +end +Base.show(io::IO,x::DateTime) = print(io,string(x)) +function Base.string(dt::Date) + y,m,d = yearmonthday(value(dt)) + yy = y < 0 ? @sprintf("%05i",y) : lpad(y,4,"0") + mm = lpad(m,2,"0") + dd = lpad(d,2,"0") + return "$yy-$mm-$dd" +end +Base.show(io::IO,x::Date) = print(io,string(x)) + +### Parsing +const english = (UTF8String=>Int)["january"=>1,"february"=>2,"march"=>3,"april"=>4, + "may"=>5,"june"=>6,"july"=>7,"august"=>8,"september"=>9, + "october"=>10,"november"=>11,"december"=>12] +const abbrenglish = (UTF8String=>Int)["jan"=>1,"feb"=>2,"mar"=>3,"apr"=>4, + "may"=>5,"jun"=>6,"jul"=>7,"aug"=>8,"sep"=>9, + "oct"=>10,"nov"=>11,"dec"=>12] +const MONTHTOVALUE = (UTF8String=>Dict{UTF8String,Int})["english"=>english] +const MONTHTOVALUEABBR = (UTF8String=>Dict{UTF8String,Int})["english"=>abbrenglish] + +# Date/DateTime Parsing +abstract Slot{P<:AbstractTime} <: AbstractTime + +immutable DelimitedSlot{P<:AbstractTime} <: Slot{P} + i::Int + period::Type{P} + width::Int + option::Int + locale::String +end + +immutable FixedWidthSlot{P<:AbstractTime} <: Slot{P} + i::Int + period::Type{P} + width::Int + option::Int + locale::String +end + +immutable DateFormat + slots::Array{Slot,1} + trans #trans[i] == how to transition FROM slots[i] TO slots[i+1] +end + +duplicates(slots) = any(map(x->count(y->x.period==y.period,slots),slots) .> 1) + +function DateFormat(f::String,locale::String="english") + slots = Dates.Slot[] + trans = {} + s = split(f,r"[^ymuUdHMSs]|(?<=([ymuUdHMSs])(?!\1))") + for (i,k) in enumerate(s) + k == "" && break + tran = i >= endof(s) ? r"$" : match(Regex("(?<=$(s[i])).*(?=$(s[i+1]))"),f).match + slot = tran == "" || tran == r"$" ? Dates.FixedWidthSlot : Dates.DelimitedSlot + width = length(k) + typ = 'y' in k ? Dates.Year : 'm' in k ? Dates.Month : + 'u' in k ? Dates.Month : 'U' in k ? Dates.Month : + 'd' in k ? Dates.Day : 'H' in k ? Dates.Hour : + 'M' in k ? Dates.Minute : 'S' in k ? Dates.Second : Dates.Millisecond + option = 'U' in k ? 2 : 'u' in k ? 1 : 0 + push!(slots,slot(i,typ,width,option,locale)) + push!(trans,tran) + end + duplicates(slots) && throw(ArgumentError("Two separate periods of the same type detected")) + return DateFormat(slots,trans) +end + +const SLOTERROR = ArgumentError("Non-digit character encountered") +slotparse(slot,x) = !ismatch(r"\D",x) ? slot.period(x) : throw(SLOTERROR) +function slotparse(slot::Slot{Month},x) + if slot.option == 0 + ismatch(r"\D",x) && throw(SLOTERROR) + return Month(x) + elseif slot.option == 1 + return Month(MONTHTOVALUEABBR[slot.locale][lowercase(x)]) + else + return Month(MONTHTOVALUE[slot.locale][lowercase(x)]) + end +end +slotparse(slot::Slot{Millisecond},x) = !ismatch(r"\D",x) ? slot.period(parsefloat("."*x)*1000.0) : throw(SLOTERROR) + +function getslot(x,slot::DelimitedSlot,df,cursor) + endind = first(search(x,df.trans[slot.i],cursor+1)) + if endind == 0 # we didn't find the next delimiter + s = x[cursor:end] + return (endof(x)+1, isdigit(s) ? slotparse(slot,s) : default(slot.period)) + end + return endind+1, slotparse(slot,x[cursor:(endind-1)]) +end +getslot(x,slot,df,cursor) = (cursor+slot.width, Dates.slotparse(slot,x[cursor:(cursor+slot.width-1)])) + +function parse(x::String,df::DateFormat) + x = strip(replace(x, r"#.*$", "")) + isempty(x) && throw(ArgumentError("Cannot parse empty string")) + (typeof(df.slots[1]) <: DelimitedSlot && first(search(x,df.trans[1])) == 0) && throw(ArgumentError("Delimiter mismsatch. Couldn't find first delimter, \"$(df.trans[1])\", in date string")) + periods = Period[] + cursor = 1 + for slot in df.slots + cursor, pe = Dates.getslot(x,slot,df,cursor) + push!(periods,pe) + cursor > endof(x) && break + end + return sort!(periods,rev=true,lt=Dates.periodisless) +end + +slotformat(slot::Slot{Year},dt) = lpad(string(value(slot.period(dt))),slot.width,"0")[(end-slot.width+1):end] +slotformat(slot,dt) = lpad(string(value(slot.period(dt))),slot.width,"0") +function slotformat(slot::Slot{Month},dt) + if slot.option == 0 + return lpad(month(dt),slot.width,"0") + elseif slot.option == 1 + return VALUETOMONTHABBR[slot.locale][month(dt)] + else + return VALUETOMONTH[slot.locale][month(dt)] + end +end +slotformat(slot::Slot{Millisecond},dt) = millisecond(dt) == 0 ? ".0"^slot.width : string(millisecond(dt)/1000.0)[3:end] + +function format(dt::TimeType,df::DateFormat) + f = "" + for slot in df.slots + f *= slotformat(slot,dt) + f *= typeof(df.trans[slot.i]) <: Regex ? "" : df.trans[slot.i] + end + return f +end + +# UI +const ISODateTimeFormat = DateFormat("yyyy-mm-ddTHH:MM:SS.s") +const ISODateFormat = DateFormat("yyyy-mm-dd") + +DateTime(dt::String,format::String;locale::String="english") = DateTime(dt,DateFormat(format,locale)) +DateTime(dt::String,df::DateFormat=ISODateTimeFormat) = DateTime(parse(dt,df)...) + +Date(dt::String,format::String;locale::String="english") = Date(dt,DateFormat(format,locale)) +Date(dt::String,df::DateFormat=ISODateFormat) = Date(parse(dt,df)...) + +format(dt::TimeType,f::String;locale::String="english") = format(dt,DateFormat(f,locale)) + +# vectorized +DateTime{T<:String}(y::AbstractArray{T},format::String;locale::String="english") = DateTime(y,DateFormat(format,locale)) +function DateTime{T<:String}(y::AbstractArray{T},df::DateFormat=ISODateFormat) + return reshape([DateTime(parse(y[i],df)...) for i in 1:length(y)], size(y)) +end +Date{T<:String}(y::AbstractArray{T},format::String;locale::String="english") = Date(y,DateFormat(format,locale)) +function Date{T<:String}(y::AbstractArray{T},x::DateFormat=ISODateFormat) + return reshape([Date(parse(y[i],f)...) for i in 1:length(y)], size(y)) +end + +format{T<:TimeType}(y::AbstractArray{T},format::String;locale::String="english") = format(y,DateFormat(format,locale)) +function format(y::AbstractArray{Date},df::DateFormat=ISODateFormat) + return reshape([format(y[i],df) for i in 1:length(y)], size(y)) +end +function format(y::AbstractArray{DateTime},df::DateFormat=ISODateTimeFormat) + return reshape([format(y[i],df) for i in 1:length(y)], size(y)) +end + +export ISODateTimeFormat, ISODateFormat, DateFormat diff --git a/base/dates/periods.jl b/base/dates/periods.jl new file mode 100644 index 0000000000000..2d245735f4310 --- /dev/null +++ b/base/dates/periods.jl @@ -0,0 +1,135 @@ +#Period types +value{P<:Period}(x::P) = 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 p in (:Year,:Month,:Week,:Day,:Hour,:Minute,:Second,:Millisecond) + # This ensures that we can't convert between Periods + @eval $p(x::Period) = throw(ArgumentError("Can't convert $(typeof(x)) to $($p)")) + # Unless the Periods are the same type + @eval $p(x::$p) = x + # Convenience method for show() + @eval _units(x::$p) = " " * lowercase(string($p)) * (abs(value(x)) == 1 ? "" : "s") + # periodisless + @eval periodisless(x::$p,y::$p) = value(x) < value(y) + # String parsing (mainly for IO code) + @eval $p(x::String) = $p(parseint(x)) +end +# Now we're safe to define Period-Number conversions +# Anything an Int64 can convert to, a Period can convert to +Base.convert{T<:Number}(::Type{T},x::Period) = convert(T,value(x)) +# Error quickly if x can't convert losslessly to Int64 +Base.convert{P<:Period}(::Type{P},x::Number) = P(convert(Int64,x)) + +#Print/show/traits +Base.string{P<:Period}(x::P) = string(value(x),_units(x)) +Base.show(io::IO,x::Period) = print(io,string(x)) +Base.zero{P<:Period}(::Union(Type{P},P)) = P(0) +Base.one{P<:Period}(::Union(Type{P},P)) = P(1) +Base.typemin{P<:Period}(::Type{P}) = P(typemin(Int64)) +Base.typemax{P<:Period}(::Type{P}) = P(typemax(Int64)) + +# Default values (as used by TimeTypes) +default{T<:DatePeriod}(p::Union(T,Type{T})) = one(p) +default{T<:TimePeriod}(p::Union(T,Type{T})) = zero(p) + +(-){P<:Period}(x::P) = P(-value(x)) +Base.isless{P<:Period}(x::P,y::P) = isless(value(x),value(y)) +=={P<:Period}(x::P,y::P) = ===(value(x),value(y)) +Base.isless{R<:Real}(x::Period,y::R) = throw(ArgumentError("Can't compare Period-$R")) +=={R<:Real}(x::Period,y::R) = throw(ArgumentError("Can't compare Period-$R")) +Base.isless{R<:Real}(y::R,x::Period) = throw(ArgumentError("Can't compare Period-$R")) +=={R<:Real}(y::R,x::Period) = throw(ArgumentError("Can't compare Period-$R")) + +Base.isless(x::Period,y::Period) = throw(ArgumentError("Can't compare $(typeof(x)) and $(typeof(y))")) +==(x::Period,y::Period) = throw(ArgumentError("Can't compare $(typeof(x)) and $(typeof(y))")) + +#Period Arithmetic: +import Base.div +let vec_ops = [:.+,:.-,:.*,:.%,:div] + for op in [:+,:-,:*,:%,vec_ops] + @eval begin + #Period-Period + ($op){P<:Period}(x::P,y::P) = P(($op)(value(x),value(y))) + #Period-Integer + ($op){P<:Period}(x::P,y::Integer) = P(($op)(value(x),int64(y))) + ($op){P<:Period}(x::Integer,y::P) = P(($op)(int64(x),value(y))) + #Period-Real + ($op){P<:Period}(x::P,y::Real) = P(($op)(value(x),convert(Int64,y))) + ($op){P<:Period}(x::Real,y::P) = P(($op)(convert(Int64,x),value(y))) + end + #Vectorized + if op in vec_ops + @eval begin + ($op){P<:Period}(x::AbstractArray{P}, y::P) = reshape([$op(i,y) for i in x], size(x)) + ($op){P<:Period}(y::P, x::AbstractArray{P}) = reshape([$op(i,y) for i in x], size(x)) + end + end + end +end + +periodisless(::Period,::Year) = true +periodisless(::Period,::Month) = true +periodisless(::Year,::Month) = false +periodisless(::Period,::Week) = true +periodisless(::Year,::Week) = false +periodisless(::Month,::Week) = false +periodisless(::Period,::Day) = true +periodisless(::Year,::Day) = false +periodisless(::Month,::Day) = false +periodisless(::Week,::Day) = false +periodisless(::Period,::Hour) = false +periodisless(::Minute,::Hour) = true +periodisless(::Second,::Hour) = true +periodisless(::Millisecond,::Hour) = true +periodisless(::Period,::Minute) = false +periodisless(::Second,::Minute) = true +periodisless(::Millisecond,::Minute) = true +periodisless(::Period,::Second) = false +periodisless(::Millisecond,::Second) = true +periodisless(::Period,::Millisecond) = false + +# Stores multiple periods in greatest to least order by type, not values +type CompoundPeriod + periods::Array{Period,1} +end +function Base.string(x::CompoundPeriod) + s = "" + for p in x.periods + s *= ", " * string(p) + end + return s[3:end] +end +Base.show(io::IO,x::CompoundPeriod) = print(io,string(x)) +# E.g. Year(1) + Day(1) +(+)(x::Period,y::Period) = CompoundPeriod(sort!(Period[x,y],rev=true,lt=periodisless)) +(+)(x::CompoundPeriod,y::Period) = (sort!(push!(x.periods,y) ,rev=true,lt=periodisless); return x) +# E.g. Year(1) - Month(1) +(-)(x::Period,y::Period) = CompoundPeriod(sort!(Period[x,-y],rev=true,lt=periodisless)) +(-)(x::CompoundPeriod,y::Period) = (sort!(push!(x.periods,-y),rev=true,lt=periodisless); return x) + +# Capture TimeType+-Period methods +typealias TTP Union(TimeType,Period) +(+)(a::TimeType,b::Period,c::Period) = (+)(a,b+c) +(-)(a::TimeType,b::Period,c::Period) = (-)(a,b-c) +(+)(a::TimeType,b::Period,c::Period,d::Period...) = (+)((+)(a,b+c),d...) +(-)(a::TimeType,b::Period,c::Period,d::Period...) = (-)((-)(a,b-c),d...) + +function (+)(x::TimeType,y::CompoundPeriod) + for p in y.periods + x += p + end + return x +end +(+)(x::CompoundPeriod,y::TimeType) = y + x + +# Periods from TimeTypes +Year(x::TimeType) = Year(year(x)) +Month(x::TimeType) = Month(month(x)) +Week(x::TimeType) = Week(week(x)) +Day(x::TimeType) = Day(day(x)) +Hour(x::TimeType) = Hour(hour(x)) +Minute(x::TimeType) = Minute(minute(x)) +Second(x::TimeType) = Second(second(x)) +Millisecond(x::TimeType) = Millisecond(millisecond(x)) diff --git a/base/dates/query.jl b/base/dates/query.jl new file mode 100644 index 0000000000000..755b11eb0f8f9 --- /dev/null +++ b/base/dates/query.jl @@ -0,0 +1,120 @@ +# Date functions + +### Core date functions + +# Monday = 1....Sunday = 7 +dayofweek(days) = mod1(days,7) + +# If the year is divisible by 4, except for every 100 years, except for every 400 years +isleap(y) = ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0) + +# Number of days in month +const DAYSINMONTH = [31,28,31,30,31,30,31,31,30,31,30,31] +daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleap(y)) + +# Number of days in year +daysinyear(y) = 365 + isleap(y) + +# Day of the year +const MONTHDAYS2 = [0,31,59,90,120,151,181,212,243,273,304,334] +dayofyear(y,m,d) = MONTHDAYS2[m] + d + (m > 2 && isleap(y)) + + +### Days of the Week +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 = [1=>"Monday",2=>"Tuesday",3=>"Wednesday", + 4=>"Thursday",5=>"Friday",6=>"Saturday",7=>"Sunday"] +const VALUETODAYOFWEEK = (UTF8String=>Dict{Int,UTF8String})["english"=>english_daysofweek] +const english_daysofweekabbr = [1=>"Mon",2=>"Tue",3=>"Wed", + 4=>"Thu",5=>"Fri",6=>"Sat",7=>"Sun"] +const VALUETODAYOFWEEKABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>english_daysofweekabbr] +dayname(dt::TimeType;locale::String="english") = VALUETODAYOFWEEK[locale][dayofweek(dt)] +dayabbr(dt::TimeType;locale::String="english") = VALUETODAYOFWEEKABBR[locale][dayofweek(dt)] + +dayofweek(dt::TimeType) = dayofweek(days(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 + +# i.e. 1st Monday? 2nd Monday? 3rd Wednesday? 5th Sunday? +dayofweekofmonth(dt::TimeType) = (d = day(dt); return d < 8 ? 1 : + d < 15 ? 2 : d < 22 ? 3 : d < 29 ? 4 : 5) + +# Total number of a day of week in the month +# e.g. are there 4 or 5 Mondays in this month? +const TWENTYNINE = IntSet(1,8,15,22,29) +const THIRTY = IntSet(1,2,8,9,15,16,22,23,29,30) +const THIRTYONE = IntSet(1,2,3,8,9,10,15,16,17,22,23,24,29,30,31) +function daysofweekinmonth(dt::TimeType) + y,m,d = yearmonthday(dt) + ld = daysinmonth(y,m) + return ld == 28 ? 4 : ld == 29 ? ((d in TWENTYNINE) ? 5 : 4) : + ld == 30 ? ((d in THIRTY) ? 5 : 4) : + (d in THIRTYONE) ? 5 : 4 +end + +@vectorize_1arg TimeType dayname +@vectorize_1arg TimeType dayabbr +@vectorize_1arg TimeType dayofweek +@vectorize_1arg TimeType dayofweekofmonth +@vectorize_1arg TimeType daysofweekinmonth + +### 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 +const english_months = [1=>"January",2=>"February",3=>"March",4=>"April", + 5=>"May",6=>"June",7=>"July",8=>"August",9=>"September", + 10=>"October",11=>"November",12=>"December"] +const VALUETOMONTH = (UTF8String=>Dict{Int,UTF8String})["english"=>english_months] +const englishabbr_months = [1=>"Jan",2=>"Feb",3=>"Mar",4=>"Apr", + 5=>"May",6=>"Jun",7=>"Jul",8=>"Aug",9=>"Sep", + 10=>"Oct",11=>"Nov",12=>"Dec"] +const VALUETOMONTHABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>englishabbr_months] +monthname(dt::TimeType;locale::String="english") = VALUETOMONTH[locale][month(dt)] +monthabbr(dt::TimeType;locale::String="english") = VALUETOMONTHABBR[locale][month(dt)] + +daysinmonth(dt::TimeType) = daysinmonth(yearmonth(dt)...) + +@vectorize_1arg TimeType monthname +@vectorize_1arg TimeType monthabbr +@vectorize_1arg TimeType daysinmonth + +### Years +isleap(dt::TimeType) = isleap(year(dt)) + +dayofyear(dt::TimeType) = dayofyear(yearmonthday(dt)...) + +daysinyear(dt::TimeType) = 365 + isleap(dt) + +@vectorize_1arg TimeType isleap +@vectorize_1arg TimeType dayofyear +@vectorize_1arg TimeType daysinyear + +### Quarters +function quarterofyear(dt::TimeType) + m = month(dt) + return m < 4 ? 1 : m < 7 ? 2 : m < 10 ? 3 : 4 +end +const QUARTERDAYS = [0,90,181,273] +dayofquarter(dt::TimeType) = dayofyear(dt) - QUARTERDAYS[quarterofyear(dt)] + +@vectorize_1arg TimeType quarterofyear +@vectorize_1arg TimeType dayofquarter + +export dayofweek, isleap, daysinmonth, daysinyear, dayofyear, dayname, dayabbr, + ismonday, istuesday, iswednesday, isthursday, isfriday, issaturday, issunday, + 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 diff --git a/base/dates/ranges.jl b/base/dates/ranges.jl new file mode 100644 index 0000000000000..aecb9782eaba0 --- /dev/null +++ b/base/dates/ranges.jl @@ -0,0 +1,81 @@ +# Date/DateTime Ranges +# Given a start and end date, how many steps/periods are in between +function Base.length(r::StepRange{Date,Day}) + n = integer(div(r.stop+r.step - r.start, r.step)) + isempty(r) ? zero(n) : n +end +function Base.length(r::StepRange{Date,Week}) + n = integer(div(r.stop+r.step - r.start, 7*value(r.step))) + isempty(r) ? zero(n) : n +end +function Base.length{T<:Union(Week,Day,TimePeriod)}(r::StepRange{DateTime,T}) + n = integer(div(r.stop+r.step - r.start, toms(r.step))) + isempty(r) ? zero(n) : n +end + +function Base.length{T<:TimeType,P<:Period}(r::StepRange{T,P}) + isempty(r) && return 0 + start,stop = r.start > r.stop ? (r.stop,r.start) : (r.start,r.stop) + step = r.step < zero(r.step) ? -r.step : r.step + i = 0 + while (start+step*i) <= stop + i += 1 + end + return i +end +#Period overflow detection +function Base.length{T<:Period}(r::StepRange{T}) + isempty(r) && return zero(T) + if r.step > one(T) + return Base.checked_add(div(value(r.stop) - value(r.start), value(r.step)), int64(1)) + elseif value(r.step) < int64(-1) + return Base.checked_add(div(value(r.start) - value(r.stop), -value(r.step)), int64(1)) + else + Base.checked_add(div(Base.checked_sub(value(r.stop), value(r.start)), value(r.step)), int64(1)) + end +end + +# Given a start and stop date, calculate the difference between +# the given stop date and the last valid date given the Period step +# last = stop - steprem(start,stop,step) +toobig(start::Date,stop::Date,step::Year) = (stop-start) > Day(3652425000*value(step)) +toobig(start::Date,stop::Date,step::Month) = (stop-start) > Day(365242500*value(step)) +toobig(start::DateTime,stop::DateTime,step::Year) = (stop-start) > Day(3652425000*value(step)) +toobig(start::DateTime,stop::DateTime,step::Month) = (stop-start) > Day(365242500*value(step)) + +Base.steprem(a::Date,b::Date,c::Day) = (b-a) % c +Base.steprem(a::Date,b::Date,c::Week) = (b-a) % (7*value(c)) + +toms(c::Week) = 604800000*value(c) +toms(c::Day) = 86400000*value(c) +toms(c::Hour) = 3600000*value(c) +toms(c::Minute) = 60000*value(c) +toms(c::Second) = 1000*value(c) +toms(c::Millisecond) = value(c) + +Base.steprem(a::DateTime,b::DateTime,c::Union(Week,Day,TimePeriod)) = (b-a) % toms(c) +function Base.steprem(start::TimeType,stop::TimeType,step::Period) + start,stop = start > stop ? (stop,start) : (start,stop) + step = step < zero(step) ? -step : step + toobig(start,stop,step) && throw(ArgumentError("Desired range is too big")) + i = 1 + while (start+step*i) <= stop + i += 1 + end + return stop - (start+step*(i-1)) +end + +# Specialize for Date-Day, DateTime-Millisecond? +import Base.in +# TODO: use binary search +function in{T<:TimeType,S<:Period}(x, r::StepRange{T,S}) + isempty(r) && return false + for d in r + d == x && return true + end + return false +end + +Base.start(r::StepRange{Date}) = 0 +Base.next(r::StepRange{Date}, i) = (r.start+r.step*i,i+1) +Base.done{S<:Period}(r::StepRange{Date,S}, i::Integer) = length(r) <= i diff --git a/base/dates/types.jl b/base/dates/types.jl new file mode 100644 index 0000000000000..6a18815c11c6c --- /dev/null +++ b/base/dates/types.jl @@ -0,0 +1,154 @@ +abstract AbstractTime + +abstract Period <: AbstractTime +abstract DatePeriod <: Period +abstract TimePeriod <: Period + +immutable Year <: DatePeriod + value::Int64 +end +immutable Month <: DatePeriod + value::Int64 +end +immutable Week <: DatePeriod + value::Int64 +end +immutable Day <: DatePeriod + value::Int64 +end + +immutable Hour <: TimePeriod + value::Int64 +end +immutable Minute <: TimePeriod + value::Int64 +end +immutable Second <: TimePeriod + value::Int64 +end +immutable Millisecond <: TimePeriod + value::Int64 +end + +# Instant types represent different monotonically increasing timelines +abstract Instant <: AbstractTime + +# UTInstant is based on UT seconds, or 1/86400th of a turn of the earth +immutable UTInstant{P<:Period} <: Instant + periods::P +end + +# Convenience default constructors +UTM(x) = UTInstant(Millisecond(x)) +UTD(x) = UTInstant(Day(x)) + +# Calendar types provide dispatch rules for interpretating instant +# timelines in human-readable form. Calendar types are used as +# type tags in the Timestamp type for dispatching to methods +# implementing the Instant=>Human-Form conversion rules. +abstract Calendar <: AbstractTime + +# ISOCalendar implements the ISO 8601 standard (en.wikipedia.org/wiki/ISO_8601) +# Notably based on the proleptic Gregorian calendar +# ISOCalendar provides interpretation rules for UTInstants to UT +immutable ISOCalendar <: Calendar end + +# TimeTypes wrap Instants to provide human representations of time +abstract TimeType <: AbstractTime + +# DateTime is a millisecond precision UTInstant interpreted thru ISOCalendar +immutable DateTime <: TimeType + instant::UTInstant{Millisecond} + DateTime(x::UTInstant{Millisecond}) = new(x) +end + +# DateTime is a day precision UTInstant interpreted thru ISOCalendar +immutable Date <: TimeType + instant::UTInstant{Day} + Date(x::UTInstant{Day}) = new(x) +end + +# Convert y,m,d to # of Rata Die days +# Works by shifting the beginning of the year to March 1, +# so a leap day is the very last day of the year +const MONTHDAYS = Int64[306,337,0,31,61,92,122,153,184,214,245,275] +function totaldays(y,m,d) + # If we're in Jan/Feb, shift the given year back one + z = m < 3 ? y - 1 : y + mdays = MONTHDAYS[m]::Int64 + # days + month_days + year_days + return d + mdays + 365z + fld(z,4) - fld(z,100) + fld(z,400) - 306 +end + +### CONSTRUCTORS ### +# Core constructors +function DateTime(y::Int64,m::Int64=1,d::Int64=1, + h::Int64=0,mi::Int64=0,s::Int64=0,ms::Int64=0) + 0 < m < 13 || throw(ArgumentError("Month: $m out of range (1:12)")) + rata = ms + 1000*(s + 60mi + 3600h + 86400*totaldays(y,m,d)) + return DateTime(UTM(rata)) +end +function Date(y::Int64,m::Int64=1,d::Int64=1) + 0 < m < 13 || throw(ArgumentError("Month: $m out of range (1:12)")) + return Date(UTD(totaldays(y,m,d))) +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), + s::Second=Second(0),ms::Millisecond=Millisecond(0)) + return DateTime(value(y),value(m),value(d), + value(h),value(mi),value(s),value(ms)) +end + +Date(y::Year,m::Month=Month(1),d::Day=Day(1)) = Date(value(y),value(m),value(d)) + +# To allow any order/combination of Periods +function DateTime(periods::Period...) + y = Year(1); m = Month(1); d = Day(1) + h = Hour(0); mi = Minute(0); s = Second(0); ms = Millisecond(0) + for p in periods + typeof(p) <: Year && (y = p) + typeof(p) <: Month && (m = p) + typeof(p) <: Day && (d = p) + typeof(p) <: Hour && (h = p) + typeof(p) <: Minute && (mi = p) + typeof(p) <: Second && (s = p) + typeof(p) <: Millisecond && (ms = p) + end + return DateTime(y,m,d,h,mi,s,ms) +end +function Date(periods::Period...) + y = Year(1); m = Month(1); d = Day(1) + for p in periods + typeof(p) <: Year && (y = p) + typeof(p) <: Month && (m = p) + typeof(p) <: Day && (d = p) + end + return Date(y,m,d) +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)) + +# Traits, Equality +Base.isfinite{T<:TimeType}(::Union(TimeType,T)) = true +calendar(dt::DateTime) = ISOCalendar +calendar(dt::Date) = ISOCalendar +Base.precision(dt::DateTime) = UTInstant{Millisecond} +Base.precision(dt::Date) = UTInstant{Day} +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) +# Date-DateTime promotion, isless, == +Base.promote_rule(::Type{Date},x::Type{DateTime}) = x +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::TimeType,y::TimeType) = isless(promote(x,y)...) +==(x::TimeType,y::TimeType) = ===(promote(x,y)...) + +export Period, Year, Month, Week, Day, Hour, Minute, Second, Millisecond, + TimeType, DateTime, Date diff --git a/base/exports.jl b/base/exports.jl index dd877c02abd07..781f4408fde31 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -7,6 +7,7 @@ export Operators, Pkg, Profile, + Dates, Sys, Test, BLAS, @@ -1013,6 +1014,11 @@ export toc, toq, +#dates + Date, + DateTime, + now, + # errors assert, backtrace, diff --git a/base/sysimg.jl b/base/sysimg.jl index b9402696d31c9..7ed450c7a6038 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -268,6 +268,10 @@ include("graphics.jl") include("profile.jl") importall .Profile +# dates +include("Dates.jl") +importall .Dates + function __init__() # Base library init reinit_stdio() diff --git a/doc/manual/dates.rst b/doc/manual/dates.rst new file mode 100644 index 0000000000000..70bf94fb4fd7b --- /dev/null +++ b/doc/manual/dates.rst @@ -0,0 +1,343 @@ +.. _man-dates: + +************************************* + Date and DateTime +************************************* + +The ``Dates`` module provides two ``TimeType`` types: ``Date`` and ``DateTime``, representing day and millisecond levels of precision, respectively. The motivation for distinct types is simple: some operations are much simpler, both in terms of code and mental reasoning, when the complexities of greater precision don't have to be dealt with. For example, since the ``Date`` type has a "day-precision" (i.e. no hours, minutes, or seconds), it means that normal considerations for time zones, daylight savings/summer time, and leap seconds are unnecessary. + +Both ``Date`` and ``DateTime`` are immutable, and wrap ``Int64`` ``UTInstant`` types, which represents a continuously increasing machine timeline based on the UT second [1]_. The ``DateTime`` type is *timezone-unaware* (in Python parlance) or is analogous to a *LocalDateTime* (in Java 8). Additional time zone functionality can be added through the `Timezones.jl package `_, which compiles the `Olsen Time Zone Database `_. Both ``Date`` and ``DateTime`` are based on the ISO 8601 standard, which follows the proleptic Gregorian calendar. One note is that the ISO 8601 standard is particular about BC/BCE dates. In common text, the last day of the BC/BCE era, 1-12-31 BC/BCE, was followed by 1-1-1 AD/CE, thus no year 0 exists. The ISO standard, however, states that 1 BC/BCE is year 0, so ``0000-12-31`` is the day before ``0001-01-01``, and year ``-0001`` (yes, negative 1 for the year) is 2 BC/BCE, year ``-0002`` is 3 BC/BCE, etc. + +.. [1] The notion of the UT second is actually quite fundamental. There are basically two different notions of time as we know it, one based on the physical rotation of the earth (one full rotation = 1 day), the other actually based on the SI second (a fixed, constant value). These are radically different! Think about it, a "UT second" defined relative to the rotation of the earth may have a different absolute length depending on the day! Anyway, the fact that the ``Dates`` package bases ``Date`` and ``DateTime`` on UT seconds is a simplifying assumption so that things like leap seconds and all their complexity can be avoided. This basis of time is formally called UT or UT1. Basing types on the UT second basically means that every minute has 60 seconds and every day has 24 hours. + +Constructors +------------ +``Date`` and ``DateTime`` types can be constructed by parts with integers or ``Period`` types, by parsing, or through adjusters (more on them later):: + + julia> DateTime(2013) + 2013-01-01T00:00:00 + + julia> DateTime(2013,7) + 2013-07-01T00:00:00 + + julia> DateTime(2013,7,1) + 2013-07-01T00:00:00 + + julia> DateTime(2013,7,1,12) + 2013-07-01T12:00:00 + + julia> DateTime(2013,7,1,12,30) + 2013-07-01T12:30:00 + + julia> DateTime(2013,7,1,12,30,59) + 2013-07-01T12:30:59 + + julia> DateTime(2013,7,1,12,30,59,1) + 2013-07-01T12:30:59.001 + + julia> Date(2013) + 2013-01-01 + + julia> Date(2013,7) + 2013-07-01 + + julia> Date(2013,7,1) + 2013-07-01 + + julia> Date(Year(2013),Month(7),Day(1)) + 2013-07-01 + + julia> Date(Month(7),Year(2013)) + 2013-07-01 + +``Date`` or ``DateTime`` parsing is accomplished by the use of format strings. Format strings work by the notion of defining *delimited* or *fixed-width* "slots" that contain a period to parse and pass to a ``TimeType`` constructor. + +Delimited slots are marked by specifying the delimiter the parser should expect between two subsequent periods; so ``"y-m-d"`` lets the parser know that between the first and second slots in a date string like ``"2014-07-16"``, it should find the ``-`` character. The ``y``, ``m``, and ``d`` characters let the parser know which periods to parse in each slot. + +Fixed-width slots are specified by repeating the period character the number of times corresponding to the width. So ``"yyyymmdd"`` would correspond to a date string like ``"20140716"``. The parser distinguishes a fixed-width slot by the absence of a delimiter, noting the transition ``"yyyymm"`` from one period character to the next. + +Support for text-form month parsing is also supported through the ``u`` and ``U`` characters, for abbreviated and full-length month names, respectively. By default, only English month names are supported, so ``u`` corresponds to "Jan", "Feb", "Mar", etc. And ``U`` corresponds to "January", "February", "March", etc. Just like the query functions ``dayname`` and ``monthname``, however, custom locales can be loaded in a similar fashion by passing in the ``locale=>Dict{UTF8String,Int}`` mapping to the ``MONTHTOVALUEABBR`` and ``MONTHTOVALUE`` for abbreviated and full-name month names, respectively. + +A full suite of parsing and formatting tests and examples is available `here `_. + +Durations/Comparisons +--------------------- + +Finding the length of time between two ``Date`` or ``DateTime`` is straightforward. The difference between ``Date`` is returned in the number of ``Day``, and ``DateTime`` in the number of ``Millisecond``. Similarly, comparing ``TimeType`` is a simple matter of comparing the underlying machine instants.:: + + julia> dt = Date(2012,2,29) + 2012-02-29 + + julia> dt2 = Date(2000,2,1) + 2000-02-01 + + julia> dump(dt) + Date + instant: UTInstant{Day} + periods: Day + value: Int64 734562 + + julia> dump(dt2) + Date + instant: UTInstant{Day} + periods: Day + value: Int64 730151 + + julia> dt > dt2 + true + + julia> dt != dt2 + true + + julia> dt + dt2 + Operation not defined for TimeTypes + + julia> dt * dt2 + Operation not defined for TimeTypes + + julia> dt / dt2 + Operation not defined for TimeTypes + + julia> dt - dt2 + 4411 days + + julia> dt2 - dt + -4411 days + + julia> dt = DateTime(2012,2,29) + 2012-02-29T00:00:00 + + julia> dt2 = DateTime(2000,2,1) + 2000-02-01T00:00:00 + + julia> dt - dt2 + 381110402000 milliseconds + +TimeType-Period Arithmetic +-------------------------- + +It's good practice when using any language/date framework to be familiar with how date-period arithmetic is handled as there are some `tricky issues `_ to deal with (though much less so for day-precision types). + +The ``Dates`` module approach tries to follow the simple principle of trying to change as little as possible when doing ``Period`` arithmetic. This approach is also often known as *calendrical* arithmetic or what you would probably guess if someone were to ask you the same calculation in a conversation. Why all the fuss? Let's take a classic example: add 1 month to January 31st, 2014. What's the answer? Javascript will say March 3 (assumes 31 days). PHP says March 2 (assumes 30 days). The fact is, there is no one right answer. In the ``Dates`` module, it would give the result of February 28th. How does it figure that out? I like to think of the classic 7-7-7 gambling game in casinos. + +Now just imagine that instead of 7-7-7, the slots are Year-Month-Day, or in our example, 2014-01-31. When you ask to add 1 month to this date, the month slot is incremented, so now we have 2014-02-31. Then the day number is checked if it is greater than the last valid day of the new month; if it is (as in the case above), the day number is adjusted down to the last valid day (28). What are ramifications with this approach? Go ahead and add another month to our date, ``2014-02-28 + Month(1) == 2014-03-28``. What? Were you expecting the last day of March? Nope, sorry, remember the 7-7-7 slots. As few slots as possible are going to change, so we first increment the month slot by 1, 2014-03-28, and boom, we're done because that's a valid date. On the other hand, if we were to add 2 months to our original date, 2014-01-31, then we end up with 2014-03-31, as expected. The other ramification of this approach is a loss in associativity when a specific ordering is forced (i.e. adding things in different orders results in different outcomes). For example:: + + julia> (Date(2014,1,29)+Day(1)) + Month(1) + 2014-02-28 + + julia> (Date(2014,1,29)+Month(1)) + Day(1) + 2014-03-01 + +What's going on there? In the first line, we're adding 1 day to January 29th, which results in 2014-01-30; then we add 1 month, so we get 2014-02-30, which then adjusts down to 2014-02-28. In the second example, we add 1 month *first*, where we get 2014-02-29, which adjusts down to 2014-02-28, and *then* add 1 day, which results in 2014-03-01. One design principle that helps in this case is that, in the presence of multiple Periods, the operations will be based on the Period *types*, not their value or order; meaning ``Year`` will always be added first, then ``Month``, then ``Week``, etc. Hence the following *does* result in associativity and Just Works:sup:`TM`:: + + julia> Date(2014,1,29) + Day(1) + Month(1) + 2014-03-01 + + julia> Date(2014,1,29) + Month(1) + Day(1) + 2014-03-01 + +Tricky? Perhaps. What is an innocent ``Dates`` user to do? The bottom line is to be aware that explicitly forcing a certain associativity, when dealing with months, may lead to some unexpected results, but otherwise, everything works as expected. Thankfully, that's pretty much the extent of the odd cases in date-period arithmetic when dealing with time in UT (avoiding the "joys" of dealing with daylight savings, leap seconds, etc.). + +Accessor Functions +------------------ + +Because the `Date` and `DateTime` types are stored as single ``Int64`` values, date parts or fields can be retrieved through accessor functions. The lowercase accessors return the field as an integer:: + + julia> t = Date(2014,1,31) + 2014-01-31 + + julia> Dates.year(t) + 2014 + + julia> Dates.month(t) + 1 + + julia> Dates.week(t) + 5 + + julia> Dates.day(t) + 31 + +While propercase return the same value in the corresponding ``Period`` type:: + + julia> Dates.Year(t) + 2014 years + + julia> Dates.Day(t) + 31 days + +Compound methods are provided, as they provide a measure of efficiency if multiple fields are needed:: + + julia> Dates.yearmonth(t) + (2014,1) + + julia> Dates.monthday(t) + (1,31) + + julia> Dates.yearmonthday(t) + (2014,1,31) + +One may also access the underlying ``UTInstant`` or integer value:: + + julia> dump(t) + Date + instant: UTInstant{Day} + periods: Day + value: Int64 735264 + + julia> t.instant + UTInstant{Day}(735264 days) + + julia> Dates.value(t) + 735264 + +Query Functions +--------------- + +Query functions provide calendrical information about a ``TimeType``. They include information about the day of the week:: + + julia> t = Date(2014,1,31) + 2014-01-31 + + julia> Dates.dayofweek(t) + 5 + + julia> Dates.dayname(t) + "Friday" + + julia> Dates.dayofweekofmonth(t) + 5 # 5th Friday of January + +Month of the year:: + + julia> Dates.monthname(t) + "January" + + julia> Dates.daysinmonth(t) + 31 + +As well as informationa about the ``TimeType``'s year and quarter:: + + julia> Dates.isleap(t) + false + + julia> Dates.dayofyear(t) + 31 + + julia> Dates.quarterofyear(t) + 1 + + julia> Dates.dayofquarter(t) + 31 + +The ``dayname`` and ``monthname`` methods can also take an optional ``locale`` keyword that can be used to return the name of the day or month of the year for other languages/locales:: + + julia> const french_daysofweek = [1=>"Lundi",2=>"Mardi",3=>"Mercredi",4=>"Jeudi",5=>"Vendredi",6=>"Samedi",7=>"Dimanche"]; + + # Load the mapping into the Dates module under locale name "french" + julia> Dates.VALUETODAYOFWEEK["french"] = french_daysofweek; + + julia> Dates.dayname(t;locale="french") + "Vendredi" + +Similarly for the ``monthname`` function, a mapping of ``locale=>Dict{Int,UTF8String}`` should be loaded in ``Dates.VALUETOMONTH``. + +Adjuster Functions +------------------ + +As convenient as date-period arithmetics are, often the kinds of calculations needed on dates take on a *calendrical* or *temporal* nature rather than a fixed number of periods. Holidays are a perfect example; most follow rules such as ``Memorial Day = Last Monday of May``, or ``Thanksgiving = 4th Thursday of November``. These kinds of temporal expressions deal with rules relative to the calendar, like first or last of the month, next Tuesday, or the first and third Wednesdays, etc. + +The ``Dates`` module provides the *adjuster* API through several convenient methods that aid in simply and succinctly expressing temporal rules. The first group of adjuster methods deal with the first and last of weeks, months, quarters, and years. They each take a single ``TimeType`` as input and return the first or last of the desired period relative to the input. + +:: + + # Adjusts the input to the Monday of the input's week + julia> Dates.firstdayofweek(Date(2014,7,16)) + 2014-07-14 + + # Adjusts to the last day of the input's month + julia> Dates.lastdayofmonth(Date(2014,7,16)) + 2014-07-31 + + # Adjusts to the last day of the input's quarter + julia> Dates.lastdayofquarter(Date(2014,7,16)) + 2014-09-30 + +The next four higher-order methods, ``tofirst``, ``tolast``, ``tonext``, and ``toprev``, generalize working with temporal expressions by taking a ``DateFunction`` as first argument, along with a starting ``TimeType``. A ``DateFunction`` is just a function that takes a single ``TimeType`` as input and returns a ``Bool``, ``true`` indicating a satisfied adjustment criterion. +For example:: + + julia> istuesday = x->Dates.dayofweek(x) == Dates.Tuesday # Returns true if Tuesday + (anonymous function) + + julia> Dates.tonext(istuesday, Date(2014,7,13)) # 2014-07-13 is a Sunday + 2014-07-15 + + # Convenience method provided for day of the week adjustments + julia> Dates.tonext(Date(2014,7,13), Dates.Tuesday) + 2014-07-15 + +This is useful with the do-block syntax for more complex temporal expressions:: + + julia> Dates.tonext(Date(2014,7,13)) do x + # Return true on the 4th Thursday of November (Thanksgiving) + Dates.dayofweek(x) == Dates.Thursday && + Dates.dayofweekofmonth(x) == 4 && + Dates.month(x) == Dates.November + end + 2014-11-27 + +The ``tofirst`` and ``tolast`` similarly take a ``DateFunction`` as first argument, but adjust to the first or last of the month by default, with a keyword to specify the first or last of the year instead:: + + julia> Dates.tofirst(istuesday, Date(2014,7,13)) # Defaults to 1st of the month + 2014-07-01 + + julia> Dates.tofirst(istuesday, Date(2014,7,13); of=Dates.Year) + 2014-01-07 + + julia> Dates.tolast(istuesday, Date(2014,7,13)) + 2014-07-29 + + julia> Dates.tolast(istuesday, Date(2014,7,13); of=Dates.Year) + 2014-12-30 + +The final method in the adjuster API is the ``recur`` function. ``recur`` vectorizes the adjustment process by taking a start and stop date (optionally specificed by a ``StepRange``), along with a ``DateFunction`` to specify all valid dates/moments to be returned in the specified range. In this case, the ``DateFunction`` is often referred to as the "inclusion" function because it specifies (by returning true) which dates/moments should be included in the returned vector of dates. + + +Period Types +------------ + +Periods are a human view of discrete, sometimes irregular durations of time. Consider 1 month; it could represent, in days, a value of 28, 29, 30, or 31 depending on the year and month context. Or a year could represent 365 or 366 days in the case of a leap year. ``Period`` types are simple ``Int64`` wrappers and are constructed by wrapping any ``Integer`` type, i.e. ``Year(1)`` or ``Month(3.0)``. Arithmetic between ``Period`` of the same type behave like ``Integer``, and limited ``Period-Real`` arithmetic is available for scaling (``*`` and ``div``) +:: + + julia> y1 = Dates.Year(1) + 1 year + + julia> y2 = Dates.Year(2) + 2 years + + julia> y3 = Dates.Year(10) + 10 years + + julia> y1 + y2 + 3 years + + julia> div(y3,y2) + 5 years + + julia> y3 - y2 + 8 years + + julia> y3 * y2 + 20 years + + julia> y3 % y2 + 0 years + + julia> y1 + 20 + 21 years + + julia> div(y3,3) # mirrors integer division + 3 years + + +Full descriptions of exported functions in the Dates module is availbe `here `_. \ No newline at end of file diff --git a/doc/stdlib/dates.rst b/doc/stdlib/dates.rst new file mode 100644 index 0000000000000..cca19682606b3 --- /dev/null +++ b/doc/stdlib/dates.rst @@ -0,0 +1,409 @@ +Dates Functions +--------------- + +All Dates functions are defined in the ``Dates`` module; note that only the ``Date`` and ``DateTime`` functions are exported; +to use all other ``Dates`` functions, you'll need to prefix each function call with an explicit ``Dates.``, e.g. ``Dates.dayofweek(dt)`` or ``Dates.now()``; +alternatively, you could call ``using Base.Dates`` to bring all exported functions into ``Main`` to be used without the ``Dates.`` prefix. + +.. currentmodule:: Base + +.. function:: ``DateTime(y, [m, d, h, mi, s, ms]) -> DateTime`` + + Construct a DateTime type by parts. Arguments must be convertible to + ``Int64``. + +.. function:: ``DateTime(periods::Period...) -> DateTime`` + + Constuct a DateTime type by ``Period`` type parts. Arguments may be in any order. + DateTime parts not provided will default to the value of ``Dates.default(period)``. + +.. function:: ``DateTime(f::Function, y[, m, d, h, mi, s]; step=Day(1), negate=false, limit=10000) -> DateTime`` + + Create a DateTime through the adjuster API. The starting point will be constructed from the + provided ``y, m, d...`` 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 (given that ``f::Function`` + is never satisfied). + +.. function:: ``DateTime(dt::Date) -> DateTime`` + + Converts a ``Date`` type to a ``DateTime``. The hour, minute, second, and millisecond + parts of the new ``DateTime`` are assumed to be zero. + +.. function:: ``DateTime(dt::String, format::String; locale="english") -> DateTime`` + + Construct a DateTime type by parsing a ``dt`` date string following the pattern given in + the ``format`` string. The following codes can be used for constructing format strings: + + | Code | Matches | Comment + | --------------- | -------- | ---------------------------- + | ``y`` | 1996, 96 | Returns year of 1996, 0096 + | ``m`` | 1, 01 | Matches 1 or 2-digit months + | ``u`` | Jan | Matches abbreviated months according to the ``locale`` keyword + | ``U`` | January | Matches full month names according to the ``locale`` keyword + | ``d`` | 1, 01 | Matches 1 or 2-digit days + | ``H`` | 00 | Matches hours + | ``M`` | 00 | Matches minutes + | ``S`` | 00 | Matches seconds + | ``s`` | .500 | Matches milliseconds + | ``yyyymmdd`` | 19960101 | Matches fixed-width year, month, and day + +.. function:: ``Date(y, [m, d]) -> Date`` + + Construct a ``Date`` type by parts. Arguments must be convertible to + ``Int64``. + +.. function:: ``Date(period::Period...)`` + + Constuct a Date type by ``Period`` type parts. Arguments may be in any order. + Date parts not provided will default to the value of ``Dates.default(period)``. + +.. function:: ``Date(f::Function, y[, m]; step=Day(1), negate=false, limit=10000) -> Date`` + + Create a Date through the adjuster API. The starting point will be constructed from the + provided ``y, m`` 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 (given that ``f::Function`` + is never satisfied). + +.. function:: ``Date(dt::DateTime) -> Date`` + + Converts a ``DateTime`` type to a ``Date``. The hour, minute, second, and millisecond + parts of the ``DateTime`` are truncated, so only the year, month and day parts are kept. + +.. function:: ``Date(dt::String, format::String; locale="english") -> Date`` + + Construct a Date type by parsing a ``dt`` date string following the pattern given in + the ``format`` string. Follows the same convention as ``DateTime`` above. + +Accessor Functions +~~~~~~~~~~~~~~~~~~ + +.. function:: ``year(dt::TimeType) -> Int64`` + + Return the year part of a Date or DateTime. Use ``Year(dt)`` to return a ``Year`` type. + +.. function:: ``month(dt::TimeType) -> Int64`` + + Return the month part of a Date or DateTime. Use ``Month(dt)`` to return a ``Month`` type. + +.. function:: ``week(dt::TimeType) -> Int64`` + + Return the ISO 8601 week number of a Date or DateTime. Use ``Week(dt)`` to return a ``Week`` type. + +.. function:: ``day(dt::TimeType) -> Int64`` + + Return the day part of a Date or DateTime. Use ``Day(dt)`` to return a ``Day`` type. + +.. function:: ``hour(dt::TimeType) -> Int64`` + + Return the hour part of a DateTime. Use ``Hour(dt)`` to return a ``Hour`` type. + +.. function:: ``minute(dt::TimeType) -> Int64`` + + Return the minute part of a DateTime. Use ``Minute(dt)`` to return a ``Minute`` type. + +.. function:: ``second(dt::TimeType) -> Int64`` + + Return the second part of a DateTime. Use ``Second(dt)`` to return a ``Second`` type. + +.. function:: ``millisecond(dt::TimeType) -> Int64`` + + Return the millisecond part of a DateTime. Use ``Millisecond(dt)`` to return a ``Millisecond`` type. + +.. function:: ``yearmonth(dt::TimeType) -> (Int64, Int64)`` + + Simultaneously return the year and month parts of a Date or DateTime. + +.. function:: ``monthday(dt::TimeType) -> (Int64, Int64)`` + + Simultaneously return the month and day parts of a Date or DateTime. + +.. function:: ``yearmonthday(dt::TimeType) -> (Int64, Int64, Int64)`` + + Simultaneously return the year, month, and day parts of a Date or DateTime. + +Query Functions +~~~~~~~~~~~~~~~ + +.. function:: ``dayname(dt::TimeType; locale="english") -> String`` + + Return the full day name corresponding to the day of the week + of the Date or DateTime in the given ``locale``. + +.. function:: ``dayabbr(dt::TimeType; locale="english") -> String`` + + Return the abbreviated name corresponding to the day of the week + of the Date or DateTime in the given ``locale``. + +.. function:: ``dayofweek(dt::TimeType) -> Int64`` + + Returns the day of the week as an ``Int64`` with ``1 = Monday, 2 = Tuesday, etc.``. + +.. function:: ``dayofweekofmonth(dt::TimeType) -> Int`` + + 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.`` Lies in the range 1:5. + +.. function:: ``daysofweekinmonth(dt::TimeType) -> Int`` + + For the day of week of ``dt``, returns the total number of that day of the week + in ``dt``'s month. Returns 4 or 5. Useful in temporal expressions for specifying + the last day of a week in a month by including ``dayofweekofmonth(dt) == daysofweekinmonth(dt)`` + in the adjuster function. + +.. function:: ``monthname(dt::TimeType; locale="english") -> String`` + + Return the full name of the month of the Date or DateTime in the given ``locale``. + +.. function:: ``monthabbr(dt::TimeType; locale="english") -> String`` + + Return the abbreviated month name of the Date or DateTime in the given ``locale``. + +.. function:: ``daysinmonth(dt::TimeType) -> Int`` + + Returns the number of days in the month of ``dt``. Value will be 28, 29, 30, or 31. + +.. function:: ``isleap(dt::TimeType) -> Bool`` + + Returns true if the year of ``dt`` is a leap year. + +.. function:: ``dayofyear(dt::TimeType) -> Int`` + + Returns the day of the year for ``dt`` with January 1st being day 1. + +.. function:: ``daysinyear(dt::TimeType) -> Int`` + + Returns 366 if the year of ``dt`` is a leap year, otherwise returns 365. + +.. function:: ``quarterofyear(dt::TimeType) -> Int`` + + Returns the quarter that ``dt`` resides in. Range of value is 1:4. + +.. function:: ``dayofquarter(dt::TimeType) -> Int`` + + Returns the day of the current quarter of ``dt``. Range of value is 1:92. + +Adjuster Functions +~~~~~~~~~~~~~~~~~~ + +.. function:: ``trunc(dt::TimeType, ::Type{Period}) -> TimeType`` + + Truncates the value of ``dt`` according to the provided ``Period`` type. + E.g. if ``dt`` is ``1996-01-01T12:30:00`` and ``trunc(dt,Day)``, + ``1996-01-01T00:00:00`` will be returned, truncating up to the ``Day``. + +.. function:: ``firstdayofweek(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the Monday of it's week. + +.. function:: ``lastdayofweek(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the Sunday of it's week. + +.. function:: ``firstdayofmonth(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the first day of it's month. + +.. function:: ``lastdayofmonth(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the last day of it's month. + +.. function:: ``firstdayofyear(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the first day of it's year. + +.. function:: ``lastdayofyear(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the last day of it's year. + +.. function:: ``firstdayofquarter(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the first day of it's quarter. + +.. function:: ``lastdayofquarter(dt::TimeType) -> TimeType`` + + Adjusts ``dt`` to the last day of it's quarter. + +.. function:: ``tonext(dt::TimeType,dow::Int;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. + +.. function:: ``toprev(dt::TimeType,dow::Int;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. + +.. function:: ``tofirst(dt::TimeType,dow::Int;of=Month) -> TimeType`` + + Adjusts ``dt`` to the first ``dow`` of it's month. Alternatively, ``of=Year`` + will adjust to the first ``dow`` of the year. + +.. function:: ``tolast(dt::TimeType,dow::Int;of=Month) -> TimeType`` + + Adjusts ``dt`` to the last ``dow`` of it's month. Alternatively, ``of=Year`` + will adjust to the last ``dow`` of the year. + +.. function:: ``tonext(func::Function,dt::TimeType;step=Day(1),negate=false,limit=10000,same=false) -> TimeType`` + + Adjusts ``dt`` by iterating at most ``limit`` iterations by ``step`` increments until + ``func`` returns true. ``func`` must take a single ``TimeType`` argument and return a ``Bool``. + ``same`` allows ``dt`` to be considered as satisfying ``func``. ``negate`` will make the adjustment + process terminate when ``func`` returns false instead of true. + +.. function:: ``toprev(func::Function,dt::TimeType;step=Day(-1),negate=false,limit=10000,same=false) -> TimeType`` + + Adjusts ``dt`` by iterating at most ``limit`` iterations by ``step`` increments until + ``func`` returns true. ``func`` must take a single ``TimeType`` argument and return a ``Bool``. + ``same`` allows ``dt`` to be considered as satisfying ``func``. ``negate`` will make the adjustment + process terminate when ``func`` returns false instead of true. + +.. function:: ``recur{T<:TimeType}(func::Function,dr::StepRange{T};negate=false,limit=10000) -> Array{T,1}`` + + ``func`` takes a single TimeType argument and returns a ``Bool`` indicating whether the input + should be "included" in the final set. ``recur`` applies ``func`` over the range of ``dr`` to + generate an Array from dates/moments for which ``func`` returns true (unless ``negate=true``). + + +Periods +~~~~~~~ + +.. function:: ``Year(y)`` + + Construct a ``Year`` type with the given ``y`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Month(m)`` + + Construct a ``Month`` type with the given ``m`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Week(w)`` + + Construct a ``Week`` type with the given ``w`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Day(d)`` + + Construct a ``Day`` type with the given ``d`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Hour(h)`` + + Construct a ``Hour`` type with the given ``h`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Minute(mi)`` + + Construct a ``Minute`` type with the given ``mi`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Second(s)`` + + Construct a ``Second`` type with the given ``s`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``Millisecond(ms)`` + + Construct a ``Millisecond`` type with the given ``ms`` value. + Input must be losslessly convertible to an ``Int64``. + +.. function:: ``default(p::Period) => Period`` + + Returns a sensible "default" value for the input Period by returning + ``one(p)`` for Year, Month, and Day, and ``zero(p)`` for Hour, Minute, + Second, and Millisecond. + +Conversion Functions +~~~~~~~~~~~~~~~~~~~~ + +.. function:: ``now() -> DateTime`` + + Returns a DateTime type corresponding to the user's system + time. Corresponds to the UTC/GMT timezone. + +.. function:: ``today() -> Date`` + + Converts the output of ``now()`` to a Date. + +.. function:: ``unix2datetime(x) -> DateTime`` + + Takes the number of seconds since unix epoch ``1970-01-01T00:00:00`` + and converts to the corresponding DateTime. + +.. function:: ``datetime2unix(dt::DateTime) -> Float64`` + + Takes the given DateTime and returns the number of seconds since + the unix epoch as a ``Float64``. + +.. function:: ``julian2datetime(j) -> DateTime`` + + Takes the number of Julian calendar days since epoch + ``-4713-11-24T12:00:00`` and returns the corresponding DateTime. + +.. function:: ``datetime2julian(dt::DateTime) -> Float64`` + + Takes the given DateTime and returns the number of Julian calendar days + since the julian epoch as a ``Float64``. + +.. function:: ``rata2datetime(days) -> DateTime`` + + Takes the number of Rata Die days since epoch ``0000-12-31T00:00:00`` + and returns the corresponding DateTime. + +.. function:: ``datetime2rata(dt::TimeType) -> Int64`` + + Returns the number of Rata Die days since epoch from the + given Date or DateTime. + + +Constants +--------- + +Days of the Week: + + ``Monday`` = ``Mon`` = 1 + + ``Tuesday`` = ``Tue`` = 2 + + ``Wednesday`` = ``Wed`` = 3 + + ``Thursday`` = ``Thu`` = 4 + + ``Friday`` = ``Fri`` = 5 + + ``Saturday`` = ``Sat`` = 6 + + ``Sunday`` = ``Sun`` = 7 + +Months of the Year: + + ``January`` = ``Jan`` = 1 + + ``February`` = ``Feb`` = 2 + + ``March`` = ``Mar`` = 3 + + ``April`` = ``Apr`` = 4 + + ``May`` = ``May`` = 5 + + ``June`` = ``Jun`` = 6 + + ``July`` = ``Jul`` = 7 + + ``August`` = ``Aug`` = 8 + + ``September`` = ``Sep`` = 9 + + ``October`` = ``Oct`` = 10 + + ``November`` = ``Nov`` = 11 + + ``December`` = ``Dec`` = 12 diff --git a/test/dates.jl b/test/dates.jl new file mode 100644 index 0000000000000..1687eb69800e1 --- /dev/null +++ b/test/dates.jl @@ -0,0 +1,11 @@ +using Base.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") \ No newline at end of file diff --git a/test/dates/accessors.jl b/test/dates/accessors.jl new file mode 100644 index 0000000000000..a483de74c53ca --- /dev/null +++ b/test/dates/accessors.jl @@ -0,0 +1,194 @@ +# yearmonthday is the opposite of totaldays +# taking Rata Die Day # and returning proleptic Gregorian date +@test Dates.yearmonthday(-306) == (0,2,29) +@test Dates.yearmonth(-306) == (0,2) +@test Dates.monthday(-306) == (2,29) +@test Dates.yearmonthday(-305) == (0,3,1) +@test Dates.yearmonth(-305) == (0,3) +@test Dates.monthday(-305) == (3,1) +@test Dates.yearmonthday(-2) == (0,12,29) +@test Dates.yearmonth(-2) == (0,12) +@test Dates.monthday(-2) == (12,29) +@test Dates.yearmonthday(-1) == (0,12,30) +@test Dates.yearmonth(-1) == (0,12) +@test Dates.monthday(-1) == (12,30) +@test Dates.yearmonthday(0) == (0,12,31) +@test Dates.yearmonth(-0) == (0,12) +@test Dates.monthday(-0) == (12,31) +@test Dates.yearmonthday(1) == (1,1,1) +@test Dates.yearmonth(1) == (1,1) +@test Dates.monthday(1) == (1,1) +# year, month, and day return the indivial components +# of yearmonthday, avoiding additional calculations when possible +@test Dates.year(-1) == 0 +@test Dates.month(-1) == 12 +@test Dates.day(-1) == 30 +@test Dates.year(0) == 0 +@test Dates.month(0) == 12 +@test Dates.day(0) == 31 +@test Dates.year(1) == 1 +@test Dates.month(1) == 1 +@test Dates.day(1) == 1 +@test Dates.yearmonthday(730120) == (2000,1,1) +@test Dates.year(730120) == 2000 +@test Dates.month(730120) == 1 +@test Dates.day(730120) == 1 + +# Test totaldays and yearmonthday from January 1st of "from" to December 31st of "to" +# test_dates(-10000,10000) takes about 15 seconds +# test_dates(year(typemin(Date)),year(typemax(Date))) is full range +# and would take.......a really long time +function test_dates(from,to) + y = m = d = 0 + test_day = Dates.totaldays(from,1,1) + for y in from:to + for m = 1:12 + for d = 1:Dates.daysinmonth(y,m) + days = Dates.totaldays(y,m,d) + @test days == test_day + @test (y,m,d) == Dates.yearmonthday(days) + test_day += 1 + end + end + end +end +test_dates(-2000,2000) + +# Test year, month, day, hour, minute +function test_dates() + y = m = d = h = mi = 0 + for y in [-2013,-1,0,1,2013] + for m = 1:12 + for d = 1:Dates.daysinmonth(y,m) + for h = 0:23 + for mi = 0:59 + dt = Dates.DateTime(y,m,d,h,mi) + @test y == Dates.year(dt) + @test m == Dates.month(dt) + @test d == Dates.day(dt) + @test d == Dates.dayofmonth(dt) + @test h == Dates.hour(dt) + @test mi == Dates.minute(dt) + #@test s == Dates.second(dt) + #@test ms == Dates.millisecond(dt) + end + end + end + end + end +end +test_dates() + +# Test second, millisecond +function test_dates() + y = m = d = h = mi = s = ms = 0 + for y in [-2013,-1,0,1,2013] + for m in [1,6,12] + for d in [1,15,Dates.daysinmonth(y,m)] + for h in [0,12,23] + for s = 0:59 + for ms in [0,1,500,999] + dt = Dates.DateTime(y,m,d,h,mi,s,ms) + @test y == Dates.year(dt) + @test m == Dates.month(dt) + @test d == Dates.day(dt) + @test h == Dates.hour(dt) + @test s == Dates.second(dt) + @test ms == Dates.millisecond(dt) + end + end + end + end + end + end +end +test_dates() + +function test_dates(from,to) + y = m = d = 0 + for y in from:to + for m = 1:12 + for d = 1:Dates.daysinmonth(y,m) + dt = Dates.Date(y,m,d) + @test y == Dates.year(dt) + @test m == Dates.month(dt) + @test d == Dates.day(dt) + end + end + end +end +test_dates(-2000,2000) + +# week function +# Tests from http://en.wikipedia.org/wiki/ISO_week_date +@test Dates.week(Dates.Date(2005,1,1)) == 53 +@test Dates.week(Dates.Date(2005,1,2)) == 53 +@test Dates.week(Dates.Date(2005,12,31)) == 52 +@test Dates.week(Dates.Date(2007,1,1)) == 1 +@test Dates.week(Dates.Date(2007,12,30)) == 52 +@test Dates.week(Dates.Date(2007,12,31)) == 1 +@test Dates.week(Dates.Date(2008,1,1)) == 1 +@test Dates.week(Dates.Date(2008,12,28)) == 52 +@test Dates.week(Dates.Date(2008,12,29)) == 1 +@test Dates.week(Dates.Date(2008,12,30)) == 1 +@test Dates.week(Dates.Date(2008,12,31)) == 1 +@test Dates.week(Dates.Date(2009,1,1)) == 1 +@test Dates.week(Dates.Date(2009,12,31)) == 53 +@test Dates.week(Dates.Date(2010,1,1)) == 53 +@test Dates.week(Dates.Date(2010,1,2)) == 53 +@test Dates.week(Dates.Date(2010,1,2)) == 53 +# Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=1999 +dt = Dates.DateTime(1999,12,27) +dt1 = Dates.Date(1999,12,27) +check = (52,52,52,52,52,52,52,1,1,1,1,1,1,1,2,2,2,2,2,2,2) +for i = 1:21 + @test Dates.week(dt) == check[i] + @test Dates.week(dt1) == check[i] + dt = dt + Dates.Day(1) + dt1 = dt1 + Dates.Day(1) +end +# Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2000 +dt = Dates.DateTime(2000,12,25) +dt1 = Dates.Date(2000,12,25) +for i = 1:21 + @test Dates.week(dt) == check[i] + @test Dates.week(dt1) == check[i] + dt = dt + Dates.Day(1) + dt1 = dt1 + Dates.Day(1) +end +# Test from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2030 +dt = Dates.DateTime(2030,12,23) +dt1 = Dates.Date(2030,12,23) +for i = 1:21 + @test Dates.week(dt) == check[i] + @test Dates.week(dt1) == check[i] + dt = dt + Dates.Day(1) + dt1 = dt1 + Dates.Day(1) +end +# Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2004 +dt = Dates.DateTime(2004,12,20) +dt1 = Dates.Date(2004,12,20) +check = (52,52,52,52,52,52,52,53,53,53,53,53,53,53,1,1,1,1,1,1,1) +for i = 1:21 + @test Dates.week(dt) == check[i] + @test Dates.week(dt1) == check[i] + dt = dt + Dates.Day(1) + dt1 = dt1 + Dates.Day(1) +end + +# Vectorized accessors +a = Dates.Date(2014,1,1) +dr = [a,a,a,a,a,a,a,a,a,a] +@test Dates.year(dr) == repmat([2014],10) +@test Dates.month(dr) == repmat([1],10) +@test Dates.day(dr) == repmat([1],10) + +a = Dates.DateTime(2014,1,1) +dr = [a,a,a,a,a,a,a,a,a,a] +@test Dates.year(dr) == repmat([2014],10) +@test Dates.month(dr) == repmat([1],10) +@test Dates.day(dr) == repmat([1],10) +@test Dates.hour(dr) == repmat([0],10) +@test Dates.minute(dr) == repmat([0],10) +@test Dates.second(dr) == repmat([0],10) +@test Dates.millisecond(dr) == repmat([0],10) \ No newline at end of file diff --git a/test/dates/adjusters.jl b/test/dates/adjusters.jl new file mode 100644 index 0000000000000..85317fe2e03bf --- /dev/null +++ b/test/dates/adjusters.jl @@ -0,0 +1,325 @@ +#trunc +dt = Dates.Date(2012,12,21) +@test trunc(dt,Dates.Year) == Dates.Date(2012) +@test trunc(dt,Dates.Month) == Dates.Date(2012,12) +@test trunc(dt,Dates.Day) == Dates.Date(2012,12,21) +dt = Dates.DateTime(2012,12,21,16,30,20,200) +@test trunc(dt,Dates.Year) == Dates.DateTime(2012) +@test trunc(dt,Dates.Month) == Dates.DateTime(2012,12) +@test trunc(dt,Dates.Day) == Dates.DateTime(2012,12,21) +@test trunc(dt,Dates.Hour) == Dates.DateTime(2012,12,21,16) +@test trunc(dt,Dates.Minute) == Dates.DateTime(2012,12,21,16,30) +@test trunc(dt,Dates.Second) == Dates.DateTime(2012,12,21,16,30,20) +@test trunc(dt,Dates.Millisecond) == Dates.DateTime(2012,12,21,16,30,20,200) + +# Date functions +jan = Dates.DateTime(2013,1,1) #Tuesday +feb = Dates.DateTime(2013,2,2) #Saturday +mar = Dates.DateTime(2013,3,3) #Sunday +apr = Dates.DateTime(2013,4,4) #Thursday +may = Dates.DateTime(2013,5,5) #Sunday +jun = Dates.DateTime(2013,6,7) #Friday +jul = Dates.DateTime(2013,7,7) #Sunday +aug = Dates.DateTime(2013,8,8) #Thursday +sep = Dates.DateTime(2013,9,9) #Monday +oct = Dates.DateTime(2013,10,10) #Thursday +nov = Dates.DateTime(2013,11,11) #Monday +dec = Dates.DateTime(2013,12,11) #Wednesday + +@test Dates.lastdayofmonth(jan) == DateTime(2013,1,31) +@test Dates.lastdayofmonth(feb) == DateTime(2013,2,28) +@test Dates.lastdayofmonth(mar) == DateTime(2013,3,31) +@test Dates.lastdayofmonth(apr) == DateTime(2013,4,30) +@test Dates.lastdayofmonth(may) == DateTime(2013,5,31) +@test Dates.lastdayofmonth(jun) == DateTime(2013,6,30) +@test Dates.lastdayofmonth(jul) == DateTime(2013,7,31) +@test Dates.lastdayofmonth(aug) == DateTime(2013,8,31) +@test Dates.lastdayofmonth(sep) == DateTime(2013,9,30) +@test Dates.lastdayofmonth(oct) == DateTime(2013,10,31) +@test Dates.lastdayofmonth(nov) == DateTime(2013,11,30) +@test Dates.lastdayofmonth(dec) == DateTime(2013,12,31) + +@test Dates.lastdayofmonth(Date(jan)) == Date(2013,1,31) +@test Dates.lastdayofmonth(Date(feb)) == Date(2013,2,28) +@test Dates.lastdayofmonth(Date(mar)) == Date(2013,3,31) +@test Dates.lastdayofmonth(Date(apr)) == Date(2013,4,30) +@test Dates.lastdayofmonth(Date(may)) == Date(2013,5,31) +@test Dates.lastdayofmonth(Date(jun)) == Date(2013,6,30) +@test Dates.lastdayofmonth(Date(jul)) == Date(2013,7,31) +@test Dates.lastdayofmonth(Date(aug)) == Date(2013,8,31) +@test Dates.lastdayofmonth(Date(sep)) == Date(2013,9,30) +@test Dates.lastdayofmonth(Date(oct)) == Date(2013,10,31) +@test Dates.lastdayofmonth(Date(nov)) == Date(2013,11,30) +@test Dates.lastdayofmonth(Date(dec)) == Date(2013,12,31) + +@test Dates.firstdayofmonth(jan) == DateTime(2013,1,1) +@test Dates.firstdayofmonth(feb) == DateTime(2013,2,1) +@test Dates.firstdayofmonth(mar) == DateTime(2013,3,1) +@test Dates.firstdayofmonth(apr) == DateTime(2013,4,1) +@test Dates.firstdayofmonth(may) == DateTime(2013,5,1) +@test Dates.firstdayofmonth(jun) == DateTime(2013,6,1) +@test Dates.firstdayofmonth(jul) == DateTime(2013,7,1) +@test Dates.firstdayofmonth(aug) == DateTime(2013,8,1) +@test Dates.firstdayofmonth(sep) == DateTime(2013,9,1) +@test Dates.firstdayofmonth(oct) == DateTime(2013,10,1) +@test Dates.firstdayofmonth(nov) == DateTime(2013,11,1) +@test Dates.firstdayofmonth(dec) == DateTime(2013,12,1) + +@test Dates.firstdayofmonth(Date(jan)) == Date(2013,1,1) +@test Dates.firstdayofmonth(Date(feb)) == Date(2013,2,1) +@test Dates.firstdayofmonth(Date(mar)) == Date(2013,3,1) +@test Dates.firstdayofmonth(Date(apr)) == Date(2013,4,1) +@test Dates.firstdayofmonth(Date(may)) == Date(2013,5,1) +@test Dates.firstdayofmonth(Date(jun)) == Date(2013,6,1) +@test Dates.firstdayofmonth(Date(jul)) == Date(2013,7,1) +@test Dates.firstdayofmonth(Date(aug)) == Date(2013,8,1) +@test Dates.firstdayofmonth(Date(sep)) == Date(2013,9,1) +@test Dates.firstdayofmonth(Date(oct)) == Date(2013,10,1) +@test Dates.firstdayofmonth(Date(nov)) == Date(2013,11,1) +@test Dates.firstdayofmonth(Date(dec)) == Date(2013,12,1) + +# Test first day of week; 2014-01-06 is a Monday = 1st day of week +a = Dates.Date(2014,1,6) +b = Dates.Date(2014,1,7) +c = Dates.Date(2014,1,8) +d = Dates.Date(2014,1,9) +e = Dates.Date(2014,1,10) +f = Dates.Date(2014,1,11) +g = Dates.Date(2014,1,12) +@test Dates.firstdayofweek(a) == a +@test Dates.firstdayofweek(b) == a +@test Dates.firstdayofweek(c) == a +@test Dates.firstdayofweek(d) == a +@test Dates.firstdayofweek(e) == a +@test Dates.firstdayofweek(f) == a +@test Dates.firstdayofweek(g) == a +# Test firstdayofweek over the course of the year +dt = a +for i = 0:364 + @test Dates.firstdayofweek(dt) == a + Dates.Week(div(i,7)) + dt += Dates.Day(1) +end +a = Dates.DateTime(2014,1,6) +b = Dates.DateTime(2014,1,7) +c = Dates.DateTime(2014,1,8) +d = Dates.DateTime(2014,1,9) +e = Dates.DateTime(2014,1,10) +f = Dates.DateTime(2014,1,11) +g = Dates.DateTime(2014,1,12) +@test Dates.firstdayofweek(a) == a +@test Dates.firstdayofweek(b) == a +@test Dates.firstdayofweek(c) == a +@test Dates.firstdayofweek(d) == a +@test Dates.firstdayofweek(e) == a +@test Dates.firstdayofweek(f) == a +@test Dates.firstdayofweek(g) == a +dt = a +for i = 0:364 + @test Dates.firstdayofweek(dt) == a + Dates.Week(div(i,7)) + dt += Dates.Day(1) +end +@test Dates.firstdayofweek(Dates.DateTime(2013,12,24)) == Dates.DateTime(2013,12,23) +# Test last day of week; Sunday = last day of week +# 2014-01-12 is a Sunday +a = Dates.Date(2014,1,6) +b = Dates.Date(2014,1,7) +c = Dates.Date(2014,1,8) +d = Dates.Date(2014,1,9) +e = Dates.Date(2014,1,10) +f = Dates.Date(2014,1,11) +g = Dates.Date(2014,1,12) +@test Dates.lastdayofweek(a) == g +@test Dates.lastdayofweek(b) == g +@test Dates.lastdayofweek(c) == g +@test Dates.lastdayofweek(d) == g +@test Dates.lastdayofweek(e) == g +@test Dates.lastdayofweek(f) == g +@test Dates.lastdayofweek(g) == g +dt = a +for i = 0:364 + @test Dates.lastdayofweek(dt) == g + Dates.Week(div(i,7)) + dt += Dates.Day(1) +end +a = Dates.DateTime(2014,1,6) +b = Dates.DateTime(2014,1,7) +c = Dates.DateTime(2014,1,8) +d = Dates.DateTime(2014,1,9) +e = Dates.DateTime(2014,1,10) +f = Dates.DateTime(2014,1,11) +g = Dates.DateTime(2014,1,12) +@test Dates.lastdayofweek(a) == g +@test Dates.lastdayofweek(b) == g +@test Dates.lastdayofweek(c) == g +@test Dates.lastdayofweek(d) == g +@test Dates.lastdayofweek(e) == g +@test Dates.lastdayofweek(f) == g +@test Dates.lastdayofweek(g) == g +dt = a +for i = 0:364 + @test Dates.lastdayofweek(dt) == g + Dates.Week(div(i,7)) + dt += Dates.Day(1) +end +@test Dates.lastdayofweek(Dates.DateTime(2013,12,24)) == Dates.DateTime(2013,12,29) + +@test Dates.firstdayofquarter(Dates.Date(2014,2,2)) == Dates.Date(2014,1,1) +@test Dates.firstdayofquarter(Dates.Date(2014,5,2)) == Dates.Date(2014,4,1) +@test Dates.firstdayofquarter(Dates.Date(2014,8,2)) == Dates.Date(2014,7,1) +@test Dates.firstdayofquarter(Dates.Date(2014,12,2)) == Dates.Date(2014,10,1) + +@test Dates.firstdayofquarter(Dates.DateTime(2014,2,2)) == Dates.DateTime(2014,1,1) +@test Dates.firstdayofquarter(Dates.DateTime(2014,5,2)) == Dates.DateTime(2014,4,1) +@test Dates.firstdayofquarter(Dates.DateTime(2014,8,2)) == Dates.DateTime(2014,7,1) +@test Dates.firstdayofquarter(Dates.DateTime(2014,12,2)) == Dates.DateTime(2014,10,1) + +@test Dates.lastdayofquarter(Dates.Date(2014,2,2)) == Dates.Date(2014,3,31) +@test Dates.lastdayofquarter(Dates.Date(2014,5,2)) == Dates.Date(2014,6,30) +@test Dates.lastdayofquarter(Dates.Date(2014,8,2)) == Dates.Date(2014,9,30) +@test Dates.lastdayofquarter(Dates.Date(2014,12,2)) == Dates.Date(2014,12,31) + +@test Dates.lastdayofquarter(Dates.DateTime(2014,2,2)) == Dates.DateTime(2014,3,31) +@test Dates.lastdayofquarter(Dates.DateTime(2014,5,2)) == Dates.DateTime(2014,6,30) +@test Dates.lastdayofquarter(Dates.DateTime(2014,8,2)) == Dates.DateTime(2014,9,30) +@test Dates.lastdayofquarter(Dates.DateTime(2014,12,2)) == Dates.DateTime(2014,12,31) + + +# Adjusters +# Adjuster Constructors +@test Dates.Date(Dates.ismonday,2014) == Dates.Date(2014,1,6) +@test Dates.Date(Dates.ismonday,2014,5) == Dates.Date(2014,5,5) + +@test Dates.DateTime(Dates.ismonday,2014) == Dates.DateTime(2014,1,6) +@test Dates.DateTime(Dates.ismonday,2014,5) == Dates.DateTime(2014,5,5) +@test Dates.DateTime(x->Dates.hour(x)==12,2014,5,21) == Dates.DateTime(2014,5,21,12) +@test Dates.DateTime(x->Dates.minute(x)==30,2014,5,21,12) == Dates.DateTime(2014,5,21,12,30) +@test Dates.DateTime(x->Dates.second(x)==30,2014,5,21,12,30) == Dates.DateTime(2014,5,21,12,30,30) +@test Dates.DateTime(x->Dates.millisecond(x)==500,2014,5,21,12,30,30) == Dates.DateTime(2014,5,21,12,30,30,500) + +# tonext, toprev, tofirst, tolast +dt = Dates.Date(2014,5,21) +@test Dates.tonext(dt,Dates.Wed) == Dates.Date(2014,5,28) +@test Dates.tonext(dt,Dates.Wed;same=true) == dt +@test Dates.tonext(dt,Dates.Thu) == Dates.Date(2014,5,22) +@test Dates.tonext(dt,Dates.Fri) == Dates.Date(2014,5,23) +@test Dates.tonext(dt,Dates.Sat) == Dates.Date(2014,5,24) +@test Dates.tonext(dt,Dates.Sun) == Dates.Date(2014,5,25) +@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 Dates.tonext(Dates.Date(0),Dates.Mon) == Dates.Date(0,1,3) + +#test func, diff steps, negate, same +@test Dates.tonext(Dates.iswednesday,dt) == Dates.Date(2014,5,28) +@test Dates.tonext(Dates.iswednesday,dt;same=true) == dt +@test Dates.tonext(Dates.isthursday,dt) == Dates.Date(2014,5,22) +@test Dates.tonext(Dates.isfriday,dt) == Dates.Date(2014,5,23) +@test Dates.tonext(Dates.issaturday,dt) == Dates.Date(2014,5,24) +@test Dates.tonext(Dates.issunday,dt) == Dates.Date(2014,5,25) +@test Dates.tonext(Dates.ismonday,dt) == Dates.Date(2014,5,26) +@test Dates.tonext(Dates.istuesday,dt) == Dates.Date(2014,5,27) +@test Dates.tonext(Dates.ismonday,Dates.Date(0)) == Dates.Date(0,1,3) + +@test Dates.tonext(x->!Dates.iswednesday(x),dt;negate=true) == Dates.Date(2014,5,28) +# Reach adjust limit +@test_throws ArgumentError Dates.tonext(Dates.iswednesday,dt;limit=6) + +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(2)) == Dates.Date(2014,6,4) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(3)) == Dates.Date(2014,6,11) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(4)) == Dates.Date(2014,6,18) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(5)) == Dates.Date(2014,6,25) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(6)) == Dates.Date(2014,7,2) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Day(7)) == Dates.Date(2014,5,28) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(1)) == Dates.Date(2014,5,28) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(2)) == Dates.Date(2014,6,4) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(3)) == Dates.Date(2014,6,11) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(4)) == Dates.Date(2014,6,18) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(5)) == Dates.Date(2014,6,25) +@test Dates.tonext(Dates.iswednesday,dt;step=Dates.Week(6)) == Dates.Date(2014,7,2) + +@test Dates.tonext(Dates.iswednesday,dt;same=true) == dt +@test Dates.tonext(Dates.isthursday,dt) == Dates.Date(2014,5,22) + +#toprev +@test Dates.toprev(dt,Dates.Wed) == Dates.Date(2014,5,14) +@test Dates.toprev(dt,Dates.Wed;same=true) == dt +@test Dates.toprev(dt,Dates.Thu) == Dates.Date(2014,5,15) +@test Dates.toprev(dt,Dates.Fri) == Dates.Date(2014,5,16) +@test Dates.toprev(dt,Dates.Sat) == Dates.Date(2014,5,17) +@test Dates.toprev(dt,Dates.Sun) == Dates.Date(2014,5,18) +@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 Dates.toprev(Dates.Date(0),Dates.Mon) == Dates.Date(-1,12,27) + +#tofirst +@test Dates.tofirst(dt,Dates.Mon) == Dates.Date(2014,5,5) +@test Dates.tofirst(dt,Dates.Tue) == Dates.Date(2014,5,6) +@test Dates.tofirst(dt,Dates.Wed) == Dates.Date(2014,5,7) +@test Dates.tofirst(dt,Dates.Thu) == Dates.Date(2014,5,1) +@test Dates.tofirst(dt,Dates.Fri) == Dates.Date(2014,5,2) +@test Dates.tofirst(dt,Dates.Sat) == Dates.Date(2014,5,3) +@test Dates.tofirst(dt,Dates.Sun) == Dates.Date(2014,5,4) + +@test Dates.tofirst(dt,Dates.Mon,of=Dates.Year) == Dates.Date(2014,1,6) +@test Dates.tofirst(dt,Dates.Tue,of=Dates.Year) == Dates.Date(2014,1,7) +@test Dates.tofirst(dt,Dates.Wed,of=Dates.Year) == Dates.Date(2014,1,1) +@test Dates.tofirst(dt,Dates.Thu,of=Dates.Year) == Dates.Date(2014,1,2) +@test Dates.tofirst(dt,Dates.Fri,of=Dates.Year) == Dates.Date(2014,1,3) +@test Dates.tofirst(dt,Dates.Sat,of=Dates.Year) == Dates.Date(2014,1,4) +@test Dates.tofirst(dt,Dates.Sun,of=Dates.Year) == Dates.Date(2014,1,5) + +@test Dates.tofirst(Dates.Date(0),Dates.Mon) == Dates.Date(0,1,3) + +#tolast +@test Dates.tolast(dt,Dates.Mon) == Dates.Date(2014,5,26) +@test Dates.tolast(dt,Dates.Tue) == Dates.Date(2014,5,27) +@test Dates.tolast(dt,Dates.Wed) == Dates.Date(2014,5,28) +@test Dates.tolast(dt,Dates.Thu) == Dates.Date(2014,5,29) +@test Dates.tolast(dt,Dates.Fri) == Dates.Date(2014,5,30) +@test Dates.tolast(dt,Dates.Sat) == Dates.Date(2014,5,31) +@test Dates.tolast(dt,Dates.Sun) == Dates.Date(2014,5,25) + +@test Dates.tolast(dt,Dates.Mon,of=Dates.Year) == Dates.Date(2014,12,29) +@test Dates.tolast(dt,Dates.Tue,of=Dates.Year) == Dates.Date(2014,12,30) +@test Dates.tolast(dt,Dates.Wed,of=Dates.Year) == Dates.Date(2014,12,31) +@test Dates.tolast(dt,Dates.Thu,of=Dates.Year) == Dates.Date(2014,12,25) +@test Dates.tolast(dt,Dates.Fri,of=Dates.Year) == Dates.Date(2014,12,26) +@test Dates.tolast(dt,Dates.Sat,of=Dates.Year) == Dates.Date(2014,12,27) +@test Dates.tolast(dt,Dates.Sun,of=Dates.Year) == Dates.Date(2014,12,28) + +@test Dates.tolast(Dates.Date(0),Dates.Mon) == Dates.Date(0,1,31) + +# recur +startdate = Dates.Date(2014,1,1) +stopdate = Dates.Date(2014,2,1) +@test length(Dates.recur(x->x==x,startdate:stopdate)) == 32 + +januarymondays2014 = [Dates.Date(2014,1,6),Dates.Date(2014,1,13),Dates.Date(2014,1,20),Dates.Date(2014,1,27)] +@test Dates.recur(Dates.ismonday,startdate,stopdate) == januarymondays2014 +@test Dates.recur(Dates.ismonday,startdate:stopdate) == januarymondays2014 +@test Dates.recur(x->!Dates.ismonday(x),startdate,stopdate;negate=true) == januarymondays2014 + +# All leap days in 20th century +@test length(Dates.recur(Dates.Date(1900):Dates.Date(2000)) do x + Dates.month(x) == Dates.Feb && Dates.day(x) == 29 +end) == 24 + +# All observed Christmas days in 20th century +@test length(Dates.recur(Dates.Date(1900):Dates.Date(2000)) do x + if Dates.month(x) != Dates.Dec + return false + else + if Dates.day(x) == 25 && Dates.dayofweek(x) < 6 + return true + elseif Dates.dayofweek(x) == 1 && Dates.day(x-Dates.Day(1)) == 25 + return true + elseif Dates.dayofweek(x) == 5 && Dates.day(x+Dates.Day(1)) == 25 + return true + else + return false + end + end +end) == 100 diff --git a/test/dates/arithmetic.jl b/test/dates/arithmetic.jl new file mode 100644 index 0000000000000..7a393739eaa00 --- /dev/null +++ b/test/dates/arithmetic.jl @@ -0,0 +1,347 @@ +# DateTime arithmetic +a = Dates.DateTime(2013,1,1,0,0,0,1) +b = Dates.DateTime(2013,1,1,0,0,0,0) +@test a - b == Dates.Millisecond(1) +@test Dates.DateTime(2013,1,2) - b == Dates.Millisecond(86400000) + +# DateTime-Year arithmetic +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Year(1) == Dates.DateTime(2000,12,27) +@test dt + Dates.Year(100) == Dates.DateTime(2099,12,27) +@test dt + Dates.Year(1000) == Dates.DateTime(2999,12,27) +@test dt - Dates.Year(1) == Dates.DateTime(1998,12,27) +@test dt - Dates.Year(100) == Dates.DateTime(1899,12,27) +@test dt - Dates.Year(1000) == Dates.DateTime(999,12,27) +dt = Dates.DateTime(2000,2,29) +@test dt + Dates.Year(1) == Dates.DateTime(2001,2,28) +@test dt - Dates.Year(1) == Dates.DateTime(1999,2,28) +@test dt + Dates.Year(4) == Dates.DateTime(2004,2,29) +@test dt - Dates.Year(4) == Dates.DateTime(1996,2,29) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Year(1) == Dates.DateTime(1973,7,1) +@test dt - Dates.Year(1) == Dates.DateTime(1971,7,1) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Year(1) == Dates.DateTime(1973,6,30,23,59,59) +@test dt - Dates.Year(1) == Dates.DateTime(1971,6,30,23,59,59) +@test dt + Dates.Year(-1) == Dates.DateTime(1971,6,30,23,59,59) +@test dt - Dates.Year(-1) == Dates.DateTime(1973,6,30,23,59,59) + +# Wrapping arithemtic for Months +# This ends up being trickier than expected because +# the user might do 2014-01-01 + Month(-14) +# monthwrap figures out the resulting month +# when adding/subtracting months from a date +@test Dates.monthwrap(1,-14) == 11 +@test Dates.monthwrap(1,-13) == 12 +@test Dates.monthwrap(1,-12) == 1 +@test Dates.monthwrap(1,-11) == 2 +@test Dates.monthwrap(1,-10) == 3 +@test Dates.monthwrap(1,-9) == 4 +@test Dates.monthwrap(1,-8) == 5 +@test Dates.monthwrap(1,-7) == 6 +@test Dates.monthwrap(1,-6) == 7 +@test Dates.monthwrap(1,-5) == 8 +@test Dates.monthwrap(1,-4) == 9 +@test Dates.monthwrap(1,-3) == 10 +@test Dates.monthwrap(1,-2) == 11 +@test Dates.monthwrap(1,-1) == 12 +@test Dates.monthwrap(1,0) == 1 +@test Dates.monthwrap(1,1) == 2 +@test Dates.monthwrap(1,2) == 3 +@test Dates.monthwrap(1,3) == 4 +@test Dates.monthwrap(1,4) == 5 +@test Dates.monthwrap(1,5) == 6 +@test Dates.monthwrap(1,6) == 7 +@test Dates.monthwrap(1,7) == 8 +@test Dates.monthwrap(1,8) == 9 +@test Dates.monthwrap(1,9) == 10 +@test Dates.monthwrap(1,10) == 11 +@test Dates.monthwrap(1,11) == 12 +@test Dates.monthwrap(1,12) == 1 +@test Dates.monthwrap(1,13) == 2 +@test Dates.monthwrap(1,24) == 1 +@test Dates.monthwrap(12,-14) == 10 +@test Dates.monthwrap(12,-13) == 11 +@test Dates.monthwrap(12,-12) == 12 +@test Dates.monthwrap(12,-11) == 1 +@test Dates.monthwrap(12,-2) == 10 +@test Dates.monthwrap(12,-1) == 11 +@test Dates.monthwrap(12,0) == 12 +@test Dates.monthwrap(12,1) == 1 +@test Dates.monthwrap(12,2) == 2 +@test Dates.monthwrap(12,11) == 11 +@test Dates.monthwrap(12,12) == 12 +@test Dates.monthwrap(12,13) == 1 + +# yearwrap figures out the resulting year +# when adding/subtracting months from a date +@test Dates.yearwrap(2000,1,-3600) == 1700 +@test Dates.yearwrap(2000,1,-37) == 1996 +@test Dates.yearwrap(2000,1,-36) == 1997 +@test Dates.yearwrap(2000,1,-35) == 1997 +@test Dates.yearwrap(2000,1,-25) == 1997 +@test Dates.yearwrap(2000,1,-24) == 1998 +@test Dates.yearwrap(2000,1,-23) == 1998 +@test Dates.yearwrap(2000,1,-14) == 1998 +@test Dates.yearwrap(2000,1,-13) == 1998 +@test Dates.yearwrap(2000,1,-12) == 1999 +@test Dates.yearwrap(2000,1,-11) == 1999 +@test Dates.yearwrap(2000,1,-2) == 1999 +@test Dates.yearwrap(2000,1,-1) == 1999 +@test Dates.yearwrap(2000,1,0) == 2000 +@test Dates.yearwrap(2000,1,1) == 2000 +@test Dates.yearwrap(2000,1,11) == 2000 +@test Dates.yearwrap(2000,1,12) == 2001 +@test Dates.yearwrap(2000,1,13) == 2001 +@test Dates.yearwrap(2000,1,23) == 2001 +@test Dates.yearwrap(2000,1,24) == 2002 +@test Dates.yearwrap(2000,1,25) == 2002 +@test Dates.yearwrap(2000,1,36) == 2003 +@test Dates.yearwrap(2000,1,3600) == 2300 +@test Dates.yearwrap(2000,2,-2) == 1999 +@test Dates.yearwrap(2000,3,10) == 2001 +@test Dates.yearwrap(2000,4,-4) == 1999 +@test Dates.yearwrap(2000,5,8) == 2001 +@test Dates.yearwrap(2000,6,-18) == 1998 +@test Dates.yearwrap(2000,6,-6) == 1999 +@test Dates.yearwrap(2000,6,6) == 2000 +@test Dates.yearwrap(2000,6,7) == 2001 +@test Dates.yearwrap(2000,6,19) == 2002 +@test Dates.yearwrap(2000,12,-3600) == 1700 +@test Dates.yearwrap(2000,12,-36) == 1997 +@test Dates.yearwrap(2000,12,-35) == 1998 +@test Dates.yearwrap(2000,12,-24) == 1998 +@test Dates.yearwrap(2000,12,-23) == 1999 +@test Dates.yearwrap(2000,12,-14) == 1999 +@test Dates.yearwrap(2000,12,-13) == 1999 +@test Dates.yearwrap(2000,12,-12) == 1999 +@test Dates.yearwrap(2000,12,-11) == 2000 +@test Dates.yearwrap(2000,12,-2) == 2000 +@test Dates.yearwrap(2000,12,-1) == 2000 +@test Dates.yearwrap(2000,12,0) == 2000 +@test Dates.yearwrap(2000,12,1) == 2001 +@test Dates.yearwrap(2000,12,11) == 2001 +@test Dates.yearwrap(2000,12,12) == 2001 +@test Dates.yearwrap(2000,12,13) == 2002 +@test Dates.yearwrap(2000,12,24) == 2002 +@test Dates.yearwrap(2000,12,25) == 2003 +@test Dates.yearwrap(2000,12,36) == 2003 +@test Dates.yearwrap(2000,12,37) == 2004 +@test Dates.yearwrap(2000,12,3600) == 2300 + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Month(1) == Dates.DateTime(2000,1,27) +@test dt + Dates.Month(-1) == Dates.DateTime(1999,11,27) +@test dt + Dates.Month(-11) == Dates.DateTime(1999,1,27) +@test dt + Dates.Month(11) == Dates.DateTime(2000,11,27) +@test dt + Dates.Month(-12) == Dates.DateTime(1998,12,27) +@test dt + Dates.Month(12) == Dates.DateTime(2000,12,27) +@test dt + Dates.Month(13) == Dates.DateTime(2001,1,27) +@test dt + Dates.Month(100) == Dates.DateTime(2008,4,27) +@test dt + Dates.Month(1000) == Dates.DateTime(2083,4,27) +@test dt - Dates.Month(1) == Dates.DateTime(1999,11,27) +@test dt - Dates.Month(-1) == Dates.DateTime(2000,1,27) +@test dt - Dates.Month(100) == Dates.DateTime(1991,8,27) +@test dt - Dates.Month(1000) == Dates.DateTime(1916,8,27) +dt = Dates.DateTime(2000,2,29) +@test dt + Dates.Month(1) == Dates.DateTime(2000,3,29) +@test dt - Dates.Month(1) == Dates.DateTime(2000,1,29) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Month(1) == Dates.DateTime(1972,8,1) +@test dt - Dates.Month(1) == Dates.DateTime(1972,6,1) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Month(1) == Dates.DateTime(1972,7,30,23,59,59) +@test dt - Dates.Month(1) == Dates.DateTime(1972,5,30,23,59,59) +@test dt + Dates.Month(-1) == Dates.DateTime(1972,5,30,23,59,59) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Week(1) == Dates.DateTime(2000,1,3) +@test dt + Dates.Week(100) == Dates.DateTime(2001,11,26) +@test dt + Dates.Week(1000) == Dates.DateTime(2019,2,25) +@test dt - Dates.Week(1) == Dates.DateTime(1999,12,20) +@test dt - Dates.Week(100) == Dates.DateTime(1998,1,26) +@test dt - Dates.Week(1000) == Dates.DateTime(1980,10,27) +dt = Dates.DateTime(2000,2,29) +@test dt + Dates.Week(1) == Dates.DateTime(2000,3,7) +@test dt - Dates.Week(1) == Dates.DateTime(2000,2,22) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Week(1) == Dates.DateTime(1972,7,8) +@test dt - Dates.Week(1) == Dates.DateTime(1972,6,24) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Week(1) == Dates.DateTime(1972,7,7,23,59,59) +@test dt - Dates.Week(1) == Dates.DateTime(1972,6,23,23,59,59) +@test dt + Dates.Week(-1) == Dates.DateTime(1972,6,23,23,59,59) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Day(1) == Dates.DateTime(1999,12,28) +@test dt + Dates.Day(100) == Dates.DateTime(2000,4,5) +@test dt + Dates.Day(1000) == Dates.DateTime(2002,9,22) +@test dt - Dates.Day(1) == Dates.DateTime(1999,12,26) +@test dt - Dates.Day(100) == Dates.DateTime(1999,9,18) +@test dt - Dates.Day(1000) == Dates.DateTime(1997,4,1) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Day(1) == Dates.DateTime(1972,7,2) +@test dt - Dates.Day(1) == Dates.DateTime(1972,6,30) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Day(1) == Dates.DateTime(1972,7,1,23,59,59) +@test dt - Dates.Day(1) == Dates.DateTime(1972,6,29,23,59,59) +@test dt + Dates.Day(-1) == Dates.DateTime(1972,6,29,23,59,59) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Hour(1) == Dates.DateTime(1999,12,27,1) +@test dt + Dates.Hour(100) == Dates.DateTime(1999,12,31,4) +@test dt + Dates.Hour(1000) == Dates.DateTime(2000,2,6,16) +@test dt - Dates.Hour(1) == Dates.DateTime(1999,12,26,23) +@test dt - Dates.Hour(100) == Dates.DateTime(1999,12,22,20) +@test dt - Dates.Hour(1000) == Dates.DateTime(1999,11,15,8) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Hour(1) == Dates.DateTime(1972,7,1,1) +@test dt - Dates.Hour(1) == Dates.DateTime(1972,6,30,23) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Hour(1) == Dates.DateTime(1972,7,1,0,59,59) +@test dt - Dates.Hour(1) == Dates.DateTime(1972,6,30,22,59,59) +@test dt + Dates.Hour(-1) == Dates.DateTime(1972,6,30,22,59,59) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Minute(1) == Dates.DateTime(1999,12,27,0,1) +@test dt + Dates.Minute(100) == Dates.DateTime(1999,12,27,1,40) +@test dt + Dates.Minute(1000) == Dates.DateTime(1999,12,27,16,40) +@test dt - Dates.Minute(1) == Dates.DateTime(1999,12,26,23,59) +@test dt - Dates.Minute(100) == Dates.DateTime(1999,12,26,22,20) +@test dt - Dates.Minute(1000) == Dates.DateTime(1999,12,26,7,20) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Minute(1) == Dates.DateTime(1972,7,1,0,1) +@test dt - Dates.Minute(1) == Dates.DateTime(1972,6,30,23,59) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Minute(1) == Dates.DateTime(1972,7,1,0,0,59) +@test dt - Dates.Minute(1) == Dates.DateTime(1972,6,30,23,58,59) +@test dt + Dates.Minute(-1) == Dates.DateTime(1972,6,30,23,58,59) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Second(1) == Dates.DateTime(1999,12,27,0,0,1) +@test dt + Dates.Second(100) == Dates.DateTime(1999,12,27,0,1,40) +@test dt + Dates.Second(1000) == Dates.DateTime(1999,12,27,0,16,40) +@test dt - Dates.Second(1) == Dates.DateTime(1999,12,26,23,59,59) +@test dt - Dates.Second(100) == Dates.DateTime(1999,12,26,23,58,20) +@test dt - Dates.Second(1000) == Dates.DateTime(1999,12,26,23,43,20) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Second(1) == Dates.DateTime(1972,7,1,0,0,1) +@test dt - Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,59) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,60) +@test dt - Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,58) +@test dt + Dates.Second(-1) == Dates.DateTime(1972,6,30,23,59,58) + +dt = Dates.DateTime(1999,12,27) +@test dt + Dates.Millisecond(1) == Dates.DateTime(1999,12,27,0,0,0,1) +@test dt + Dates.Millisecond(100) == Dates.DateTime(1999,12,27,0,0,0,100) +@test dt + Dates.Millisecond(1000) == Dates.DateTime(1999,12,27,0,0,1) +@test dt - Dates.Millisecond(1) == Dates.DateTime(1999,12,26,23,59,59,999) +@test dt - Dates.Millisecond(100) == Dates.DateTime(1999,12,26,23,59,59,900) +@test dt - Dates.Millisecond(1000) == Dates.DateTime(1999,12,26,23,59,59) +dt = Dates.DateTime(1972,6,30,23,59,60) +@test dt + Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,60,1) +@test dt - Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,59,999) +dt = Dates.DateTime(1972,6,30,23,59,59) +@test dt + Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,59,1) +@test dt - Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,58,999) +@test dt + Dates.Millisecond(-1) == Dates.DateTime(1972,6,30,23,59,58,999) + +dt = Dates.Date(1999,12,27) +@test dt + Dates.Year(1) == Dates.Date(2000,12,27) +@test dt + Dates.Year(100) == Dates.Date(2099,12,27) +@test dt + Dates.Year(1000) == Dates.Date(2999,12,27) +@test dt - Dates.Year(1) == Dates.Date(1998,12,27) +@test dt - Dates.Year(100) == Dates.Date(1899,12,27) +@test dt - Dates.Year(1000) == Dates.Date(999,12,27) +dt = Dates.Date(2000,2,29) +@test dt + Dates.Year(1) == Dates.Date(2001,2,28) +@test dt - Dates.Year(1) == Dates.Date(1999,2,28) +@test dt + Dates.Year(4) == Dates.Date(2004,2,29) +@test dt - Dates.Year(4) == Dates.Date(1996,2,29) + +dt = Dates.Date(1999,12,27) +@test dt + Dates.Month(1) == Dates.Date(2000,1,27) +@test dt + Dates.Month(100) == Dates.Date(2008,4,27) +@test dt + Dates.Month(1000) == Dates.Date(2083,4,27) +@test dt - Dates.Month(1) == Dates.Date(1999,11,27) +@test dt - Dates.Month(100) == Dates.Date(1991,8,27) +@test dt - Dates.Month(1000) == Dates.Date(1916,8,27) +dt = Dates.Date(2000,2,29) +@test dt + Dates.Month(1) == Dates.Date(2000,3,29) +@test dt - Dates.Month(1) == Dates.Date(2000,1,29) + +dt = Dates.Date(1999,12,27) +@test dt + Dates.Week(1) == Dates.Date(2000,1,3) +@test dt + Dates.Week(100) == Dates.Date(2001,11,26) +@test dt + Dates.Week(1000) == Dates.Date(2019,2,25) +@test dt - Dates.Week(1) == Dates.Date(1999,12,20) +@test dt - Dates.Week(100) == Dates.Date(1998,1,26) +@test dt - Dates.Week(1000) == Dates.Date(1980,10,27) +dt = Dates.Date(2000,2,29) +@test dt + Dates.Week(1) == Dates.Date(2000,3,7) +@test dt - Dates.Week(1) == Dates.Date(2000,2,22) + +dt = Dates.Date(1999,12,27) +@test dt + Dates.Day(1) == Dates.Date(1999,12,28) +@test dt + Dates.Day(100) == Dates.Date(2000,4,5) +@test dt + Dates.Day(1000) == Dates.Date(2002,9,22) +@test dt - Dates.Day(1) == Dates.Date(1999,12,26) +@test dt - Dates.Day(100) == Dates.Date(1999,9,18) +@test dt - Dates.Day(1000) == Dates.Date(1997,4,1) + +# Vectorized arithmetic +a = Dates.Date(2014,1,1) +dr = [a,a,a,a,a,a,a,a,a,a] +b = a + Dates.Year(1) +@test dr .+ Dates.Year(1) == repmat([b],10) +b = a + Dates.Month(1) +@test dr .+ Dates.Month(1) == repmat([b],10) +b = a + Dates.Day(1) +@test dr .+ Dates.Day(1) == repmat([b],10) +b = a - Dates.Year(1) +@test dr .- Dates.Year(1) == repmat([b],10) +b = a - Dates.Month(1) +@test dr .- Dates.Month(1) == repmat([b],10) +b = a - Dates.Day(1) +@test dr .- Dates.Day(1) == repmat([b],10) + +# Vectorized arithmetic +b = a + Dates.Year(1) +@test dr .+ Dates.Year(1) == repmat([b],10) +b = a + Dates.Month(1) +@test dr .+ Dates.Month(1) == repmat([b],10) +b = a + Dates.Day(1) +@test dr .+ Dates.Day(1) == repmat([b],10) +b = a - Dates.Year(1) +@test dr .- Dates.Year(1) == repmat([b],10) +b = a - Dates.Month(1) +@test dr .- Dates.Month(1) == repmat([b],10) +b = a - Dates.Day(1) +@test dr .- Dates.Day(1) == repmat([b],10) + +# Month arithmetic minimizes "edit distance", or number of changes +# needed to get a correct answer +# This approach results in a few cases of non-associativity +a = Dates.Date(2012,1,29) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,1,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,2,29) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,3,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,4,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,5,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,6,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,8,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,9,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,10,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) +a = Dates.Date(2012,11,30) +@test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) diff --git a/test/dates/conversions.jl b/test/dates/conversions.jl new file mode 100644 index 0000000000000..a7a3c011f8a50 --- /dev/null +++ b/test/dates/conversions.jl @@ -0,0 +1,42 @@ +# Test conversion to and from unix +@test Dates.unix2datetime(Dates.datetime2unix(DateTime(2000,1,1))) == DateTime(2000,1,1) +@test Dates.value(Dates.DateTime(1970)) == Dates.UNIXEPOCH + +# Tests from here: http://en.wikipedia.org/wiki/Unix_time +@test string(Dates.unix2datetime(1095379198.75)) == string("2004-09-16T23:59:58.75") +@test string(Dates.unix2datetime(1095379199.00)) == string("2004-09-16T23:59:59") +@test string(Dates.unix2datetime(1095379199.25)) == string("2004-09-16T23:59:59.25") +@test string(Dates.unix2datetime(1095379199.50)) == string("2004-09-16T23:59:59.5") +@test string(Dates.unix2datetime(1095379199.75)) == string("2004-09-16T23:59:59.75") +@test string(Dates.unix2datetime(1095379200.00)) == string("2004-09-17T00:00:00") +@test string(Dates.unix2datetime(1095379200.25)) == string("2004-09-17T00:00:00.25") +@test string(Dates.unix2datetime(1095379200.50)) == string("2004-09-17T00:00:00.5") +@test string(Dates.unix2datetime(1095379200.75)) == string("2004-09-17T00:00:00.75") +@test string(Dates.unix2datetime(1095379201.00)) == string("2004-09-17T00:00:01") +@test string(Dates.unix2datetime(1095379201.25)) == string("2004-09-17T00:00:01.25") +@test string(Dates.unix2datetime(915148798.75)) == string("1998-12-31T23:59:58.75") +@test string(Dates.unix2datetime(915148799.00)) == string("1998-12-31T23:59:59") +@test string(Dates.unix2datetime(915148799.25)) == string("1998-12-31T23:59:59.25") +@test string(Dates.unix2datetime(915148799.50)) == string("1998-12-31T23:59:59.5") +@test string(Dates.unix2datetime(915148799.75)) == string("1998-12-31T23:59:59.75") +@test string(Dates.unix2datetime(915148800.00)) == string("1999-01-01T00:00:00") +@test string(Dates.unix2datetime(915148800.25)) == string("1999-01-01T00:00:00.25") +@test string(Dates.unix2datetime(915148800.50)) == string("1999-01-01T00:00:00.5") +@test string(Dates.unix2datetime(915148800.75)) == string("1999-01-01T00:00:00.75") +@test string(Dates.unix2datetime(915148801.00)) == string("1999-01-01T00:00:01") +@test string(Dates.unix2datetime(915148801.25)) == string("1999-01-01T00:00:01.25") + +@test Date(Dates.rata2datetime(734869)) == Date(2013,1,1) +@test Dates.datetime2rata(Dates.rata2datetime(734869)) == 734869 + +# Tests from here: http://mysite.verizon.net/aesir_research/date/back.htm#JDN +@test Dates.julian2datetime(1721119.5) == DateTime(0,3,1) +@test Dates.julian2datetime(1721424.5) == DateTime(0,12,31) +@test Dates.julian2datetime(1721425.5) == DateTime(1,1,1) +@test Dates.julian2datetime(2299149.5) == DateTime(1582,10,4) +@test Dates.julian2datetime(2415020.5) == DateTime(1900,1,1) +@test Dates.julian2datetime(2415385.5) == DateTime(1901,1,1) +@test Dates.julian2datetime(2440587.5) == DateTime(1970,1,1) +@test Dates.julian2datetime(2444239.5) == DateTime(1980,1,1) +@test Dates.julian2datetime(2452695.625) == DateTime(2003,2,25,3) +@test Dates.datetime2julian(DateTime(2013,12,3,21)) == 2456630.375 diff --git a/test/dates/io.jl b/test/dates/io.jl new file mode 100644 index 0000000000000..440becdb97c93 --- /dev/null +++ b/test/dates/io.jl @@ -0,0 +1,254 @@ +# Test string/show representation of Date +@test string(Dates.Date(1,1,1)) == "0001-01-01" # January 1st, 1 AD/CE +@test string(Dates.Date(0,12,31)) == "0000-12-31" # December 31, 1 BC/BCE +@test Dates.Date(1,1,1) - Dates.Date(0,12,31) == Dates.Day(1) +@test Dates.Date(Dates.UTD(-306)) == Dates.Date(0,2,29) +@test string(Dates.Date(0,1,1)) == "0000-01-01" # January 1st, 1 BC/BCE +@test string(Dates.Date(-1,1,1)) == "-0001-01-01" # January 1st, 2 BC/BCE +@test string(Dates.Date(-1000000,1,1)) == "-1000000-01-01" +@test string(Dates.Date(1000000,1,1)) == "1000000-01-01" +@test string(Dates.DateTime(2000,1,1,0,0,0,1)) == "2000-01-01T00:00:00.001" +@test string(Dates.DateTime(2000,1,1,0,0,0,2)) == "2000-01-01T00:00:00.002" +@test string(Dates.DateTime(2000,1,1,0,0,0,500)) == "2000-01-01T00:00:00.5" +@test string(Dates.DateTime(2000,1,1,0,0,0,998)) == "2000-01-01T00:00:00.998" +@test string(Dates.DateTime(2000,1,1,0,0,0,999)) == "2000-01-01T00:00:00.999" + +# DateTime parsing +# Useful reference for different locales: http://library.princeton.edu/departments/tsd/katmandu/reference/months.html + +# Common Parsing Patterns +#'1996-January-15' +dt = Dates.DateTime(1996,1,15) +f = "yy-mm-dd" +a = "96-01-15" +@test DateTime(a,f) + Dates.Year(1900) == dt +@test Dates.format(dt,f) == a +a1 = "96-1-15" +@test Dates.DateTime(a1,f) + Dates.Year(1900) == dt +@test Dates.format(dt,"yy-m-dd") == a1 +a2 = "96-1-1" +@test Dates.DateTime(a2,f) + Dates.Year(1900) + Dates.Day(14) == dt +@test Dates.format(dt-Dates.Day(14),"yy-m-d") == a2 +a3 = "1996-1-15" +@test Dates.DateTime(a3,f) == dt +@test Dates.format(dt,"yyyy-m-d") == a3 +a4 = "1996-Jan-15" +@test_throws ArgumentError Dates.DateTime(a4,f) # Trying to use month name, but specified only "mm" + +f = "yy/uuu/dd" +b = "96/Feb/15" +@test Dates.DateTime(b,f) + Dates.Year(1900) == dt + Dates.Month(1) +@test Dates.format(dt+Dates.Month(1),f) == b +b1 = "1996/Feb/15" +@test Dates.DateTime(b1,f) == dt + Dates.Month(1) +@test Dates.format(dt+Dates.Month(1),"yyyy/uuu/dd") == b1 +b2 = "96/Feb/1" +@test Dates.DateTime(b2,f) + Dates.Year(1900) + Dates.Day(14) == dt + Dates.Month(1) +@test Dates.format(dt+Dates.Month(1)-Dates.Day(14),"yy/uuu/d") == b2 +# Here we've specifed a text month name, but given a number +b3 = "96/2/15" +@test_throws KeyError Dates.DateTime(b3,f) + +f = "yy:dd:mm" +c = "96:15:01" +@test Dates.DateTime(c,f) + Dates.Year(1900) == dt +@test Dates.format(dt,f) == c +c1 = "1996:15:01" +@test Dates.DateTime(c1,f) == dt +@test Dates.format(dt,"yyyy:dd:mm") == c1 +c2 = "96:15:1" +@test Dates.DateTime(c2,f) + Dates.Year(1900) == dt +@test Dates.format(dt,"yy:dd:m") == c2 +c3 = "96:1:01" +@test Dates.DateTime(c3,f) + Dates.Year(1900) + Dates.Day(14) == dt +@test Dates.format(dt-Dates.Day(14),"yy:m:dd") == c3 +c4 = "1996:15:01 # random comment" +@test Dates.DateTime(c4,f) == dt + +f = "yyyy,uuu,dd" +d = "1996,Jan,15" +@test Dates.DateTime(d,f) == dt +@test Dates.format(dt,f) == d +d1 = "96,Jan,15" +@test Dates.DateTime(d1,f) + Dates.Year(1900) == dt +@test Dates.format(dt,"yy,uuu,dd") == d1 +d2 = "1996,Jan,1" +@test Dates.DateTime(d2,f) + Dates.Day(14) == dt +@test Dates.format(dt-Dates.Day(14),"yyyy,uuu,d") == d2 +d3 = "1996,2,15" +@test_throws KeyError Dates.DateTime(d3,f) + +f = "yyyy.U.dd" +e = "1996.January.15" +@test Dates.DateTime(e,f) == dt +@test Dates.format(dt,f) == e +e1 = "96.January.15" +@test Dates.DateTime(e1,f) + Dates.Year(1900) == dt +@test Dates.format(dt,"yy.U.dd") == e1 + +fo = "yyyy m dd" +f = "1996 1 15" +@test Dates.DateTime(f,fo) == dt +@test Dates.format(dt,fo) == f +f1 = "1996 01 15" +@test Dates.DateTime(f1,fo) == dt +@test Dates.format(dt,"yyyy mm dd") == f1 +f2 = "1996 1 1" +@test Dates.DateTime(f2,fo) + Dates.Day(14) == dt +@test Dates.format(dt-Dates.Day(14),"yyyy m d") == f2 + +j = "1996-01-15" +f = "yyyy-mm-dd zzz" +@test Dates.DateTime(j,f) == dt +@test Dates.format(dt,f) == j*" zzz" +k = "1996-01-15 10:00:00" +f = "yyyy-mm-dd HH:MM:SS zzz" +@test Dates.DateTime(k,f) == dt + Dates.Hour(10) +@test Dates.format(dt+Dates.Hour(10),f) == k*" zzz" +l = "1996-01-15 10:10:10.25" +f = "yyyy-mm-dd HH:MM:SS.ss zzz" +@test Dates.DateTime(l,f) == dt + Dates.Hour(10) + Dates.Minute(10) + Dates.Second(10) + Dates.Millisecond(250) +@test Dates.format(dt+Dates.Hour(10)+Dates.Minute(10)+Dates.Second(10)+Dates.Millisecond(250),f) == l*" zzz" + +r = "1/15/1996" # Excel +f = "m/dd/yyyy" +@test Dates.DateTime(r,f) == dt +@test Dates.format(dt,f) == r +s = "19960115" +f = "yyyymmdd" +@test Dates.DateTime(s,f) == dt +@test Dates.format(dt,f) == s +v = "1996-01-15 10:00:00" +f = "yyyy-mm-dd HH:MM:SS" +@test Dates.DateTime(v,f) == dt + Dates.Hour(10) +@test Dates.format(dt+Dates.Hour(10),f) == v +w = "1996-01-15T10:00:00" +f = "yyyy-mm-ddTHH:MM:SS zzz" +@test Dates.DateTime(w,f) == dt + Dates.Hour(10) +@test Dates.format(dt+Dates.Hour(10),f) == w*" zzz" + +f = "yyyy/m" +y = "1996/1" +@test Dates.DateTime(y,f) == dt - Dates.Day(14) +@test Dates.format(dt,f) == y +y1 = "1996/1/15" +@test_throws ArgumentError Dates.DateTime(y1,f) +y2 = "96/1" +@test Dates.DateTime(y2,f) + Dates.Year(1900) == dt - Dates.Day(14) +@test Dates.format(dt,"yy/m") == y2 + +f = "yyyy" +z = "1996" +@test Dates.DateTime(z,f) == dt - Dates.Day(14) +@test Dates.format(dt,f) == z +z1 = "1996-3" +@test_throws ArgumentError Dates.DateTime(z1,f) +z2 = "1996-3-1" +@test_throws ArgumentError Dates.DateTime(z2,f) + +aa = "1/5/1996" +f = "m/d/yyyy" +@test Dates.DateTime(aa,f) == dt - Dates.Day(10) +@test Dates.format(dt-Dates.Day(10),f) == aa +bb = "5/1/1996" +f = "d/m/yyyy" +@test Dates.DateTime(bb,f) == dt - Dates.Day(10) +@test Dates.format(dt-Dates.Day(10),f) == bb +cc = "01151996" +f = "mmddyyyy" +@test Dates.DateTime(cc,f) == dt +@test Dates.format(dt,f) == cc +dd = "15011996" +f = "ddmmyyyy" +@test Dates.DateTime(dd,f) == dt +@test Dates.format(dt,f) == dd +ee = "01199615" +f = "mmyyyydd" +@test Dates.DateTime(ee,f) == dt +@test Dates.format(dt,f) == ee +ff = "1996-15-Jan" +f = "yyyy-dd-uuu" +@test Dates.DateTime(ff,f) == dt +@test Dates.format(dt,f) == ff +gg = "Jan-1996-15" +f = "uuu-yyyy-dd" +@test Dates.DateTime(gg,f) == dt +@test Dates.format(dt,f) == gg + +# from Jiahao +@test Dates.Date("2009年12月01日","yyyy年mm月dd日") == Dates.Date(2009,12,1) +@test Dates.format(Dates.Date(2009,12,1),"yyyy年mm月dd日") == "2009年12月01日" +@test Dates.Date("2009-12-01","yyyy-mm-dd") == Dates.Date(2009,12,1) + +# French: from Milan +f = "dd/mm/yyyy" +f2 = "dd/mm/yy" +@test Dates.Date("28/05/2014",f) == Dates.Date(2014,5,28) +@test Dates.Date("28/05/14",f2) + Dates.Year(2000) == Dates.Date(2014,5,28) + +const french = ["janv"=>1,"févr"=>2,"mars"=>3,"avril"=>4,"mai"=>5,"juin"=>6,"juil"=>7,"août"=>8,"sept"=>9,"oct"=>10,"nov"=>11,"déc"=>12] +Dates.MONTHTOVALUEABBR["french"] = french +Dates.VALUETOMONTHABBR["french"] = [v=>k for (k,v) in french] + +f = "dd uuuuu yyyy" +@test Dates.Date("28 mai 2014",f;locale="french") == Dates.Date(2014,5,28) +@test Dates.format(Dates.Date(2014,5,28),f;locale="french") == "28 mai 2014" +@test Dates.Date("28 févr 2014",f;locale="french") == Dates.Date(2014,2,28) +@test Dates.format(Dates.Date(2014,2,28),f;locale="french") == "28 févr 2014" +@test Dates.Date("28 août 2014",f;locale="french") == Dates.Date(2014,8,28) +@test Dates.format(Dates.Date(2014,8,28),f;locale="french") == "28 août 2014" +@test Dates.Date("28 avril 2014",f;locale="french") == Dates.Date(2014,4,28) +@test Dates.format(Dates.Date(2014,4,28),f;locale="french") == "28 avril 2014" + +f = "dd u yyyy" +@test Dates.Date("28 avril 2014",f;locale="french") == Dates.Date(2014,4,28) +f = "dduuuuuyyyy" +@test Dates.Date("28avril2014",f;locale="french") == Dates.Date(2014,4,28) +@test_throws KeyError Dates.Date("28mai2014",f;locale="french") + +# From Tony Fong +f = "dduuuyy" +@test Dates.Date("01Dec09",f) + Dates.Year(2000) == Dates.Date(2009,12,1) +@test Dates.format(Dates.Date(2009,12,1),f) == "01Dec09" +f = "dduuuyyyy" +@test Dates.Date("01Dec2009",f) == Dates.Date(2009,12,1) +@test Dates.format(Dates.Date(2009,12,1),f) == "01Dec2009" +f = "duy" +const globex = ["f"=>Dates.Jan,"g"=>Dates.Feb,"h"=>Dates.Mar,"j"=>Dates.Apr,"k"=>Dates.May,"m"=>Dates.Jun, + "n"=>Dates.Jul,"q"=>Dates.Aug,"u"=>Dates.Sep,"v"=>Dates.Oct,"x"=>Dates.Nov,"z"=>Dates.Dec] +Dates.MONTHTOVALUEABBR["globex"] = globex +Dates.VALUETOMONTHABBR["globex"] = [v=>uppercase(k) for (k,v) in globex] +@test Dates.Date("1F4",f;locale="globex") + Dates.Year(2010) == Dates.Date(2014,1,1) +@test Dates.format(Dates.Date(2014,1,1),f;locale="globex") == "1F4" + +# From Matt Bauman +f = "yyyy-mm-ddTHH:MM:SS" +@test Dates.DateTime("2014-05-28T16:46:04",f) == Dates.DateTime(2014,5,28,16,46,04) + +# Try to break stuff + +# Specified mm/dd, but date string has day/mm +@test_throws ArgumentError Dates.DateTime("18/05/2009","mm/dd/yyyy") +@test_throws ArgumentError Dates.DateTime("18/05/2009 16","mm/dd/yyyy hh") +# Used "mm" for months AND minutes +@test_throws ArgumentError Dates.DateTime("18/05/2009 16:12","mm/dd/yyyy hh:mm") +# Date string has different delimiters than format string +@test_throws ArgumentError Dates.DateTime("18:05:2009","mm/dd/yyyy") + +f = "y m d" +@test Dates.Date("1 1 1",f) == Dates.Date(1) +@test Dates.Date("10000000000 1 1",f) == Dates.Date(10000000000) +@test_throws ArgumentError Dates.Date("1 13 1",f) +@test Dates.Date("1 1 32",f) == Dates.Date(1,2,1) +@test Dates.Date(" 1 1 32",f) == Dates.Date(1,2,1) +@test_throws ArgumentError Dates.Date("# 1 1 32",f) +# can't find 1st space delimiter,s o fails +@test_throws ArgumentError Dates.Date("1",f) +@test Dates.Date("1 2",f) == Dates.Date(1,2) +# can't find space delimiter (finds '/'), so fails +@test_throws ArgumentError Dates.Date("2000/1",f) + +@test Dates.DateTime("20140529 120000","yyyymmdd HHMMSS") == Dates.DateTime(2014,5,29,12) + +@test Dates.Date(string(Dates.Date(dt))) == Dates.Date(dt) +@test Dates.DateTime(string(dt)) == dt diff --git a/test/dates/periods.jl b/test/dates/periods.jl new file mode 100644 index 0000000000000..4fa788268be4b --- /dev/null +++ b/test/dates/periods.jl @@ -0,0 +1,299 @@ +# Period testing +@test -Dates.Year(1) == Dates.Year(-1) +@test Dates.Year(1) > Dates.Year(0) +@test (Dates.Year(1) < Dates.Year(0)) == false +@test Dates.Year(1) == Dates.Year(1) +@test Dates.Year(1) + Dates.Year(1) == Dates.Year(2) +@test Dates.Year(1) - Dates.Year(1) == Dates.Year(0) +@test Dates.Year(1) * Dates.Year(1) == Dates.Year(1) +@test Dates.Year(10) % Dates.Year(4) == Dates.Year(2) +@test div(Dates.Year(10),Dates.Year(3)) == Dates.Year(3) +@test div(Dates.Year(10),Dates.Year(4)) == Dates.Year(2) +t = Dates.Year(1) +t2 = Dates.Year(2) +@test ([t,t,t,t,t] .+ Dates.Year(1)) == ([t2,t2,t2,t2,t2]) +@test (Dates.Year(1) .+ [t,t,t,t,t]) == ([t2,t2,t2,t2,t2]) +@test ([t2,t2,t2,t2,t2] .- Dates.Year(1)) == ([t,t,t,t,t]) +@test ([t,t,t,t,t] .* Dates.Year(1)) == ([t,t,t,t,t]) +@test ([t,t,t,t,t] .% t2) == ([t,t,t,t,t]) +@test div([t,t,t,t,t],Dates.Year(1)) == ([t,t,t,t,t]) + +#Period arithmetic +y = Dates.Year(1) +m = Dates.Month(1) +w = Dates.Week(1) +d = Dates.Day(1) +h = Dates.Hour(1) +mi = Dates.Minute(1) +s = Dates.Second(1) +ms = Dates.Millisecond(1) +@test Dates.Year(y) == y +@test Dates.Month(m) == m +@test Dates.Week(w) == w +@test Dates.Day(d) == d +@test Dates.Hour(h) == h +@test Dates.Minute(mi) == mi +@test Dates.Second(s) == s +@test Dates.Millisecond(ms) == ms +@test typeof(int8(y)) <: Int8 +@test typeof(uint8(y)) <: Uint8 +@test typeof(int16(y)) <: Int16 +@test typeof(uint16(y)) <: Uint16 +@test typeof(int32(y)) <: Int32 +@test typeof(uint32(y)) <: Uint32 +@test typeof(int64(y)) <: Int64 +@test typeof(uint64(y)) <: Uint64 +@test typeof(int128(y)) <: Int128 +@test typeof(uint128(y)) <: Uint128 +@test typeof(convert(BigInt,y)) <: BigInt +@test typeof(convert(BigFloat,y)) <: BigFloat +@test typeof(convert(Complex,y)) <: Complex +@test typeof(convert(Rational,y)) <: Rational +@test typeof(float16(y)) <: Float16 +@test typeof(float32(y)) <: Float32 +@test typeof(float64(y)) <: Float64 +@test convert(Dates.Year,convert(Int8,1)) == y +@test convert(Dates.Year,convert(Uint8,1)) == y +@test convert(Dates.Year,convert(Int16,1)) == y +@test convert(Dates.Year,convert(Uint16,1)) == y +@test convert(Dates.Year,convert(Int32,1)) == y +@test convert(Dates.Year,convert(Uint32,1)) == y +@test convert(Dates.Year,convert(Int64,1)) == y +@test convert(Dates.Year,convert(Uint64,1)) == y +@test convert(Dates.Year,convert(Int128,1)) == y +@test convert(Dates.Year,convert(Uint128,1)) == y +@test convert(Dates.Year,convert(BigInt,1)) == y +@test convert(Dates.Year,convert(BigFloat,1)) == y +@test convert(Dates.Year,convert(Complex,1)) == y +@test convert(Dates.Year,convert(Rational,1)) == y +#@test convert(Dates.Year,convert(Float16,1)) == y +@test convert(Dates.Year,convert(Float32,1)) == y +@test convert(Dates.Year,convert(Float64,1)) == y +@test y == y +@test m == m +@test w == w +@test d == d +@test h == h +@test mi == mi +@test s == s +@test ms == ms +@test_throws ArgumentError y != m +@test_throws ArgumentError m != w +@test_throws ArgumentError w != d +@test_throws ArgumentError d != h +@test_throws ArgumentError h != mi +@test_throws ArgumentError mi != s +@test_throws ArgumentError s != ms +@test_throws ArgumentError ms != y +y2 = Dates.Year(2) +@test y < y2 +@test y2 > y +@test y != y2 + +@test Dates.Year(int8(1)) == y +@test Dates.Year(uint8(1)) == y +@test Dates.Year(int16(1)) == y +@test Dates.Year(uint16(1)) == y +@test Dates.Year(int(1)) == y +@test Dates.Year(uint(1)) == y +@test Dates.Year(int64(1)) == y +@test Dates.Year(uint64(1)) == y +@test Dates.Year(int128(1)) == y +@test Dates.Year(uint128(1)) == y +@test Dates.Year(big(1)) == y +@test Dates.Year(BigFloat(1)) == y +@test Dates.Year(float(1)) == y +@test Dates.Year(float32(1)) == y +@test Dates.Year(Rational(1)) == y +@test Dates.Year(complex(1)) == y +@test_throws InexactError Dates.Year(BigFloat(1.2)) == y +@test_throws InexactError Dates.Year(1.2) == y +@test_throws InexactError Dates.Year(float32(1.2)) == y +@test_throws InexactError Dates.Year(3//4) == y +@test_throws InexactError Dates.Year(complex(1.2)) == y +@test_throws InexactError Dates.Year(float16(1.2)) == y +@test Dates.Year(true) == y +@test Dates.Year(false) != y +@test Dates.Year('\x01') == y +@test_throws MethodError Dates.Year(:hey) == y +@test Dates.Year(real(1)) == y +@test_throws ArgumentError Dates.Year(m) == y +@test_throws ArgumentError Dates.Year(w) == y +@test_throws ArgumentError Dates.Year(d) == y +@test_throws ArgumentError Dates.Year(h) == y +@test_throws ArgumentError Dates.Year(mi) == y +@test_throws ArgumentError Dates.Year(s) == y +@test_throws ArgumentError Dates.Year(ms) == y +@test Dates.Year(Date(2013,1,1)) == Dates.Year(2013) +@test Dates.Year(DateTime(2013,1,1)) == Dates.Year(2013) +@test typeof(y+m) <: Dates.CompoundPeriod +@test typeof(m+y) <: Dates.CompoundPeriod +@test typeof(y+w) <: Dates.CompoundPeriod +@test typeof(y+d) <: Dates.CompoundPeriod +@test typeof(y+h) <: Dates.CompoundPeriod +@test typeof(y+mi) <: Dates.CompoundPeriod +@test typeof(y+s) <: Dates.CompoundPeriod +@test typeof(y+ms) <: Dates.CompoundPeriod +@test_throws ArgumentError y > m +@test_throws ArgumentError d < w +@test typemax(Dates.Year) == Dates.Year(typemax(Int64)) +@test typemax(Dates.Year) + y == Dates.Year(-9223372036854775808) +@test typemin(Dates.Year) == Dates.Year(-9223372036854775808) +#Period-Real arithmetic +@test y + 1 == Dates.Year(2) +@test 1 + y == Dates.Year(2) +@test y + true == Dates.Year(2) +@test true + y == Dates.Year(2) +@test y + '\x01' == Dates.Year(2) +@test '\x01' + y == Dates.Year(2) +@test y + 1.0 == Dates.Year(2) +@test_throws InexactError y + 1.2 +@test y + 1f0 == Dates.Year(2) +@test_throws InexactError y + 1.2f0 +@test y + BigFloat(1) == Dates.Year(2) +@test_throws InexactError y + BigFloat(1.2) +@test y + 1.0 == Dates.Year(2) +@test_throws InexactError y + 1.2 +@test y * 4 == Dates.Year(4) +@test y * 4f0 == Dates.Year(4) +@test_throws InexactError y * 3//4 == Dates.Year(1) +@test div(y,2) == Dates.Year(0) +@test div(2,y) == Dates.Year(2) +@test div(y,y) == Dates.Year(1) +@test y*10 % 5 == Dates.Year(0) +@test 5 % y*10 == Dates.Year(0) +@test_throws ArgumentError y > 3 +@test_throws ArgumentError 4 < y +@test_throws ArgumentError 1 == y +t = [y,y,y,y,y] +@test t .+ Dates.Year(2) == [Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3)] +dt = Dates.DateTime(2012,12,21) +# Associativity +test = ((((((((dt + y) - m) + w) - d) + h) - mi) + s) - ms) +@test test == dt + y - m + w - d + h - mi + s - ms +@test test == y - m + w - d + dt + h - mi + s - ms +@test test == dt - m + y - d + w - mi + h - ms + s +@test test == dt + (y - m + w - d + h - mi + s - ms) +@test test == dt + y - m + w - d + (h - mi + s - ms) +@test (dt + Dates.Year(4)) + Dates.Day(1) == dt + (Dates.Year(4) + Dates.Day(1)) +@test Dates.Date(2014,1,29) + Dates.Month(1) + Dates.Day(1) + Dates.Month(1) + Dates.Day(1) == + Dates.Date(2014,1,29) + Dates.Day(1) + Dates.Month(1) + Dates.Month(1) + Dates.Day(1) +@test Dates.Date(2014,1,29) + Dates.Month(1) + Dates.Day(1) == Dates.Date(2014,1,29) + Dates.Day(1) + Dates.Month(1) +# traits +@test Dates._units(Dates.Year(0)) == " years" +@test Dates._units(Dates.Year(1)) == " year" +@test Dates._units(Dates.Year(-1)) == " year" +@test Dates._units(Dates.Year(2)) == " years" +@test Dates.string(Dates.Year(0)) == "0 years" +@test Dates.string(Dates.Year(1)) == "1 year" +@test Dates.string(Dates.Year(-1)) == "-1 year" +@test Dates.string(Dates.Year(2)) == "2 years" +@test zero(Dates.Year) == Dates.Year(0) +@test zero(Dates.Year(10)) == Dates.Year(0) +@test zero(Dates.Month) == Dates.Month(0) +@test zero(Dates.Month(10)) == Dates.Month(0) +@test zero(Dates.Day) == Dates.Day(0) +@test zero(Dates.Day(10)) == Dates.Day(0) +@test zero(Dates.Hour) == Dates.Hour(0) +@test zero(Dates.Hour(10)) == Dates.Hour(0) +@test zero(Dates.Minute) == Dates.Minute(0) +@test zero(Dates.Minute(10)) == Dates.Minute(0) +@test zero(Dates.Second) == Dates.Second(0) +@test zero(Dates.Second(10)) == Dates.Second(0) +@test zero(Dates.Millisecond) == Dates.Millisecond(0) +@test zero(Dates.Millisecond(10)) == Dates.Millisecond(0) +@test one(Dates.Year) == Dates.Year(1) +@test one(Dates.Year(10)) == Dates.Year(1) +@test one(Dates.Month) == Dates.Month(1) +@test one(Dates.Month(10)) == Dates.Month(1) +@test one(Dates.Day) == Dates.Day(1) +@test one(Dates.Day(10)) == Dates.Day(1) +@test one(Dates.Hour) == Dates.Hour(1) +@test one(Dates.Hour(10)) == Dates.Hour(1) +@test one(Dates.Minute) == Dates.Minute(1) +@test one(Dates.Minute(10)) == Dates.Minute(1) +@test one(Dates.Second) == Dates.Second(1) +@test one(Dates.Second(10)) == Dates.Second(1) +@test one(Dates.Millisecond) == Dates.Millisecond(1) +@test one(Dates.Millisecond(10)) == Dates.Millisecond(1) +@test Dates.Year(-1) < Dates.Year(1) +@test !(Dates.Year(-1) > Dates.Year(1)) +@test Dates.Year(1) == Dates.Year(1) +@test_throws ArgumentError Dates.Year(1) == 1 +@test_throws ArgumentError 1 == Dates.Year(1) +@test_throws ArgumentError Dates.Year(1) < 1 +@test_throws ArgumentError 1 < Dates.Year(1) +@test Dates.Month(-1) < Dates.Month(1) +@test !(Dates.Month(-1) > Dates.Month(1)) +@test Dates.Month(1) == Dates.Month(1) +@test_throws ArgumentError Dates.Month(1) == 1 +@test_throws ArgumentError 1 == Dates.Month(1) +@test_throws ArgumentError Dates.Month(1) < 1 +@test_throws ArgumentError 1 < Dates.Month(1) +@test Dates.Day(-1) < Dates.Day(1) +@test !(Dates.Day(-1) > Dates.Day(1)) +@test Dates.Day(1) == Dates.Day(1) +@test_throws ArgumentError Dates.Day(1) == 1 +@test_throws ArgumentError 1 == Dates.Day(1) +@test_throws ArgumentError Dates.Day(1) < 1 +@test_throws ArgumentError 1 < Dates.Day(1) +@test Dates.Hour(-1) < Dates.Hour(1) +@test !(Dates.Hour(-1) > Dates.Hour(1)) +@test Dates.Hour(1) == Dates.Hour(1) +@test_throws ArgumentError Dates.Hour(1) == 1 +@test_throws ArgumentError 1 == Dates.Hour(1) +@test_throws ArgumentError Dates.Hour(1) < 1 +@test_throws ArgumentError 1 < Dates.Hour(1) +@test Dates.Minute(-1) < Dates.Minute(1) +@test !(Dates.Minute(-1) > Dates.Minute(1)) +@test Dates.Minute(1) == Dates.Minute(1) +@test_throws ArgumentError Dates.Minute(1) == 1 +@test_throws ArgumentError 1 == Dates.Minute(1) +@test_throws ArgumentError Dates.Minute(1) < 1 +@test_throws ArgumentError 1 < Dates.Minute(1) +@test Dates.Second(-1) < Dates.Second(1) +@test !(Dates.Second(-1) > Dates.Second(1)) +@test Dates.Second(1) == Dates.Second(1) +@test_throws ArgumentError Dates.Second(1) == 1 +@test_throws ArgumentError 1 == Dates.Second(1) +@test_throws ArgumentError Dates.Second(1) < 1 +@test_throws ArgumentError 1 < Dates.Second(1) +@test Dates.Millisecond(-1) < Dates.Millisecond(1) +@test !(Dates.Millisecond(-1) > Dates.Millisecond(1)) +@test Dates.Millisecond(1) == Dates.Millisecond(1) +@test_throws ArgumentError Dates.Millisecond(1) == 1 +@test_throws ArgumentError 1 == Dates.Millisecond(1) +@test_throws ArgumentError Dates.Millisecond(1) < 1 +@test_throws ArgumentError 1 < Dates.Millisecond(1) +@test_throws ArgumentError Dates.Year(1) < Dates.Millisecond(1) +@test_throws ArgumentError Dates.Year(1) == Dates.Millisecond(1) + +@test Dates.Year("1") == y +@test Dates.Month("1") == m +@test Dates.Week("1") == w +@test Dates.Day("1") == d +@test Dates.Hour("1") == h +@test Dates.Minute("1") == mi +@test Dates.Second("1") == s +@test Dates.Millisecond("1") == ms +@test_throws ErrorException Dates.Year("1.0") + +dt = Dates.DateTime(2014) +@test typeof(Dates.Year(dt)) <: Dates.Year +@test typeof(Dates.Month(dt)) <: Dates.Month +@test typeof(Dates.Week(dt)) <: Dates.Week +@test typeof(Dates.Day(dt)) <: Dates.Day +@test typeof(Dates.Hour(dt)) <: Dates.Hour +@test typeof(Dates.Minute(dt)) <: Dates.Minute +@test typeof(Dates.Second(dt)) <: Dates.Second +@test typeof(Dates.Millisecond(dt)) <: Dates.Millisecond + +# Default values +@test Dates.default(Dates.Year) == y +@test Dates.default(Dates.Month) == m +@test Dates.default(Dates.Week) == w +@test Dates.default(Dates.Day) == d +@test Dates.default(Dates.Hour) == zero(Dates.Hour) +@test Dates.default(Dates.Minute) == zero(Dates.Minute) +@test Dates.default(Dates.Second) == zero(Dates.Second) +@test Dates.default(Dates.Millisecond) == zero(Dates.Millisecond) \ No newline at end of file diff --git a/test/dates/query.jl b/test/dates/query.jl new file mode 100644 index 0000000000000..0d1160adcec33 --- /dev/null +++ b/test/dates/query.jl @@ -0,0 +1,215 @@ +# Name functions +jan = Dates.DateTime(2013,1,1) #Tuesday +feb = Dates.DateTime(2013,2,2) #Saturday +mar = Dates.DateTime(2013,3,3) #Sunday +apr = Dates.DateTime(2013,4,4) #Thursday +may = Dates.DateTime(2013,5,5) #Sunday +jun = Dates.DateTime(2013,6,7) #Friday +jul = Dates.DateTime(2013,7,7) #Sunday +aug = Dates.DateTime(2013,8,8) #Thursday +sep = Dates.DateTime(2013,9,9) #Monday +oct = Dates.DateTime(2013,10,10) #Thursday +nov = Dates.DateTime(2013,11,11) #Monday +dec = Dates.DateTime(2013,12,11) #Wednesday +monthnames = ["January","February","March","April", + "May","June","July","August","September", + "October","November","December"] +daysofweek = [Dates.Tue,Dates.Sat,Dates.Sun,Dates.Thu,Dates.Sun,Dates.Fri, + Dates.Sun,Dates.Thu,Dates.Mon,Dates.Thu,Dates.Mon,Dates.Wed] +dows = ["Tuesday","Saturday","Sunday","Thursday","Sunday","Friday", + "Sunday","Thursday","Monday","Thursday","Monday","Wednesday"] +for (i,dt) in enumerate([jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec]) + @test Dates.month(dt) == i + @test Dates.monthname(dt) == monthnames[i] + @test Dates.monthabbr(dt) == monthnames[i][1:3] + @test Dates.dayofweek(dt) == daysofweek[i] + @test Dates.dayname(dt) == dows[i] + @test Dates.dayabbr(dt) == dows[i][1:3] +end + +# Customizing locale +const french_daysofweek = [1=>"Lundi",2=>"Mardi",3=>"Mercredi",4=>"Jeudi", + 5=>"Vendredi",6=>"Samedi",7=>"Dimanche"] +Dates.VALUETODAYOFWEEK["french"] = french_daysofweek +@test Dates.dayname(nov;locale="french") == "Lundi" +@test Dates.dayname(jan;locale="french") == "Mardi" +@test Dates.dayname(dec;locale="french") == "Mercredi" +@test Dates.dayname(apr;locale="french") == "Jeudi" +@test Dates.dayname(jun;locale="french") == "Vendredi" +@test Dates.dayname(feb;locale="french") == "Samedi" +@test Dates.dayname(may;locale="french") == "Dimanche" + +const french_months = [1=>"janvier",2=>"février",3=>"mars",4=>"avril",5=>"mai",6=>"juin", + 7=>"juillet",8=>"août",9=>"septembre",10=>"octobre",11=>"novembre",12=>"décembre"] +Dates.VALUETOMONTH["french"] = french_months +@test Dates.monthname(jan;locale="french") == "janvier" +@test Dates.monthname(feb;locale="french") == "février" +@test Dates.monthname(mar;locale="french") == "mars" +@test Dates.monthname(apr;locale="french") == "avril" +@test Dates.monthname(may;locale="french") == "mai" +@test Dates.monthname(jun;locale="french") == "juin" +@test Dates.monthname(jul;locale="french") == "juillet" +@test Dates.monthname(aug;locale="french") == "août" +@test Dates.monthname(sep;locale="french") == "septembre" +@test Dates.monthname(oct;locale="french") == "octobre" +@test Dates.monthname(nov;locale="french") == "novembre" +@test Dates.monthname(dec;locale="french") == "décembre" + +@test Dates.daysinmonth(2000,1) == 31 +@test Dates.daysinmonth(2000,2) == 29 +@test Dates.daysinmonth(2000,3) == 31 +@test Dates.daysinmonth(2000,4) == 30 +@test Dates.daysinmonth(2000,5) == 31 +@test Dates.daysinmonth(2000,6) == 30 +@test Dates.daysinmonth(2000,7) == 31 +@test Dates.daysinmonth(2000,8) == 31 +@test Dates.daysinmonth(2000,9) == 30 +@test Dates.daysinmonth(2000,10) == 31 +@test Dates.daysinmonth(2000,11) == 30 +@test Dates.daysinmonth(2000,12) == 31 +@test Dates.daysinmonth(2001,2) == 28 + +@test Dates.isleap(1900) == false +@test Dates.isleap(2000) == true +@test Dates.isleap(2004) == true +@test Dates.isleap(2008) == true +@test Dates.isleap(0) == true +@test Dates.isleap(1) == false +@test Dates.isleap(-1) == false +@test Dates.isleap(4) == true +@test Dates.isleap(-4) == true + +@test Dates.isleap(Dates.DateTime(1900)) == false +@test Dates.isleap(Dates.DateTime(2000)) == true +@test Dates.isleap(Dates.DateTime(2004)) == true +@test Dates.isleap(Dates.DateTime(2008)) == true +@test Dates.isleap(Dates.DateTime(0)) == true +@test Dates.isleap(Dates.DateTime(1)) == false +@test Dates.isleap(Dates.DateTime(-1)) == false +@test Dates.isleap(Dates.DateTime(4)) == true +@test Dates.isleap(Dates.DateTime(-4)) == true + +@test Dates.daysinyear(2000) == 366 +@test Dates.daysinyear(2001) == 365 +@test Dates.daysinyear(2000) == 366 +@test Dates.daysinyear(2001) == 365 + +@test Dates.daysinyear(Dates.Date(2000)) == 366 +@test Dates.daysinyear(Dates.Date(2001)) == 365 +@test Dates.daysinyear(Dates.DateTime(2000)) == 366 +@test Dates.daysinyear(Dates.DateTime(2001)) == 365 + +# Days of week from Monday = 1 to Sunday = 7 +@test Dates.dayofweek(Dates.DateTime(2013,12,22)) == 7 +@test Dates.dayofweek(Dates.DateTime(2013,12,23)) == 1 +@test Dates.dayofweek(Dates.DateTime(2013,12,24)) == 2 +@test Dates.dayofweek(Dates.DateTime(2013,12,25)) == 3 +@test Dates.dayofweek(Dates.DateTime(2013,12,26)) == 4 +@test Dates.dayofweek(Dates.DateTime(2013,12,27)) == 5 +@test Dates.dayofweek(Dates.DateTime(2013,12,28)) == 6 +@test Dates.dayofweek(Dates.DateTime(2013,12,29)) == 7 + +# There are 5 Sundays in December, 2013 +@test Dates.daysofweekinmonth(Dates.DateTime(2013,12,1)) == 5 +# There are 4 Sundays in November, 2013 +@test Dates.daysofweekinmonth(Dates.DateTime(2013,11,24)) == 4 + +@test Dates.dayofweekofmonth(Dates.DateTime(2013,12,1)) == 1 +@test Dates.dayofweekofmonth(Dates.DateTime(2013,12,8)) == 2 +@test Dates.dayofweekofmonth(Dates.DateTime(2013,12,15)) == 3 +@test Dates.dayofweekofmonth(Dates.DateTime(2013,12,22)) == 4 +@test Dates.dayofweekofmonth(Dates.DateTime(2013,12,29)) == 5 + +@test Dates.dayofyear(2000,1,1) == 1 +@test Dates.dayofyear(2004,1,1) == 1 +@test Dates.dayofyear(20013,1,1) == 1 +# Leap year +@test Dates.dayofyear(2000,12,31) == 366 +# Non-leap year +@test Dates.dayofyear(2001,12,31) == 365 + +@test Dates.dayofyear(Dates.DateTime(2000,1,1)) == 1 +@test Dates.dayofyear(Dates.DateTime(2004,1,1)) == 1 +@test Dates.dayofyear(Dates.DateTime(20013,1,1)) == 1 +# Leap year +@test Dates.dayofyear(Dates.DateTime(2000,12,31)) == 366 +# Non-leap year +@test Dates.dayofyear(Dates.DateTime(2001,12,31)) == 365 +# Test every day of a year +dt = Dates.DateTime(2000,1,1) +for i = 1:366 + @test Dates.dayofyear(dt) == i + dt += Dates.Day(1) +end +dt = Dates.DateTime(2001,1,1) +for i = 1:365 + @test Dates.dayofyear(dt) == i + dt += Dates.Day(1) +end + +@test Dates.quarterofyear(Dates.Date(2000,1,1)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,1,31)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,2,1)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,2,29)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,3,1)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,3,31)) == 1 +@test Dates.quarterofyear(Dates.Date(2000,4,1)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,4,30)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,5,1)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,5,31)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,6,1)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,6,30)) == 2 +@test Dates.quarterofyear(Dates.Date(2000,7,1)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,7,31)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,8,1)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,8,31)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,9,1)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,9,30)) == 3 +@test Dates.quarterofyear(Dates.Date(2000,10,1)) == 4 +@test Dates.quarterofyear(Dates.Date(2000,10,31)) == 4 +@test Dates.quarterofyear(Dates.Date(2000,11,1)) == 4 +@test Dates.quarterofyear(Dates.Date(2000,11,30)) == 4 +@test Dates.quarterofyear(Dates.Date(2000,12,1)) == 4 +@test Dates.quarterofyear(Dates.Date(2000,12,31)) == 4 + +@test Dates.quarterofyear(Dates.DateTime(2000,1,1)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,1,31)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,2,1)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,2,29)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,3,1)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,3,31)) == 1 +@test Dates.quarterofyear(Dates.DateTime(2000,4,1)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,4,30)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,5,1)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,5,31)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,6,1)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,6,30)) == 2 +@test Dates.quarterofyear(Dates.DateTime(2000,7,1)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,7,31)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,8,1)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,8,31)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,9,1)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,9,30)) == 3 +@test Dates.quarterofyear(Dates.DateTime(2000,10,1)) == 4 +@test Dates.quarterofyear(Dates.DateTime(2000,10,31)) == 4 +@test Dates.quarterofyear(Dates.DateTime(2000,11,1)) == 4 +@test Dates.quarterofyear(Dates.DateTime(2000,11,30)) == 4 +@test Dates.quarterofyear(Dates.DateTime(2000,12,1)) == 4 +@test Dates.quarterofyear(Dates.DateTime(2000,12,31)) == 4 + +@test Dates.dayofquarter(Dates.Date(2014,1,1)) == 1 +@test Dates.dayofquarter(Dates.Date(2014,4,1)) == 1 +@test Dates.dayofquarter(Dates.Date(2014,7,1)) == 1 +@test Dates.dayofquarter(Dates.Date(2014,10,1)) == 1 +@test Dates.dayofquarter(Dates.Date(2014,3,31)) == 90 +@test Dates.dayofquarter(Dates.Date(2014,6,30)) == 91 +@test Dates.dayofquarter(Dates.Date(2014,9,30)) == 92 +@test Dates.dayofquarter(Dates.Date(2014,12,31)) == 92 +@test Dates.dayofquarter(Dates.DateTime(2014,1,1)) == 1 +@test Dates.dayofquarter(Dates.DateTime(2014,4,1)) == 1 +@test Dates.dayofquarter(Dates.DateTime(2014,7,1)) == 1 +@test Dates.dayofquarter(Dates.DateTime(2014,10,1)) == 1 +@test Dates.dayofquarter(Dates.DateTime(2014,3,31)) == 90 +@test Dates.dayofquarter(Dates.DateTime(2014,6,30)) == 91 +@test Dates.dayofquarter(Dates.DateTime(2014,9,30)) == 92 +@test Dates.dayofquarter(Dates.DateTime(2014,12,31)) == 92 diff --git a/test/dates/ranges.jl b/test/dates/ranges.jl new file mode 100644 index 0000000000000..4df7cc9c53fe9 --- /dev/null +++ b/test/dates/ranges.jl @@ -0,0 +1,115 @@ +# Ranges +a = Dates.Date(2013,1,1) +b = Dates.Date(2013,2,1) +dr = a:b +@test size(dr) == (32,) +@test length(dr) == 32 +@test findin(dr,dr) == [1:32] +@test findin(a:Dates.Date(2013,1,14),dr) == [1:14] +@test findin(a:Dates.Date(2012,12,31),dr) == Int64[] +@test isempty(a:Dates.Date(2012,12,31)) +@test reverse(dr) == b:Dates.Day(-1):a +@test map!(x->x+Dates.Day(1),Array(Date,32),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] +@test map(x->x+Dates.Day(1),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] +@test minimum(dr) == a +@test maximum(dr) == b +for (i,d) in enumerate(dr) + @test d == a + Dates.Day(i-1) +end +for (i,d) in enumerate(a:Dates.Day(2):b) + @test d == a + Dates.Day((i-1)*2) +end +@test_throws MethodError dr + 1 #TODO +@test a in dr +@test b in dr +@test Dates.Date(2013,1,3) in dr +@test Dates.Date(2013,1,15) in dr +@test Dates.Date(2013,1,26) in dr +@test !(Dates.Date(2012,1,1) in dr) +em = a:Dates.Date(2012,12,31) +@test !(a in em) #empty range +@test sort(dr) == dr +@test sort(em) == em +@test issorted(dr) +@test issorted(em) +@test !issorted(reverse(dr)) +@test sort(reverse(dr)) == dr +# dr + Dates.Day(1) #TODO +@test length(b:Dates.Day(-1):a) == 32 +@test length(b:a) == 0 +@test length(b:Dates.Day(1):a) == 0 +@test length(a:Dates.Day(2):b) == 16 +@test last(a:Dates.Day(2):b) == Dates.Date(2013,1,31) +@test length(a:Dates.Day(7):b) == 5 +@test last(a:Dates.Day(7):b) == Dates.Date(2013,1,29) +@test length(a:Dates.Day(32):b) == 1 +@test last(a:Dates.Day(32):b) == Dates.Date(2013,1,1) +@test (a:b)[1] == Dates.Date(2013,1,1) +@test (a:b)[2] == Dates.Date(2013,1,2) +@test (a:b)[7] == Dates.Date(2013,1,7) +@test (a:b)[end] == b +@test first(a:Dates.Date(20000,1,1)) == a +@test first(a:Dates.Date(200000,1,1)) == a +@test first(a:Dates.Date(2000000,1,1)) == a +@test first(a:Dates.Date(20000000,1,1)) == a +@test first(a:Dates.Date(200000000,1,1)) == a +@test first(a:typemax(Dates.Date)) == a +@test first(typemin(Dates.Date):typemax(Dates.Date)) == typemin(Dates.Date) +@test length(typemin(Dates.Date):Dates.Week(1):typemax(Dates.Date)) == 26351950414948059 +#toobig +@test_throws ArgumentError length(typemin(Dates.Date):Dates.Month(1):typemax(Dates.Date)) +@test_throws ArgumentError length(typemin(Dates.Date):Dates.Year(1):typemax(Dates.Date)) +@test_throws ArgumentError length(typemin(Dates.DateTime):Dates.Month(1):typemax(Dates.DateTime)) +@test_throws ArgumentError length(typemin(Dates.DateTime):Dates.Year(1):typemax(Dates.DateTime)) + +@test length(typemin(Dates.DateTime):Dates.Week(1):typemax(Dates.DateTime)) == 15250284420 +@test length(typemin(Dates.DateTime):Dates.Day(1):typemax(Dates.DateTime)) == 106751990938 +@test length(typemin(Dates.DateTime):Dates.Hour(1):typemax(Dates.DateTime)) == 2562047782512 +@test length(typemin(Dates.DateTime):Dates.Minute(1):typemax(Dates.DateTime)) == 153722866950720 +@test length(typemin(Dates.DateTime):Dates.Second(1):typemax(Dates.DateTime)) == 9223372017043200 +@test length(typemin(DateTime):typemax(DateTime)) == 9223372017043199001 + +c = Dates.Date(2013,6,1) +@test length(a:Dates.Month(1):c) == 6 +@test [a:Dates.Month(1):c] == [a + Dates.Month(1)*i for i in 0:5] +@test [a:Dates.Month(2):Dates.Date(2013,1,2)] == [a] +@test [c:Dates.Month(-1):a] == reverse([a:Dates.Month(1):c]) + +d = Dates.Date(2020,1,1) +@test length(a:Dates.Year(1):d) == 8 +@test first(a:Dates.Year(1):d) == a +@test last(a:Dates.Year(1):d) == d +@test length(a:Dates.Month(12):d) == 8 +@test first(a:Dates.Month(12):d) == a +@test last(a:Dates.Month(12):d) == d +@test length(a:Dates.Week(52):d) == 8 +@test first(a:Dates.Week(52):d) == a +@test last(a:Dates.Week(52):d) == Dates.Date(2019,12,24) +@test length(a:Dates.Day(365):d) == 8 +@test first(a:Dates.Day(365):d) == a +@test last(a:Dates.Day(365):d) == Dates.Date(2019,12,31) + +@test length(a:Dates.Year(1):Dates.Date(2020,2,1)) == 8 +@test length(a:Dates.Year(1):Dates.Date(2020,6,1)) == 8 +@test length(a:Dates.Year(1):Dates.Date(2020,11,1)) == 8 +@test length(a:Dates.Year(1):Dates.Date(2020,12,31)) == 8 +@test length(a:Dates.Year(1):Dates.Date(2021,1,1)) == 9 +@test length(Dates.Date(2000):Dates.Year(-10):Dates.Date(1900)) == 11 +@test length(Dates.Date(2000,6,23):Dates.Year(-10):Dates.Date(1900,2,28)) == 11 +@test length(Dates.Date(2000,1,1):Dates.Year(1):Dates.Date(2000,2,1)) == 1 + +@test length(Dates.Year(1):Dates.Year(10)) == 10 +@test length(Dates.Year(10):Dates.Year(-1):Dates.Year(1)) == 10 +@test length(Dates.Year(10):Dates.Year(-2):Dates.Year(1)) == 5 +@test_throws OverflowError length(typemin(Dates.Year):typemax(Dates.Year)) +@test_throws MethodError Dates.Date(0):Dates.DateTime(2000) +@test_throws MethodError Dates.Date(0):Dates.Year(10) +@test length(range(Dates.Date(2000),366)) == 366 +@test last(range(Dates.Date(2000),366)) == Dates.Date(2000,12,31) +@test last(range(Dates.Date(2001),365)) == Dates.Date(2001,12,31) +@test last(range(Dates.Date(2000),367)) == last(range(Dates.Date(2000),Dates.Month(12),2)) == last(range(Dates.Date(2000),Dates.Year(1),2)) +@test last(range(Dates.DateTime(2000),Dates.Day(366),2)) == last(range(Dates.DateTime(2000),Dates.Hour(8784),2)) + +# Issue 5 +lastdaysofmonth = [Dates.Date(2014,i,Dates.daysinmonth(2014,i)) for i=1:12] +@test [Date(2014,1,31):Dates.Month(1):Date(2015)] == lastdaysofmonth \ No newline at end of file diff --git a/test/dates/types.jl b/test/dates/types.jl new file mode 100644 index 0000000000000..7ad70d4fe5c04 --- /dev/null +++ b/test/dates/types.jl @@ -0,0 +1,143 @@ +# Date internal algorithms +@test Dates.totaldays(0,2,28) == -307 +@test Dates.totaldays(0,2,29) == -306 +@test Dates.totaldays(0,3,1) == -305 +@test Dates.totaldays(0,12,31) == 0 +# Rata Die Days # start from 0001-01-01 +@test Dates.totaldays(1,1,1) == 1 +@test Dates.totaldays(1,1,2) == 2 +@test Dates.totaldays(2013,1,1) == 734869 + +# Create "test" check manually +test = Dates.DateTime(Dates.UTM(63492681600000)) +# Test DateTime construction by parts +@test Dates.DateTime(2013) == test +@test Dates.DateTime(2013,1) == test +@test Dates.DateTime(2013,1,1) == test +@test Dates.DateTime(2013,1,1,0) == test +@test Dates.DateTime(2013,1,1,0,0) == test +@test Dates.DateTime(2013,1,1,0,0,0) == test +@test Dates.DateTime(2013,1,1,0,0,0,0) == test +test = Dates.Date(Dates.UTD(734869)) +# Test Date construction by parts +@test Dates.Date(2013) == test +@test Dates.Date(2013,1) == test +@test Dates.Date(2013,1,1) == test + +# Test various input types for Date/DateTime +test = Date(1,1,1) +@test Date(int8(1),int8(1),int8(1)) == test +@test Date(uint8(1),uint8(1),uint8(1)) == test +@test Date(int16(1),int16(1),int16(1)) == test +@test Date(uint8(1),uint8(1),uint8(1)) == test +@test Date(int32(1),int32(1),int32(1)) == test +@test Date(uint32(1),uint32(1),uint32(1)) == test +@test Date(int64(1),int64(1),int64(1)) == test +@test Date('\x01','\x01','\x01') == test +@test Date(true,true,true) == test +@test Date(false,true,false) == test - Dates.Year(1) - Dates.Day(1) +@test Date(false,true,true) == test - Dates.Year(1) +@test Date(true,true,false) == test - Dates.Day(1) +@test Date(uint64(1),uint64(1),uint64(1)) == test +@test Date(0xffffffffffffffff,uint64(1),uint64(1)) == test - Dates.Year(2) +@test Date(int128(1),int128(1),int128(1)) == test +@test Date(170141183460469231731687303715884105727,int128(1),int128(1)) == test - Dates.Year(2) +@test Date(uint128(1),uint128(1),uint128(1)) == test +@test Date(big(1),big(1),big(1)) == test +@test Date(big(1),big(1),big(1)) == test +# Potentially won't work if can't losslessly convert to Int64 +@test Date(BigFloat(1),BigFloat(1),BigFloat(1)) == test +@test Date(complex(1),complex(1),complex(1)) == test +@test Date(float64(1),float64(1),float64(1)) == test +@test Date(float32(1),float32(1),float32(1)) == test +@test Date(float16(1),float16(1),float16(1)) == test +@test Date(Rational(1),Rational(1),Rational(1)) == test +@test_throws InexactError Date(BigFloat(1.2),BigFloat(1),BigFloat(1)) +@test_throws InexactError Date(1 + im,complex(1),complex(1)) +@test_throws InexactError Date(1.2,1.0,1.0) +@test_throws InexactError Date(1.2f0,1.f0,1.f0) +@test_throws InexactError Date(3//4,Rational(1),Rational(1)) == test +# Currently no method for convert(Int64,::Float16) + +# Months must be in range +@test_throws ArgumentError Dates.DateTime(2013,0,1) +@test_throws ArgumentError Dates.DateTime(2013,13,1) + +# Days/Hours/Minutes/Seconds/Milliseconds roll back/forward +@test Dates.DateTime(2013,1,0) == Dates.DateTime(2012,12,31) +@test Dates.DateTime(2013,1,32) == Dates.DateTime(2013,2,1) +@test Dates.DateTime(2013,1,1,24) == Dates.DateTime(2013,1,2) +@test Dates.DateTime(2013,1,1,-1) == Dates.DateTime(2012,12,31,23) +@test Dates.Date(2013,1,0) == Dates.Date(2012,12,31) +@test Dates.Date(2013,1,32) == Dates.Date(2013,2,1) + +# Test DateTime traits +a = Dates.DateTime(2000) +b = Dates.Date(2000) +@test Dates.calendar(a) == Dates.ISOCalendar +@test Dates.calendar(b) == Dates.ISOCalendar +@test Dates.precision(a) == Dates.UTInstant{Dates.Millisecond} +@test Dates.precision(b) == Dates.UTInstant{Dates.Day} +@test string(typemax(Dates.DateTime)) == "146138512-12-31T23:59:59" +@test string(typemin(Dates.DateTime)) == "-146138511-01-01T00:00:00" +@test typemax(Dates.DateTime) - typemin(Dates.DateTime) == Dates.Millisecond(9223372017043199000) +@test string(typemax(Dates.Date)) == "252522163911149-12-31" +@test string(typemin(Dates.Date)) == "-252522163911150-01-01" +@test convert(Real,b) == 730120 +@test convert(Float64,b) == 730120.0 +@test convert(Int32,b) == 730120 +@test convert(Real,a) == 63082368000000 +@test convert(Float64,a) == 63082368000000.0 +@test convert(Int64,a) == 63082368000000 +# Date-DateTime conversion/promotion +@test Dates.DateTime(a) == a +@test Dates.Date(a) == b +@test Dates.DateTime(b) == a +@test Dates.Date(b) == b +@test a == b +@test a == a +@test b == a +@test b == b +@test !(a < b) +@test !(b < a) +c = Dates.DateTime(2000) +d = Dates.Date(2000) +@test ==(a,c) +@test ==(c,a) +@test ==(d,b) +@test ==(b,d) +@test ==(a,d) +@test ==(d,a) +@test ==(b,c) +@test ==(c,b) +b = Dates.Date(2001) +@test b > a +@test a < b +@test a != b +@test Date(DateTime(Date(2012,7,1))) == Date(2012,7,1) + +y = Dates.Year(1) +m = Dates.Month(1) +w = Dates.Week(1) +d = Dates.Day(1) +h = Dates.Hour(1) +mi = Dates.Minute(1) +s = Dates.Second(1) +ms = Dates.Millisecond(1) +@test Dates.DateTime(y) == Dates.DateTime(1) +@test Dates.DateTime(y,m) == Dates.DateTime(1,1) +@test Dates.DateTime(y,m,d) == Dates.DateTime(1,1,1) +@test Dates.DateTime(y,m,d,h) == Dates.DateTime(1,1,1,1) +@test Dates.DateTime(y,m,d,h,mi) == Dates.DateTime(1,1,1,1,1) +@test Dates.DateTime(y,m,d,h,mi,s) == Dates.DateTime(1,1,1,1,1,1) +@test Dates.DateTime(y,m,d,h,mi,s,ms) == Dates.DateTime(1,1,1,1,1,1,1) +@test Dates.DateTime(Dates.Day(10),Dates.Month(2),y) == Dates.DateTime(1,2,10) +@test Dates.DateTime(Dates.Second(10),Dates.Month(2),y,Dates.Hour(4)) == Dates.DateTime(1,2,1,4,0,10) +@test Dates.DateTime(Dates.Year(1),Dates.Month(2),Dates.Day(1),Dates.Hour(4),Dates.Second(10)) == Dates.DateTime(1,2,1,4,0,10) +@test Dates.Date(y) == Dates.Date(1) +@test Dates.Date(y,m) == Dates.Date(1,1) +@test Dates.Date(y,m,d) == Dates.Date(1,1,1) +@test Dates.Date(m) == Dates.Date(1,1,1) +@test Dates.Date(d,y) == Dates.Date(1,1,1) +@test Dates.Date(d,m) == Dates.Date(1,1,1) +@test Dates.Date(m,y) == Dates.Date(1,1,1) diff --git a/test/runtests.jl b/test/runtests.jl index 8913b083a72cc..5fc6db9d4dd41 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ testnames = [ "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", "floatapprox", "readdlm", "regex", "float16", "combinatorics", "sysinfo", "rounding", "ranges", "mod2pi", "euler", "show", - "lineedit", "replcompletions", "repl", "test", "examples", "goto" + "lineedit", "replcompletions", "repl", "test", "examples", "goto", "dates" ] @unix_only push!(testnames, "unicode") From c1824f84a7180cb449238a13d1f1817d683cecad Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:19:49 -0400 Subject: [PATCH 02/28] Add missing millisecond to month/year-TimeType arithmetic --- base/dates/arithmetic.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/base/dates/arithmetic.jl b/base/dates/arithmetic.jl index b7db1ab5315af..f433351bb4fb3 100644 --- a/base/dates/arithmetic.jl +++ b/base/dates/arithmetic.jl @@ -5,16 +5,17 @@ end (+)(x::Instant) = x (-){T<:Instant}(x::T,y::T) = x.periods - y.periods -# DateTime arithmetic +# 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)) + 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) @@ -22,14 +23,14 @@ function (+)(dt::Date,y::Year) 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)) + 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 -# Date/DateTime-Month arithmetic +# 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 @@ -40,7 +41,7 @@ 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)) + 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) @@ -52,7 +53,7 @@ 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)) + 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) From 9cb5922ed4e3d09a09f40f7e9b68cd4d03f130d5 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:21:03 -0400 Subject: [PATCH 03/28] Cleanup some definitions for Period types. Removes redundants and adds a few math operations --- base/dates/periods.jl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/base/dates/periods.jl b/base/dates/periods.jl index 2d245735f4310..37818cd015ecb 100644 --- a/base/dates/periods.jl +++ b/base/dates/periods.jl @@ -15,12 +15,13 @@ for p in (:Year,:Month,:Week,:Day,:Hour,:Minute,:Second,:Millisecond) @eval periodisless(x::$p,y::$p) = value(x) < value(y) # String parsing (mainly for IO code) @eval $p(x::String) = $p(parseint(x)) + # Period accessors + @eval $p(x::TimeType) = $p($(symbol(lowercase(string(p))))(x)) end # Now we're safe to define Period-Number conversions # Anything an Int64 can convert to, a Period can convert to Base.convert{T<:Number}(::Type{T},x::Period) = convert(T,value(x)) -# Error quickly if x can't convert losslessly to Int64 -Base.convert{P<:Period}(::Type{P},x::Number) = P(convert(Int64,x)) +Base.convert{T<:Period}(::Type{T},x::Real) = T(int64(x)) #Print/show/traits Base.string{P<:Period}(x::P) = string(value(x),_units(x)) @@ -36,25 +37,22 @@ default{T<:TimePeriod}(p::Union(T,Type{T})) = zero(p) (-){P<:Period}(x::P) = P(-value(x)) Base.isless{P<:Period}(x::P,y::P) = isless(value(x),value(y)) +Base.isless{R<:Real}(x::Period,y::R) = isless(int64(y),value(x)) +Base.isless{R<:Real}(y::R,x::Period) = isless(int64(y),value(x)) =={P<:Period}(x::P,y::P) = ===(value(x),value(y)) -Base.isless{R<:Real}(x::Period,y::R) = throw(ArgumentError("Can't compare Period-$R")) -=={R<:Real}(x::Period,y::R) = throw(ArgumentError("Can't compare Period-$R")) -Base.isless{R<:Real}(y::R,x::Period) = throw(ArgumentError("Can't compare Period-$R")) -=={R<:Real}(y::R,x::Period) = throw(ArgumentError("Can't compare Period-$R")) +=={R<:Real}(x::Period,y::R) = ==(int64(y),value(x)) +=={R<:Real}(y::R,x::Period) = ==(int64(y),value(x)) Base.isless(x::Period,y::Period) = throw(ArgumentError("Can't compare $(typeof(x)) and $(typeof(y))")) ==(x::Period,y::Period) = throw(ArgumentError("Can't compare $(typeof(x)) and $(typeof(y))")) #Period Arithmetic: -import Base.div +import Base.div, Base.mod, Base.gcd, Base.lcm let vec_ops = [:.+,:.-,:.*,:.%,:div] - for op in [:+,:-,:*,:%,vec_ops] + for op in [:+,:-,:*,:%,:mod,:gcd,:lcm,vec_ops] @eval begin #Period-Period ($op){P<:Period}(x::P,y::P) = P(($op)(value(x),value(y))) - #Period-Integer - ($op){P<:Period}(x::P,y::Integer) = P(($op)(value(x),int64(y))) - ($op){P<:Period}(x::Integer,y::P) = P(($op)(int64(x),value(y))) #Period-Real ($op){P<:Period}(x::P,y::Real) = P(($op)(value(x),convert(Int64,y))) ($op){P<:Period}(x::Real,y::P) = P(($op)(convert(Int64,x),value(y))) @@ -69,6 +67,10 @@ let vec_ops = [:.+,:.-,:.*,:.%,:div] end end +# intfuncs +Base.gcdx{T<:Period}(a::T,b::T) = ((g,x,y)=gcdx(Dates.value(a),Dates.value(b)); return T(g),x,y) +Base.abs{T<:Period}(a::T) = T(abs(value(a))) + periodisless(::Period,::Year) = true periodisless(::Period,::Month) = true periodisless(::Year,::Month) = false @@ -110,7 +112,6 @@ Base.show(io::IO,x::CompoundPeriod) = print(io,string(x)) (-)(x::CompoundPeriod,y::Period) = (sort!(push!(x.periods,-y),rev=true,lt=periodisless); return x) # Capture TimeType+-Period methods -typealias TTP Union(TimeType,Period) (+)(a::TimeType,b::Period,c::Period) = (+)(a,b+c) (-)(a::TimeType,b::Period,c::Period) = (-)(a,b-c) (+)(a::TimeType,b::Period,c::Period,d::Period...) = (+)((+)(a,b+c),d...) From 142aad2b8f6a0f08a1f8d8228b7a9b6cfb551f09 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:22:08 -0400 Subject: [PATCH 04/28] Add internal conversion methods to Day and Millisecond. This builds a foundation for better range support --- base/dates/periods.jl | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/base/dates/periods.jl b/base/dates/periods.jl index 37818cd015ecb..2fed874c84f49 100644 --- a/base/dates/periods.jl +++ b/base/dates/periods.jl @@ -125,12 +125,27 @@ function (+)(x::TimeType,y::CompoundPeriod) end (+)(x::CompoundPeriod,y::TimeType) = y + x -# Periods from TimeTypes -Year(x::TimeType) = Year(year(x)) -Month(x::TimeType) = Month(month(x)) -Week(x::TimeType) = Week(week(x)) -Day(x::TimeType) = Day(day(x)) -Hour(x::TimeType) = Hour(hour(x)) -Minute(x::TimeType) = Minute(minute(x)) -Second(x::TimeType) = Second(second(x)) -Millisecond(x::TimeType) = Millisecond(millisecond(x)) +# Convert fixed value Periods to # of milliseconds +typealias FixedPeriod Union(Week,Day,Hour,Minute,Second,Millisecond) + +toms(c::Millisecond) = value(c) +toms(c::Second) = 1000*value(c) +toms(c::Minute) = 60000*value(c) +toms(c::Hour) = 3600000*value(c) +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) + +Millisecond{T<:FixedPeriod}(c::T) = Millisecond(toms(c)) + +days(c::Millisecond) = div(value(c),86400000) +days(c::Second) = div(value(c),86400) +days(c::Minute) = div(value(c),1440) +days(c::Hour) = div(value(c),24) +days(c::Day) = value(c) +days(c::Week) = 7*value(c) +days(c::Year) = 365.2425*value(c) +days(c::Month) = 30.436875*value(c) + +Day{T<:FixedPeriod}(c::T) = Day(days(c)) \ No newline at end of file From 0920ff644edca6f796c3a14eed777553fe935df6 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:23:23 -0400 Subject: [PATCH 05/28] Consolidate FixedPeriod-TimeType arithmetic by using new conversion methods. Also give better typed results with vectorized arithmetic operations --- base/dates/arithmetic.jl | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/base/dates/arithmetic.jl b/base/dates/arithmetic.jl index f433351bb4fb3..9cac50aad9cc7 100644 --- a/base/dates/arithmetic.jl +++ b/base/dates/arithmetic.jl @@ -65,22 +65,12 @@ end (-)(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::Week) = return DateTime(UTM(value(x)+604800000*value(y))) -(-)(x::DateTime,y::Week) = return DateTime(UTM(value(x)-604800000*value(y))) -(+)(x::DateTime,y::Day) = return DateTime(UTM(value(x)+86400000 *value(y))) -(-)(x::DateTime,y::Day) = return DateTime(UTM(value(x)-86400000 *value(y))) -(+)(x::DateTime,y::Hour) = return DateTime(UTM(value(x)+3600000 *value(y))) -(-)(x::DateTime,y::Hour) = return DateTime(UTM(value(x)-3600000 *value(y))) -(+)(x::DateTime,y::Minute) = return DateTime(UTM(value(x)+60000 *value(y))) -(-)(x::DateTime,y::Minute) = return DateTime(UTM(value(x)-60000 *value(y))) -(+)(x::DateTime,y::Second) = return DateTime(UTM(value(x)+1000*value(y))) -(-)(x::DateTime,y::Second) = return DateTime(UTM(value(x)-1000*value(y))) -(+)(x::DateTime,y::Millisecond) = return DateTime(UTM(value(x)+value(y))) -(-)(x::DateTime,y::Millisecond) = return DateTime(UTM(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))) (+)(y::Period,x::TimeType) = x + y (-)(y::Period,x::TimeType) = x - y -(.+){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape([i + y for i in x], size(x)) -(.-){T<:TimeType}(x::AbstractArray{T}, y::Period) = reshape([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}(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 \ No newline at end of file From 76344a5f6066be3ae33145a7fee2a8bd2a70abf0 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:28:27 -0400 Subject: [PATCH 06/28] Make now() return a DateTime corresponding to the user's timezone (set on their machine) --- base/dates/conversions.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/base/dates/conversions.jl b/base/dates/conversions.jl index 649f6f5aa5cf4..244d739c74c7b 100644 --- a/base/dates/conversions.jl +++ b/base/dates/conversions.jl @@ -10,14 +10,17 @@ Base.convert{R<:Real}(::Type{R},x::Date) = convert(R,value(x)) @vectorize_1arg Date DateTime ### External Conversions -const UNIXEPOCH = value(DateTime(1970)) #Rata Die milliseconds for 1970-01-01T00:00:00Z +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:00Z +# Returns unix seconds since 1970-01-01T00:00:00 datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH)/1000.0 -now() = unix2datetime(time()) +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)...) @@ -29,7 +32,7 @@ function julian2datetime(f) rata = JULIANEPOCH + int64(86400000*f) return DateTime(UTM(rata)) end -# Returns # of julian days since -4713-11-24T12:00:00Z +# Returns # of julian days since -4713-11-24T12:00:00 datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0 @vectorize_1arg Any unix2datetime From a899dcea42f5137f06879dcf384b11eb61190257 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:30:39 -0400 Subject: [PATCH 07/28] Rename isleap to isleapyear and remove some definitions that will be moved to types.jl --- base/dates/query.jl | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/base/dates/query.jl b/base/dates/query.jl index 755b11eb0f8f9..d5ff6262681be 100644 --- a/base/dates/query.jl +++ b/base/dates/query.jl @@ -1,24 +1,16 @@ # Date functions -### Core date functions +### Core query functions # Monday = 1....Sunday = 7 dayofweek(days) = mod1(days,7) -# If the year is divisible by 4, except for every 100 years, except for every 400 years -isleap(y) = ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0) - -# Number of days in month -const DAYSINMONTH = [31,28,31,30,31,30,31,31,30,31,30,31] -daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleap(y)) - # Number of days in year -daysinyear(y) = 365 + isleap(y) +daysinyear(y) = 365 + isleapyear(y) # Day of the year -const MONTHDAYS2 = [0,31,59,90,120,151,181,212,243,273,304,334] -dayofyear(y,m,d) = MONTHDAYS2[m] + d + (m > 2 && isleap(y)) - +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 const Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday = 1,2,3,4,5,6,7 @@ -88,13 +80,13 @@ daysinmonth(dt::TimeType) = daysinmonth(yearmonth(dt)...) @vectorize_1arg TimeType daysinmonth ### Years -isleap(dt::TimeType) = isleap(year(dt)) +isleapyear(dt::TimeType) = isleapyear(year(dt)) dayofyear(dt::TimeType) = dayofyear(yearmonthday(dt)...) -daysinyear(dt::TimeType) = 365 + isleap(dt) +daysinyear(dt::TimeType) = 365 + isleapyear(dt) -@vectorize_1arg TimeType isleap +@vectorize_1arg TimeType isleapyear @vectorize_1arg TimeType dayofyear @vectorize_1arg TimeType daysinyear @@ -109,7 +101,7 @@ dayofquarter(dt::TimeType) = dayofyear(dt) - QUARTERDAYS[quarterofyear(dt)] @vectorize_1arg TimeType quarterofyear @vectorize_1arg TimeType dayofquarter -export dayofweek, isleap, daysinmonth, daysinyear, dayofyear, dayname, dayabbr, +export dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr, ismonday, istuesday, iswednesday, isthursday, isfriday, issaturday, issunday, dayofweekofmonth, daysofweekinmonth, monthname, monthabbr, quarterofyear, dayofquarter, From aafe02f9b1a256a40671063447106c64d15cd01c Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:31:45 -0400 Subject: [PATCH 08/28] Update comments and remove unneccesary inner constructors in Date/DateTime --- base/dates/types.jl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/base/dates/types.jl b/base/dates/types.jl index 6a18815c11c6c..553c1445e216b 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -42,30 +42,26 @@ end UTM(x) = UTInstant(Millisecond(x)) UTD(x) = UTInstant(Day(x)) -# Calendar types provide dispatch rules for interpretating instant -# timelines in human-readable form. Calendar types are used as -# type tags in the Timestamp type for dispatching to methods -# implementing the Instant=>Human-Form conversion rules. +# Calendar types provide rules for interpretating instant +# timelines in human-readable form. abstract Calendar <: AbstractTime # ISOCalendar implements the ISO 8601 standard (en.wikipedia.org/wiki/ISO_8601) # Notably based on the proleptic Gregorian calendar -# ISOCalendar provides interpretation rules for UTInstants to UT +# ISOCalendar provides interpretation rules for UTInstants to civil date and time parts immutable ISOCalendar <: Calendar end # TimeTypes wrap Instants to provide human representations of time abstract TimeType <: AbstractTime -# DateTime is a millisecond precision UTInstant interpreted thru ISOCalendar +# DateTime is a millisecond precision UTInstant interpreted by ISOCalendar immutable DateTime <: TimeType instant::UTInstant{Millisecond} - DateTime(x::UTInstant{Millisecond}) = new(x) end -# DateTime is a day precision UTInstant interpreted thru ISOCalendar +# DateTime is a day precision UTInstant interpreted by ISOCalendar immutable Date <: TimeType instant::UTInstant{Day} - Date(x::UTInstant{Day}) = new(x) end # Convert y,m,d to # of Rata Die days From 533d9db3131bc89a13ce5714743da2f6c6e589b8 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:32:36 -0400 Subject: [PATCH 09/28] Move definitions of daysinmonth and isleapyear to types.jl from query.jl to be used in validating inputs --- base/dates/types.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/base/dates/types.jl b/base/dates/types.jl index 553c1445e216b..eba5c21565d9e 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -67,15 +67,22 @@ end # Convert y,m,d to # of Rata Die days # Works by shifting the beginning of the year to March 1, # so a leap day is the very last day of the year -const MONTHDAYS = Int64[306,337,0,31,61,92,122,153,184,214,245,275] +const SHIFTEDMONTHDAYS = [306,337,0,31,61,92,122,153,184,214,245,275] function totaldays(y,m,d) # If we're in Jan/Feb, shift the given year back one z = m < 3 ? y - 1 : y - mdays = MONTHDAYS[m]::Int64 + mdays = SHIFTEDMONTHDAYS[m] # days + month_days + year_days return d + mdays + 365z + fld(z,4) - fld(z,100) + fld(z,400) - 306 end +# If the year is divisible by 4, except for every 100 years, except for every 400 years +isleapyear(y) = ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0) + +# Number of days in month +const DAYSINMONTH = [31,28,31,30,31,30,31,31,30,31,30,31] +daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleapyear(y)) + ### CONSTRUCTORS ### # Core constructors function DateTime(y::Int64,m::Int64=1,d::Int64=1, From 760b7d31358b319a3b1bd170a2e59936903b2c57 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:32:56 -0400 Subject: [PATCH 10/28] Validate all inputs when building Date/DateTime by parts --- base/dates/types.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/dates/types.jl b/base/dates/types.jl index eba5c21565d9e..ae65c287061d6 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -88,11 +88,17 @@ daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleapyear(y)) function DateTime(y::Int64,m::Int64=1,d::Int64=1, h::Int64=0,mi::Int64=0,s::Int64=0,ms::Int64=0) 0 < m < 13 || throw(ArgumentError("Month: $m out of range (1:12)")) + 0 < d < daysinmonth(y,m)+1 || throw(ArgumentError("Day: $d out of range (1:$daysinmonth(y,m))")) + -1 < h < 24 || throw(ArgumentError("Hour: $h out of range (1:23)")) + -1 < mi < 60 || throw(ArgumentError("Minute: $mi out of range (1:59)")) + -1 < s < 60 || throw(ArgumentError("Second: $s out of range (1:59)")) + -1 < ms < 1000 || throw(ArgumentError("Millisecond: $ms out of range (1:999)")) rata = ms + 1000*(s + 60mi + 3600h + 86400*totaldays(y,m,d)) return DateTime(UTM(rata)) end function Date(y::Int64,m::Int64=1,d::Int64=1) 0 < m < 13 || throw(ArgumentError("Month: $m out of range (1:12)")) + 0 < d < daysinmonth(y,m)+1 || throw(ArgumentError("Day: $d out of range (1:$daysinmonth(y,m))")) return Date(UTD(totaldays(y,m,d))) end From 07e2c42f9e7af7fafcd8034ad8de358c49aef39d Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:33:28 -0400 Subject: [PATCH 11/28] Cleanup a few trait definitions and exports in types.jl --- base/dates/types.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/base/dates/types.jl b/base/dates/types.jl index ae65c287061d6..932a238e42b62 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -143,21 +143,22 @@ 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), Date(y,m=1,d=1) = Date(_c(y),_c(m),_c(d)) # Traits, Equality -Base.isfinite{T<:TimeType}(::Union(TimeType,T)) = true +Base.isfinite{T<:TimeType}(::Union(Type{T},T)) = true calendar(dt::DateTime) = ISOCalendar calendar(dt::Date) = ISOCalendar -Base.precision(dt::DateTime) = UTInstant{Millisecond} -Base.precision(dt::Date) = UTInstant{Day} +Base.precision(dt::DateTime) = Millisecond +Base.precision(dt::Date) = Day 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) # Date-DateTime promotion, isless, == -Base.promote_rule(::Type{Date},x::Type{DateTime}) = x +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::TimeType,y::TimeType) = isless(promote(x,y)...) ==(x::TimeType,y::TimeType) = ===(promote(x,y)...) -export Period, Year, Month, Week, Day, Hour, Minute, Second, Millisecond, +export Period, DatePeriod, TimePeriod, + Year, Month, Week, Day, Hour, Minute, Second, Millisecond, TimeType, DateTime, Date From 1a2a86b7e9769d4c92e40e4fc3a59aa2601215b5 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:34:14 -0400 Subject: [PATCH 12/28] Remove all uses of splatting to avoid related performance hits --- base/dates/query.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/dates/query.jl b/base/dates/query.jl index d5ff6262681be..f7ccf767f5624 100644 --- a/base/dates/query.jl +++ b/base/dates/query.jl @@ -13,6 +13,8 @@ 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) = dayofweek(days(dt)) + 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 = [1=>"Monday",2=>"Tuesday",3=>"Wednesday", @@ -24,8 +26,6 @@ const VALUETODAYOFWEEKABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>engli dayname(dt::TimeType;locale::String="english") = VALUETODAYOFWEEK[locale][dayofweek(dt)] dayabbr(dt::TimeType;locale::String="english") = VALUETODAYOFWEEKABBR[locale][dayofweek(dt)] -dayofweek(dt::TimeType) = dayofweek(days(dt)) - # Convenience methods for each day ismonday(dt::TimeType) = dayofweek(dt) == Mon istuesday(dt::TimeType) = dayofweek(dt) == Tue @@ -73,7 +73,7 @@ const VALUETOMONTHABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>englishab monthname(dt::TimeType;locale::String="english") = VALUETOMONTH[locale][month(dt)] monthabbr(dt::TimeType;locale::String="english") = VALUETOMONTHABBR[locale][month(dt)] -daysinmonth(dt::TimeType) = daysinmonth(yearmonth(dt)...) +daysinmonth(dt::TimeType) = ((y,m) = yearmonth(dt); return daysinmonth(y,m)) @vectorize_1arg TimeType monthname @vectorize_1arg TimeType monthabbr @@ -82,7 +82,7 @@ daysinmonth(dt::TimeType) = daysinmonth(yearmonth(dt)...) ### Years isleapyear(dt::TimeType) = isleapyear(year(dt)) -dayofyear(dt::TimeType) = dayofyear(yearmonthday(dt)...) +dayofyear(dt::TimeType) = ((y,m,d) = yearmonthday(dt); return dayofyear(y,m,d)) daysinyear(dt::TimeType) = 365 + isleapyear(dt) From a4b61d25028cff36fd86e1aaf55e827599e0a401 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:34:35 -0400 Subject: [PATCH 13/28] Add dayname/monthname definitions that take integer arguments for the month --- base/dates/query.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/dates/query.jl b/base/dates/query.jl index f7ccf767f5624..10ddcd4e5a185 100644 --- a/base/dates/query.jl +++ b/base/dates/query.jl @@ -23,6 +23,8 @@ const VALUETODAYOFWEEK = (UTF8String=>Dict{Int,UTF8String})["english"=>english_d const english_daysofweekabbr = [1=>"Mon",2=>"Tue",3=>"Wed", 4=>"Thu",5=>"Fri",6=>"Sat",7=>"Sun"] const VALUETODAYOFWEEKABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>english_daysofweekabbr] +dayname(dt::Integer;locale::String="english") = VALUETODAYOFWEEK[locale][dt] +dayabbr(dt::Integer;locale::String="english") = VALUETODAYOFWEEKABBR[locale][dt] dayname(dt::TimeType;locale::String="english") = VALUETODAYOFWEEK[locale][dayofweek(dt)] dayabbr(dt::TimeType;locale::String="english") = VALUETODAYOFWEEKABBR[locale][dayofweek(dt)] @@ -70,6 +72,8 @@ const englishabbr_months = [1=>"Jan",2=>"Feb",3=>"Mar",4=>"Apr", 5=>"May",6=>"Jun",7=>"Jul",8=>"Aug",9=>"Sep", 10=>"Oct",11=>"Nov",12=>"Dec"] const VALUETOMONTHABBR = (UTF8String=>Dict{Int,UTF8String})["english"=>englishabbr_months] +monthname(dt::Integer;locale::String="english") = VALUETOMONTH[locale][dt] +monthabbr(dt::Integer;locale::String="english") = VALUETOMONTHABBR[locale][dt] monthname(dt::TimeType;locale::String="english") = VALUETOMONTH[locale][month(dt)] monthabbr(dt::TimeType;locale::String="english") = VALUETOMONTHABBR[locale][month(dt)] From e02baea7ff7d92939f3424a18d299eb71edaf76c Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:35:31 -0400 Subject: [PATCH 14/28] Allow the use of delimters at the beginning of a format string. --- base/dates/io.jl | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/base/dates/io.jl b/base/dates/io.jl index 686ac8dc7506c..6a3378ae90271 100644 --- a/base/dates/io.jl +++ b/base/dates/io.jl @@ -52,30 +52,33 @@ end immutable DateFormat slots::Array{Slot,1} + begtran # optional transition from the start of a string to the 1st slot trans #trans[i] == how to transition FROM slots[i] TO slots[i+1] end duplicates(slots) = any(map(x->count(y->x.period==y.period,slots),slots) .> 1) function DateFormat(f::String,locale::String="english") - slots = Dates.Slot[] + slots = Slot[] trans = {} - s = split(f,r"[^ymuUdHMSs]|(?<=([ymuUdHMSs])(?!\1))") + begtran = match(r"^.*?(?=[ymuUdHMSs])",f).match + ss = split(f,r"^.*?(?=[ymuUdHMSs])") + s = split(begtran == "" ? ss[1] : ss[2],r"[^ymuUdHMSs]|(?<=([ymuUdHMSs])(?!\1))") for (i,k) in enumerate(s) k == "" && break tran = i >= endof(s) ? r"$" : match(Regex("(?<=$(s[i])).*(?=$(s[i+1]))"),f).match - slot = tran == "" || tran == r"$" ? Dates.FixedWidthSlot : Dates.DelimitedSlot + slot = tran == "" || tran == r"$" ? FixedWidthSlot : DelimitedSlot width = length(k) - typ = 'y' in k ? Dates.Year : 'm' in k ? Dates.Month : - 'u' in k ? Dates.Month : 'U' in k ? Dates.Month : - 'd' in k ? Dates.Day : 'H' in k ? Dates.Hour : - 'M' in k ? Dates.Minute : 'S' in k ? Dates.Second : Dates.Millisecond + typ = 'y' in k ? Year : 'm' in k ? Month : + 'u' in k ? Month : 'U' in k ? Month : + 'd' in k ? Day : 'H' in k ? Hour : + 'M' in k ? Minute : 'S' in k ? Second : Millisecond option = 'U' in k ? 2 : 'u' in k ? 1 : 0 push!(slots,slot(i,typ,width,option,locale)) push!(trans,tran) end duplicates(slots) && throw(ArgumentError("Two separate periods of the same type detected")) - return DateFormat(slots,trans) + return DateFormat(slots,begtran,trans) end const SLOTERROR = ArgumentError("Non-digit character encountered") @@ -100,20 +103,21 @@ function getslot(x,slot::DelimitedSlot,df,cursor) end return endind+1, slotparse(slot,x[cursor:(endind-1)]) end -getslot(x,slot,df,cursor) = (cursor+slot.width, Dates.slotparse(slot,x[cursor:(cursor+slot.width-1)])) +getslot(x,slot,df,cursor) = (cursor+slot.width, slotparse(slot,x[cursor:(cursor+slot.width-1)])) function parse(x::String,df::DateFormat) x = strip(replace(x, r"#.*$", "")) + x = replace(x,df.begtran,"") isempty(x) && throw(ArgumentError("Cannot parse empty string")) - (typeof(df.slots[1]) <: DelimitedSlot && first(search(x,df.trans[1])) == 0) && throw(ArgumentError("Delimiter mismsatch. Couldn't find first delimter, \"$(df.trans[1])\", in date string")) + (typeof(df.slots[1]) <: DelimitedSlot && first(search(x,df.trans[1])) == 0) && throw(ArgumentError("Delimiter mismatch. Couldn't find first delimiter, \"$(df.trans[1])\", in date string")) periods = Period[] cursor = 1 for slot in df.slots - cursor, pe = Dates.getslot(x,slot,df,cursor) + cursor, pe = getslot(x,slot,df,cursor) push!(periods,pe) cursor > endof(x) && break end - return sort!(periods,rev=true,lt=Dates.periodisless) + return sort!(periods,rev=true,lt=periodisless) end slotformat(slot::Slot{Year},dt) = lpad(string(value(slot.period(dt))),slot.width,"0")[(end-slot.width+1):end] @@ -153,19 +157,19 @@ format(dt::TimeType,f::String;locale::String="english") = format(dt,DateFormat(f # vectorized DateTime{T<:String}(y::AbstractArray{T},format::String;locale::String="english") = DateTime(y,DateFormat(format,locale)) function DateTime{T<:String}(y::AbstractArray{T},df::DateFormat=ISODateFormat) - return reshape([DateTime(parse(y[i],df)...) for i in 1:length(y)], size(y)) + return reshape(DateTime[DateTime(parse(y[i],df)...) for i in 1:length(y)], size(y)) end Date{T<:String}(y::AbstractArray{T},format::String;locale::String="english") = Date(y,DateFormat(format,locale)) -function Date{T<:String}(y::AbstractArray{T},x::DateFormat=ISODateFormat) - return reshape([Date(parse(y[i],f)...) for i in 1:length(y)], size(y)) +function Date{T<:String}(y::AbstractArray{T},df::DateFormat=ISODateFormat) + return reshape(Date[Date(parse(y[i],df)...) for i in 1:length(y)], size(y)) end -format{T<:TimeType}(y::AbstractArray{T},format::String;locale::String="english") = format(y,DateFormat(format,locale)) +format{T<:TimeType}(y::AbstractArray{T},format::String;locale::String="english") = Dates.format(y,DateFormat(format,locale)) function format(y::AbstractArray{Date},df::DateFormat=ISODateFormat) - return reshape([format(y[i],df) for i in 1:length(y)], size(y)) + return reshape([Dates.format(y[i],df) for i in 1:length(y)], size(y)) end function format(y::AbstractArray{DateTime},df::DateFormat=ISODateTimeFormat) - return reshape([format(y[i],df) for i in 1:length(y)], size(y)) + return reshape([Dates.format(y[i],df) for i in 1:length(y)], size(y)) end export ISODateTimeFormat, ISODateFormat, DateFormat From b6169739b4812ba642b387f969329402f0611f6c Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:38:39 -0400 Subject: [PATCH 15/28] Make the DateFunction constructor smarter at detecting when the provided function is not correct --- base/dates/adjusters.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/base/dates/adjusters.jl b/base/dates/adjusters.jl index 1119f3d507ee4..7508aa649803f 100644 --- a/base/dates/adjusters.jl +++ b/base/dates/adjusters.jl @@ -60,9 +60,13 @@ lastdayofquarter(dt::DateTime) = DateTime(lastdayofquarter(Date(dt))) immutable DateFunction f::Function # validate boolean, single-arg inner constructor - function DateFunction(f::Function,negate::Bool,dt::Dates.TimeType) - f(dt) in (true,false) || throw(ArgumentError("Provided function must take a single TimeType argument and return true or false")) - n = !negate ? identity : (!) + 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 From 43729b3aa1e3ec89d3b9b795fc38c029aa830b81 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:39:06 -0400 Subject: [PATCH 16/28] Remove redundant method definitions for Date/DateTime --- base/dates/adjusters.jl | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/base/dates/adjusters.jl b/base/dates/adjusters.jl index 7508aa649803f..b98066572ad4e 100644 --- a/base/dates/adjusters.jl +++ b/base/dates/adjusters.jl @@ -86,20 +86,11 @@ function adjust(func::Function,start;step::Period=Day(1),negate::Bool=false,limi end # Constructors using DateFunctions -function Date(func::Function,y;step::Period=Day(1),negate::Bool=false,limit::Int=10000) - return adjust(DateFunction(func,negate,Date(y)),Date(y),step,limit) -end -function Date(func::Function,y,m;step::Period=Day(1),negate::Bool=false,limit::Int=10000) - return adjust(DateFunction(func,negate,Date(y,m)),Date(y,m),step,limit) -end -function Date(func::Function,y,m,d;step::Period=Day(1),negate::Bool=false,limit::Int=10000) +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;step::Period=Day(1),negate::Bool=false,limit::Int=10000) - return adjust(DateFunction(func,negate,DateTime(y)),DateTime(y),step,limit) -end -function DateTime(func::Function,y,m;step::Period=Day(1),negate::Bool=false,limit::Int=10000) +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) From 1c4c5f4118891bbaf3ec692f726870b4407da160 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:39:38 -0400 Subject: [PATCH 17/28] recur now handles 'empty ranges' and the code has been cleaned up --- base/dates/adjusters.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/base/dates/adjusters.jl b/base/dates/adjusters.jl index b98066572ad4e..dec86bdd8b6c3 100644 --- a/base/dates/adjusters.jl +++ b/base/dates/adjusters.jl @@ -140,14 +140,15 @@ function tolast(dt::TimeType,dow::Int;of::Union(Type{Year},Type{Month})=Month) 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[] - #sizehint(a,) - df = DateFunction(fun,negate,start) + check = start <= stop ? 1 : -1 + df = Dates.DateFunction(fun,negate,start) while true next = Dates.adjust(df,start,step,limit) - next > stop && break + cmp(next,stop) == check && break push!(a,next) - start = start == next ? start+step : next+step + start = next + step end return a end From c92273bea0bc988623f373e6e4d26d2eec08f869 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:41:06 -0400 Subject: [PATCH 18/28] Another round of updates on the docs --- doc/manual/dates.rst | 103 ++++++++++++++++++++++++++----------------- doc/stdlib/dates.rst | 40 +++++++++-------- 2 files changed, 84 insertions(+), 59 deletions(-) diff --git a/doc/manual/dates.rst b/doc/manual/dates.rst index 70bf94fb4fd7b..dbc3c555abbe9 100644 --- a/doc/manual/dates.rst +++ b/doc/manual/dates.rst @@ -4,15 +4,15 @@ Date and DateTime ************************************* -The ``Dates`` module provides two ``TimeType`` types: ``Date`` and ``DateTime``, representing day and millisecond levels of precision, respectively. The motivation for distinct types is simple: some operations are much simpler, both in terms of code and mental reasoning, when the complexities of greater precision don't have to be dealt with. For example, since the ``Date`` type has a "day-precision" (i.e. no hours, minutes, or seconds), it means that normal considerations for time zones, daylight savings/summer time, and leap seconds are unnecessary. +The ``Dates`` module provides two types for working with dates: ``Date`` and ``DateTime``, representing day and millisecond precision, respectively; both are subtypes of the abstract ``TimeType``. The motivation for distinct types is simple: some operations are much simpler, both in terms of code and mental reasoning, when the complexities of greater precision don't have to be dealt with. For example, since the ``Date`` type only resolves to the precision of a single date (i.e. no hours, minutes, or seconds), normal considerations for time zones, daylight savings/summer time, and leap seconds are unnecessary and avoided. -Both ``Date`` and ``DateTime`` are immutable, and wrap ``Int64`` ``UTInstant`` types, which represents a continuously increasing machine timeline based on the UT second [1]_. The ``DateTime`` type is *timezone-unaware* (in Python parlance) or is analogous to a *LocalDateTime* (in Java 8). Additional time zone functionality can be added through the `Timezones.jl package `_, which compiles the `Olsen Time Zone Database `_. Both ``Date`` and ``DateTime`` are based on the ISO 8601 standard, which follows the proleptic Gregorian calendar. One note is that the ISO 8601 standard is particular about BC/BCE dates. In common text, the last day of the BC/BCE era, 1-12-31 BC/BCE, was followed by 1-1-1 AD/CE, thus no year 0 exists. The ISO standard, however, states that 1 BC/BCE is year 0, so ``0000-12-31`` is the day before ``0001-01-01``, and year ``-0001`` (yes, negative 1 for the year) is 2 BC/BCE, year ``-0002`` is 3 BC/BCE, etc. +Both ``Date`` and ``DateTime`` are basically immutable ``Int64`` wrappers. The single ``instant`` field of either type is actually a ``UTInstant{P}`` type, which represents a continuously increasing machine timeline based on the UT second [1]_. The ``DateTime`` type is *timezone-unaware* (in Python parlance) or is analogous to a *LocalDateTime* in Java 8. Additional time zone functionality can be added through the `Timezones.jl package `_, which compiles the `Olsen Time Zone Database `_. Both ``Date`` and ``DateTime`` are based on the ISO 8601 standard, which follows the proleptic Gregorian calendar. One note is that the ISO 8601 standard is particular about BC/BCE dates. In general, the last day of the BC/BCE era, 1-12-31 BC/BCE, was followed by 1-1-1 AD/CE, thus no year 0 exists. The ISO standard, however, states that 1 BC/BCE is year 0, so ``0000-12-31`` is the day before ``0001-01-01``, and year ``-0001`` (yes, negative 1 for the year) is 2 BC/BCE, year ``-0002`` is 3 BC/BCE, etc. -.. [1] The notion of the UT second is actually quite fundamental. There are basically two different notions of time as we know it, one based on the physical rotation of the earth (one full rotation = 1 day), the other actually based on the SI second (a fixed, constant value). These are radically different! Think about it, a "UT second" defined relative to the rotation of the earth may have a different absolute length depending on the day! Anyway, the fact that the ``Dates`` package bases ``Date`` and ``DateTime`` on UT seconds is a simplifying assumption so that things like leap seconds and all their complexity can be avoided. This basis of time is formally called UT or UT1. Basing types on the UT second basically means that every minute has 60 seconds and every day has 24 hours. +.. [1] The notion of the UT second is actually quite fundamental. There are basically two different notions of time generally accepted, one based on the physical rotation of the earth (one full rotation = 1 day), the other based on the SI second (a fixed, constant value). These are radically different! Think about it, a "UT second", as defined relative to the rotation of the earth, may have a different absolute length depending on the day! Anyway, the fact that ``Date`` and ``DateTime`` are based on UT seconds is a simplifying, yet honest assumption so that things like leap seconds and all their complexity can be avoided. This basis of time is formally called `UT `_ or UT1. Basing types on the UT second basically means that every minute has 60 seconds and every day has 24 hours and leads to more natural calculations when working with calendar dates. Constructors ------------ -``Date`` and ``DateTime`` types can be constructed by parts with integers or ``Period`` types, by parsing, or through adjusters (more on them later):: +``Date`` and ``DateTime`` types can be constructed by integer or ``Period`` types, by parsing, or through adjusters (more on those later):: julia> DateTime(2013) 2013-01-01T00:00:00 @@ -44,26 +44,26 @@ Constructors julia> Date(2013,7,1) 2013-07-01 - julia> Date(Year(2013),Month(7),Day(1)) + julia> Date(Dates.Year(2013),Dates.Month(7),Dates.Day(1)) 2013-07-01 - julia> Date(Month(7),Year(2013)) + julia> Date(Dates.Month(7),Dates.Year(2013)) 2013-07-01 -``Date`` or ``DateTime`` parsing is accomplished by the use of format strings. Format strings work by the notion of defining *delimited* or *fixed-width* "slots" that contain a period to parse and pass to a ``TimeType`` constructor. +``Date`` or ``DateTime`` parsing is accomplished by the use of format strings. Format strings work by the notion of defining *delimited* or *fixed-width* "slots" that contain a period to parse and pass to a ``Date`` or ``DateTime`` constructor. Delimited slots are marked by specifying the delimiter the parser should expect between two subsequent periods; so ``"y-m-d"`` lets the parser know that between the first and second slots in a date string like ``"2014-07-16"``, it should find the ``-`` character. The ``y``, ``m``, and ``d`` characters let the parser know which periods to parse in each slot. Fixed-width slots are specified by repeating the period character the number of times corresponding to the width. So ``"yyyymmdd"`` would correspond to a date string like ``"20140716"``. The parser distinguishes a fixed-width slot by the absence of a delimiter, noting the transition ``"yyyymm"`` from one period character to the next. -Support for text-form month parsing is also supported through the ``u`` and ``U`` characters, for abbreviated and full-length month names, respectively. By default, only English month names are supported, so ``u`` corresponds to "Jan", "Feb", "Mar", etc. And ``U`` corresponds to "January", "February", "March", etc. Just like the query functions ``dayname`` and ``monthname``, however, custom locales can be loaded in a similar fashion by passing in the ``locale=>Dict{UTF8String,Int}`` mapping to the ``MONTHTOVALUEABBR`` and ``MONTHTOVALUE`` for abbreviated and full-name month names, respectively. +Support for text-form month parsing is also supported through the ``u`` and ``U`` characters, for abbreviated and full-length month names, respectively. By default, only English month names are supported, so ``u`` corresponds to "Jan", "Feb", "Mar", etc. And ``U`` corresponds to "January", "February", "March", etc. Just like the query functions ``dayname`` and ``monthname``, however, custom locales can be loaded in a similar fashion by passing in the ``locale=>Dict{UTF8String,Int}`` mapping to the ``Dates.MONTHTOVALUEABBR`` and ``Dates.MONTHTOVALUE`` dicts for abbreviated and full-name month names, respectively. -A full suite of parsing and formatting tests and examples is available `here `_. +A full suite of parsing and formatting tests and examples is available `here `_. Durations/Comparisons --------------------- -Finding the length of time between two ``Date`` or ``DateTime`` is straightforward. The difference between ``Date`` is returned in the number of ``Day``, and ``DateTime`` in the number of ``Millisecond``. Similarly, comparing ``TimeType`` is a simple matter of comparing the underlying machine instants.:: +Finding the length of time between two ``Date`` or ``DateTime`` is straightforward given their underlying representation as ``UTInstant{Day}`` and ``UTInstant{Millisecond}``, respectively. The difference between ``Date`` is returned in the number of ``Day``, and ``DateTime`` in the number of ``Millisecond``. Similarly, comparing ``TimeType`` is a simple matter of comparing the underlying machine instants (which in turn compares the internal ``Int64`` values).:: julia> dt = Date(2012,2,29) 2012-02-29 @@ -113,30 +113,6 @@ Finding the length of time between two ``Date`` or ``DateTime`` is straightforwa julia> dt - dt2 381110402000 milliseconds -TimeType-Period Arithmetic --------------------------- - -It's good practice when using any language/date framework to be familiar with how date-period arithmetic is handled as there are some `tricky issues `_ to deal with (though much less so for day-precision types). - -The ``Dates`` module approach tries to follow the simple principle of trying to change as little as possible when doing ``Period`` arithmetic. This approach is also often known as *calendrical* arithmetic or what you would probably guess if someone were to ask you the same calculation in a conversation. Why all the fuss? Let's take a classic example: add 1 month to January 31st, 2014. What's the answer? Javascript will say March 3 (assumes 31 days). PHP says March 2 (assumes 30 days). The fact is, there is no one right answer. In the ``Dates`` module, it would give the result of February 28th. How does it figure that out? I like to think of the classic 7-7-7 gambling game in casinos. - -Now just imagine that instead of 7-7-7, the slots are Year-Month-Day, or in our example, 2014-01-31. When you ask to add 1 month to this date, the month slot is incremented, so now we have 2014-02-31. Then the day number is checked if it is greater than the last valid day of the new month; if it is (as in the case above), the day number is adjusted down to the last valid day (28). What are ramifications with this approach? Go ahead and add another month to our date, ``2014-02-28 + Month(1) == 2014-03-28``. What? Were you expecting the last day of March? Nope, sorry, remember the 7-7-7 slots. As few slots as possible are going to change, so we first increment the month slot by 1, 2014-03-28, and boom, we're done because that's a valid date. On the other hand, if we were to add 2 months to our original date, 2014-01-31, then we end up with 2014-03-31, as expected. The other ramification of this approach is a loss in associativity when a specific ordering is forced (i.e. adding things in different orders results in different outcomes). For example:: - - julia> (Date(2014,1,29)+Day(1)) + Month(1) - 2014-02-28 - - julia> (Date(2014,1,29)+Month(1)) + Day(1) - 2014-03-01 - -What's going on there? In the first line, we're adding 1 day to January 29th, which results in 2014-01-30; then we add 1 month, so we get 2014-02-30, which then adjusts down to 2014-02-28. In the second example, we add 1 month *first*, where we get 2014-02-29, which adjusts down to 2014-02-28, and *then* add 1 day, which results in 2014-03-01. One design principle that helps in this case is that, in the presence of multiple Periods, the operations will be based on the Period *types*, not their value or order; meaning ``Year`` will always be added first, then ``Month``, then ``Week``, etc. Hence the following *does* result in associativity and Just Works:sup:`TM`:: - - julia> Date(2014,1,29) + Day(1) + Month(1) - 2014-03-01 - - julia> Date(2014,1,29) + Month(1) + Day(1) - 2014-03-01 - -Tricky? Perhaps. What is an innocent ``Dates`` user to do? The bottom line is to be aware that explicitly forcing a certain associativity, when dealing with months, may lead to some unexpected results, but otherwise, everything works as expected. Thankfully, that's pretty much the extent of the odd cases in date-period arithmetic when dealing with time in UT (avoiding the "joys" of dealing with daylight savings, leap seconds, etc.). Accessor Functions ------------------ @@ -166,7 +142,7 @@ While propercase return the same value in the corresponding ``Period`` type:: julia> Dates.Day(t) 31 days -Compound methods are provided, as they provide a measure of efficiency if multiple fields are needed:: +Compound methods are provided, as they provide a measure of efficiency if multiple fields are needed at the same time:: julia> Dates.yearmonth(t) (2014,1) @@ -218,7 +194,7 @@ Month of the year:: As well as informationa about the ``TimeType``'s year and quarter:: - julia> Dates.isleap(t) + julia> Dates.isleapyear(t) false julia> Dates.dayofyear(t) @@ -242,12 +218,38 @@ The ``dayname`` and ``monthname`` methods can also take an optional ``locale`` k Similarly for the ``monthname`` function, a mapping of ``locale=>Dict{Int,UTF8String}`` should be loaded in ``Dates.VALUETOMONTH``. +TimeType-Period Arithmetic +-------------------------- + +It's good practice when using any language/date framework to be familiar with how date-period arithmetic is handled as there are some `tricky issues `_ to deal with (though much less so for day-precision types). + +The ``Dates`` module approach tries to follow the simple principle of trying to change as little as possible when doing ``Period`` arithmetic. This approach is also often known as *calendrical* arithmetic or what you would probably guess if someone were to ask you the same calculation in a conversation. Why all the fuss about this? Let's take a classic example: add 1 month to January 31st, 2014. What's the answer? Javascript will say `March 3 `_ (assumes 31 days). PHP says `March 2 `_ (assumes 30 days). The fact is, there is no right answer. In the ``Dates`` module, it gives the result of February 28th. How does it figure that out? I like to think of the classic 7-7-7 gambling game in casinos. + +Now just imagine that instead of 7-7-7, the slots are Year-Month-Day, or in our example, 2014-01-31. When you ask to add 1 month to this date, the month slot is incremented, so now we have 2014-02-31. Then the day number is checked if it is greater than the last valid day of the new month; if it is (as in the case above), the day number is adjusted down to the last valid day (28). What are the ramifications with this approach? Go ahead and add another month to our date, ``2014-02-28 + Month(1) == 2014-03-28``. What? Were you expecting the last day of March? Nope, sorry, remember the 7-7-7 slots. As few slots as possible are going to change, so we first increment the month slot by 1, 2014-03-28, and boom, we're done because that's a valid date. On the other hand, if we were to add 2 months to our original date, 2014-01-31, then we end up with 2014-03-31, as expected. The other ramification of this approach is a loss in associativity when a specific ordering is forced (i.e. adding things in different orders results in different outcomes). For example:: + + julia> (Date(2014,1,29)+Dates.Day(1)) + Dates.Month(1) + 2014-02-28 + + julia> (Date(2014,1,29)+Dates.Month(1)) + Dates.Day(1) + 2014-03-01 + +What's going on there? In the first line, we're adding 1 day to January 29th, which results in 2014-01-30; then we add 1 month, so we get 2014-02-30, which then adjusts down to 2014-02-28. In the second example, we add 1 month *first*, where we get 2014-02-29, which adjusts down to 2014-02-28, and *then* add 1 day, which results in 2014-03-01. One design principle that helps in this case is that, in the presence of multiple Periods, the operations will be ordered by the Periods' *types*, not their value or positional order; this means ``Year`` will always be added first, then ``Month``, then ``Week``, etc. Hence the following *does* result in associativity and Just Works:sup:`TM`:: + + julia> Date(2014,1,29) + Dates.Day(1) + Dates.Month(1) + 2014-03-01 + + julia> Date(2014,1,29) + Dates.Month(1) + Dates.Day(1) + 2014-03-01 + +Tricky? Perhaps. What is an innocent ``Dates`` user to do? The bottom line is to be aware that explicitly forcing a certain associativity, when dealing with months, may lead to some unexpected results, but otherwise, everything should work as expected. Thankfully, that's pretty much the extent of the odd cases in date-period arithmetic when dealing with time in UT (avoiding the "joys" of dealing with daylight savings, leap seconds, etc.). + + Adjuster Functions ------------------ As convenient as date-period arithmetics are, often the kinds of calculations needed on dates take on a *calendrical* or *temporal* nature rather than a fixed number of periods. Holidays are a perfect example; most follow rules such as ``Memorial Day = Last Monday of May``, or ``Thanksgiving = 4th Thursday of November``. These kinds of temporal expressions deal with rules relative to the calendar, like first or last of the month, next Tuesday, or the first and third Wednesdays, etc. -The ``Dates`` module provides the *adjuster* API through several convenient methods that aid in simply and succinctly expressing temporal rules. The first group of adjuster methods deal with the first and last of weeks, months, quarters, and years. They each take a single ``TimeType`` as input and return the first or last of the desired period relative to the input. +The ``Dates`` module provides the *adjuster* API through several convenient methods that aid in simply and succinctly expressing temporal rules. The first group of adjuster methods deal with the first and last of weeks, months, quarters, and years. They each take a single ``TimeType`` as input and return or *adjust to* the first or last of the desired period relative to the input. :: @@ -263,7 +265,7 @@ The ``Dates`` module provides the *adjuster* API through several convenient meth julia> Dates.lastdayofquarter(Date(2014,7,16)) 2014-09-30 -The next four higher-order methods, ``tofirst``, ``tolast``, ``tonext``, and ``toprev``, generalize working with temporal expressions by taking a ``DateFunction`` as first argument, along with a starting ``TimeType``. A ``DateFunction`` is just a function that takes a single ``TimeType`` as input and returns a ``Bool``, ``true`` indicating a satisfied adjustment criterion. +The next four higher-order methods, ``tofirst``, ``tolast``, ``tonext``, and ``toprev``, generalize working with temporal expressions by taking a ``DateFunction`` as first argument, along with a starting ``TimeType``. A ``DateFunction`` is just a function, usually anonymous, that takes a single ``TimeType`` as input and returns a ``Bool``, ``true`` indicating a satisfied adjustment criterion. For example:: julia> istuesday = x->Dates.dayofweek(x) == Dates.Tuesday # Returns true if Tuesday @@ -300,13 +302,32 @@ The ``tofirst`` and ``tolast`` similarly take a ``DateFunction`` as first argume julia> Dates.tolast(istuesday, Date(2014,7,13); of=Dates.Year) 2014-12-30 -The final method in the adjuster API is the ``recur`` function. ``recur`` vectorizes the adjustment process by taking a start and stop date (optionally specificed by a ``StepRange``), along with a ``DateFunction`` to specify all valid dates/moments to be returned in the specified range. In this case, the ``DateFunction`` is often referred to as the "inclusion" function because it specifies (by returning true) which dates/moments should be included in the returned vector of dates. +The final method in the adjuster API is the ``recur`` function. ``recur`` vectorizes the adjustment process by taking a start and stop date (optionally specificed by a ``StepRange``), along with a ``DateFunction`` to specify all valid dates/moments to be returned in the specified range. In this case, the ``DateFunction`` is often referred to as the "inclusion" function because it specifies (by returning true) which dates/moments should be included in the returned vector of dates. + +:: + # Pittsburgh street cleaning; Every 2nd Tuesday from April to November + # Date range from January 1st, 2014 to January 1st, 2015 + julia> dr = Dates.Date(2014):Dates.Date(2015); + julia> recur(dr) do x + Dates.dayofweek(x) == Dates.Tue && + Dates.April <= Dates.month(x) <= Dates.Nov && + Dates.dayofweekofmonth(x) == 2 + end + 8-element Array{Date,1}: + 2014-04-08 + 2014-05-13 + 2014-06-10 + 2014-07-08 + 2014-08-12 + 2014-09-09 + 2014-10-14 + 2014-11-11 Period Types ------------ -Periods are a human view of discrete, sometimes irregular durations of time. Consider 1 month; it could represent, in days, a value of 28, 29, 30, or 31 depending on the year and month context. Or a year could represent 365 or 366 days in the case of a leap year. ``Period`` types are simple ``Int64`` wrappers and are constructed by wrapping any ``Integer`` type, i.e. ``Year(1)`` or ``Month(3.0)``. Arithmetic between ``Period`` of the same type behave like ``Integer``, and limited ``Period-Real`` arithmetic is available for scaling (``*`` and ``div``) +Periods are a human view of discrete, sometimes irregular durations of time. Consider 1 month; it could represent, in days, a value of 28, 29, 30, or 31 depending on the year and month context. Or a year could represent 365 or 366 days in the case of a leap year. ``Period`` types are simple ``Int64`` wrappers and are constructed by wrapping any convertible to ``Int64`` type, i.e. ``Year(1)`` or ``Month(3.0)``. Arithmetic between ``Period`` of the same type behave like integers, and limited ``Period-Real`` arithmetic is available. :: julia> y1 = Dates.Year(1) @@ -340,4 +361,4 @@ Periods are a human view of discrete, sometimes irregular durations of time. Con 3 years -Full descriptions of exported functions in the Dates module is availbe `here `_. \ No newline at end of file +Function API reference for the Dates module is available `here `_. diff --git a/doc/stdlib/dates.rst b/doc/stdlib/dates.rst index cca19682606b3..4935e5a8c4ab2 100644 --- a/doc/stdlib/dates.rst +++ b/doc/stdlib/dates.rst @@ -1,8 +1,8 @@ Dates Functions --------------- -All Dates functions are defined in the ``Dates`` module; note that only the ``Date`` and ``DateTime`` functions are exported; -to use all other ``Dates`` functions, you'll need to prefix each function call with an explicit ``Dates.``, e.g. ``Dates.dayofweek(dt)`` or ``Dates.now()``; +All Dates functions are defined in the ``Dates`` module; note that only the ``Date``, ``DateTime``, and ``now`` functions are exported; +to use all other ``Dates`` functions, you'll need to prefix each function call with an explicit ``Dates.``, e.g. ``Dates.dayofweek(dt)``; alternatively, you could call ``using Base.Dates`` to bring all exported functions into ``Main`` to be used without the ``Dates.`` prefix. .. currentmodule:: Base @@ -23,7 +23,7 @@ alternatively, you could call ``using Base.Dates`` to bring all exported functio provided ``y, m, d...`` 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 (given that ``f::Function`` + the max number of iterations the adjustment API will pursue before throwing an error (in the case that ``f::Function`` is never satisfied). .. function:: ``DateTime(dt::Date) -> DateTime`` @@ -33,7 +33,7 @@ alternatively, you could call ``using Base.Dates`` to bring all exported functio .. function:: ``DateTime(dt::String, format::String; locale="english") -> DateTime`` - Construct a DateTime type by parsing a ``dt`` date string following the pattern given in + Construct a DateTime type by parsing the ``dt`` date string following the pattern given in the ``format`` string. The following codes can be used for constructing format strings: | Code | Matches | Comment @@ -49,6 +49,10 @@ alternatively, you could call ``using Base.Dates`` to bring all exported functio | ``s`` | .500 | Matches milliseconds | ``yyyymmdd`` | 19960101 | Matches fixed-width year, month, and day + All characters not listed above are treated as delimiters between date and time slots. + So a ``dt`` string of "1996-01-15T00:00:00.0" would have a ``format`` string + like "y-m-dTH:M:S.s". + .. function:: ``Date(y, [m, d]) -> Date`` Construct a ``Date`` type by parts. Arguments must be convertible to @@ -78,6 +82,11 @@ alternatively, you could call ``using Base.Dates`` to bring all exported functio Construct a Date type by parsing a ``dt`` date string following the pattern given in the ``format`` string. Follows the same convention as ``DateTime`` above. +.. function:: ``now() -> DateTime`` + + Returns a DateTime type corresponding to the user's system + time. + Accessor Functions ~~~~~~~~~~~~~~~~~~ @@ -146,7 +155,7 @@ Query Functions 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.`` Lies in the range 1:5. + 2 = Second Monday of the month, etc.`` In the range 1:5. .. function:: ``daysofweekinmonth(dt::TimeType) -> Int`` @@ -167,7 +176,7 @@ Query Functions Returns the number of days in the month of ``dt``. Value will be 28, 29, 30, or 31. -.. function:: ``isleap(dt::TimeType) -> Bool`` +.. function:: ``isleapyear(dt::TimeType) -> Bool`` Returns true if the year of ``dt`` is a leap year. @@ -193,8 +202,7 @@ Adjuster Functions .. function:: ``trunc(dt::TimeType, ::Type{Period}) -> TimeType`` Truncates the value of ``dt`` according to the provided ``Period`` type. - E.g. if ``dt`` is ``1996-01-01T12:30:00`` and ``trunc(dt,Day)``, - ``1996-01-01T00:00:00`` will be returned, truncating up to the ``Day``. + E.g. if ``dt`` is ``1996-01-01T12:30:00``, then ``trunc(dt,Day) == 1996-01-01T00:00:00``. .. function:: ``firstdayofweek(dt::TimeType) -> TimeType`` @@ -254,21 +262,22 @@ Adjuster Functions Adjusts ``dt`` by iterating at most ``limit`` iterations by ``step`` increments until ``func`` returns true. ``func`` must take a single ``TimeType`` argument and return a ``Bool``. - ``same`` allows ``dt`` to be considered as satisfying ``func``. ``negate`` will make the adjustment + ``same`` 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=Day(-1),negate=false,limit=10000,same=false) -> TimeType`` Adjusts ``dt`` by iterating at most ``limit`` iterations by ``step`` increments until ``func`` returns true. ``func`` must take a single ``TimeType`` argument and return a ``Bool``. - ``same`` allows ``dt`` to be considered as satisfying ``func``. ``negate`` will make the adjustment + ``same`` allows ``dt`` to be considered in satisfying ``func``. ``negate`` will make the adjustment process terminate when ``func`` returns false instead of true. .. function:: ``recur{T<:TimeType}(func::Function,dr::StepRange{T};negate=false,limit=10000) -> Array{T,1}`` ``func`` takes a single TimeType argument and returns a ``Bool`` indicating whether the input - should be "included" in the final set. ``recur`` applies ``func`` over the range of ``dr`` to - generate an Array from dates/moments for which ``func`` returns true (unless ``negate=true``). + should be "included" in the final set. ``recur`` applies ``func`` over each element in the + range 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. Periods @@ -323,14 +332,9 @@ Periods Conversion Functions ~~~~~~~~~~~~~~~~~~~~ -.. function:: ``now() -> DateTime`` - - Returns a DateTime type corresponding to the user's system - time. Corresponds to the UTC/GMT timezone. - .. function:: ``today() -> Date`` - Converts the output of ``now()`` to a Date. + Returns the date portion of ``now()``. .. function:: ``unix2datetime(x) -> DateTime`` From 74dfe682b8e32627d1fdf1d0dbe30e1211496cda Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:42:38 -0400 Subject: [PATCH 19/28] Big overhaul of date ranges.jl. Code is now much more concise and correct by using a single inner 'len' function that is used in length(), steprem(), and in(). Also added lots and lots of tests to try to cover all combinations of Date/DateTime, different Period steps, with positive and negative step values. --- base/dates/ranges.jl | 92 +++------ test/dates/ranges.jl | 446 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 434 insertions(+), 104 deletions(-) diff --git a/base/dates/ranges.jl b/base/dates/ranges.jl index aecb9782eaba0..161de7ff502b0 100644 --- a/base/dates/ranges.jl +++ b/base/dates/ranges.jl @@ -1,81 +1,33 @@ # Date/DateTime Ranges -# Given a start and end date, how many steps/periods are in between -function Base.length(r::StepRange{Date,Day}) - n = integer(div(r.stop+r.step - r.start, r.step)) - isempty(r) ? zero(n) : n -end -function Base.length(r::StepRange{Date,Week}) - n = integer(div(r.stop+r.step - r.start, 7*value(r.step))) - isempty(r) ? zero(n) : n -end -function Base.length{T<:Union(Week,Day,TimePeriod)}(r::StepRange{DateTime,T}) - n = integer(div(r.stop+r.step - r.start, toms(r.step))) - isempty(r) ? zero(n) : n -end -function Base.length{T<:TimeType,P<:Period}(r::StepRange{T,P}) - isempty(r) && return 0 - start,stop = r.start > r.stop ? (r.stop,r.start) : (r.start,r.stop) - step = r.step < zero(r.step) ? -r.step : r.step - i = 0 - while (start+step*i) <= stop +# Override default step; otherwise it would be Millisecond(1) +Base.colon{T<:DateTime}(start::T, stop::T) = StepRange(start, Day(1), stop) + +# Given a start and end date, how many steps/periods are in between +guess(a::DateTime,b::DateTime,c) = ifloor((int128(b) - int128(a))/toms(c)) +guess(a::Date,b::Date,c) = int(div(int(b - a),days(c))) +function len(a,b,c) + lo, hi, st = min(a,b), max(a,b), abs(c) + i = guess(a,b,c)-1 + while lo+st*i <= hi i += 1 end - return i -end -#Period overflow detection -function Base.length{T<:Period}(r::StepRange{T}) - isempty(r) && return zero(T) - if r.step > one(T) - return Base.checked_add(div(value(r.stop) - value(r.start), value(r.step)), int64(1)) - elseif value(r.step) < int64(-1) - return Base.checked_add(div(value(r.start) - value(r.stop), -value(r.step)), int64(1)) - else - Base.checked_add(div(Base.checked_sub(value(r.stop), value(r.start)), value(r.step)), int64(1)) - end + return i-1 end +Base.length{T<:TimeType}(r::StepRange{T}) = isempty(r) ? 0 : len(r.start,r.stop,r.step) + 1 +# Period ranges hook into Int64 overflow detection +Base.length{P<:Period}(r::StepRange{P}) = length(StepRange(value(r.start),value(r.step),value(r.stop))) -# Given a start and stop date, calculate the difference between -# the given stop date and the last valid date given the Period step +# Used to calculate the last valid date in the range given the start, stop, and step # last = stop - steprem(start,stop,step) -toobig(start::Date,stop::Date,step::Year) = (stop-start) > Day(3652425000*value(step)) -toobig(start::Date,stop::Date,step::Month) = (stop-start) > Day(365242500*value(step)) -toobig(start::DateTime,stop::DateTime,step::Year) = (stop-start) > Day(3652425000*value(step)) -toobig(start::DateTime,stop::DateTime,step::Month) = (stop-start) > Day(365242500*value(step)) +Base.steprem{T<:TimeType}(a::T,b::T,c) = b - (a + c*len(a,b,c)) -Base.steprem(a::Date,b::Date,c::Day) = (b-a) % c -Base.steprem(a::Date,b::Date,c::Week) = (b-a) % (7*value(c)) - -toms(c::Week) = 604800000*value(c) -toms(c::Day) = 86400000*value(c) -toms(c::Hour) = 3600000*value(c) -toms(c::Minute) = 60000*value(c) -toms(c::Second) = 1000*value(c) -toms(c::Millisecond) = value(c) - -Base.steprem(a::DateTime,b::DateTime,c::Union(Week,Day,TimePeriod)) = (b-a) % toms(c) -function Base.steprem(start::TimeType,stop::TimeType,step::Period) - start,stop = start > stop ? (stop,start) : (start,stop) - step = step < zero(step) ? -step : step - toobig(start,stop,step) && throw(ArgumentError("Desired range is too big")) - i = 1 - while (start+step*i) <= stop - i += 1 - end - return stop - (start+step*(i-1)) -end - -# Specialize for Date-Day, DateTime-Millisecond? import Base.in -# TODO: use binary search -function in{T<:TimeType,S<:Period}(x, r::StepRange{T,S}) - isempty(r) && return false - for d in r - d == x && return true - end - return false +function in{T<:TimeType}(x::T, r::StepRange{T}) + n = len(first(r),x,step(r)) + 1 + n >= 1 && n <= length(r) && r[n] == x end -Base.start(r::StepRange{Date}) = 0 -Base.next(r::StepRange{Date}, i) = (r.start+r.step*i,i+1) -Base.done{S<:Period}(r::StepRange{Date,S}, i::Integer) = length(r) <= i +Base.start{T<:TimeType}(r::StepRange{T}) = 0 +Base.next{T<:TimeType}(r::StepRange{T}, i) = (r.start+r.step*i,i+1) +Base.done{T<:TimeType,S<:Period}(r::StepRange{T,S}, i::Integer) = length(r) <= i \ No newline at end of file diff --git a/test/dates/ranges.jl b/test/dates/ranges.jl index 4df7cc9c53fe9..bc9de8c08904a 100644 --- a/test/dates/ranges.jl +++ b/test/dates/ranges.jl @@ -1,40 +1,367 @@ -# Ranges +function test_all_combos() + for T in (Dates.Date,Dates.DateTime) + f1 = T(2014); l1 = T(2013,12,31) + f2 = T(2014); l2 = T(2014) + f3 = T(-2000); l3 = T(2000) + f4 = typemin(T); l4 = typemax(T) + + for P in subtypes(Dates.DatePeriod) + for pos_step in (P(1),P(2),P(50),P(2048),P(10000)) + # empty range + dr = f1:pos_step:l1 + @test length(dr) == 0 + @test isempty(dr) + @test first(dr) == f1 + @test last(dr) == f1-one(pos_step) #pending #7900 + @test length([i for i in dr]) == 0 + @test_throws ErrorException minimum(dr) + @test_throws ErrorException maximum(dr) + @test_throws BoundsError dr[1] + @test findin(dr,dr) == Int64[] + @test [dr] == T[] + @test isempty(reverse(dr)) + @test length(reverse(dr)) == 0 + @test first(reverse(dr)) == f1-one(pos_step) #pending #7900 + @test last(reverse(dr)) == f1 + @test issorted(dr) + @test sortperm(dr) == 1:1:0 + @test !(f1 in dr) + @test !(l1 in dr) + @test !(f1-pos_step in dr) + @test !(l1+pos_step in dr) + + for (f,l) in ((f2,l2),(f3,l3),(f4,l4)) + dr = f:pos_step:l + len = length(dr) + @test len > 0 + @test typeof(len) <: Int + @test !isempty(dr) + @test first(dr) == f + @test last(dr) <= l + @test minimum(dr) == first(dr) + @test maximum(dr) == last(dr) + @test dr[1] == f + @test dr[end] <= l + @test next(dr,start(dr)) == (first(dr),1) + + if len < 10000 + dr1 = [i for i in dr] #expensive + @test length(dr1) == len + @test findin(dr,dr) == [1:len] #exp + @test length([dr]) == len + end + @test !isempty(reverse(dr)) + @test length(reverse(dr)) == len + @test last(reverse(dr)) == f + @test issorted(dr) + @test f in dr + + end + end + for neg_step in (P(-1),P(-2),P(-50),P(-2048),P(-10000)) + # empty range + dr = l1:neg_step:f1 + @test length(dr) == 0 + @test isempty(dr) + @test first(dr) == l1 + @test last(dr) == l1+one(neg_step) #pending #7900 + @test length([i for i in dr]) == 0 + @test_throws ErrorException minimum(dr) + @test_throws ErrorException maximum(dr) + @test_throws BoundsError dr[1] + @test findin(dr,dr) == Int64[] + @test [dr] == T[] + @test isempty(reverse(dr)) + @test length(reverse(dr)) == 0 + @test first(reverse(dr)) == l1+one(neg_step) #pending #7900 + @test last(reverse(dr)) == l1 + @test !issorted(dr) + @test sortperm(dr) == 0:-1:1 + @test !(l1 in dr) + @test !(l1 in dr) + @test !(l1-neg_step in dr) + @test !(l1+neg_step in dr) + + for (f,l) in ((f2,l2),(f3,l3),(f4,l4)) + dr = l:neg_step:f + len = length(dr) + @test len > 0 + @test typeof(len) <: Int + @test !isempty(dr) + @test first(dr) == l + @test last(dr) >= f + @test minimum(dr) == last(dr) + @test maximum(dr) == first(dr) + @test dr[1] == l + @test dr[end] >= f + @test next(dr,start(dr)) == (first(dr),1) + + if len < 10000 + dr1 = [i for i in dr] + @test length(dr1) == len + @test findin(dr,dr) == [1:len] + @test length([dr]) == len + end + @test !isempty(reverse(dr)) + @test length(reverse(dr)) == len + @test !issorted(dr) + @test l in dr + end + end + end + if T == Dates.DateTime + for P in subtypes(Dates.TimePeriod) + for pos_step in (P(1),P(2),P(50),P(2048),P(10000)) + # empty range + dr = f1:pos_step:l1 + @test length(dr) == 0 + @test isempty(dr) + @test first(dr) == f1 + @test last(dr) == f1-one(pos_step) #pending #7900 + @test length([i for i in dr]) == 0 + @test_throws ErrorException minimum(dr) + @test_throws ErrorException maximum(dr) + @test_throws BoundsError dr[1] + @test findin(dr,dr) == Int64[] + @test [dr] == T[] + @test isempty(reverse(dr)) + @test length(reverse(dr)) == 0 + @test first(reverse(dr)) == f1-one(pos_step) #pending #7900 + @test last(reverse(dr)) == f1 + @test issorted(dr) + @test sortperm(dr) == 1:1:0 + @test !(f1 in dr) + @test !(l1 in dr) + @test !(f1-pos_step in dr) + @test !(l1+pos_step in dr) + + for (f,l) in ((f2,l2),(f3,l3),(f4,l4)) + dr = f:pos_step:l + len = length(dr) + @test len > 0 + @test typeof(len) <: Int + @test !isempty(dr) + @test first(dr) == f + @test last(dr) <= l + @test minimum(dr) == first(dr) + @test maximum(dr) == last(dr) + @test dr[1] == f + @test dr[end] <= l + @test next(dr,start(dr)) == (first(dr),1) + + if len < 10000 + dr1 = [i for i in dr] + @test length(dr1) == len + @test findin(dr,dr) == [1:len] + @test length([dr]) == len + end + @test !isempty(reverse(dr)) + @test length(reverse(dr)) == len + @test last(reverse(dr)) == f + @test issorted(dr) + @test f in dr + + end + end + for neg_step in (P(-1),P(-2),P(-50),P(-2048),P(-10000)) + # empty range + dr = l1:neg_step:f1 + @test length(dr) == 0 + @test isempty(dr) + @test first(dr) == l1 + @test last(dr) == l1+one(neg_step) #pending #7900 + @test length([i for i in dr]) == 0 + @test_throws ErrorException minimum(dr) + @test_throws ErrorException maximum(dr) + @test_throws BoundsError dr[1] + @test findin(dr,dr) == Int64[] + @test [dr] == T[] + @test isempty(reverse(dr)) + @test length(reverse(dr)) == 0 + @test first(reverse(dr)) == l1+one(neg_step) #pending #7900 + @test last(reverse(dr)) <= l1 + @test !issorted(dr) + @test sortperm(dr) == 0:-1:1 + @test !(l1 in dr) + @test !(l1 in dr) + @test !(l1-neg_step in dr) + @test !(l1+neg_step in dr) + + for (f,l) in ((f2,l2),(f3,l3),(f4,l4)) + dr = l:neg_step:f + len = length(dr) + @test len > 0 + @test typeof(len) <: Int + @test !isempty(dr) + @test first(dr) == l + @test last(dr) >= f + @test minimum(dr) == last(dr) + @test maximum(dr) == first(dr) + @test dr[1] == l + @test dr[end] >= f + @test next(dr,start(dr)) == (first(dr),1) + + if len < 10000 + dr1 = [i for i in dr] + @test length(dr1) == len + @test findin(dr,dr) == [1:len] + @test length([dr]) == len + end + @test !isempty(reverse(dr)) + @test length(reverse(dr)) == len + @test last(reverse(dr)) >= l + @test !issorted(dr) + @test l in dr + + end + end + end + end + end +end +@time test_all_combos() + +# All the range representations we want to test +# Date ranges +dr = Dates.DateTime(2013,1,1):Dates.DateTime(2013,2,1) +dr1 = Dates.DateTime(2013,1,1):Dates.DateTime(2013,1,1) +dr2 = Dates.DateTime(2013,1,1):Dates.DateTime(2012,2,1) # empty range +dr3 = Dates.DateTime(2013,1,1):Dates.Day(-1):Dates.DateTime(2012) # negative step +# Big ranges +dr4 = Dates.DateTime(0):Dates.DateTime(20000,1,1) +dr5 = Dates.DateTime(0):Dates.DateTime(200000,1,1) +dr6 = Dates.DateTime(0):Dates.DateTime(2000000,1,1) +dr7 = Dates.DateTime(0):Dates.DateTime(20000000,1,1) +dr8 = Dates.DateTime(0):Dates.DateTime(200000000,1,1) +dr9 = typemin(Dates.DateTime):typemax(Dates.DateTime) +# Non-default steps +dr10 = typemax(Dates.DateTime):Dates.Day(-1):typemin(Dates.DateTime) +dr11 = typemin(Dates.DateTime):Dates.Week(1):typemax(Dates.DateTime) + +dr12 = typemin(Dates.DateTime):Dates.Month(1):typemax(Dates.DateTime) +dr13 = typemin(Dates.DateTime):Dates.Year(1):typemax(Dates.DateTime) + +dr14 = typemin(Dates.DateTime):Dates.Week(10):typemax(Dates.DateTime) +dr15 = typemin(Dates.DateTime):Dates.Month(100):typemax(Dates.DateTime) +dr16 = typemin(Dates.DateTime):Dates.Year(1000):typemax(Dates.DateTime) +dr17 = typemax(Dates.DateTime):Dates.Week(-10000):typemin(Dates.DateTime) +dr18 = typemax(Dates.DateTime):Dates.Month(-100000):typemin(Dates.DateTime) +dr19 = typemax(Dates.DateTime):Dates.Year(-1000000):typemin(Dates.DateTime) +dr20 = typemin(Dates.DateTime):Dates.Day(2):typemax(Dates.DateTime) + +drs = {dr,dr1,dr2,dr3,dr4,dr5,dr6,dr7,dr8,dr9,dr10, + dr11,dr12,dr13,dr14,dr15,dr16,dr17,dr18,dr19,dr20} +drs2 = map(x->Dates.Date(first(x)):step(x):Dates.Date(last(x)),drs) + +@test map(length,drs) == map(x->size(x)[1],drs) +@test map(length,drs) == map(x->length(Dates.Date(first(x)):step(x):Dates.Date(last(x))),drs) +@test map(length,drs) == map(x->length(reverse(x)),drs) +@test all(map(x->findin(x,x)==[1:length(x)],drs[1:4])) +@test isempty(dr2) +@test all(map(x->reverse(x) == range(last(x), -step(x), length(x)),drs)) +@test all(map(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end])) +@test all(map(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end])) +@test all(map(drs[1:3]) do dd + for (i,d) in enumerate(dd) + @test d == (first(dd) + Dates.Day(i-1)) + end + true +end) +@test_throws MethodError dr + 1 +a = Dates.DateTime(2013,1,1) +b = Dates.DateTime(2013,2,1) +@test map!(x->x+Dates.Day(1),Array(Dates.DateTime,32),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] +@test map(x->x+Dates.Day(1),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] + +@test map(x->a in x,drs[1:4]) == [true,true,false,true] +@test a in dr +@test b in dr +@test Dates.DateTime(2013,1,3) in dr +@test Dates.DateTime(2013,1,15) in dr +@test Dates.DateTime(2013,1,26) in dr +@test !(Dates.DateTime(2012,1,1) in dr) + +@test all(map(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs)) +@test all(map(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs)) + +@test length(b:Dates.Day(-1):a) == 32 +@test length(b:a) == 0 +@test length(b:Dates.Day(1):a) == 0 +@test length(a:Dates.Day(2):b) == 16 +@test last(a:Dates.Day(2):b) == Dates.DateTime(2013,1,31) +@test length(a:Dates.Day(7):b) == 5 +@test last(a:Dates.Day(7):b) == Dates.DateTime(2013,1,29) +@test length(a:Dates.Day(32):b) == 1 +@test last(a:Dates.Day(32):b) == Dates.DateTime(2013,1,1) +@test (a:b)[1] == Dates.DateTime(2013,1,1) +@test (a:b)[2] == Dates.DateTime(2013,1,2) +@test (a:b)[7] == Dates.DateTime(2013,1,7) +@test (a:b)[end] == b +@test first(a:Dates.DateTime(20000,1,1)) == a +@test first(a:Dates.DateTime(200000,1,1)) == a +@test first(a:Dates.DateTime(2000000,1,1)) == a +@test first(a:Dates.DateTime(20000000,1,1)) == a +@test first(a:Dates.DateTime(200000000,1,1)) == a +@test first(a:typemax(Dates.DateTime)) == a +@test first(typemin(Dates.DateTime):typemax(Dates.DateTime)) == typemin(Dates.DateTime) + +# Date ranges +dr = Dates.Date(2013,1,1):Dates.Date(2013,2,1) +dr1 = Dates.Date(2013,1,1):Dates.Date(2013,1,1) +dr2 = Dates.Date(2013,1,1):Dates.Date(2012,2,1) # empty range +dr3 = Dates.Date(2013,1,1):Dates.Day(-1):Dates.Date(2012,1,1) # negative step +# Big ranges +dr4 = Dates.Date(0):Dates.Date(20000,1,1) +dr5 = Dates.Date(0):Dates.Date(200000,1,1) +dr6 = Dates.Date(0):Dates.Date(2000000,1,1) +dr7 = Dates.Date(0):Dates.Date(20000000,1,1) +dr8 = Dates.Date(0):Dates.Date(200000000,1,1) +dr9 = typemin(Dates.Date):typemax(Dates.Date) +# Non-default steps +dr10 = typemax(Dates.Date):Dates.Day(-1):typemin(Dates.Date) +dr11 = typemin(Dates.Date):Dates.Week(1):typemax(Dates.Date) +dr12 = typemin(Dates.Date):Dates.Month(1):typemax(Dates.Date) +dr13 = typemin(Dates.Date):Dates.Year(1):typemax(Dates.Date) +dr14 = typemin(Dates.Date):Dates.Week(10):typemax(Dates.Date) +dr15 = typemin(Dates.Date):Dates.Month(100):typemax(Dates.Date) +dr16 = typemin(Dates.Date):Dates.Year(1000):typemax(Dates.Date) +dr17 = typemax(Dates.Date):Dates.Week(-10000):typemin(Dates.Date) +dr18 = typemax(Dates.Date):Dates.Month(-100000):typemin(Dates.Date) +dr19 = typemax(Dates.Date):Dates.Year(-1000000):typemin(Dates.Date) +dr20 = typemin(Dates.Date):Dates.Day(2):typemax(Dates.Date) + +drs = {dr,dr1,dr2,dr3,dr4,dr5,dr6,dr7,dr8,dr9,dr10, + dr11,dr12,dr13,dr14,dr15,dr16,dr17,dr18,dr19,dr20} + +@test map(length,drs) == map(x->size(x)[1],drs) +@test all(map(x->findin(x,x)==[1:length(x)],drs[1:4])) +@test isempty(dr2) +@test all(map(x->reverse(x) == last(x):-step(x):first(x),drs)) +@test all(map(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end])) +@test all(map(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end])) +@test all(map(drs[1:3]) do dd + for (i,d) in enumerate(dd) + @test d == (first(dd) + Dates.Day(i-1)) + end + true +end) +@test_throws MethodError dr + 1 a = Dates.Date(2013,1,1) b = Dates.Date(2013,2,1) -dr = a:b -@test size(dr) == (32,) -@test length(dr) == 32 -@test findin(dr,dr) == [1:32] -@test findin(a:Dates.Date(2013,1,14),dr) == [1:14] -@test findin(a:Dates.Date(2012,12,31),dr) == Int64[] -@test isempty(a:Dates.Date(2012,12,31)) -@test reverse(dr) == b:Dates.Day(-1):a -@test map!(x->x+Dates.Day(1),Array(Date,32),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] +@test map!(x->x+Dates.Day(1),Array(Dates.Date,32),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] @test map(x->x+Dates.Day(1),dr) == [(a+Dates.Day(1)):(b+Dates.Day(1))] -@test minimum(dr) == a -@test maximum(dr) == b -for (i,d) in enumerate(dr) - @test d == a + Dates.Day(i-1) -end -for (i,d) in enumerate(a:Dates.Day(2):b) - @test d == a + Dates.Day((i-1)*2) -end -@test_throws MethodError dr + 1 #TODO + +@test map(x->a in x,drs[1:4]) == [true,true,false,true] @test a in dr @test b in dr @test Dates.Date(2013,1,3) in dr @test Dates.Date(2013,1,15) in dr @test Dates.Date(2013,1,26) in dr @test !(Dates.Date(2012,1,1) in dr) -em = a:Dates.Date(2012,12,31) -@test !(a in em) #empty range -@test sort(dr) == dr -@test sort(em) == em -@test issorted(dr) -@test issorted(em) -@test !issorted(reverse(dr)) -@test sort(reverse(dr)) == dr -# dr + Dates.Day(1) #TODO + +@test all(map(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs)) +@test all(map(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs)) + @test length(b:Dates.Day(-1):a) == 32 @test length(b:a) == 0 @test length(b:Dates.Day(1):a) == 0 @@ -55,19 +382,21 @@ em = a:Dates.Date(2012,12,31) @test first(a:Dates.Date(200000000,1,1)) == a @test first(a:typemax(Dates.Date)) == a @test first(typemin(Dates.Date):typemax(Dates.Date)) == typemin(Dates.Date) + +# Non-default step sizes @test length(typemin(Dates.Date):Dates.Week(1):typemax(Dates.Date)) == 26351950414948059 -#toobig -@test_throws ArgumentError length(typemin(Dates.Date):Dates.Month(1):typemax(Dates.Date)) -@test_throws ArgumentError length(typemin(Dates.Date):Dates.Year(1):typemax(Dates.Date)) -@test_throws ArgumentError length(typemin(Dates.DateTime):Dates.Month(1):typemax(Dates.DateTime)) -@test_throws ArgumentError length(typemin(Dates.DateTime):Dates.Year(1):typemax(Dates.DateTime)) +# Big Month/Year ranges +@test length(typemin(Dates.Date):Dates.Month(1):typemax(Dates.Date)) == 6060531933867600 +@test length(typemin(Dates.Date):Dates.Year(1):typemax(Dates.Date)) == 505044327822300 +@test length(typemin(Dates.DateTime):Dates.Month(1):typemax(Dates.DateTime)) == 3507324288 +@test length(typemin(Dates.DateTime):Dates.Year(1):typemax(Dates.DateTime)) == 292277024 @test length(typemin(Dates.DateTime):Dates.Week(1):typemax(Dates.DateTime)) == 15250284420 @test length(typemin(Dates.DateTime):Dates.Day(1):typemax(Dates.DateTime)) == 106751990938 @test length(typemin(Dates.DateTime):Dates.Hour(1):typemax(Dates.DateTime)) == 2562047782512 @test length(typemin(Dates.DateTime):Dates.Minute(1):typemax(Dates.DateTime)) == 153722866950720 @test length(typemin(Dates.DateTime):Dates.Second(1):typemax(Dates.DateTime)) == 9223372017043200 -@test length(typemin(DateTime):typemax(DateTime)) == 9223372017043199001 +@test length(typemin(DateTime):Dates.Millisecond(1):typemax(DateTime)) == 9223372017043199001 c = Dates.Date(2013,6,1) @test length(a:Dates.Month(1):c) == 6 @@ -75,6 +404,19 @@ c = Dates.Date(2013,6,1) @test [a:Dates.Month(2):Dates.Date(2013,1,2)] == [a] @test [c:Dates.Month(-1):a] == reverse([a:Dates.Month(1):c]) +@test length(range(Date(2000),366)) == 366 +function testlengths(n) + a = Dates.Date(2000) + for i = 1:n + @test length(range(a,i)) == i + end + return a+Dates.Day(n) +end +testlengths(100000) + +# Custom definition to override default step of DateTime ranges +@test typeof(step(Dates.DateTime(2000):Dates.DateTime(2001))) == Dates.Day + d = Dates.Date(2020,1,1) @test length(a:Dates.Year(1):d) == 8 @test first(a:Dates.Year(1):d) == a @@ -98,6 +440,42 @@ d = Dates.Date(2020,1,1) @test length(Dates.Date(2000,6,23):Dates.Year(-10):Dates.Date(1900,2,28)) == 11 @test length(Dates.Date(2000,1,1):Dates.Year(1):Dates.Date(2000,2,1)) == 1 +function testyearranges(n) + a = b = Dates.Date(0) + for i = 1:n + @test length(a:Dates.Year(1):b) == i + b += Dates.Year(1) + end +end +testyearranges(100000) + +function testmonthranges(n) + a = Dates.Date(1985,12,5) + b = Dates.Date(1986,12,27) + c = Dates.DateTime(1985,12,5) + d = Dates.DateTime(1986,12,27) + for i = 1:n + @test length(a:Dates.Month(1):b) == 13 + @test length(a:Dates.Year(1):b) == 2 + @test length(c:Dates.Month(1):d) == 13 + @test length(c:Dates.Year(1):d) == 2 + a += Dates.Day(1) + b += Dates.Day(1) + end + return b +end +testmonthranges(10000) + +function testmonthranges2(n) + a = b = Dates.Date(2000) + for i = 1:n + @test length(a:Dates.Month(1):b) == i + b += Dates.Month(1) + end + return b +end +testmonthranges2(100000) + @test length(Dates.Year(1):Dates.Year(10)) == 10 @test length(Dates.Year(10):Dates.Year(-1):Dates.Year(1)) == 10 @test length(Dates.Year(10):Dates.Year(-2):Dates.Year(1)) == 5 From 25db0d7dd38896533fbf952ef5756383755d9312 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:43:07 -0400 Subject: [PATCH 20/28] Another round of testing updates with all the recent commits. --- base/dates/accessors.jl | 1 - test/dates/accessors.jl | 2 +- test/dates/adjusters.jl | 252 ++++++++++++++++++++++++++++---------- test/dates/arithmetic.jl | 39 ++---- test/dates/conversions.jl | 25 ++-- test/dates/io.jl | 27 +++- test/dates/periods.jl | 115 +++++++++-------- test/dates/query.jl | 46 ++----- test/dates/types.jl | 57 ++++++--- 9 files changed, 359 insertions(+), 205 deletions(-) diff --git a/base/dates/accessors.jl b/base/dates/accessors.jl index d91718355537a..b394f5cdafb5d 100644 --- a/base/dates/accessors.jl +++ b/base/dates/accessors.jl @@ -55,7 +55,6 @@ dayofmonth(dt::TimeType) = day(dt) yearmonth(dt::TimeType) = yearmonth(days(dt)) monthday(dt::TimeType) = monthday(days(dt)) yearmonthday(dt::TimeType) = yearmonthday(days(dt)) -#TODO: add hourminute, minutesecond, hourminutesecond @vectorize_1arg TimeType year @vectorize_1arg TimeType month diff --git a/test/dates/accessors.jl b/test/dates/accessors.jl index a483de74c53ca..394be1a252155 100644 --- a/test/dates/accessors.jl +++ b/test/dates/accessors.jl @@ -52,7 +52,7 @@ function test_dates(from,to) end end end -test_dates(-2000,2000) +test_dates(-1000,3000) # Test year, month, day, hour, minute function test_dates() diff --git a/test/dates/adjusters.jl b/test/dates/adjusters.jl index 85317fe2e03bf..4c593147e0c5a 100644 --- a/test/dates/adjusters.jl +++ b/test/dates/adjusters.jl @@ -26,57 +26,57 @@ oct = Dates.DateTime(2013,10,10) #Thursday nov = Dates.DateTime(2013,11,11) #Monday dec = Dates.DateTime(2013,12,11) #Wednesday -@test Dates.lastdayofmonth(jan) == DateTime(2013,1,31) -@test Dates.lastdayofmonth(feb) == DateTime(2013,2,28) -@test Dates.lastdayofmonth(mar) == DateTime(2013,3,31) -@test Dates.lastdayofmonth(apr) == DateTime(2013,4,30) -@test Dates.lastdayofmonth(may) == DateTime(2013,5,31) -@test Dates.lastdayofmonth(jun) == DateTime(2013,6,30) -@test Dates.lastdayofmonth(jul) == DateTime(2013,7,31) -@test Dates.lastdayofmonth(aug) == DateTime(2013,8,31) -@test Dates.lastdayofmonth(sep) == DateTime(2013,9,30) -@test Dates.lastdayofmonth(oct) == DateTime(2013,10,31) -@test Dates.lastdayofmonth(nov) == DateTime(2013,11,30) -@test Dates.lastdayofmonth(dec) == DateTime(2013,12,31) - -@test Dates.lastdayofmonth(Date(jan)) == Date(2013,1,31) -@test Dates.lastdayofmonth(Date(feb)) == Date(2013,2,28) -@test Dates.lastdayofmonth(Date(mar)) == Date(2013,3,31) -@test Dates.lastdayofmonth(Date(apr)) == Date(2013,4,30) -@test Dates.lastdayofmonth(Date(may)) == Date(2013,5,31) -@test Dates.lastdayofmonth(Date(jun)) == Date(2013,6,30) -@test Dates.lastdayofmonth(Date(jul)) == Date(2013,7,31) -@test Dates.lastdayofmonth(Date(aug)) == Date(2013,8,31) -@test Dates.lastdayofmonth(Date(sep)) == Date(2013,9,30) -@test Dates.lastdayofmonth(Date(oct)) == Date(2013,10,31) -@test Dates.lastdayofmonth(Date(nov)) == Date(2013,11,30) -@test Dates.lastdayofmonth(Date(dec)) == Date(2013,12,31) - -@test Dates.firstdayofmonth(jan) == DateTime(2013,1,1) -@test Dates.firstdayofmonth(feb) == DateTime(2013,2,1) -@test Dates.firstdayofmonth(mar) == DateTime(2013,3,1) -@test Dates.firstdayofmonth(apr) == DateTime(2013,4,1) -@test Dates.firstdayofmonth(may) == DateTime(2013,5,1) -@test Dates.firstdayofmonth(jun) == DateTime(2013,6,1) -@test Dates.firstdayofmonth(jul) == DateTime(2013,7,1) -@test Dates.firstdayofmonth(aug) == DateTime(2013,8,1) -@test Dates.firstdayofmonth(sep) == DateTime(2013,9,1) -@test Dates.firstdayofmonth(oct) == DateTime(2013,10,1) -@test Dates.firstdayofmonth(nov) == DateTime(2013,11,1) -@test Dates.firstdayofmonth(dec) == DateTime(2013,12,1) - -@test Dates.firstdayofmonth(Date(jan)) == Date(2013,1,1) -@test Dates.firstdayofmonth(Date(feb)) == Date(2013,2,1) -@test Dates.firstdayofmonth(Date(mar)) == Date(2013,3,1) -@test Dates.firstdayofmonth(Date(apr)) == Date(2013,4,1) -@test Dates.firstdayofmonth(Date(may)) == Date(2013,5,1) -@test Dates.firstdayofmonth(Date(jun)) == Date(2013,6,1) -@test Dates.firstdayofmonth(Date(jul)) == Date(2013,7,1) -@test Dates.firstdayofmonth(Date(aug)) == Date(2013,8,1) -@test Dates.firstdayofmonth(Date(sep)) == Date(2013,9,1) -@test Dates.firstdayofmonth(Date(oct)) == Date(2013,10,1) -@test Dates.firstdayofmonth(Date(nov)) == Date(2013,11,1) -@test Dates.firstdayofmonth(Date(dec)) == Date(2013,12,1) +@test Dates.lastdayofmonth(jan) == Dates.DateTime(2013,1,31) +@test Dates.lastdayofmonth(feb) == Dates.DateTime(2013,2,28) +@test Dates.lastdayofmonth(mar) == Dates.DateTime(2013,3,31) +@test Dates.lastdayofmonth(apr) == Dates.DateTime(2013,4,30) +@test Dates.lastdayofmonth(may) == Dates.DateTime(2013,5,31) +@test Dates.lastdayofmonth(jun) == Dates.DateTime(2013,6,30) +@test Dates.lastdayofmonth(jul) == Dates.DateTime(2013,7,31) +@test Dates.lastdayofmonth(aug) == Dates.DateTime(2013,8,31) +@test Dates.lastdayofmonth(sep) == Dates.DateTime(2013,9,30) +@test Dates.lastdayofmonth(oct) == Dates.DateTime(2013,10,31) +@test Dates.lastdayofmonth(nov) == Dates.DateTime(2013,11,30) +@test Dates.lastdayofmonth(dec) == Dates.DateTime(2013,12,31) + +@test Dates.lastdayofmonth(Date(jan)) == Dates.Date(2013,1,31) +@test Dates.lastdayofmonth(Date(feb)) == Dates.Date(2013,2,28) +@test Dates.lastdayofmonth(Date(mar)) == Dates.Date(2013,3,31) +@test Dates.lastdayofmonth(Date(apr)) == Dates.Date(2013,4,30) +@test Dates.lastdayofmonth(Date(may)) == Dates.Date(2013,5,31) +@test Dates.lastdayofmonth(Date(jun)) == Dates.Date(2013,6,30) +@test Dates.lastdayofmonth(Date(jul)) == Dates.Date(2013,7,31) +@test Dates.lastdayofmonth(Date(aug)) == Dates.Date(2013,8,31) +@test Dates.lastdayofmonth(Date(sep)) == Dates.Date(2013,9,30) +@test Dates.lastdayofmonth(Date(oct)) == Dates.Date(2013,10,31) +@test Dates.lastdayofmonth(Date(nov)) == Dates.Date(2013,11,30) +@test Dates.lastdayofmonth(Date(dec)) == Dates.Date(2013,12,31) + +@test Dates.firstdayofmonth(jan) == Dates.DateTime(2013,1,1) +@test Dates.firstdayofmonth(feb) == Dates.DateTime(2013,2,1) +@test Dates.firstdayofmonth(mar) == Dates.DateTime(2013,3,1) +@test Dates.firstdayofmonth(apr) == Dates.DateTime(2013,4,1) +@test Dates.firstdayofmonth(may) == Dates.DateTime(2013,5,1) +@test Dates.firstdayofmonth(jun) == Dates.DateTime(2013,6,1) +@test Dates.firstdayofmonth(jul) == Dates.DateTime(2013,7,1) +@test Dates.firstdayofmonth(aug) == Dates.DateTime(2013,8,1) +@test Dates.firstdayofmonth(sep) == Dates.DateTime(2013,9,1) +@test Dates.firstdayofmonth(oct) == Dates.DateTime(2013,10,1) +@test Dates.firstdayofmonth(nov) == Dates.DateTime(2013,11,1) +@test Dates.firstdayofmonth(dec) == Dates.DateTime(2013,12,1) + +@test Dates.firstdayofmonth(Date(jan)) == Dates.Date(2013,1,1) +@test Dates.firstdayofmonth(Date(feb)) == Dates.Date(2013,2,1) +@test Dates.firstdayofmonth(Date(mar)) == Dates.Date(2013,3,1) +@test Dates.firstdayofmonth(Date(apr)) == Dates.Date(2013,4,1) +@test Dates.firstdayofmonth(Date(may)) == Dates.Date(2013,5,1) +@test Dates.firstdayofmonth(Date(jun)) == Dates.Date(2013,6,1) +@test Dates.firstdayofmonth(Date(jul)) == Dates.Date(2013,7,1) +@test Dates.firstdayofmonth(Date(aug)) == Dates.Date(2013,8,1) +@test Dates.firstdayofmonth(Date(sep)) == Dates.Date(2013,9,1) +@test Dates.firstdayofmonth(Date(oct)) == Dates.Date(2013,10,1) +@test Dates.firstdayofmonth(Date(nov)) == Dates.Date(2013,11,1) +@test Dates.firstdayofmonth(Date(dec)) == Dates.Date(2013,12,1) # Test first day of week; 2014-01-06 is a Monday = 1st day of week a = Dates.Date(2014,1,6) @@ -295,31 +295,151 @@ dt = Dates.Date(2014,5,21) # recur startdate = Dates.Date(2014,1,1) stopdate = Dates.Date(2014,2,1) -@test length(Dates.recur(x->x==x,startdate:stopdate)) == 32 +@test length(Dates.recur(x->true,startdate:stopdate)) == 32 +@test length(Dates.recur(x->true,stopdate:Dates.Day(-1):startdate)) == 32 januarymondays2014 = [Dates.Date(2014,1,6),Dates.Date(2014,1,13),Dates.Date(2014,1,20),Dates.Date(2014,1,27)] @test Dates.recur(Dates.ismonday,startdate,stopdate) == januarymondays2014 @test Dates.recur(Dates.ismonday,startdate:stopdate) == januarymondays2014 @test Dates.recur(x->!Dates.ismonday(x),startdate,stopdate;negate=true) == januarymondays2014 +@test_throws ArgumentError recur((x,y)->x+y,Dates.Date(2013):Dates.Date(2014)) +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,2))) == 32 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,1))) == 1 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,2))) == 2 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,3))) == 3 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,4))) == 4 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,5))) == 5 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,6))) == 6 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,7))) == 7 +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2013,1,8))) == 8 +@test length(recur(x->true,Dates.Date(2013):Dates.Month(1):Dates.Date(2013,1,1))) == 1 +@test length(recur(x->true,Dates.Date(2013):Dates.Day(-1):Dates.Date(2012,1,1))) == 367 +# Empty range +@test length(recur(x->true,Dates.Date(2013):Dates.Date(2012,1,1))) == 0 + # All leap days in 20th century @test length(Dates.recur(Dates.Date(1900):Dates.Date(2000)) do x Dates.month(x) == Dates.Feb && Dates.day(x) == 29 end) == 24 -# All observed Christmas days in 20th century -@test length(Dates.recur(Dates.Date(1900):Dates.Date(2000)) do x - if Dates.month(x) != Dates.Dec - return false +# Thanksgiving: 4th Thursday of November +thanksgiving = x->Dates.dayofweek(x) == Dates.Thu && + Dates.month(x) == Dates.Nov && + Dates.dayofweekofmonth(x) == 4 + +d = Dates.Date(2014,6,5) + +@test Dates.tonext(d) do x + thanksgiving(x) +end == Dates.Date(2014,11,27) + +@test Dates.toprev(d) do x + thanksgiving(x) +end == Dates.Date(2013,11,28) + +# Pittsburgh street cleaning +dr = Dates.Date(2014):Dates.Date(2015) +@test length(recur(dr) do x + Dates.dayofweek(x) == Dates.Tue && + Dates.April < Dates.month(x) < Dates.Nov && + Dates.dayofweekofmonth(x) == 2 +end) == 6 + +# U.S. Federal Holidays +newyears(y) = (y,1,1) +independenceday(y) = (y,7,4) +veteransday(y) = (y,11,11) +christmas(y) = (y,12,25) + +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 && + Dates.month(dt) == Dates.Jan && Dates.dayofweekofmonth(dt) == 3 +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 && + Dates.month(dt) == May && + Dates.dayofweekofmonth(dt) == Dates.daysofweekinmonth(dt) +islaborday(dt) = Dates.dayofweek(dt) == Dates.Mon && + Dates.month(dt) == Dates.Sep && dayofweekofmonth(dt) == 1 +iscolumbusday(dt) = Dates.dayofweek(dt) == Dates.Mon && + Dates.month(dt) == Dates.Oct && Dates.dayofweekofmonth(dt) == 2 +isthanksgiving(dt) = Dates.dayofweek(dt) == Dates.Thu && + Dates.month(dt) == Dates.Nov && Dates.dayofweekofmonth(dt) == 4 + +function easter(y) + # Butcher's Algorithm: http://www.smart.net/~mmontes/butcher.html + a=y%19 + b=div(y,100) + c=y%100 + d=div(b,4) + e=b%4 + f=div(b+8,25) + g=div(b-f+1,3) + h=(19*a+b-d-g+15)%30 + i=div(c,4) + k=c%4 + l=(32+2*e+2*i-h-k)%7 + m=div(a+11*h+22*l,451) + month=div(h+l-7*m+114,31) + p=(h+l-7*m+114)%31 + return (y,month,p+1) +end +iseaster(dt) = Dates.yearmonthday(dt) == easter(Dates.year(dt)) + +const HOLIDAYS = x->isnewyears(x) || isindependenceday(x) || + isveteransday(x) || ischristmas(x) || + ismartinlutherking(x) || ispresidentsday(x) || + ismemorialday(x) || islaborday(x) || + iscolumbusday(x) || isthanksgiving(x) + +@test length(Dates.recur(HOLIDAYS,dr)) == 11 + +const OBSERVEDHOLIDAYS = x->begin + # If the holiday is on a weekday + if HOLIDAYS(x) && Dates.dayofweek(x) < Dates.Saturday + return true + # Holiday is observed Monday if falls on Sunday + elseif Dates.dayofweek(x) == 1 && 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)) + return true else - if Dates.day(x) == 25 && Dates.dayofweek(x) < 6 - return true - elseif Dates.dayofweek(x) == 1 && Dates.day(x-Dates.Day(1)) == 25 - return true - elseif Dates.dayofweek(x) == 5 && Dates.day(x+Dates.Day(1)) == 25 - return true - else - return false - end + return false + end +end + +observed = Dates.recur(OBSERVEDHOLIDAYS,Dates.Date(1999):Dates.Date(2000)) +@test length(observed) == 11 +@test observed[10] == Dates.Date(1999,12,24) +@test observed[11] == Dates.Date(1999,12,31) + +# Get all business/working days of 2014 +# Since we have already defined observed holidays, +# we just look at weekend days and use the "negate" keyword of recur +# validate with http://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 +end) == 251 + +# First day of the next month for each day of 2014 +@test length([Dates.firstdayofmonth(i+Dates.Month(1)) + for i in Dates.Date(2014):Dates.Date(2014,12,31)]) == 365 + +# From those goofy email forwards claiming a "special, lucky month" +# that has 5 Fridays, 5 Saturdays, and 5 Sundays and that it only +# occurs every 823 years..... +@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 + dt += Dates.Day(1) end -end) == 100 + return sum == 15 +end) == 15 # On average, there's one of those months every year \ No newline at end of file diff --git a/test/dates/arithmetic.jl b/test/dates/arithmetic.jl index 7a393739eaa00..127df38f0df35 100644 --- a/test/dates/arithmetic.jl +++ b/test/dates/arithmetic.jl @@ -17,9 +17,6 @@ dt = Dates.DateTime(2000,2,29) @test dt - Dates.Year(1) == Dates.DateTime(1999,2,28) @test dt + Dates.Year(4) == Dates.DateTime(2004,2,29) @test dt - Dates.Year(4) == Dates.DateTime(1996,2,29) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Year(1) == Dates.DateTime(1973,7,1) -@test dt - Dates.Year(1) == Dates.DateTime(1971,7,1) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Year(1) == Dates.DateTime(1973,6,30,23,59,59) @test dt - Dates.Year(1) == Dates.DateTime(1971,6,30,23,59,59) @@ -146,9 +143,6 @@ dt = Dates.DateTime(1999,12,27) dt = Dates.DateTime(2000,2,29) @test dt + Dates.Month(1) == Dates.DateTime(2000,3,29) @test dt - Dates.Month(1) == Dates.DateTime(2000,1,29) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Month(1) == Dates.DateTime(1972,8,1) -@test dt - Dates.Month(1) == Dates.DateTime(1972,6,1) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Month(1) == Dates.DateTime(1972,7,30,23,59,59) @test dt - Dates.Month(1) == Dates.DateTime(1972,5,30,23,59,59) @@ -164,9 +158,6 @@ dt = Dates.DateTime(1999,12,27) dt = Dates.DateTime(2000,2,29) @test dt + Dates.Week(1) == Dates.DateTime(2000,3,7) @test dt - Dates.Week(1) == Dates.DateTime(2000,2,22) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Week(1) == Dates.DateTime(1972,7,8) -@test dt - Dates.Week(1) == Dates.DateTime(1972,6,24) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Week(1) == Dates.DateTime(1972,7,7,23,59,59) @test dt - Dates.Week(1) == Dates.DateTime(1972,6,23,23,59,59) @@ -179,9 +170,6 @@ dt = Dates.DateTime(1999,12,27) @test dt - Dates.Day(1) == Dates.DateTime(1999,12,26) @test dt - Dates.Day(100) == Dates.DateTime(1999,9,18) @test dt - Dates.Day(1000) == Dates.DateTime(1997,4,1) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Day(1) == Dates.DateTime(1972,7,2) -@test dt - Dates.Day(1) == Dates.DateTime(1972,6,30) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Day(1) == Dates.DateTime(1972,7,1,23,59,59) @test dt - Dates.Day(1) == Dates.DateTime(1972,6,29,23,59,59) @@ -194,9 +182,6 @@ dt = Dates.DateTime(1999,12,27) @test dt - Dates.Hour(1) == Dates.DateTime(1999,12,26,23) @test dt - Dates.Hour(100) == Dates.DateTime(1999,12,22,20) @test dt - Dates.Hour(1000) == Dates.DateTime(1999,11,15,8) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Hour(1) == Dates.DateTime(1972,7,1,1) -@test dt - Dates.Hour(1) == Dates.DateTime(1972,6,30,23) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Hour(1) == Dates.DateTime(1972,7,1,0,59,59) @test dt - Dates.Hour(1) == Dates.DateTime(1972,6,30,22,59,59) @@ -209,9 +194,6 @@ dt = Dates.DateTime(1999,12,27) @test dt - Dates.Minute(1) == Dates.DateTime(1999,12,26,23,59) @test dt - Dates.Minute(100) == Dates.DateTime(1999,12,26,22,20) @test dt - Dates.Minute(1000) == Dates.DateTime(1999,12,26,7,20) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Minute(1) == Dates.DateTime(1972,7,1,0,1) -@test dt - Dates.Minute(1) == Dates.DateTime(1972,6,30,23,59) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Minute(1) == Dates.DateTime(1972,7,1,0,0,59) @test dt - Dates.Minute(1) == Dates.DateTime(1972,6,30,23,58,59) @@ -224,13 +206,6 @@ dt = Dates.DateTime(1999,12,27) @test dt - Dates.Second(1) == Dates.DateTime(1999,12,26,23,59,59) @test dt - Dates.Second(100) == Dates.DateTime(1999,12,26,23,58,20) @test dt - Dates.Second(1000) == Dates.DateTime(1999,12,26,23,43,20) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Second(1) == Dates.DateTime(1972,7,1,0,0,1) -@test dt - Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,59) -dt = Dates.DateTime(1972,6,30,23,59,59) -@test dt + Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,60) -@test dt - Dates.Second(1) == Dates.DateTime(1972,6,30,23,59,58) -@test dt + Dates.Second(-1) == Dates.DateTime(1972,6,30,23,59,58) dt = Dates.DateTime(1999,12,27) @test dt + Dates.Millisecond(1) == Dates.DateTime(1999,12,27,0,0,0,1) @@ -239,9 +214,6 @@ dt = Dates.DateTime(1999,12,27) @test dt - Dates.Millisecond(1) == Dates.DateTime(1999,12,26,23,59,59,999) @test dt - Dates.Millisecond(100) == Dates.DateTime(1999,12,26,23,59,59,900) @test dt - Dates.Millisecond(1000) == Dates.DateTime(1999,12,26,23,59,59) -dt = Dates.DateTime(1972,6,30,23,59,60) -@test dt + Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,60,1) -@test dt - Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,59,999) dt = Dates.DateTime(1972,6,30,23,59,59) @test dt + Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,59,1) @test dt - Dates.Millisecond(1) == Dates.DateTime(1972,6,30,23,59,58,999) @@ -345,3 +317,14 @@ a = Dates.Date(2012,10,30) @test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) a = Dates.Date(2012,11,30) @test (a+Dates.Day(1))+Dates.Month(1) != (a+Dates.Month(1))+Dates.Day(1) + + +dt = Dates.DateTime(2000,1,1,12,30,45,500) +dt2 = dt + Dates.Year(1) +@test Dates.year(dt2) == 2001 +@test Dates.month(dt2) == 1 +@test Dates.day(dt2) == 1 +@test Dates.hour(dt2) == 12 +@test Dates.minute(dt2) == 30 +@test Dates.second(dt2) == 45 +@test Dates.millisecond(dt2) == 500 \ No newline at end of file diff --git a/test/dates/conversions.jl b/test/dates/conversions.jl index a7a3c011f8a50..7d23480c808c5 100644 --- a/test/dates/conversions.jl +++ b/test/dates/conversions.jl @@ -26,17 +26,20 @@ @test string(Dates.unix2datetime(915148801.00)) == string("1999-01-01T00:00:01") @test string(Dates.unix2datetime(915148801.25)) == string("1999-01-01T00:00:01.25") -@test Date(Dates.rata2datetime(734869)) == Date(2013,1,1) +@test Date(Dates.rata2datetime(734869)) == Dates.Date(2013,1,1) @test Dates.datetime2rata(Dates.rata2datetime(734869)) == 734869 # Tests from here: http://mysite.verizon.net/aesir_research/date/back.htm#JDN -@test Dates.julian2datetime(1721119.5) == DateTime(0,3,1) -@test Dates.julian2datetime(1721424.5) == DateTime(0,12,31) -@test Dates.julian2datetime(1721425.5) == DateTime(1,1,1) -@test Dates.julian2datetime(2299149.5) == DateTime(1582,10,4) -@test Dates.julian2datetime(2415020.5) == DateTime(1900,1,1) -@test Dates.julian2datetime(2415385.5) == DateTime(1901,1,1) -@test Dates.julian2datetime(2440587.5) == DateTime(1970,1,1) -@test Dates.julian2datetime(2444239.5) == DateTime(1980,1,1) -@test Dates.julian2datetime(2452695.625) == DateTime(2003,2,25,3) -@test Dates.datetime2julian(DateTime(2013,12,3,21)) == 2456630.375 +@test Dates.julian2datetime(1721119.5) == Dates.DateTime(0,3,1) +@test Dates.julian2datetime(1721424.5) == Dates.DateTime(0,12,31) +@test Dates.julian2datetime(1721425.5) == Dates.DateTime(1,1,1) +@test Dates.julian2datetime(2299149.5) == Dates.DateTime(1582,10,4) +@test Dates.julian2datetime(2415020.5) == Dates.DateTime(1900,1,1) +@test Dates.julian2datetime(2415385.5) == Dates.DateTime(1901,1,1) +@test Dates.julian2datetime(2440587.5) == Dates.DateTime(1970,1,1) +@test Dates.julian2datetime(2444239.5) == Dates.DateTime(1980,1,1) +@test Dates.julian2datetime(2452695.625) == Dates.DateTime(2003,2,25,3) +@test Dates.datetime2julian(Dates.DateTime(2013,12,3,21)) == 2456630.375 + +@test typeof(Dates.now()) <: Dates.DateTime +@test typeof(Dates.today()) <: Dates.Date \ No newline at end of file diff --git a/test/dates/io.jl b/test/dates/io.jl index 440becdb97c93..bedbf766deef4 100644 --- a/test/dates/io.jl +++ b/test/dates/io.jl @@ -239,8 +239,8 @@ f = "y m d" @test Dates.Date("1 1 1",f) == Dates.Date(1) @test Dates.Date("10000000000 1 1",f) == Dates.Date(10000000000) @test_throws ArgumentError Dates.Date("1 13 1",f) -@test Dates.Date("1 1 32",f) == Dates.Date(1,2,1) -@test Dates.Date(" 1 1 32",f) == Dates.Date(1,2,1) +@test_throws ArgumentError Dates.Date("1 1 32",f) +@test_throws ArgumentError Dates.Date(" 1 1 32",f) @test_throws ArgumentError Dates.Date("# 1 1 32",f) # can't find 1st space delimiter,s o fails @test_throws ArgumentError Dates.Date("1",f) @@ -252,3 +252,26 @@ f = "y m d" @test Dates.Date(string(Dates.Date(dt))) == Dates.Date(dt) @test Dates.DateTime(string(dt)) == dt + +# Vectorized +dr = ["2000-01-01","2000-01-02","2000-01-03","2000-01-04","2000-01-05" + ,"2000-01-06","2000-01-07","2000-01-08","2000-01-09","2000-01-10"] +dr2 = [Dates.Date(2000):Dates.Date(2000,1,10)] +@test Dates.Date(dr) == dr2 +@test Dates.Date(dr,"yyyy-mm-dd") == dr2 +@test Dates.DateTime(dr) == Dates.DateTime(dr2) +@test Dates.DateTime(dr,"yyyy-mm-dd") == Dates.DateTime(dr2) + +@test Dates.format(dr2) == dr +@test Dates.format(dr2,"yyyy-mm-dd") == dr + +@test typeof(Dates.Date(dr)) == Array{Date,1} + +# Issue 13 +t = Dates.DateTime(1,1,1,14,51,0,118) +@test Dates.DateTime("[14:51:00.118]","[HH:MM:SS.sss]") == t +@test Dates.DateTime("14:51:00.118", "HH:MM:SS.sss") == t +@test Dates.DateTime("[14:51:00.118?", "[HH:MM:SS.sss?") == t +@test Dates.DateTime("?14:51:00.118?", "?HH:MM:SS.sss?") == t +@test Dates.DateTime("x14:51:00.118", "xHH:MM:SS.sss") == t +@test Dates.DateTime("14:51:00.118]", "HH:MM:SS.sss]") == t \ No newline at end of file diff --git a/test/dates/periods.jl b/test/dates/periods.jl index 4fa788268be4b..36eda2424bf4b 100644 --- a/test/dates/periods.jl +++ b/test/dates/periods.jl @@ -52,23 +52,23 @@ ms = Dates.Millisecond(1) @test typeof(float16(y)) <: Float16 @test typeof(float32(y)) <: Float32 @test typeof(float64(y)) <: Float64 -@test convert(Dates.Year,convert(Int8,1)) == y -@test convert(Dates.Year,convert(Uint8,1)) == y -@test convert(Dates.Year,convert(Int16,1)) == y -@test convert(Dates.Year,convert(Uint16,1)) == y -@test convert(Dates.Year,convert(Int32,1)) == y -@test convert(Dates.Year,convert(Uint32,1)) == y -@test convert(Dates.Year,convert(Int64,1)) == y -@test convert(Dates.Year,convert(Uint64,1)) == y -@test convert(Dates.Year,convert(Int128,1)) == y -@test convert(Dates.Year,convert(Uint128,1)) == y -@test convert(Dates.Year,convert(BigInt,1)) == y -@test convert(Dates.Year,convert(BigFloat,1)) == y -@test convert(Dates.Year,convert(Complex,1)) == y -@test convert(Dates.Year,convert(Rational,1)) == y -#@test convert(Dates.Year,convert(Float16,1)) == y -@test convert(Dates.Year,convert(Float32,1)) == y -@test convert(Dates.Year,convert(Float64,1)) == y +@test Dates.Year(convert(Int8,1)) == y +@test Dates.Year(convert(Uint8,1)) == y +@test Dates.Year(convert(Int16,1)) == y +@test Dates.Year(convert(Uint16,1)) == y +@test Dates.Year(convert(Int32,1)) == y +@test Dates.Year(convert(Uint32,1)) == y +@test Dates.Year(convert(Int64,1)) == y +@test Dates.Year(convert(Uint64,1)) == y +@test Dates.Year(convert(Int128,1)) == y +@test Dates.Year(convert(Uint128,1)) == y +@test Dates.Year(convert(BigInt,1)) == y +@test Dates.Year(convert(BigFloat,1)) == y +@test Dates.Year(convert(Complex,1)) == y +@test Dates.Year(convert(Rational,1)) == y +@test Dates.Year(convert(Float16,1)) == y +@test Dates.Year(convert(Float32,1)) == y +@test Dates.Year(convert(Float64,1)) == y @test y == y @test m == m @test w == w @@ -162,9 +162,9 @@ y2 = Dates.Year(2) @test div(y,y) == Dates.Year(1) @test y*10 % 5 == Dates.Year(0) @test 5 % y*10 == Dates.Year(0) -@test_throws ArgumentError y > 3 -@test_throws ArgumentError 4 < y -@test_throws ArgumentError 1 == y +@test (y > 3) == false +@test (4 < y) == false +@test 1 == y t = [y,y,y,y,y] @test t .+ Dates.Year(2) == [Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3)] dt = Dates.DateTime(2012,12,21) @@ -219,52 +219,52 @@ test = ((((((((dt + y) - m) + w) - d) + h) - mi) + s) - ms) @test Dates.Year(-1) < Dates.Year(1) @test !(Dates.Year(-1) > Dates.Year(1)) @test Dates.Year(1) == Dates.Year(1) -@test_throws ArgumentError Dates.Year(1) == 1 -@test_throws ArgumentError 1 == Dates.Year(1) -@test_throws ArgumentError Dates.Year(1) < 1 -@test_throws ArgumentError 1 < Dates.Year(1) +@test Dates.Year(1) == 1 +@test 1 == Dates.Year(1) +@test (Dates.Year(1) < 1) == false +@test (1 < Dates.Year(1)) == false @test Dates.Month(-1) < Dates.Month(1) @test !(Dates.Month(-1) > Dates.Month(1)) @test Dates.Month(1) == Dates.Month(1) -@test_throws ArgumentError Dates.Month(1) == 1 -@test_throws ArgumentError 1 == Dates.Month(1) -@test_throws ArgumentError Dates.Month(1) < 1 -@test_throws ArgumentError 1 < Dates.Month(1) +@test Dates.Month(1) == 1 +@test 1 == Dates.Month(1) +@test (Dates.Month(1) < 1) == false +@test (1 < Dates.Month(1)) == false @test Dates.Day(-1) < Dates.Day(1) @test !(Dates.Day(-1) > Dates.Day(1)) @test Dates.Day(1) == Dates.Day(1) -@test_throws ArgumentError Dates.Day(1) == 1 -@test_throws ArgumentError 1 == Dates.Day(1) -@test_throws ArgumentError Dates.Day(1) < 1 -@test_throws ArgumentError 1 < Dates.Day(1) +@test Dates.Day(1) == 1 +@test 1 == Dates.Day(1) +@test (Dates.Day(1) < 1) == false +@test (1 < Dates.Day(1)) == false @test Dates.Hour(-1) < Dates.Hour(1) @test !(Dates.Hour(-1) > Dates.Hour(1)) @test Dates.Hour(1) == Dates.Hour(1) -@test_throws ArgumentError Dates.Hour(1) == 1 -@test_throws ArgumentError 1 == Dates.Hour(1) -@test_throws ArgumentError Dates.Hour(1) < 1 -@test_throws ArgumentError 1 < Dates.Hour(1) +@test Dates.Hour(1) == 1 +@test 1 == Dates.Hour(1) +@test (Dates.Hour(1) < 1) == false +@test (1 < Dates.Hour(1)) == false @test Dates.Minute(-1) < Dates.Minute(1) @test !(Dates.Minute(-1) > Dates.Minute(1)) @test Dates.Minute(1) == Dates.Minute(1) -@test_throws ArgumentError Dates.Minute(1) == 1 -@test_throws ArgumentError 1 == Dates.Minute(1) -@test_throws ArgumentError Dates.Minute(1) < 1 -@test_throws ArgumentError 1 < Dates.Minute(1) +@test Dates.Minute(1) == 1 +@test 1 == Dates.Minute(1) +@test (Dates.Minute(1) < 1) == false +@test (1 < Dates.Minute(1)) == false @test Dates.Second(-1) < Dates.Second(1) @test !(Dates.Second(-1) > Dates.Second(1)) @test Dates.Second(1) == Dates.Second(1) -@test_throws ArgumentError Dates.Second(1) == 1 -@test_throws ArgumentError 1 == Dates.Second(1) -@test_throws ArgumentError Dates.Second(1) < 1 -@test_throws ArgumentError 1 < Dates.Second(1) +@test Dates.Second(1) == 1 +@test 1 == Dates.Second(1) +@test (Dates.Second(1) < 1) == false +@test (1 < Dates.Second(1)) == false @test Dates.Millisecond(-1) < Dates.Millisecond(1) @test !(Dates.Millisecond(-1) > Dates.Millisecond(1)) @test Dates.Millisecond(1) == Dates.Millisecond(1) -@test_throws ArgumentError Dates.Millisecond(1) == 1 -@test_throws ArgumentError 1 == Dates.Millisecond(1) -@test_throws ArgumentError Dates.Millisecond(1) < 1 -@test_throws ArgumentError 1 < Dates.Millisecond(1) +@test Dates.Millisecond(1) == 1 +@test 1 == Dates.Millisecond(1) +@test (Dates.Millisecond(1) < 1) == false +@test (1 < Dates.Millisecond(1)) == false @test_throws ArgumentError Dates.Year(1) < Dates.Millisecond(1) @test_throws ArgumentError Dates.Year(1) == Dates.Millisecond(1) @@ -277,6 +277,7 @@ test = ((((((((dt + y) - m) + w) - d) + h) - mi) + s) - ms) @test Dates.Second("1") == s @test Dates.Millisecond("1") == ms @test_throws ErrorException Dates.Year("1.0") +@test Dates.Year(parsefloat("1.0")) == y dt = Dates.DateTime(2014) @test typeof(Dates.Year(dt)) <: Dates.Year @@ -296,4 +297,20 @@ dt = Dates.DateTime(2014) @test Dates.default(Dates.Hour) == zero(Dates.Hour) @test Dates.default(Dates.Minute) == zero(Dates.Minute) @test Dates.default(Dates.Second) == zero(Dates.Second) -@test Dates.default(Dates.Millisecond) == zero(Dates.Millisecond) \ No newline at end of file +@test Dates.default(Dates.Millisecond) == zero(Dates.Millisecond) + +# Conversions +@test Dates.toms(ms) == Dates.value(Dates.Millisecond(ms)) == 1 +@test Dates.toms(s) == Dates.value(Dates.Millisecond(s)) == 1000 +@test Dates.toms(mi) == Dates.value(Dates.Millisecond(mi)) == 60000 +@test Dates.toms(h) == Dates.value(Dates.Millisecond(h)) == 3600000 +@test Dates.toms(d) == Dates.value(Dates.Millisecond(d)) == 86400000 +@test Dates.toms(w) == Dates.value(Dates.Millisecond(w)) == 604800000 + +@test Dates.days(ms) == Dates.days(s) == Dates.days(mi) == Dates.days(h) == 0 +@test Dates.days(Dates.Millisecond(86400000)) == 1 +@test Dates.days(Dates.Second(86400)) == 1 +@test Dates.days(Dates.Minute(1440)) == 1 +@test Dates.days(Dates.Hour(24)) == 1 +@test Dates.days(d) == 1 +@test Dates.days(w) == 7 \ No newline at end of file diff --git a/test/dates/query.jl b/test/dates/query.jl index 0d1160adcec33..f6efc3a460042 100644 --- a/test/dates/query.jl +++ b/test/dates/query.jl @@ -21,10 +21,14 @@ dows = ["Tuesday","Saturday","Sunday","Thursday","Sunday","Friday", for (i,dt) in enumerate([jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec]) @test Dates.month(dt) == i @test Dates.monthname(dt) == monthnames[i] + @test Dates.monthname(i) == monthnames[i] @test Dates.monthabbr(dt) == monthnames[i][1:3] + @test Dates.monthabbr(i) == monthnames[i][1:3] @test Dates.dayofweek(dt) == daysofweek[i] @test Dates.dayname(dt) == dows[i] + @test Dates.dayname(dayofweek(dt)) == dows[i] @test Dates.dayabbr(dt) == dows[i][1:3] + @test Dates.dayabbr(dayofweek(dt)) == dows[i][1:3] end # Customizing locale @@ -55,39 +59,15 @@ Dates.VALUETOMONTH["french"] = french_months @test Dates.monthname(nov;locale="french") == "novembre" @test Dates.monthname(dec;locale="french") == "décembre" -@test Dates.daysinmonth(2000,1) == 31 -@test Dates.daysinmonth(2000,2) == 29 -@test Dates.daysinmonth(2000,3) == 31 -@test Dates.daysinmonth(2000,4) == 30 -@test Dates.daysinmonth(2000,5) == 31 -@test Dates.daysinmonth(2000,6) == 30 -@test Dates.daysinmonth(2000,7) == 31 -@test Dates.daysinmonth(2000,8) == 31 -@test Dates.daysinmonth(2000,9) == 30 -@test Dates.daysinmonth(2000,10) == 31 -@test Dates.daysinmonth(2000,11) == 30 -@test Dates.daysinmonth(2000,12) == 31 -@test Dates.daysinmonth(2001,2) == 28 - -@test Dates.isleap(1900) == false -@test Dates.isleap(2000) == true -@test Dates.isleap(2004) == true -@test Dates.isleap(2008) == true -@test Dates.isleap(0) == true -@test Dates.isleap(1) == false -@test Dates.isleap(-1) == false -@test Dates.isleap(4) == true -@test Dates.isleap(-4) == true - -@test Dates.isleap(Dates.DateTime(1900)) == false -@test Dates.isleap(Dates.DateTime(2000)) == true -@test Dates.isleap(Dates.DateTime(2004)) == true -@test Dates.isleap(Dates.DateTime(2008)) == true -@test Dates.isleap(Dates.DateTime(0)) == true -@test Dates.isleap(Dates.DateTime(1)) == false -@test Dates.isleap(Dates.DateTime(-1)) == false -@test Dates.isleap(Dates.DateTime(4)) == true -@test Dates.isleap(Dates.DateTime(-4)) == true +@test Dates.isleapyear(Dates.DateTime(1900)) == false +@test Dates.isleapyear(Dates.DateTime(2000)) == true +@test Dates.isleapyear(Dates.DateTime(2004)) == true +@test Dates.isleapyear(Dates.DateTime(2008)) == true +@test Dates.isleapyear(Dates.DateTime(0)) == true +@test Dates.isleapyear(Dates.DateTime(1)) == false +@test Dates.isleapyear(Dates.DateTime(-1)) == false +@test Dates.isleapyear(Dates.DateTime(4)) == true +@test Dates.isleapyear(Dates.DateTime(-4)) == true @test Dates.daysinyear(2000) == 366 @test Dates.daysinyear(2001) == 365 diff --git a/test/dates/types.jl b/test/dates/types.jl index 7ad70d4fe5c04..3cca9c5a6bb39 100644 --- a/test/dates/types.jl +++ b/test/dates/types.jl @@ -8,6 +8,30 @@ @test Dates.totaldays(1,1,2) == 2 @test Dates.totaldays(2013,1,1) == 734869 +@test Dates.daysinmonth(2000,1) == 31 +@test Dates.daysinmonth(2000,2) == 29 +@test Dates.daysinmonth(2000,3) == 31 +@test Dates.daysinmonth(2000,4) == 30 +@test Dates.daysinmonth(2000,5) == 31 +@test Dates.daysinmonth(2000,6) == 30 +@test Dates.daysinmonth(2000,7) == 31 +@test Dates.daysinmonth(2000,8) == 31 +@test Dates.daysinmonth(2000,9) == 30 +@test Dates.daysinmonth(2000,10) == 31 +@test Dates.daysinmonth(2000,11) == 30 +@test Dates.daysinmonth(2000,12) == 31 +@test Dates.daysinmonth(2001,2) == 28 + +@test Dates.isleapyear(1900) == false +@test Dates.isleapyear(2000) == true +@test Dates.isleapyear(2004) == true +@test Dates.isleapyear(2008) == true +@test Dates.isleapyear(0) == true +@test Dates.isleapyear(1) == false +@test Dates.isleapyear(-1) == false +@test Dates.isleapyear(4) == true +@test Dates.isleapyear(-4) == true + # Create "test" check manually test = Dates.DateTime(Dates.UTM(63492681600000)) # Test DateTime construction by parts @@ -35,9 +59,9 @@ test = Date(1,1,1) @test Date(int64(1),int64(1),int64(1)) == test @test Date('\x01','\x01','\x01') == test @test Date(true,true,true) == test -@test Date(false,true,false) == test - Dates.Year(1) - Dates.Day(1) +@test_throws ArgumentError Date(false,true,false) @test Date(false,true,true) == test - Dates.Year(1) -@test Date(true,true,false) == test - Dates.Day(1) +@test_throws ArgumentError Date(true,true,false) @test Date(uint64(1),uint64(1),uint64(1)) == test @test Date(0xffffffffffffffff,uint64(1),uint64(1)) == test - Dates.Year(2) @test Date(int128(1),int128(1),int128(1)) == test @@ -57,27 +81,32 @@ test = Date(1,1,1) @test_throws InexactError Date(1.2,1.0,1.0) @test_throws InexactError Date(1.2f0,1.f0,1.f0) @test_throws InexactError Date(3//4,Rational(1),Rational(1)) == test -# Currently no method for convert(Int64,::Float16) -# Months must be in range +# Months, days, hours, minutes, seconds, and milliseconds must be in range +@test_throws ArgumentError Dates.Date(2013,0,1) +@test_throws ArgumentError Dates.Date(2013,13,1) +@test_throws ArgumentError Dates.Date(2013,1,0) +@test_throws ArgumentError Dates.Date(2013,1,32) @test_throws ArgumentError Dates.DateTime(2013,0,1) @test_throws ArgumentError Dates.DateTime(2013,13,1) - -# Days/Hours/Minutes/Seconds/Milliseconds roll back/forward -@test Dates.DateTime(2013,1,0) == Dates.DateTime(2012,12,31) -@test Dates.DateTime(2013,1,32) == Dates.DateTime(2013,2,1) -@test Dates.DateTime(2013,1,1,24) == Dates.DateTime(2013,1,2) -@test Dates.DateTime(2013,1,1,-1) == Dates.DateTime(2012,12,31,23) -@test Dates.Date(2013,1,0) == Dates.Date(2012,12,31) -@test Dates.Date(2013,1,32) == Dates.Date(2013,2,1) +@test_throws ArgumentError Dates.DateTime(2013,1,0) +@test_throws ArgumentError Dates.DateTime(2013,1,32) +@test_throws ArgumentError Dates.DateTime(2013,1,1,24) +@test_throws ArgumentError Dates.DateTime(2013,1,1,-1) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,-1) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,60) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,0,-1) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,0,60) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,0,0,-1) +@test_throws ArgumentError Dates.DateTime(2013,1,1,0,0,0,1000) # Test DateTime traits a = Dates.DateTime(2000) b = Dates.Date(2000) @test Dates.calendar(a) == Dates.ISOCalendar @test Dates.calendar(b) == Dates.ISOCalendar -@test Dates.precision(a) == Dates.UTInstant{Dates.Millisecond} -@test Dates.precision(b) == Dates.UTInstant{Dates.Day} +@test Dates.precision(a) == Dates.Millisecond +@test Dates.precision(b) == Dates.Day @test string(typemax(Dates.DateTime)) == "146138512-12-31T23:59:59" @test string(typemin(Dates.DateTime)) == "-146138511-01-01T00:00:00" @test typemax(Dates.DateTime) - typemin(Dates.DateTime) == Dates.Millisecond(9223372017043199000) From 491dfdfa28293b2bf9d8ffe1472495049c3a2ded Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 10:59:39 -0400 Subject: [PATCH 21/28] Update range test with recent change in #7900. --- test/dates/ranges.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/dates/ranges.jl b/test/dates/ranges.jl index bc9de8c08904a..9a45e73114756 100644 --- a/test/dates/ranges.jl +++ b/test/dates/ranges.jl @@ -12,7 +12,7 @@ function test_all_combos() @test length(dr) == 0 @test isempty(dr) @test first(dr) == f1 - @test last(dr) == f1-one(pos_step) #pending #7900 + @test last(dr) == f1-one(l1 - f1) @test length([i for i in dr]) == 0 @test_throws ErrorException minimum(dr) @test_throws ErrorException maximum(dr) @@ -21,7 +21,7 @@ function test_all_combos() @test [dr] == T[] @test isempty(reverse(dr)) @test length(reverse(dr)) == 0 - @test first(reverse(dr)) == f1-one(pos_step) #pending #7900 + @test first(reverse(dr)) == f1-one(l1 - f1) @test last(reverse(dr)) == f1 @test issorted(dr) @test sortperm(dr) == 1:1:0 @@ -45,9 +45,9 @@ function test_all_combos() @test next(dr,start(dr)) == (first(dr),1) if len < 10000 - dr1 = [i for i in dr] #expensive + dr1 = [i for i in dr] @test length(dr1) == len - @test findin(dr,dr) == [1:len] #exp + @test findin(dr,dr) == [1:len] @test length([dr]) == len end @test !isempty(reverse(dr)) @@ -64,7 +64,7 @@ function test_all_combos() @test length(dr) == 0 @test isempty(dr) @test first(dr) == l1 - @test last(dr) == l1+one(neg_step) #pending #7900 + @test last(dr) == l1+one(l1 - f1) @test length([i for i in dr]) == 0 @test_throws ErrorException minimum(dr) @test_throws ErrorException maximum(dr) @@ -73,7 +73,7 @@ function test_all_combos() @test [dr] == T[] @test isempty(reverse(dr)) @test length(reverse(dr)) == 0 - @test first(reverse(dr)) == l1+one(neg_step) #pending #7900 + @test first(reverse(dr)) == l1+one(l1 - f1) @test last(reverse(dr)) == l1 @test !issorted(dr) @test sortperm(dr) == 0:-1:1 @@ -117,7 +117,7 @@ function test_all_combos() @test length(dr) == 0 @test isempty(dr) @test first(dr) == f1 - @test last(dr) == f1-one(pos_step) #pending #7900 + @test last(dr) == f1-one(l1 - f1) @test length([i for i in dr]) == 0 @test_throws ErrorException minimum(dr) @test_throws ErrorException maximum(dr) @@ -126,7 +126,7 @@ function test_all_combos() @test [dr] == T[] @test isempty(reverse(dr)) @test length(reverse(dr)) == 0 - @test first(reverse(dr)) == f1-one(pos_step) #pending #7900 + @test first(reverse(dr)) == f1-one(l1 - f1) @test last(reverse(dr)) == f1 @test issorted(dr) @test sortperm(dr) == 1:1:0 @@ -169,7 +169,7 @@ function test_all_combos() @test length(dr) == 0 @test isempty(dr) @test first(dr) == l1 - @test last(dr) == l1+one(neg_step) #pending #7900 + @test last(dr) == l1+one(l1 - f1) @test length([i for i in dr]) == 0 @test_throws ErrorException minimum(dr) @test_throws ErrorException maximum(dr) @@ -178,7 +178,7 @@ function test_all_combos() @test [dr] == T[] @test isempty(reverse(dr)) @test length(reverse(dr)) == 0 - @test first(reverse(dr)) == l1+one(neg_step) #pending #7900 + @test first(reverse(dr)) == l1+one(l1 - f1) @test last(reverse(dr)) <= l1 @test !issorted(dr) @test sortperm(dr) == 0:-1:1 From 3d6ecf02963830165651ef0f893e64fe1ce9b77a Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 8 Aug 2014 11:31:05 -0400 Subject: [PATCH 22/28] Remove [at]time invocation from range tests --- test/dates/ranges.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dates/ranges.jl b/test/dates/ranges.jl index 9a45e73114756..c6e16b4371c3c 100644 --- a/test/dates/ranges.jl +++ b/test/dates/ranges.jl @@ -219,7 +219,7 @@ function test_all_combos() end end end -@time test_all_combos() +test_all_combos() # All the range representations we want to test # Date ranges From 898b20e1bdd9dbf9aacbb0b4026013200bea3b81 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 09:40:05 -0400 Subject: [PATCH 23/28] Fix formatting of millisecond periods --- base/dates/io.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/dates/io.jl b/base/dates/io.jl index 6a3378ae90271..78cf85a5005c0 100644 --- a/base/dates/io.jl +++ b/base/dates/io.jl @@ -131,7 +131,7 @@ function slotformat(slot::Slot{Month},dt) return VALUETOMONTH[slot.locale][month(dt)] end end -slotformat(slot::Slot{Millisecond},dt) = millisecond(dt) == 0 ? ".0"^slot.width : string(millisecond(dt)/1000.0)[3:end] +slotformat(slot::Slot{Millisecond},dt) = rpad(string(millisecond(dt)/1000.0)[3:end], slot.width, "0") function format(dt::TimeType,df::DateFormat) f = "" From 5b17de87f658c09300d31f947797b01b732aa50f Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 09:42:08 -0400 Subject: [PATCH 24/28] Tighten up vectorized type for conversion functions --- base/dates/conversions.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/dates/conversions.jl b/base/dates/conversions.jl index 244d739c74c7b..59e43b71ebafd 100644 --- a/base/dates/conversions.jl +++ b/base/dates/conversions.jl @@ -35,11 +35,11 @@ end # Returns # of julian days since -4713-11-24T12:00:00 datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0 -@vectorize_1arg Any unix2datetime +@vectorize_1arg Real unix2datetime @vectorize_1arg DateTime datetime2unix -@vectorize_1arg Any rata2datetime +@vectorize_1arg Real rata2datetime @vectorize_1arg DateTime datetime2rata -@vectorize_1arg Any julian2datetime +@vectorize_1arg Real julian2datetime @vectorize_1arg DateTime datetime2julian export unix2datetime, datetime2unix, now, today, From e2fd7213c5a00904962813556f544679e3f70648 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 15:25:50 -0400 Subject: [PATCH 25/28] Don't importall .Dates in Base --- base/sysimg.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/sysimg.jl b/base/sysimg.jl index 334f54e3766f1..0456c818bb123 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -269,7 +269,6 @@ importall .Profile # dates include("Dates.jl") -importall .Dates function __init__() # Base library init From cb6240d69bbc1c260200608997ee4eb291255ea0 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 15:28:16 -0400 Subject: [PATCH 26/28] Move all Dates exports to base/Dates.jl --- base/Dates.jl | 27 +++++++++++++++++++++++++++ base/dates/accessors.jl | 5 +---- base/dates/adjusters.jl | 8 +------- base/dates/conversions.jl | 5 +---- base/dates/io.jl | 4 +--- base/dates/query.jl | 12 +----------- base/dates/types.jl | 6 +----- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/base/Dates.jl b/base/Dates.jl index 7689e8304d107..abac13e634451 100644 --- a/base/Dates.jl +++ b/base/Dates.jl @@ -10,4 +10,31 @@ 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 \ No newline at end of file diff --git a/base/dates/accessors.jl b/base/dates/accessors.jl index b394f5cdafb5d..495d2bb5b820f 100644 --- a/base/dates/accessors.jl +++ b/base/dates/accessors.jl @@ -68,7 +68,4 @@ yearmonthday(dt::TimeType) = yearmonthday(days(dt)) @vectorize_1arg TimeType dayofmonth @vectorize_1arg TimeType yearmonth @vectorize_1arg TimeType monthday -@vectorize_1arg TimeType yearmonthday - -export yearmonthday, yearmonth, monthday, year, month, week, day, - hour, minute, second, millisecond, dayofmonth \ No newline at end of file +@vectorize_1arg TimeType yearmonthday \ No newline at end of file diff --git a/base/dates/adjusters.jl b/base/dates/adjusters.jl index dec86bdd8b6c3..17ddca789d121 100644 --- a/base/dates/adjusters.jl +++ b/base/dates/adjusters.jl @@ -154,10 +154,4 @@ function recur{T<:TimeType}(fun::Function,start::T,stop::T;step::Period=Day(1),n 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 - -export firstdayofweek, lastdayofweek, - firstdayofmonth, lastdayofmonth, - firstdayofyear, lastdayofyear, - firstdayofquarter, lastdayofquarter, - adjust, tonext, toprev, tofirst, tolast, recur +end \ No newline at end of file diff --git a/base/dates/conversions.jl b/base/dates/conversions.jl index 59e43b71ebafd..f5364bf84e70c 100644 --- a/base/dates/conversions.jl +++ b/base/dates/conversions.jl @@ -40,7 +40,4 @@ datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0 @vectorize_1arg Real rata2datetime @vectorize_1arg DateTime datetime2rata @vectorize_1arg Real julian2datetime -@vectorize_1arg DateTime datetime2julian - -export unix2datetime, datetime2unix, now, today, - rata2datetime, datetime2rata, julian2datetime, datetime2julian +@vectorize_1arg DateTime datetime2julian \ No newline at end of file diff --git a/base/dates/io.jl b/base/dates/io.jl index 78cf85a5005c0..1c5081379bf31 100644 --- a/base/dates/io.jl +++ b/base/dates/io.jl @@ -170,6 +170,4 @@ function format(y::AbstractArray{Date},df::DateFormat=ISODateFormat) end function format(y::AbstractArray{DateTime},df::DateFormat=ISODateTimeFormat) return reshape([Dates.format(y[i],df) for i in 1:length(y)], size(y)) -end - -export ISODateTimeFormat, ISODateFormat, DateFormat +end \ No newline at end of file diff --git a/base/dates/query.jl b/base/dates/query.jl index 10ddcd4e5a185..141f04b03d7d0 100644 --- a/base/dates/query.jl +++ b/base/dates/query.jl @@ -103,14 +103,4 @@ const QUARTERDAYS = [0,90,181,273] dayofquarter(dt::TimeType) = dayofyear(dt) - QUARTERDAYS[quarterofyear(dt)] @vectorize_1arg TimeType quarterofyear -@vectorize_1arg TimeType dayofquarter - -export dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr, - ismonday, istuesday, iswednesday, isthursday, isfriday, issaturday, issunday, - 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 +@vectorize_1arg TimeType dayofquarter \ No newline at end of file diff --git a/base/dates/types.jl b/base/dates/types.jl index 932a238e42b62..37c5c6d4ce96a 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -157,8 +157,4 @@ 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::TimeType,y::TimeType) = isless(promote(x,y)...) -==(x::TimeType,y::TimeType) = ===(promote(x,y)...) - -export Period, DatePeriod, TimePeriod, - Year, Month, Week, Day, Hour, Minute, Second, Millisecond, - TimeType, DateTime, Date +==(x::TimeType,y::TimeType) = ===(promote(x,y)...) \ No newline at end of file From 642fcc4bc809ba181ef1fb6164fe0544b28b0077 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 15:41:03 -0400 Subject: [PATCH 27/28] Cleanup Dates export list --- base/Dates.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/Dates.jl b/base/Dates.jl index abac13e634451..48359be746cf6 100644 --- a/base/Dates.jl +++ b/base/Dates.jl @@ -12,10 +12,10 @@ include("dates/io.jl") export Period, DatePeriod, TimePeriod, Year, Month, Week, Day, Hour, Minute, Second, Millisecond, - TimeType, DateTime, Date + TimeType, DateTime, Date, # accessors.jl yearmonthday, yearmonth, monthday, year, month, week, day, - hour, minute, second, millisecond, dayofmonth + hour, minute, second, millisecond, dayofmonth, # query.jl dayofweek, isleapyear, daysinmonth, daysinyear, dayofyear, dayname, dayabbr, dayofweekofmonth, daysofweekinmonth, monthname, monthabbr, @@ -24,16 +24,16 @@ export Period, DatePeriod, TimePeriod, 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 + Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, # conversions.jl unix2datetime, datetime2unix, now, today, - rata2datetime, datetime2rata, julian2datetime, datetime2julian + rata2datetime, datetime2rata, julian2datetime, datetime2julian, # adjusters.jl firstdayofweek, lastdayofweek, firstdayofmonth, lastdayofmonth, firstdayofyear, lastdayofyear, firstdayofquarter, lastdayofquarter, - adjust, tonext, toprev, tofirst, tolast, recur + adjust, tonext, toprev, tofirst, tolast, recur, # io.jl ISODateTimeFormat, ISODateFormat, DateFormat From 65e0d8deb07ea5fa97786fc06f808e27b60301f5 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 15 Aug 2014 15:57:09 -0400 Subject: [PATCH 28/28] Fix failing test in dates/query.jl --- test/dates/query.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dates/query.jl b/test/dates/query.jl index f6efc3a460042..3ae0652e0725f 100644 --- a/test/dates/query.jl +++ b/test/dates/query.jl @@ -26,9 +26,9 @@ for (i,dt) in enumerate([jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec]) @test Dates.monthabbr(i) == monthnames[i][1:3] @test Dates.dayofweek(dt) == daysofweek[i] @test Dates.dayname(dt) == dows[i] - @test Dates.dayname(dayofweek(dt)) == dows[i] + @test Dates.dayname(Dates.dayofweek(dt)) == dows[i] @test Dates.dayabbr(dt) == dows[i][1:3] - @test Dates.dayabbr(dayofweek(dt)) == dows[i][1:3] + @test Dates.dayabbr(Dates.dayofweek(dt)) == dows[i][1:3] end # Customizing locale