Skip to content

Commit

Permalink
Merge pull request #20226 from JuliaLang/jq/timetocleanup
Browse files Browse the repository at this point in the history
Various Date/Time fixes
  • Loading branch information
tkelman authored Jan 28, 2017
2 parents e63fec8 + 0fbc43e commit 78448fa
Show file tree
Hide file tree
Showing 20 changed files with 1,752 additions and 1,746 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ Library improvements

* `notify` now returns a count of tasks woken up ([#19841]).

* A new `Dates.Time` type was added that supports representing the time of day with up to nanosecond resolution ([#12274]).

Compiler/Runtime improvements
-----------------------------

Expand Down
59 changes: 30 additions & 29 deletions base/dates/accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,63 @@
# 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)
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
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)
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
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)
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)
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
const WEEK_INDEX = (15, 23, 3, 11)
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
w = div(abs(days - 1), 7) % 20871
c, w = divrem((w + (w >= 10435)), 5218)
w = (w * 28 + WEEK_INDEX[c + 1]) % 1461
return div(w, 28) + 1
end

# Accessor functions
value(dt::TimeType) = dt.instant.periods.value
value(t::Time) = t.instant.value
days(dt::Date) = value(dt)
days(dt::DateTime) = fld(value(dt),86400000)
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)
hour(t::Time) = mod(fld(value(t),3600000000000),Int64(24))
minute(t::Time) = mod(fld(value(t),60000000000),Int64(60))
second(t::Time) = mod(fld(value(t),1000000000),Int64(60))
millisecond(t::Time) = mod(fld(value(t),Int64(1000000)),Int64(1000))
microsecond(t::Time) = mod(fld(value(t),Int64(1000)),Int64(1000))
nanosecond(t::Time) = mod(value(t),Int64(1000))
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)
hour(t::Time) = mod(fld(value(t), 3600000000000), Int64(24))
minute(t::Time) = mod(fld(value(t), 60000000000), Int64(60))
second(t::Time) = mod(fld(value(t), 1000000000), Int64(60))
millisecond(t::Time) = mod(fld(value(t), Int64(1000000)), Int64(1000))
microsecond(t::Time) = mod(fld(value(t), Int64(1000)), Int64(1000))
nanosecond(t::Time) = mod(value(t), Int64(1000))

dayofmonth(dt::TimeType) = day(dt)

Expand Down
29 changes: 16 additions & 13 deletions base/dates/adjusters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ The step size in adjusting can be provided manually through the `step` keyword.
`limit` provides a limit to the max number of iterations the adjustment API will
pursue before throwing an error (given that `f::Function` is never satisfied).
"""
function Date(func::Function, y, m=1, d=1;step::Period=Day(1), negate=nothing, limit::Int=10000)
function Date(func::Function, y, m=1, d=1; step::Period=Day(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:Date, func, "func,y,m,d", negate)
return adjust(DateFunction(func, Date(y, m, d)), Date(y, m, d), step, limit)
end
Expand Down Expand Up @@ -197,7 +197,10 @@ function DateTime(func::Function, y, m, d, h, mi, s; step::Period=Millisecond(1)
end

"""
Time(f::Function, h[, mi, s, ms, us]; step=Second(1), limit=10000) -> Time
Time(f::Function, h, mi=0; step::Period=Second(1), limit::Int=10000)
Time(f::Function, h, mi, s; step::Period=Millisecond(1), limit::Int=10000)
Time(f::Function, h, mi, s, ms; step::Period=Microsecond(1), limit::Int=10000)
Time(f::Function, h, mi, s, ms, us; step::Period=Nanosecond(1), limit::Int=10000)
Create a `Time` through the adjuster API. The starting point will be constructed from the
provided `h, mi, s, ms, us` arguments, and will be adjusted until `f::Function` returns `true`.
Expand Down Expand Up @@ -237,51 +240,51 @@ ISDAYOFWEEK = Dict(Mon => DateFunction(ismonday, Date(0)),

# "same" indicates whether the current date can be considered or not
"""
tonext(dt::TimeType,dow::Int;same::Bool=false) -> TimeType
tonext(dt::TimeType, dow::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.
"""
tonext(dt::TimeType, dow::Int; same::Bool=false) = adjust(ISDAYOFWEEK[dow], same ? dt : dt+Day(1), Day(1), 7)
tonext(dt::TimeType, dow::Int; same::Bool=false) = adjust(ISDAYOFWEEK[dow], same ? dt : dt + Day(1), Day(1), 7)

# Return the next TimeType where func evals true using step in incrementing
"""
tonext(func::Function,dt::TimeType;step=Day(1),limit=10000,same=false) -> TimeType
tonext(func::Function, dt::TimeType; step=Day(1), 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 in satisfying `func`.
"""
function tonext(func::Function, dt::TimeType;step::Period=Day(1), negate=nothing, limit::Int=10000, same::Bool=false)
function tonext(func::Function, dt::TimeType; step::Period=Day(1), negate=nothing, limit::Int=10000, same::Bool=false)
func = deprecate_negate(:tonext, func, "func,dt", negate)
return adjust(DateFunction(func, dt), same ? dt : dt+step, step, limit)
return adjust(DateFunction(func, dt), same ? dt : dt + step, step, limit)
end

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

"""
toprev(func::Function,dt::TimeType;step=Day(-1),limit=10000,same=false) -> TimeType
toprev(func::Function, dt::TimeType; step=Day(-1), 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 in satisfying `func`.
"""
function toprev(func::Function, dt::TimeType; step::Period=Day(-1), negate=nothing, limit::Int=10000, same::Bool=false)
func = deprecate_negate(:toprev, func, "func,dt", negate)
return adjust(DateFunction(func, dt), same ? dt : dt+step, step, limit)
return adjust(DateFunction(func, dt), same ? dt : dt + step, step, limit)
end

# Return the first TimeType that falls on dow in the Month or Year
"""
tofirst(dt::TimeType,dow::Int;of=Month) -> TimeType
tofirst(dt::TimeType, dow::Int; of=Month) -> TimeType
Adjusts `dt` to the first `dow` of its month. Alternatively, `of=Year` will adjust to the
first `dow` of the year.
Expand All @@ -293,7 +296,7 @@ end

# Return the last TimeType that falls on dow in the Month or Year
"""
tolast(dt::TimeType,dow::Int;of=Month) -> TimeType
tolast(dt::TimeType, dow::Int; of=Month) -> TimeType
Adjusts `dt` to the last `dow` of its month. Alternatively, `of=Year` will adjust to the
last `dow` of the year.
Expand Down
80 changes: 37 additions & 43 deletions base/dates/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

# Instant arithmetic
(+)(x::Instant) = x
(-){T<:Instant}(x::T,y::T) = x.periods - y.periods
(-){T<:Instant}(x::T, y::T) = x.periods - y.periods

# TimeType arithmetic
(+)(x::TimeType) = x
(-){T<:TimeType}(x::T,y::T) = x.instant - y.instant
(-){T<:TimeType}(x::T, y::T) = x.instant - y.instant

# Date-Time arithmetic
"""
Expand All @@ -24,76 +24,70 @@ end
(+)(t::Time, dt::Date) = dt + t

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

# TimeType-Month arithmetic
# monthwrap adds two months with wraparound behavior (i.e. 12 + 1 == 1)
monthwrap(m1,m2) = (v = mod1(m1+m2,12); return v < 0 ? 12 + v : v)
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)
yearwrap(y, m1, m2) = y + fld(m1 + m2 - 1, 12)

function (+)(dt::DateTime,z::Month)
function (+)(dt::DateTime, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,value(z))
mm = monthwrap(m,value(z)); ld = daysinmonth(ny,mm)
return DateTime(ny,mm,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
ny = yearwrap(y, m, value(z))
mm = monthwrap(m, value(z)); ld = daysinmonth(ny, mm)
return DateTime(ny, mm, d <= ld ? d : ld, hour(dt), minute(dt), second(dt), millisecond(dt))
end
function (+)(dt::Date,z::Month)
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)
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)
function (-)(dt::DateTime, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y,m,-value(z))
mm = monthwrap(m,-value(z)); ld = daysinmonth(ny,mm)
return DateTime(ny,mm,d <= ld ? d : ld,hour(dt),minute(dt),second(dt),millisecond(dt))
ny = yearwrap(y, m, -value(z))
mm = monthwrap(m, -value(z)); ld = daysinmonth(ny, mm)
return DateTime(ny, mm, d <= ld ? d : ld, hour(dt), minute(dt), second(dt), millisecond(dt))
end
function (-)(dt::Date,z::Month)
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)
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::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) + value(y)))
(-)(x::Date, y::Day) = return Date(UTD(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)))
(+)(x::Time, y::TimePeriod) = return Time(Nanosecond(value(x) + tons(y)))
(-)(x::Time, y::TimePeriod) = return Time(Nanosecond(value(x) - tons(y)))
(+)(y::Period, x::TimeType) = x + y
(-)(y::Period, x::TimeType) = x - y

for op in (:+, :-)
@eval begin
# GeneralPeriod, AbstractArray{TimeType}
($op){T<:TimeType}(x::AbstractArray{T}, y::GeneralPeriod) = broadcast($op,x,y)
($op){T<:TimeType}(y::GeneralPeriod, x::AbstractArray{T}) = broadcast($op,x,y)

# TimeType, StridedArray{GeneralPeriod}
($op){T<:TimeType,P<:GeneralPeriod}(x::StridedArray{P}, y::T) = broadcast($op,x,y)
($op){P<:GeneralPeriod}(y::TimeType, x::StridedArray{P}) = broadcast($op,x,y)
end
end
(+){T<:TimeType}(x::AbstractArray{T}, y::GeneralPeriod) = x .+ y
(+){T<:TimeType,P<:GeneralPeriod}(x::StridedArray{P}, y::T) = x .+ y
(+){T<:TimeType}(y::GeneralPeriod, x::AbstractArray{T}) = x .+ y
(+){P<:GeneralPeriod}(y::TimeType, x::StridedArray{P}) = x .+ y
(-){T<:TimeType}(x::AbstractArray{T}, y::GeneralPeriod) = x .- y
(-){T<:TimeType,P<:GeneralPeriod}(x::StridedArray{P}, y::T) = x .- y

# TimeType, AbstractArray{TimeType}
(-){T<:TimeType}(x::AbstractArray{T}, y::T) = x .- y
Expand Down
14 changes: 7 additions & 7 deletions base/dates/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ Converts a `DateTime` to a `Date`. The hour, minute, second, and millisecond par
the `DateTime` are truncated, so only the year, month and day parts are used in
construction.
"""
Date(dt::TimeType) = convert(Date,dt)
Date(dt::TimeType) = convert(Date, dt)

"""
DateTime(dt::Date) -> DateTime
Converts a `Date` to a `DateTime`. The hour, minute, second, and millisecond parts of
the new `DateTime` are assumed to be zero.
"""
DateTime(dt::TimeType) = convert(DateTime,dt)
DateTime(dt::TimeType) = convert(DateTime, dt)

"""
Time(dt::DateTime) -> Time
Expand All @@ -27,7 +27,7 @@ the `DateTime` are used to create the new `Time`. Microsecond and nanoseconds ar
"""
Time(dt::DateTime) = convert(Time, dt)

Base.convert(::Type{DateTime}, dt::Date) = DateTime(UTM(value(dt)*86400000))
Base.convert(::Type{DateTime}, dt::Date) = DateTime(UTM(value(dt) * 86400000))
Base.convert(::Type{Date}, dt::DateTime) = Date(UTD(days(dt)))
Base.convert(::Type{Time}, dt::DateTime) = Time(Nanosecond((value(dt) % 86400000) * 1000000))

Expand Down Expand Up @@ -55,7 +55,7 @@ end
Takes the given `DateTime` and returns the number of seconds
since the unix epoch `1970-01-01T00:00:00` as a `Float64`.
"""
datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH)/1000.0
datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH) / 1000.0

"""
now() -> DateTime
Expand All @@ -66,7 +66,7 @@ locale.
function now()
tv = Libc.TimeVal()
tm = Libc.TmStruct(tv.sec)
return DateTime(tm.year+1900,tm.month+1,tm.mday,tm.hour,tm.min,tm.sec,div(tv.usec,1000))
return DateTime(tm.year + 1900, tm.month + 1, tm.mday, tm.hour, tm.min, tm.sec, div(tv.usec, 1000))
end

"""
Expand Down Expand Up @@ -99,7 +99,7 @@ Returns the number of Rata Die days since epoch from the given `Date` or `DateTi
datetime2rata(dt::TimeType) = days(dt)

# Julian conversions
const JULIANEPOCH = value(DateTime(-4713,11,24,12))
const JULIANEPOCH = value(DateTime(-4713, 11, 24, 12))

"""
julian2datetime(julian_days) -> DateTime
Expand All @@ -118,4 +118,4 @@ end
Takes the given `DateTime` and returns the number of Julian calendar days since the julian
epoch `-4713-11-24T12:00:00` as a `Float64`.
"""
datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH)/86400000.0
datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH) / 86400000.0
Loading

2 comments on commit 78448fa

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels

Please sign in to comment.