fix incorrect folder name for julia-0.6.x

Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
This commit is contained in:
2018-06-11 03:28:36 -07:00
parent 5e0e436e4e
commit 0e4acfb8f2
722 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
Dates
The `Dates` module provides `Date`, `DateTime`, `Time` types, and related functions.
The types are not aware of time zones, based on UT seconds
(86400 seconds a day, avoiding leap seconds), and
use the proleptic Gregorian calendar, as specified in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
For time zone functionality, see the TimeZones.jl package.
```jldoctest
julia> dt = DateTime(2017,12,31,23,59,59,999)
2017-12-31T23:59:59.999
julia> d1 = Date(Dates.Month(12), Dates.Year(2017))
2017-12-01
julia> d2 = Date("2017-12-31", Dates.DateFormat("y-m-d"))
2017-12-31
julia> Dates.yearmonthday(d2)
(2017, 12, 31)
julia> d2-d1
30 days
```
Please see the manual section on [`Date`](@ref) and [`DateTime`](@ref)
for more information.
"""
module Dates
importall ..Base.Operators
import ..Base.broadcast
using Base.Iterators
include("types.jl")
include("periods.jl")
include("accessors.jl")
include("query.jl")
include("arithmetic.jl")
include("conversions.jl")
include("ranges.jl")
include("adjusters.jl")
include("rounding.jl")
include("io.jl")
include("parse.jl")
export Period, DatePeriod, TimePeriod,
Year, Month, Week, Day, Hour, Minute, Second, Millisecond,
Microsecond, Nanosecond,
TimeZone, UTC, TimeType, DateTime, Date, Time,
# periods.jl
canonicalize,
# accessors.jl
yearmonthday, yearmonth, monthday, year, month, week, day,
hour, minute, second, millisecond, dayofmonth,
microsecond, nanosecond,
# 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,
# io.jl
ISODateTimeFormat, ISODateFormat, DateFormat, RFC1123Format, @dateformat_str
end # module

View File

@@ -0,0 +1,144 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# 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
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 + 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)
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))
dayofmonth(dt::TimeType) = day(dt)
yearmonth(dt::TimeType) = yearmonth(days(dt))
monthday(dt::TimeType) = monthday(days(dt))
yearmonthday(dt::TimeType) = yearmonthday(days(dt))
# Documentation for exported accessors
for func in (:year, :month)
name = string(func)
@eval begin
@doc """
$($name)(dt::TimeType) -> Int64
The $($name) of a `Date` or `DateTime` as an [`Int64`](@ref).
""" $func(dt::TimeType)
end
end
"""
week(dt::TimeType) -> Int64
Return the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date) of a `Date` or
`DateTime` as an [`Int64`](@ref). Note that the first week of a year is the week that
contains the first Thursday of the year which can result in dates prior to January 4th
being in the last week of the previous year. For example `week(Date(2005,1,1))` is the 53rd
week of 2004.
"""
week(dt::TimeType)
for func in (:day, :dayofmonth)
name = string(func)
@eval begin
@doc """
$($name)(dt::TimeType) -> Int64
The day of month of a `Date` or `DateTime` as an [`Int64`](@ref).
""" $func(dt::TimeType)
end
end
"""
hour(dt::DateTime) -> Int64
The hour of day of a `DateTime` as an [`Int64`](@ref).
"""
hour(dt::DateTime)
for func in (:minute, :second, :millisecond)
name = string(func)
@eval begin
@doc """
$($name)(dt::DateTime) -> Int64
The $($name) of a `DateTime` as an [`Int64`](@ref).
""" $func(dt::DateTime)
end
end
for parts in (["year", "month"], ["month", "day"], ["year", "month", "day"])
name = join(parts)
func = Symbol(name)
@eval begin
@doc """
$($name)(dt::TimeType) -> ($(join(repeated(Int64, length($parts)), ", ")))
Simultaneously return the $(join($parts, ", ", " and ")) parts of a `Date` or
`DateTime`.
""" $func(dt::TimeType)
end
end
for func in (:hour, :minute, :second, :millisecond, :microsecond, :nanosecond)
name = string(func)
@eval begin
@doc """
$($name)(t::Time) -> Int64
The $($name) of a `Time` as an [`Int64`](@ref).
""" $func(t::Time)
end
end

View File

@@ -0,0 +1,307 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
### truncation
Base.trunc(dt::Date, p::Type{Year}) = Date(UTD(totaldays(year(dt), 1, 1)))
Base.trunc(dt::Date, p::Type{Month}) = firstdayofmonth(dt)
Base.trunc(dt::Date, p::Type{Day}) = dt
Base.trunc(dt::DateTime, p::Type{Year}) = DateTime(trunc(Date(dt), Year))
Base.trunc(dt::DateTime, p::Type{Month}) = DateTime(trunc(Date(dt), Month))
Base.trunc(dt::DateTime, p::Type{Day}) = DateTime(Date(dt))
Base.trunc(dt::DateTime, p::Type{Hour}) = dt - Minute(dt) - Second(dt) - Millisecond(dt)
Base.trunc(dt::DateTime, p::Type{Minute}) = dt - Second(dt) - Millisecond(dt)
Base.trunc(dt::DateTime, p::Type{Second}) = dt - Millisecond(dt)
Base.trunc(dt::DateTime, p::Type{Millisecond}) = dt
Base.trunc(t::Time, p::Type{Hour}) = Time(Hour(t))
Base.trunc(t::Time, p::Type{Minute}) = Time(Hour(t), Minute(t))
Base.trunc(t::Time, p::Type{Second}) = Time(Hour(t), Minute(t), Second(t))
Base.trunc(t::Time, p::Type{Millisecond}) = t - Microsecond(t) - Nanosecond(t)
Base.trunc(t::Time, p::Type{Microsecond}) = t - Nanosecond(t)
Base.trunc(t::Time, p::Type{Nanosecond}) = t
"""
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`, then `trunc(dt,Day) == 1996-01-01T00:00:00`.
"""
Dates.trunc(::Dates.TimeType, ::Type{Dates.Period})
# Adjusters
"""
firstdayofweek(dt::TimeType) -> TimeType
Adjusts `dt` to the Monday of its week.
"""
function firstdayofweek end
firstdayofweek(dt::Date) = Date(UTD(value(dt) - dayofweek(dt) + 1))
firstdayofweek(dt::DateTime) = DateTime(firstdayofweek(Date(dt)))
"""
lastdayofweek(dt::TimeType) -> TimeType
Adjusts `dt` to the Sunday of its week.
"""
function lastdayofweek end
lastdayofweek(dt::Date) = Date(UTD(value(dt) + (7 - dayofweek(dt))))
lastdayofweek(dt::DateTime) = DateTime(lastdayofweek(Date(dt)))
"""
firstdayofmonth(dt::TimeType) -> TimeType
Adjusts `dt` to the first day of its month.
"""
function firstdayofmonth end
firstdayofmonth(dt::Date) = Date(UTD(value(dt) - day(dt) + 1))
firstdayofmonth(dt::DateTime) = DateTime(firstdayofmonth(Date(dt)))
"""
lastdayofmonth(dt::TimeType) -> TimeType
Adjusts `dt` to the last day of its month.
"""
function lastdayofmonth end
function lastdayofmonth(dt::Date)
y, m, d = yearmonthday(dt)
return Date(UTD(value(dt) + daysinmonth(y, m) - d))
end
lastdayofmonth(dt::DateTime) = DateTime(lastdayofmonth(Date(dt)))
"""
firstdayofyear(dt::TimeType) -> TimeType
Adjusts `dt` to the first day of its year.
"""
function firstdayofyear end
firstdayofyear(dt::Date) = Date(UTD(value(dt) - dayofyear(dt) + 1))
firstdayofyear(dt::DateTime) = DateTime(firstdayofyear(Date(dt)))
"""
lastdayofyear(dt::TimeType) -> TimeType
Adjusts `dt` to the last day of its year.
"""
function lastdayofyear end
function lastdayofyear(dt::Date)
y, m, d = yearmonthday(dt)
return Date(UTD(value(dt) + daysinyear(y) - dayofyear(y, m, d)))
end
lastdayofyear(dt::DateTime) = DateTime(lastdayofyear(Date(dt)))
"""
firstdayofquarter(dt::TimeType) -> TimeType
Adjusts `dt` to the first day of its quarter.
"""
function firstdayofquarter end
function firstdayofquarter(dt::Date)
y,m = yearmonth(dt)
mm = m < 4 ? 1 : m < 7 ? 4 : m < 10 ? 7 : 10
return Date(y, mm, 1)
end
firstdayofquarter(dt::DateTime) = DateTime(firstdayofquarter(Date(dt)))
"""
lastdayofquarter(dt::TimeType) -> TimeType
Adjusts `dt` to the last day of its quarter.
"""
function lastdayofquarter end
function lastdayofquarter(dt::Date)
y,m = yearmonth(dt)
mm, d = m < 4 ? (3, 31) : m < 7 ? (6, 30) : m < 10 ? (9, 30) : (12, 31)
return Date(y, mm, d)
end
lastdayofquarter(dt::DateTime) = DateTime(lastdayofquarter(Date(dt)))
# Temporal Adjusters
struct DateFunction
f::Function
# validate boolean, single-arg inner constructor
function DateFunction(f::ANY, dt::TimeType)
isa(f(dt), Bool) || throw(ArgumentError("Provided function must take a single TimeType argument and return true or false"))
return new(f)
end
end
Base.show(io::IO, df::DateFunction) = println(io, df.f)
# 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=nothing, limit::Int=10000)
func = deprecate_negate(:adjust, func, "func,start", negate)
return adjust(DateFunction(func, start), start, step, limit)
end
# Constructors using DateFunctions
"""
Date(f::Function, y[, m, d]; step=Day(1), limit=10000) -> Date
Create a `Date` 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.
`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)
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
"""
DateTime(f::Function, y[, m, d, h, mi, s]; step=Day(1), 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.
`limit` provides a limit to the max number of iterations the adjustment API will
pursue before throwing an error (in the case that `f::Function` is never satisfied).
"""
DateTime(::Function, args...)
function DateTime(func::Function, y, m=1; step::Period=Day(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:DateTime, func, "func,y,m", negate)
return adjust(DateFunction(func, DateTime(y, m)), DateTime(y, m), step, limit)
end
function DateTime(func::Function, y, m, d; step::Period=Hour(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:DateTime, func, "func,y,m,d", negate)
return adjust(DateFunction(func, DateTime(y)), DateTime(y, m, d), step, limit)
end
function DateTime(func::Function, y, m, d, h; step::Period=Minute(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:DateTime, func, "func,y,m,d,h", negate)
return adjust(DateFunction(func, DateTime(y)), DateTime(y, m, d, h), step, limit)
end
function DateTime(func::Function, y, m, d, h, mi; step::Period=Second(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:DateTime, func, "func,y,m,d,h,mi", negate)
return adjust(DateFunction(func, 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=nothing, limit::Int=10000)
func = deprecate_negate(:DateTime, func, "func,y,m,d,h,mi,s", negate)
return adjust(DateFunction(func, DateTime(y)), DateTime(y, m, d, h, mi, s), step, limit)
end
"""
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`.
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 (in the case that `f::Function` is never satisfied). Note that the default step
will adjust to allow for greater precision for the given arguments; i.e. if hour, minute, and second
arguments are provided, the default step will be `Millisecond(1)` instead of `Second(1)`.
"""
Time(::Function, args...)
function Time(func::Function, h, mi=0; step::Period=Second(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:Time, func, "func,h,mi", negate)
return adjust(DateFunction(func, Time(h, mi)), Time(h, mi), step, limit)
end
function Time(func::Function, h, mi, s; step::Period=Millisecond(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:Time, func, "func,h,mi,s", negate)
return adjust(DateFunction(func, Time(h, mi, s)), Time(h, mi, s), step, limit)
end
function Time(func::Function, h, mi, s, ms; step::Period=Microsecond(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:Time, func, "func,h,mi,s,ms", negate)
return adjust(DateFunction(func, Time(h, mi, s, ms)), Time(h, mi, s, ms), step, limit)
end
function Time(func::Function, h, mi, s, ms, us; step::Period=Nanosecond(1), negate=nothing, limit::Int=10000)
func = deprecate_negate(:Time, func, "func,h,mi,s,ms,us", negate)
return adjust(DateFunction(func, Time(h, mi, s, ms, us)), Time(h, mi, s, ms, us), step, limit)
end
# Return the next TimeType that falls on dow
ISDAYOFWEEK = Dict(Mon => DateFunction(ismonday, Date(0)),
Tue => DateFunction(istuesday, Date(0)),
Wed => DateFunction(iswednesday, Date(0)),
Thu => DateFunction(isthursday, Date(0)),
Fri => DateFunction(isfriday, Date(0)),
Sat => DateFunction(issaturday, Date(0)),
Sun => DateFunction(issunday, Date(0)))
# "same" indicates whether the current date can be considered or not
"""
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)
# 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
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`](@ref).
`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)
func = deprecate_negate(:tonext, func, "func,dt", negate)
return adjust(DateFunction(func, dt), same ? dt : dt + step, step, limit)
end
"""
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(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`](@ref).
`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)
end
# Return the first TimeType that falls on dow in the Month or Year
"""
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.
"""
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
"""
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.
"""
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

View File

@@ -0,0 +1,98 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Instant arithmetic
(+)(x::Instant) = x
(-)(x::T, y::T) where {T<:Instant} = x.periods - y.periods
# TimeType arithmetic
(+)(x::TimeType) = x
(-)(x::T, y::T) where {T<:TimeType} = x.instant - y.instant
# Date-Time arithmetic
"""
dt::Date + t::Time -> DateTime
The addition of a `Date` with a `Time` produces a `DateTime`. The hour, minute, second, and millisecond parts of
the `Time` are used along with the year, month, and day of the `Date` to create the new `DateTime`.
Non-zero microseconds or nanoseconds in the `Time` type will result in an `InexactError` being thrown.
"""
function (+)(dt::Date, t::Time)
(microsecond(t) > 0 || nanosecond(t) > 0) && throw(InexactError())
y, m, d = yearmonthday(dt)
return DateTime(y, m, d, hour(t), minute(t), second(t), millisecond(t))
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))
end
function (+)(dt::Date,y::Year)
oy, m, d = yearmonthday(dt); ny = oy + value(y); ld = daysinmonth(ny, m)
return Date(ny, m, d <= ld ? d : ld)
end
function (-)(dt::DateTime,y::Year)
oy, m, d = yearmonthday(dt); ny = oy - value(y); ld = daysinmonth(ny, m)
return DateTime(ny, m, d <= ld ? d : ld, hour(dt), minute(dt), second(dt), millisecond(dt))
end
function (-)(dt::Date,y::Year)
oy, m, d = yearmonthday(dt); ny = oy - value(y); ld = daysinmonth(ny, m)
return Date(ny, m, d <= ld ? d : ld)
end
# TimeType-Month arithmetic
# monthwrap adds two months with wraparound behavior (i.e. 12 + 1 == 1)
monthwrap(m1, m2) = (v = mod1(m1 + m2, 12); return v < 0 ? 12 + v : v)
# yearwrap takes a starting year/month and a month to add and returns
# the resulting year with wraparound behavior (i.e. 2000-12 + 1 == 2001)
yearwrap(y, m1, m2) = y + fld(m1 + m2 - 1, 12)
function (+)(dt::DateTime, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y, m, value(z))
mm = monthwrap(m, value(z)); ld = daysinmonth(ny, mm)
return DateTime(ny, mm, d <= ld ? d : ld, hour(dt), minute(dt), second(dt), millisecond(dt))
end
function (+)(dt::Date, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y, m, value(z))
mm = monthwrap(m, value(z)); ld = daysinmonth(ny, mm)
return Date(ny, mm, d <= ld ? d : ld)
end
function (-)(dt::DateTime, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y, m, -value(z))
mm = monthwrap(m, -value(z)); ld = daysinmonth(ny, mm)
return DateTime(ny, mm, d <= ld ? d : ld, hour(dt), minute(dt), second(dt), millisecond(dt))
end
function (-)(dt::Date, z::Month)
y,m,d = yearmonthday(dt)
ny = yearwrap(y, m, -value(z))
mm = monthwrap(m, -value(z)); ld = daysinmonth(ny, mm)
return Date(ny, mm, d <= ld ? d : ld)
end
(+)(x::Date, y::Week) = return Date(UTD(value(x) + 7 * value(y)))
(-)(x::Date, y::Week) = return Date(UTD(value(x) - 7 * value(y)))
(+)(x::Date, y::Day) = return Date(UTD(value(x) + 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
(+)(x::AbstractArray{<:TimeType}, y::GeneralPeriod) = x .+ y
(+)(x::StridedArray{<:GeneralPeriod}, y::TimeType) = x .+ y
(+)(y::GeneralPeriod, x::AbstractArray{<:TimeType}) = x .+ y
(+)(y::TimeType, x::StridedArray{<:GeneralPeriod}) = x .+ y
(-)(x::AbstractArray{<:TimeType}, y::GeneralPeriod) = x .- y
(-)(x::StridedArray{<:GeneralPeriod}, y::TimeType) = x .- y
# TimeType, AbstractArray{TimeType}
(-)(x::AbstractArray{T}, y::T) where {T<:TimeType} = x .- y
(-)(y::T, x::AbstractArray{T}) where {T<:TimeType} = y .- x
# AbstractArray{TimeType}, AbstractArray{TimeType}
(-)(x::OrdinalRange{T}, y::OrdinalRange{T}) where {T<:TimeType} = collect(x) - collect(y)
(-)(x::Range{T}, y::Range{T}) where {T<:TimeType} = collect(x) - collect(y)

View File

@@ -0,0 +1,121 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Conversion/Promotion
"""
Date(dt::DateTime) -> Date
Converts a `DateTime` to a `Date`. The hour, minute, second, and millisecond parts of
the `DateTime` are truncated, so only the year, month and day parts are used in
construction.
"""
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)
"""
Time(dt::DateTime) -> Time
Converts a `DateTime` to a `Time`. The hour, minute, second, and millisecond parts of
the `DateTime` are used to create the new `Time`. Microsecond and nanoseconds are zero by default.
"""
Time(dt::DateTime) = convert(Time, dt)
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))
Base.convert(::Type{DateTime},x::Millisecond) = DateTime(Dates.UTInstant(x)) # Converts Rata Die milliseconds to a DateTime
Base.convert(::Type{Millisecond},dt::DateTime) = Millisecond(value(dt)) # Converts DateTime to Rata Die milliseconds
Base.convert(::Type{Date},x::Day) = Date(Dates.UTInstant(x)) # Converts Rata Die days to a Date
Base.convert(::Type{Day},dt::Date) = Day(value(dt)) # Converts Date to Rata Die days
### External Conversions
const UNIXEPOCH = value(DateTime(1970)) #Rata Die milliseconds for 1970-01-01T00:00:00
"""
unix2datetime(x) -> DateTime
Takes the number of seconds since unix epoch `1970-01-01T00:00:00` and converts to the
corresponding `DateTime`.
"""
function unix2datetime(x)
rata = UNIXEPOCH + round(Int64, Int64(1000) * x)
return DateTime(UTM(rata))
end
"""
datetime2unix(dt::DateTime) -> Float64
Takes the given `DateTime` and returns the number of seconds
since the unix epoch `1970-01-01T00:00:00` as a [`Float64`](@ref).
"""
datetime2unix(dt::DateTime) = (value(dt) - UNIXEPOCH) / 1000.0
"""
now() -> DateTime
Returns a `DateTime` corresponding to the user's system time including the system timezone
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))
end
"""
today() -> Date
Returns the date portion of `now()`.
"""
today() = Date(now())
"""
now(::Type{UTC}) -> DateTime
Returns a `DateTime` corresponding to the user's system time as UTC/GMT.
"""
now(::Type{UTC}) = unix2datetime(time())
"""
rata2datetime(days) -> DateTime
Takes the number of Rata Die days since epoch `0000-12-31T00:00:00` and returns the
corresponding `DateTime`.
"""
rata2datetime(days) = DateTime(yearmonthday(days)...)
"""
datetime2rata(dt::TimeType) -> Int64
Returns the number of Rata Die days since epoch from the given `Date` or `DateTime`.
"""
datetime2rata(dt::TimeType) = days(dt)
# Julian conversions
const JULIANEPOCH = value(DateTime(-4713, 11, 24, 12))
"""
julian2datetime(julian_days) -> DateTime
Takes the number of Julian calendar days since epoch `-4713-11-24T12:00:00` and returns the
corresponding `DateTime`.
"""
function julian2datetime(f)
rata = JULIANEPOCH + round(Int64, Int64(86400000) * f)
return DateTime(UTM(rata))
end
"""
datetime2julian(dt::DateTime) -> Float64
Takes the given `DateTime` and returns the number of Julian calendar days since the julian
epoch `-4713-11-24T12:00:00` as a [`Float64`](@ref).
"""
datetime2julian(dt::DateTime) = (value(dt) - JULIANEPOCH) / 86400000.0

View File

@@ -0,0 +1,547 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
AbstractDateToken
A token used in parsing or formatting a date time string. Each subtype must
define the tryparsenext and format methods.
"""
abstract type AbstractDateToken end
"""
tryparsenext(tok::AbstractDateToken, str::String, i::Int, len::Int, locale::DateLocale)
`tryparsenext` parses for the `tok` token in `str` starting at index `i`.
`len` is the length of the string. parsing can be optionally based on the
`locale`. If a `tryparsenext` method does not need a locale, it can leave
the argument out in the method definition.
Returns a tuple of 2 elements `(res, idx)`, where:
* `res` is a `Nullable{T}` - the result of the parsing, null if parsing failed.
* `idx` is an `Int` - if parsing failed, the index at which it failed; if
parsing succeeded, `idx` is the index _after_ the index at which parsing ended.
"""
function tryparsenext end
"""
format(io::IO, tok::AbstractDateToken, dt::TimeType, locale)
Format the `tok` token from `dt` and write it to `io`. The formatting can
be based on `locale`.
All subtypes of `AbstractDateToken` must define this method in order
to be able to print a Date / DateTime object according to a `DateFormat`
containing that token.
"""
function format end
# fallback to tryparsenext/format methods that don't care about locale
@inline function tryparsenext(d::AbstractDateToken, str, i, len, locale)
tryparsenext(d, str, i, len)
end
function Base.string(t::Time)
h, mi, s = hour(t), minute(t), second(t)
hh = lpad(h, 2, "0")
mii = lpad(mi, 2, "0")
ss = lpad(s, 2, "0")
nss = tons(Millisecond(t)) + tons(Microsecond(t)) + tons(Nanosecond(t))
ns = nss == 0 ? "" : rstrip(@sprintf("%.9f", nss / 1e+9)[2:end], '0')
return "$hh:$mii:$ss$ns"
end
Base.show(io::IO, x::Time) = print(io, string(x))
@inline function format(io, d::AbstractDateToken, dt, locale)
format(io, d, dt)
end
# Information for parsing and formatting date time values.
struct DateFormat{S, T<:Tuple}
tokens::T
locale::DateLocale
end
### Token types ###
struct DatePart{letter} <: AbstractDateToken
width::Int
fixed::Bool
end
@inline min_width(d::DatePart) = d.fixed ? d.width : 1
@inline max_width(d::DatePart) = d.fixed ? d.width : 0
function _show_content(io::IO, d::DatePart{c}) where c
for i = 1:d.width
write(io, c)
end
end
function Base.show(io::IO, d::DatePart{c}) where c
write(io, "DatePart(")
_show_content(io, d)
write(io, ")")
end
### Parse tokens
for c in "yYmdHMS"
@eval begin
@inline function tryparsenext(d::DatePart{$c}, str, i, len)
tryparsenext_base10(str, i, len, min_width(d), max_width(d))
end
end
end
for (tok, fn) in zip("uUeE", [monthabbr_to_value, monthname_to_value, dayabbr_to_value, dayname_to_value])
@eval @inline function tryparsenext(d::DatePart{$tok}, str, i, len, locale)
word, i = tryparsenext_word(str, i, len, locale, max_width(d))
val = isnull(word) ? 0 : $fn(get(word), locale)
if val == 0
return Nullable{Int64}(), i
else
return Nullable{Int64}(val), i
end
end
end
@inline function tryparsenext(d::DatePart{'s'}, str, i, len)
ms, ii = tryparsenext_base10(str, i, len, min_width(d), max_width(d))
if !isnull(ms)
val = get(ms)
len = ii - i
if len > 3
val, r = divrem(val, Int64(10) ^ (len - 3))
r == 0 || throw(InexactError())
else
val *= Int64(10) ^ (3 - len)
end
ms = Nullable{Int64}(val)
end
return ms, ii
end
### Format tokens
for (c, fn) in zip("YmdHMS", [year, month, day, hour, minute, second])
@eval function format(io, d::DatePart{$c}, dt)
write(io, dec($fn(dt), d.width))
end
end
for (tok, fn) in zip("uU", [monthabbr, monthname])
@eval function format(io, d::DatePart{$tok}, dt, locale)
write(io, $fn(month(dt), locale))
end
end
for (tok, fn) in zip("eE", [dayabbr, dayname])
@eval function format(io, ::DatePart{$tok}, dt, locale)
write(io, $fn(dayofweek(dt), locale))
end
end
@inline function format(io, d::DatePart{'y'}, dt)
y = year(dt)
n = d.width
# the last n digits of y
# will be 0 padded if y has less than n digits
str = dec(y, n)
l = endof(str)
if l == n
# fast path
write(io, str)
else
write(io, SubString(str, l - (n - 1), l))
end
end
function format(io, d::DatePart{'s'}, dt)
ms = millisecond(dt)
if ms % 100 == 0
str = dec(div(ms, 100), 1)
elseif ms % 10 == 0
str = dec(div(ms, 10), 2)
else
str = dec(ms, 3)
end
write(io, rpad(str, d.width, '0'))
end
### Delimiters
struct Delim{T, length} <: AbstractDateToken
d::T
end
Delim(d::Char) = Delim{Char, 1}(d)
Delim(d::String) = Delim{String, length(d)}(d)
@inline function tryparsenext(d::Delim{Char, N}, str, i::Int, len) where N
R = Nullable{Bool}
for j=1:N
i > len && return (R(), i)
c, i = next(str, i)
c != d.d && return (R(), i)
end
return R(true), i
end
@inline function tryparsenext(d::Delim{String, N}, str, i::Int, len) where N
R = Nullable{Bool}
i1 = i
i2 = start(d.d)
for j = 1:N
if i1 > len
return R(), i1
end
c1, i1 = next(str, i1)
c2, i2 = next(d.d, i2)
if c1 != c2
return R(), i1
end
end
return R(true), i1
end
@inline function format(io, d::Delim, dt, locale)
write(io, d.d)
end
function _show_content(io::IO, d::Delim{Char, N}) where N
if d.d in keys(CONVERSION_SPECIFIERS)
for i = 1:N
write(io, '\\', d.d)
end
else
for i = 1:N
write(io, d.d)
end
end
end
function _show_content(io::IO, d::Delim)
for c in d.d
if c in keys(CONVERSION_SPECIFIERS)
write(io, '\\')
end
write(io, c)
end
end
function Base.show(io::IO, d::Delim)
write(io, "Delim(")
_show_content(io, d)
write(io, ")")
end
### DateFormat construction
abstract type DayOfWeekToken end # special addition to Period types
# Map conversion specifiers or character codes to tokens.
# Note: Allow addition of new character codes added by packages
const CONVERSION_SPECIFIERS = Dict{Char, Type}(
'y' => Year,
'Y' => Year,
'm' => Month,
'u' => Month,
'U' => Month,
'e' => DayOfWeekToken,
'E' => DayOfWeekToken,
'd' => Day,
'H' => Hour,
'M' => Minute,
'S' => Second,
's' => Millisecond,
)
# Default values are needed when a conversion specifier is used in a DateFormat for parsing
# and we have reached the end of the input string.
# Note: Allow `Any` value as a default to support extensibility
const CONVERSION_DEFAULTS = Dict{Type, Any}(
Year => Int64(1),
Month => Int64(1),
DayOfWeekToken => Int64(0),
Day => Int64(1),
Hour => Int64(0),
Minute => Int64(0),
Second => Int64(0),
Millisecond => Int64(0),
)
# Specifies the required fields in order to parse a TimeType
# Note: Allows for addition of new TimeTypes
const CONVERSION_TRANSLATIONS = Dict{Type{<:TimeType}, Tuple}(
Date => (Year, Month, Day),
DateTime => (Year, Month, Day, Hour, Minute, Second, Millisecond),
)
"""
DateFormat(format::AbstractString, locale="english") -> DateFormat
Construct a date formatting object that can be used for parsing date strings or
formatting a date object as a string. The following character codes can be used to construct the `format`
string:
| Code | Matches | Comment |
|:-----------|:----------|:-------------------------------------------------------------|
| `y` | 1996, 96 | Returns year of 1996, 0096 |
| `Y` | 1996, 96 | Returns year of 1996, 0096. Equivalent to `y` |
| `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 |
| `e` | Mon, Tues | Matches abbreviated days of the week |
| `E` | Monday | Matches full name days of the week |
| `yyyymmdd` | 19960101 | Matches fixed-width year, month, and day |
Characters not listed above are normally treated as delimiters between date and time slots.
For example a `dt` string of "1996-01-15T00:00:00.0" would have a `format` string like
"y-m-dTH:M:S.s". If you need to use a code character as a delimiter you can escape it using
backslash. The date "1995y01m" would have the format "y\\ym\\m".
Creating a DateFormat object is expensive. Whenever possible, create it once and use it many times
or try the `dateformat""` string macro. Using this macro creates the DateFormat object once at
macro expansion time and reuses it later. see [`@dateformat_str`](@ref).
See [`DateTime`](@ref) and [`format`](@ref) for how to use a DateFormat object to parse and write Date strings
respectively.
"""
function DateFormat(f::AbstractString, locale::DateLocale=ENGLISH)
tokens = AbstractDateToken[]
prev = ()
prev_offset = 1
letters = String(collect(keys(CONVERSION_SPECIFIERS)))
for m in eachmatch(Regex("(?<!\\\\)([\\Q$letters\\E])\\1*"), f)
tran = replace(f[prev_offset:m.offset - 1], r"\\(.)", s"\1")
if !isempty(prev)
letter, width = prev
typ = CONVERSION_SPECIFIERS[letter]
push!(tokens, DatePart{letter}(width, isempty(tran)))
end
if !isempty(tran)
push!(tokens, Delim(length(tran) == 1 ? first(tran) : tran))
end
letter = f[m.offset]
width = length(m.match)
prev = (letter, width)
prev_offset = m.offset + width
end
tran = replace(f[prev_offset:endof(f)], r"\\(.)", s"\1")
if !isempty(prev)
letter, width = prev
typ = CONVERSION_SPECIFIERS[letter]
push!(tokens, DatePart{letter}(width, false))
end
if !isempty(tran)
push!(tokens, Delim(length(tran) == 1 ? first(tran) : tran))
end
tokens_tuple = (tokens...)
return DateFormat{Symbol(f),typeof(tokens_tuple)}(tokens_tuple, locale)
end
function DateFormat(f::AbstractString, locale::AbstractString)
DateFormat(f, LOCALES[locale])
end
function Base.show(io::IO, df::DateFormat)
write(io, "dateformat\"")
for t in df.tokens
_show_content(io, t)
end
write(io, '"')
end
"""
dateformat"Y-m-d H:M:S"
Create a [`DateFormat`](@ref) object. Similar to `DateFormat("Y-m-d H:M:S")`
but creates the DateFormat object once during macro expansion.
See [`DateFormat`](@ref) for details about format specifiers.
"""
macro dateformat_str(str)
DateFormat(str)
end
# Standard formats
const ISODateTimeFormat = DateFormat("yyyy-mm-dd\\THH:MM:SS.s")
const ISODateFormat = DateFormat("yyyy-mm-dd")
const RFC1123Format = DateFormat("e, dd u yyyy HH:MM:SS")
default_format(::Type{DateTime}) = ISODateTimeFormat
default_format(::Type{Date}) = ISODateFormat
### API
const Locale = Union{DateLocale, String}
"""
DateTime(dt::AbstractString, format::AbstractString; locale="english") -> DateTime
Construct a `DateTime` by parsing the `dt` date string following the pattern given in
the `format` string.
This method creates a `DateFormat` object each time it is called. If you are parsing many
date strings of the same format, consider creating a [`DateFormat`](@ref) object once and using
that as the second argument instead.
"""
function DateTime(dt::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(DateTime, dt, DateFormat(format, locale))
end
"""
DateTime(dt::AbstractString, df::DateFormat) -> DateTime
Construct a `DateTime` by parsing the `dt` date string following the pattern given in
the [`DateFormat`](@ref) object. Similar to
`DateTime(::AbstractString, ::AbstractString)` but more efficient when repeatedly parsing
similarly formatted date strings with a pre-created `DateFormat` object.
"""
DateTime(dt::AbstractString, df::DateFormat=ISODateTimeFormat) = parse(DateTime, dt, df)
"""
Date(dt::AbstractString, format::AbstractString; locale="english") -> Date
Construct a `Date` object by parsing a `dt` date string following the pattern given in the
`format` string. Follows the same conventions as
`DateTime(::AbstractString, ::AbstractString)`.
"""
function Date(dt::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Date, dt, DateFormat(format, locale))
end
"""
Date(dt::AbstractString, df::DateFormat) -> Date
Parse a date from a date string `dt` using a `DateFormat` object `df`.
"""
Date(dt::AbstractString,df::DateFormat=ISODateFormat) = parse(Date, dt, df)
@generated function format{S, T}(io::IO, dt::TimeType, fmt::DateFormat{S, T})
N = nfields(T)
quote
ts = fmt.tokens
loc = fmt.locale
Base.@nexprs $N i -> format(io, ts[i], dt, loc)
end
end
function format(dt::TimeType, fmt::DateFormat, bufsize=12)
# preallocate to reduce resizing
io = IOBuffer(Vector{UInt8}(bufsize), true, true)
format(io, dt, fmt)
String(io.data[1:io.ptr - 1])
end
"""
format(dt::TimeType, format::AbstractString; locale="english") -> AbstractString
Construct a string by using a `TimeType` object and applying the provided `format`. The
following character codes can be used to construct the `format` string:
| Code | Examples | Comment |
|:-----------|:----------|:-------------------------------------------------------------|
| `y` | 6 | Numeric year with a fixed width |
| `Y` | 1996 | Numeric year with a minimum width |
| `m` | 1, 12 | Numeric month with a minimum width |
| `u` | Jan | Month name shortened to 3-chars according to the `locale` |
| `U` | January | Full month name according to the `locale` keyword |
| `d` | 1, 31 | Day of the month with a minimum width |
| `H` | 0, 23 | Hour (24-hour clock) with a minimum width |
| `M` | 0, 59 | Minute with a minimum width |
| `S` | 0, 59 | Second with a minimum width |
| `s` | 000, 500 | Millisecond with a minimum width of 3 |
| `e` | Mon, Tue | Abbreviated days of the week |
| `E` | Monday | Full day of week name |
The number of sequential code characters indicate the width of the code. A format of
`yyyy-mm` specifies that the code `y` should have a width of four while `m` a width of two.
Codes that yield numeric digits have an associated mode: fixed-width or minimum-width.
The fixed-width mode left-pads the value with zeros when it is shorter than the specified
width and truncates the value when longer. Minimum-width mode works the same as fixed-width
except that it does not truncate values longer than the width.
When creating a `format` you can use any non-code characters as a separator. For example to
generate the string "1996-01-15T00:00:00" you could use `format`: "yyyy-mm-ddTHH:MM:SS".
Note that if you need to use a code character as a literal you can use the escape character
backslash. The string "1996y01m" can be produced with the format "yyyy\\ymm\\m".
"""
function format(dt::TimeType, f::AbstractString; locale::Locale=ENGLISH)
format(dt, DateFormat(f, locale))
end
# show
function Base.show(io::IO, dt::DateTime)
if millisecond(dt) == 0
format(io, dt, dateformat"YYYY-mm-dd\THH:MM:SS")
else
format(io, dt, dateformat"YYYY-mm-dd\THH:MM:SS.s")
end
end
function Base.show(io::IO, dt::Date)
format(io, dt, dateformat"YYYY-mm-dd")
end
function Base.string(dt::DateTime)
if millisecond(dt) == 0
format(dt, dateformat"YYYY-mm-dd\THH:MM:SS", 24)
else
format(dt, dateformat"YYYY-mm-dd\THH:MM:SS.s", 26)
end
end
function Base.string(dt::Date)
# don't use format - bypassing IOBuffer creation
# saves a bit of time here.
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
# vectorized
function DateTime(Y::AbstractArray{<:AbstractString}, f::AbstractString; locale::Locale=ENGLISH)
DateTime(Y, DateFormat(f, locale))
end
function DateTime(Y::AbstractArray{<:AbstractString}, df::DateFormat=ISODateTimeFormat)
return reshape(DateTime[parse(DateTime, y, df) for y in Y], size(Y))
end
function Date(Y::AbstractArray{<:AbstractString}, f::AbstractString; locale::Locale=ENGLISH)
Date(Y, DateFormat(f, locale))
end
function Date(Y::AbstractArray{<:AbstractString}, df::DateFormat=ISODateFormat)
return reshape(Date[Date(parse(Date, y, df)) for y in Y], size(Y))
end
function format(Y::AbstractArray{<:TimeType}, f::AbstractString; locale::Locale=ENGLISH)
format(Y, DateFormat(f, locale))
end
function format(Y::AbstractArray{T}, df::DateFormat=default_format(T)) where T<:TimeType
return reshape([format(y, df) for y in Y], size(Y))
end

View File

@@ -0,0 +1,311 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
### Parsing utilities
_directives(::Type{DateFormat{S,T}}) where {S,T} = T.parameters
character_codes(df::Type{DateFormat{S,T}}) where {S,T} = character_codes(_directives(df))
function character_codes(directives::SimpleVector)
letters = sizehint!(Char[], length(directives))
for (i, directive) in enumerate(directives)
if directive <: DatePart
letter = first(directive.parameters)
push!(letters, letter)
end
end
return letters
end
genvar(t::DataType) = Symbol(lowercase(string(Base.datatype_name(t))))
"""
tryparsenext_core(str::AbstractString, pos::Int, len::Int, df::DateFormat, raise=false)
Parses the string according to the directives within the DateFormat. Parsing will start at
character index `pos` and will stop when all directives are used or we have parsed up to
the end of the string, `len`. When a directive cannot be parsed the returned value tuple
will be null if `raise` is false otherwise an exception will be thrown.
Returns a 3-element tuple `(values, pos, num_parsed)`:
* `values::Nullable{Tuple}`: A tuple which contains a value for each `DatePart` within the
`DateFormat` in the order in which they occur. If the string ends before we finish parsing
all the directives the missing values will be filled in with default values.
* `pos::Int`: The character index at which parsing stopped.
* `num_parsed::Int`: The number of values which were parsed and stored within `values`.
Useful for distinguishing parsed values from default values.
"""
@generated function tryparsenext_core(str::AbstractString, pos::Int, len::Int,
df::DateFormat, raise::Bool=false)
directives = _directives(df)
letters = character_codes(directives)
tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
value_names = Symbol[genvar(t) for t in tokens]
value_defaults = Tuple(CONVERSION_DEFAULTS[t] for t in tokens)
R = typeof(value_defaults)
# Pre-assign variables to defaults. Allows us to use `@goto done` without worrying about
# unassigned variables.
assign_defaults = Expr[
quote
$name = $default
end
for (name, default) in zip(value_names, value_defaults)
]
vi = 1
parsers = Expr[
begin
if directives[i] <: DatePart
name = value_names[vi]
nullable = Symbol(:nullable_, name)
vi += 1
quote
pos > len && @goto done
$nullable, next_pos = tryparsenext(directives[$i], str, pos, len, locale)
isnull($nullable) && @goto error
$name = unsafe_get($nullable)
pos = next_pos
num_parsed += 1
directive_index += 1
end
else
quote
pos > len && @goto done
nullable_delim, next_pos = tryparsenext(directives[$i], str, pos, len, locale)
isnull(nullable_delim) && @goto error
pos = next_pos
directive_index += 1
end
end
end
for i in 1:length(directives)
]
quote
directives = df.tokens
locale::DateLocale = df.locale
num_parsed = 0
directive_index = 1
$(assign_defaults...)
$(parsers...)
pos > len || @goto error
@label done
return Nullable{$R}($(Expr(:tuple, value_names...))), pos, num_parsed
@label error
if raise
if directive_index > length(directives)
throw(ArgumentError("Found extra characters at the end of date time string"))
else
d = directives[directive_index]
throw(ArgumentError("Unable to parse date time. Expected directive $d at char $pos"))
end
end
return Nullable{$R}(), pos, 0
end
end
"""
tryparsenext_internal(::Type{<:TimeType}, str, pos, len, df::DateFormat, raise=false)
Parses the string according to the directives within the DateFormat. The specified TimeType
type determines the type of and order of tokens returned. If the given DateFormat or string
does not provide a required token a default value will be used. When the string cannot be
parsed the returned value tuple will be null if `raise` is false otherwise an exception will
be thrown.
Returns a 2-element tuple `(values, pos)`:
* `values::Nullable{Tuple}`: A tuple which contains a value for each token as specified by
the passed in type.
* `pos::Int`: The character index at which parsing stopped.
"""
@generated function tryparsenext_internal(::Type{T}, str::AbstractString, pos::Int, len::Int,
df::DateFormat, raise::Bool=false) where T<:TimeType
letters = character_codes(df)
tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
value_names = Symbol[genvar(t) for t in tokens]
output_tokens = CONVERSION_TRANSLATIONS[T]
output_names = Symbol[genvar(t) for t in output_tokens]
output_defaults = Tuple(CONVERSION_DEFAULTS[t] for t in output_tokens)
R = typeof(output_defaults)
# Pre-assign output variables to defaults. Ensures that all output variables are
# assigned as the value tuple returned from `tryparsenext_core` may not include all
# of the required variables.
assign_defaults = Expr[
quote
$name = $default
end
for (name, default) in zip(output_names, output_defaults)
]
# Unpacks the value tuple returned by `tryparsenext_core` into separate variables.
value_tuple = Expr(:tuple, value_names...)
quote
values, pos, num_parsed = tryparsenext_core(str, pos, len, df, raise)
isnull(values) && return Nullable{$R}(), pos
$(assign_defaults...)
$value_tuple = unsafe_get(values)
return Nullable{$R}($(Expr(:tuple, output_names...))), pos
end
end
@inline function tryparsenext_base10(str::AbstractString, i::Int, len::Int, min_width::Int=1, max_width::Int=0)
i > len && (return Nullable{Int64}(), i)
min_pos = min_width <= 0 ? i : i + min_width - 1
max_pos = max_width <= 0 ? len : min(i + max_width - 1, len)
d::Int64 = 0
@inbounds while i <= max_pos
c, ii = next(str, i)
if '0' <= c <= '9'
d = d * 10 + (c - '0')
else
break
end
i = ii
end
if i <= min_pos
return Nullable{Int64}(), i
else
return Nullable{Int64}(d), i
end
end
@inline function tryparsenext_word(str::AbstractString, i, len, locale, maxchars=0)
word_start, word_end = i, 0
max_pos = maxchars <= 0 ? len : min(chr2ind(str, ind2chr(str,i) + maxchars - 1), len)
@inbounds while i <= max_pos
c, ii = next(str, i)
if isalpha(c)
word_end = i
else
break
end
i = ii
end
if word_end == 0
return Nullable{SubString}(), i
else
return Nullable{SubString}(SubString(str, word_start, word_end)), i
end
end
function Base.parse(::Type{DateTime}, s::AbstractString, df::typeof(ISODateTimeFormat))
i, end_pos = start(s), endof(s)
dm = dd = Int64(1)
th = tm = ts = tms = Int64(0)
nv, i = tryparsenext_base10(s, i, end_pos, 1)
dy = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto error
c, i = next(s, i)
c != '-' && @goto error
i > end_pos && @goto done
nv, i = tryparsenext_base10(s, i, end_pos, 1, 2)
dm = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto done
c, i = next(s, i)
c != '-' && @goto error
i > end_pos && @goto done
nv, i = tryparsenext_base10(s, i, end_pos, 1, 2)
dd = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto done
c, i = next(s, i)
c != 'T' && @goto error
i > end_pos && @goto done
nv, i = tryparsenext_base10(s, i, end_pos, 1, 2)
th = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto done
c, i = next(s, i)
c != ':' && @goto error
i > end_pos && @goto done
nv, i = tryparsenext_base10(s, i, end_pos, 1, 2)
tm = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto done
c, i = next(s, i)
c != ':' && @goto error
i > end_pos && @goto done
nv, i = tryparsenext_base10(s, i, end_pos, 1, 2)
ts = isnull(nv) ? (@goto error) : unsafe_get(nv)
i > end_pos && @goto done
c, i = next(s, i)
c != '.' && @goto error
i > end_pos && @goto done
nv, j = tryparsenext_base10(s, i, end_pos, 1, 3)
tms = isnull(nv) ? (@goto error) : unsafe_get(nv)
tms *= 10 ^ (3 - (j - i))
j > end_pos || @goto error
@label done
return DateTime(dy, dm, dd, th, tm, ts, tms)
@label error
throw(ArgumentError("Invalid DateTime string"))
end
function Base.parse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType
pos, len = start(str), endof(str)
values, pos = tryparsenext_internal(T, str, pos, len, df, true)
T(unsafe_get(values)...)
end
function Base.tryparse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType
pos, len = start(str), endof(str)
values, pos = tryparsenext_internal(T, str, pos, len, df, false)
if isnull(values)
Nullable{T}()
elseif isnull(validargs(T, unsafe_get(values)...))
# TODO: validargs gets called twice, since it's called again in the T constructor
Nullable{T}(T(unsafe_get(values)...))
else
Nullable{T}()
end
end
"""
parse_components(str::AbstractString, df::DateFormat) -> Array{Any}
Parse the string into its components according to the directives in the DateFormat.
Each component will be a distinct type, typically a subtype of Period. The order of the
components will match the order of the `DatePart` directives within the DateFormat. The
number of components may be less than the total number of `DatePart`.
"""
@generated function parse_components(str::AbstractString, df::DateFormat)
letters = character_codes(df)
tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
quote
pos, len = start(str), endof(str)
values, pos, num_parsed = tryparsenext_core(str, pos, len, df, true)
t = unsafe_get(values)
types = $(Expr(:tuple, tokens...))
result = Vector{Any}(num_parsed)
for (i, typ) in enumerate(types)
i > num_parsed && break
result[i] = typ(t[i]) # Constructing types takes most of the time
end
return result
end
end

View File

@@ -0,0 +1,472 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
#Period types
value(x::Period) = 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 period in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
period_str = string(period)
accessor_str = lowercase(period_str)
# Convenience method for show()
@eval _units(x::$period) = " " * $accessor_str * (abs(value(x)) == 1 ? "" : "s")
# periodisless
@eval periodisless(x::$period, y::$period) = value(x) < value(y)
# AbstractString parsing (mainly for IO code)
@eval $period(x::AbstractString) = $period(Base.parse(Int64, x))
# Period accessors
typs = period in (:Microsecond, :Nanosecond) ? ["Time"] :
period in (:Hour, :Minute, :Second, :Millisecond) ? ["Time", "DateTime"] : ["Date", "DateTime"]
reference = period == :Week ? " For details see [`$accessor_str(::Union{Date, DateTime})`](@ref)." : ""
for typ_str in typs
@eval begin
@doc """
$($period_str)(dt::$($typ_str)) -> $($period_str)
The $($accessor_str) part of a $($typ_str) as a `$($period_str)`.$($reference)
""" $period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
end
end
@eval begin
@doc """
$($period_str)(v)
Construct a `$($period_str)` object with the given `v` value. Input must be
losslessly convertible to an [`Int64`](@ref).
""" $period(v)
end
end
#Print/show/traits
Base.string(x::Period) = string(value(x), _units(x))
Base.show(io::IO,x::Period) = print(io, string(x))
Base.zero(::Union{Type{P},P}) where {P<:Period} = P(0)
Base.one(::Union{Type{P},P}) where {P<:Period} = 1 # see #16116
Base.typemin(::Type{P}) where {P<:Period} = P(typemin(Int64))
Base.typemax(::Type{P}) where {P<:Period} = P(typemax(Int64))
# Default values (as used by TimeTypes)
"""
default(p::Period) -> Period
Returns a sensible "default" value for the input Period by returning `T(1)` for Year,
Month, and Day, and `T(0)` for Hour, Minute, Second, and Millisecond.
"""
function default end
default(p::Union{T,Type{T}}) where {T<:DatePeriod} = T(1)
default(p::Union{T,Type{T}}) where {T<:TimePeriod} = T(0)
(-)(x::P) where {P<:Period} = P(-value(x))
==(x::P, y::P) where {P<:Period} = value(x) == value(y)
==(x::Period, y::Period) = (==)(promote(x, y)...)
Base.isless(x::P, y::P) where {P<:Period} = isless(value(x), value(y))
Base.isless(x::Period, y::Period) = isless(promote(x, y)...)
# Period Arithmetic, grouped by dimensionality:
import Base: div, fld, mod, rem, gcd, lcm, +, -, *, /, %
for op in (:+, :-, :lcm, :gcd)
@eval ($op)(x::P, y::P) where {P<:Period} = P(($op)(value(x), value(y)))
end
for op in (:/, :div, :fld)
@eval begin
($op)(x::P, y::P) where {P<:Period} = ($op)(value(x), value(y))
($op)(x::P, y::Real) where {P<:Period} = P(($op)(value(x), Int64(y)))
end
end
for op in (:rem, :mod)
@eval begin
($op)(x::P, y::P) where {P<:Period} = P(($op)(value(x), value(y)))
($op)(x::P, y::Real) where {P<:Period} = P(($op)(value(x), Int64(y)))
end
end
(*)(x::P, y::Real) where {P<:Period} = P(value(x) * Int64(y))
(*)(y::Real, x::Period) = x * y
for (op, Ty, Tz) in ((:*, Real, :P),
(:/, :P, Float64), (:/, Real, :P))
@eval begin
function ($op){P<:Period}(X::StridedArray{P}, y::$Ty)
Z = similar(X, $Tz)
for (Idst, Isrc) in zip(eachindex(Z), eachindex(X))
@inbounds Z[Idst] = ($op)(X[Isrc], y)
end
return Z
end
end
end
# intfuncs
Base.gcdx{T<:Period}(a::T, b::T) = ((g, x, y) = gcdx(value(a), 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
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(::Microsecond,::Hour) = true
periodisless(::Nanosecond,::Hour) = true
periodisless(::Period,::Minute) = false
periodisless(::Second,::Minute) = true
periodisless(::Millisecond,::Minute) = true
periodisless(::Microsecond,::Minute) = true
periodisless(::Nanosecond,::Minute) = true
periodisless(::Period,::Second) = false
periodisless(::Millisecond,::Second) = true
periodisless(::Microsecond,::Second) = true
periodisless(::Nanosecond,::Second) = true
periodisless(::Period,::Millisecond) = false
periodisless(::Microsecond,::Millisecond) = true
periodisless(::Nanosecond,::Millisecond) = true
periodisless(::Period,::Microsecond) = false
periodisless(::Nanosecond,::Microsecond) = true
periodisless(::Period,::Nanosecond) = false
# return (next coarser period, conversion factor):
coarserperiod{P<:Period}(::Type{P}) = (P, 1)
coarserperiod(::Type{Nanosecond}) = (Microsecond, 1000)
coarserperiod(::Type{Microsecond}) = (Millisecond, 1000)
coarserperiod(::Type{Millisecond}) = (Second, 1000)
coarserperiod(::Type{Second}) = (Minute, 60)
coarserperiod(::Type{Minute}) = (Hour, 60)
coarserperiod(::Type{Hour}) = (Day, 24)
coarserperiod(::Type{Day}) = (Week, 7)
coarserperiod(::Type{Month}) = (Year, 12)
# Stores multiple periods in greatest to least order by type, not values,
# canonicalized to eliminate zero periods, merge equal period types,
# and convert more-precise periods to less-precise periods when possible
"""
CompoundPeriod
A `CompoundPeriod` is useful for expressing time periods that are not a fixed multiple of
smaller periods. For example, \"a year and a day\" is not a fixed number of days, but can
be expressed using a `CompoundPeriod`. In fact, a `CompoundPeriod` is automatically
generated by addition of different period types, e.g. `Year(1) + Day(1)` produces a
`CompoundPeriod` result.
"""
mutable struct CompoundPeriod <: AbstractTime
periods::Array{Period, 1}
function CompoundPeriod(p::Vector{Period})
n = length(p)
if n > 1
sort!(p, rev=true, lt=periodisless)
# canonicalize p by merging equal period types and removing zeros
i = j = 1
while j <= n
k = j + 1
while k <= n
if typeof(p[j]) == typeof(p[k])
p[j] += p[k]
k += 1
else
break
end
end
if p[j] != zero(p[j])
p[i] = p[j]
i += 1
end
j = k
end
n = i - 1 # new length
p = resize!(p, n)
elseif n == 1 && value(p[1]) == 0
p = Period[]
end
return new(p)
end
end
"""
CompoundPeriod(periods) -> CompoundPeriod
Construct a `CompoundPeriod` from a `Vector` of `Period`s. All `Period`s of the same type
will be added together.
# Examples
```jldoctest
julia> Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13))
25 hours
julia> Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1))
-1 hour, 1 minute
julia> Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2))
1 month, -2 weeks
julia> Dates.CompoundPeriod(Dates.Minute(50000))
50000 minutes
```
"""
CompoundPeriod(p::Vector{<:Period}) = CompoundPeriod(Vector{Period}(p))
CompoundPeriod(t::Time) = CompoundPeriod(Period[Hour(t), Minute(t), Second(t), Millisecond(t),
Microsecond(t), Nanosecond(t)])
CompoundPeriod(p::Period...) = CompoundPeriod(Period[p...])
"""
canonicalize(::CompoundPeriod) -> CompoundPeriod
Reduces the `CompoundPeriod` into its canonical form by applying the following rules:
* Any `Period` large enough be partially representable by a coarser `Period` will be broken
into multiple `Period`s (eg. `Hour(30)` becomes `Day(1) + Hour(6)`)
* `Period`s with opposite signs will be combined when possible
(eg. `Hour(1) - Day(1)` becomes `-Hour(23)`)
# Examples
```jldoctest
julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13)))
1 day, 1 hour
julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1)))
-59 minutes
julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2)))
1 month, -2 weeks
julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Minute(50000)))
4 weeks, 6 days, 17 hours, 20 minutes
```
"""
function canonicalize(x::CompoundPeriod)
# canonicalize Periods by pushing "overflow" into a coarser period.
p = x.periods
n = length(p)
if n > 0
pc = sizehint!(Period[], n)
P = typeof(p[n])
v = value(p[n])
i = n - 1
while true
Pc, f = coarserperiod(P)
if i > 0 && typeof(p[i]) == P
v += value(p[i])
i -= 1
end
v0 = f == 1 ? v : rem(v, f)
v0 != 0 && push!(pc, P(v0))
if v != v0
P = Pc
v = div(v - v0, f)
elseif i > 0
P = typeof(p[i])
v = value(p[i])
i -= 1
else
break
end
end
p = reverse!(pc)
n = length(p)
else
return x
end
# reduce the amount of mixed positive/negative Periods.
if n > 0
pc = sizehint!(Period[], n)
i = n
while i > 0
j = i
# Determine sign of the largest period in this group which
# can be converted into via coarserperiod.
last = Union{}
current = typeof(p[i])
while i > 0 && current != last
if typeof(p[i]) == current
i -= 1
end
last, current = current, coarserperiod(current)[1]
end
s = sign(value(p[i + 1]))
# Adjust all the periods in the group based upon the
# largest period sign.
P = typeof(p[j])
v = 0
while j > i
Pc, f = coarserperiod(P)
if j > 0 && typeof(p[j]) == P
v += value(p[j])
j -= 1
end
v0 = f == 1 ? v : mod(v, f * s)
v0 != 0 && push!(pc, P(v0))
if v != v0
P = Pc
v = div(v - v0, f)
elseif j > 0
P = typeof(p[j])
v = 0
else
break
end
end
end
p = reverse!(pc)
end
return CompoundPeriod(p)
end
Base.convert(::Type{CompoundPeriod}, x::Period) = CompoundPeriod(Period[x])
function Base.string(x::CompoundPeriod)
if isempty(x.periods)
return "empty period"
else
s = ""
for p in x.periods
s *= ", " * string(p)
end
return s[3:end]
end
end
Base.show(io::IO,x::CompoundPeriod) = print(io, string(x))
# E.g. Year(1) + Day(1)
(+)(x::Period,y::Period) = CompoundPeriod(Period[x, y])
(+)(x::CompoundPeriod, y::Period) = CompoundPeriod(vcat(x.periods, y))
(+)(y::Period, x::CompoundPeriod) = x + y
(+)(x::CompoundPeriod, y::CompoundPeriod) = CompoundPeriod(vcat(x.periods, y.periods))
# E.g. Year(1) - Month(1)
(-)(x::Period, y::Period) = CompoundPeriod(Period[x, -y])
(-)(x::CompoundPeriod, y::Period) = CompoundPeriod(vcat(x.periods, -y))
(-)(x::CompoundPeriod) = CompoundPeriod(-x.periods)
(-)(y::Union{Period, CompoundPeriod}, x::CompoundPeriod) = (-x) + y
GeneralPeriod = Union{Period, CompoundPeriod}
(+)(x::GeneralPeriod) = x
(+)(x::StridedArray{<:GeneralPeriod}) = x
for op in (:+, :-)
@eval begin
($op)(x::GeneralPeriod, Y::StridedArray{<:GeneralPeriod}) = broadcast($op, x, Y)
($op)(Y::StridedArray{<:GeneralPeriod}, x::GeneralPeriod) = broadcast($op, Y, x)
($op)(X::StridedArray{<:GeneralPeriod}, Y::StridedArray{<:GeneralPeriod}) =
reshape(CompoundPeriod[($op)(x, y) for (x, y) in zip(X, Y)], promote_shape(size(X), size(Y)))
end
end
(==)(x::CompoundPeriod, y::Period) = x == CompoundPeriod(y)
(==)(x::Period, y::CompoundPeriod) = y == x
(==)(x::CompoundPeriod, y::CompoundPeriod) = canonicalize(x).periods == canonicalize(y).periods
Base.isequal(x::CompoundPeriod, y::Period) = isequal(x, CompoundPeriod(y))
Base.isequal(x::Period, y::CompoundPeriod) = isequal(y, x)
Base.isequal(x::CompoundPeriod, y::CompoundPeriod) = x.periods == y.periods
# Capture TimeType+-Period methods
(+)(a::TimeType, b::Period, c::Period) = (+)(a, b + c)
(+)(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
function (-)(x::TimeType, y::CompoundPeriod)
for p in y.periods
x -= p
end
return x
end
# Fixed-value Periods (periods corresponding to a well-defined time interval,
# as opposed to variable calendar intervals like Year).
const FixedPeriod = Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond}
# like div but throw an error if remainder is nonzero
function divexact(x, y)
q, r = divrem(x, y)
r == 0 || throw(InexactError())
return q
end
# FixedPeriod conversions and promotion rules
const fixedperiod_conversions = [(Week, 7), (Day, 24), (Hour, 60), (Minute, 60), (Second, 1000), (Millisecond, 1000), (Microsecond, 1000), (Nanosecond, 1)]
for i = 1:length(fixedperiod_conversions)
T, n = fixedperiod_conversions[i]
N = Int64(1)
for j = (i - 1):-1:1 # less-precise periods
Tc, nc = fixedperiod_conversions[j]
N *= nc
vmax = typemax(Int64) ÷ N
vmin = typemin(Int64) ÷ N
@eval function Base.convert(::Type{$T}, x::$Tc)
$vmin value(x) $vmax || throw(InexactError())
return $T(value(x) * $N)
end
end
N = n
for j = (i + 1):length(fixedperiod_conversions) # more-precise periods
Tc, nc = fixedperiod_conversions[j]
@eval Base.convert(::Type{$T}, x::$Tc) = $T(divexact(value(x), $N))
@eval Base.promote_rule(::Type{$T}, ::Type{$Tc}) = $Tc
N *= nc
end
end
# other periods with fixed conversions but which aren't fixed time periods
const OtherPeriod = Union{Month, Year}
let vmax = typemax(Int64) ÷ 12, vmin = typemin(Int64) ÷ 12
@eval function Base.convert(::Type{Month}, x::Year)
$vmin value(x) $vmax || throw(InexactError())
Month(value(x) * 12)
end
end
Base.convert(::Type{Year}, x::Month) = Year(divexact(value(x), 12))
Base.promote_rule(::Type{Year}, ::Type{Month}) = Month
# disallow comparing fixed to other periods
(==)(x::FixedPeriod, y::OtherPeriod) = throw(MethodError(==, (x, y)))
(==)(x::OtherPeriod, y::FixedPeriod) = throw(MethodError(==, (x, y)))
Base.isless(x::FixedPeriod, y::OtherPeriod) = throw(MethodError(isless, (x, y)))
Base.isless(x::OtherPeriod, y::FixedPeriod) = throw(MethodError(isless, (x, y)))
# truncating conversions to milliseconds and days:
toms(c::Nanosecond) = div(value(c), 1000000)
toms(c::Microsecond) = div(value(c), 1000)
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)
toms(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(toms, c.periods))
tons(x) = toms(x) * 1000000
tons(x::Microsecond) = value(x) * 1000
tons(x::Nanosecond) = value(x)
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)
days(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(days, c.periods))

View File

@@ -0,0 +1,249 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Date Locales
struct DateLocale
months::Vector{String}
months_abbr::Vector{String}
days_of_week::Vector{String}
days_of_week_abbr::Vector{String}
month_value::Dict{String, Int}
month_abbr_value::Dict{String, Int}
day_of_week_value::Dict{String, Int}
day_of_week_abbr_value::Dict{String, Int}
end
function locale_dict(names::Vector{<:AbstractString})
result = Dict{String, Int}()
# Keep both the common case-sensitive version of the name and an all lowercase
# version for case-insensitive matches. Storing both allows us to avoid using the
# lowercase function during parsing.
for i in 1:length(names)
name = names[i]
result[name] = i
result[lowercase(name)] = i
end
return result
end
"""
DateLocale(["January", "February",...], ["Jan", "Feb",...],
["Monday", "Tuesday",...], ["Mon", "Tue",...])
Create a locale for parsing or printing textual month names.
Arguments:
- `months::Vector`: 12 month names
- `months_abbr::Vector`: 12 abbreviated month names
- `days_of_week::Vector`: 7 days of week
- `days_of_week_abbr::Vector`: 7 days of week abbreviated
This object is passed as the last argument to `tryparsenext` and `format`
methods defined for each `AbstractDateToken` type.
"""
function DateLocale(months::Vector, months_abbr::Vector,
days_of_week::Vector, days_of_week_abbr::Vector)
DateLocale(
months, months_abbr, days_of_week, days_of_week_abbr,
locale_dict(months), locale_dict(months_abbr),
locale_dict(days_of_week), locale_dict(days_of_week_abbr),
)
end
const ENGLISH = DateLocale(
["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"],
["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
)
const LOCALES = Dict{String, DateLocale}("english" => ENGLISH)
for (fn, field) in zip(
[:dayname_to_value, :dayabbr_to_value, :monthname_to_value, :monthabbr_to_value],
[:day_of_week_value, :day_of_week_abbr_value, :month_value, :month_abbr_value],
)
@eval @inline function $fn(word::AbstractString, locale::DateLocale)
# Maximize performance by attempting to avoid the use of `lowercase` and trying
# a case-sensitive lookup first
value = get(locale.$field, word, 0)
if value == 0
value = get(locale.$field, lowercase(word), 0)
end
value
end
end
# Date functions
### Core query functions
# Monday = 1....Sunday = 7
dayofweek(days) = mod1(days, 7)
# Number of days in year
"""
daysinyear(dt::TimeType) -> Int
Returns 366 if the year of `dt` is a leap year, otherwise returns 365.
"""
daysinyear(y) = 365 + isleapyear(y)
# Day of the year
const MONTHDAYS = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)
dayofyear(y, m, d) = MONTHDAYS[m] + d + (m > 2 && isleapyear(y))
### Days of the Week
"""
dayofweek(dt::TimeType) -> Int64
Returns the day of the week as an [`Int64`](@ref) with `1 = Monday, 2 = Tuesday, etc.`.
"""
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
dayname(day::Integer, locale::DateLocale) = locale.days_of_week[day]
dayabbr(day::Integer, locale::DateLocale) = locale.days_of_week_abbr[day]
dayname(day::Integer; locale::AbstractString="english") = dayname(day, LOCALES[locale])
dayabbr(day::Integer; locale::AbstractString="english") = dayabbr(day, LOCALES[locale])
"""
dayname(dt::TimeType; locale="english") -> AbstractString
Return the full day name corresponding to the day of the week of the `Date` or `DateTime` in
the given `locale`.
"""
function dayname(dt::TimeType;locale::AbstractString="english")
dayname(dayofweek(dt); locale=locale)
end
"""
dayabbr(dt::TimeType; locale="english") -> AbstractString
Return the abbreviated name corresponding to the day of the week of the `Date` or `DateTime`
in the given `locale`.
"""
function dayabbr(dt::TimeType;locale::AbstractString="english")
dayabbr(dayofweek(dt); locale=locale)
end
# 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) -> 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.` In the range 1:5.
"""
function dayofweekofmonth(dt::TimeType)
d = day(dt)
return d < 8 ? 1 : d < 15 ? 2 : d < 22 ? 3 : d < 29 ? 4 : 5
end
# 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])
"""
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 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
### 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
monthname(month::Integer, locale::DateLocale) = locale.months[month]
monthabbr(month::Integer, locale::DateLocale) = locale.months_abbr[month]
monthname(month::Integer; locale::AbstractString="english") = monthname(month, LOCALES[locale])
monthabbr(month::Integer; locale::AbstractString="english") = monthabbr(month, LOCALES[locale])
"""
monthname(dt::TimeType; locale="english") -> AbstractString
Return the full name of the month of the `Date` or `DateTime` in the given `locale`.
"""
function monthname(dt::TimeType; locale::AbstractString="english")
monthname(month(dt); locale=locale)
end
"""
monthabbr(dt::TimeType; locale="english") -> AbstractString
Return the abbreviated month name of the `Date` or `DateTime` in the given `locale`.
"""
function monthabbr(dt::TimeType; locale::AbstractString="english")
monthabbr(month(dt); locale=locale)
end
"""
daysinmonth(dt::TimeType) -> Int
Returns the number of days in the month of `dt`. Value will be 28, 29, 30, or 31.
"""
daysinmonth(dt::TimeType) = ((y, m) = yearmonth(dt); return daysinmonth(y, m))
### Years
"""
isleapyear(dt::TimeType) -> Bool
Returns `true` if the year of `dt` is a leap year.
"""
isleapyear(dt::TimeType) = isleapyear(year(dt))
"""
dayofyear(dt::TimeType) -> Int
Returns the day of the year for `dt` with January 1st being day 1.
"""
dayofyear(dt::TimeType) = ((y, m, d) = yearmonthday(dt); return dayofyear(y, m, d))
daysinyear(dt::TimeType) = 365 + isleapyear(dt)
### Quarters
"""
quarterofyear(dt::TimeType) -> Int
Returns the quarter that `dt` resides in. Range of value is 1:4.
"""
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) -> Int
Returns the day of the current quarter of `dt`. Range of value is 1:92.
"""
dayofquarter(dt::TimeType) = dayofyear(dt) - QUARTERDAYS[quarterofyear(dt)]

View File

@@ -0,0 +1,48 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Date/DateTime Ranges
# Override default step; otherwise it would be Millisecond(1)
Base.colon(start::T, stop::T) where {T<:DateTime} = StepRange(start, Day(1), stop)
Base.colon(start::T, stop::T) where {T<:Date} = StepRange(start, Day(1), stop)
Base.colon(start::T, stop::T) where {T<:Time} = StepRange(start, Second(1), stop)
Base.range(start::DateTime, len::Integer) = range(start, Day(1), len)
Base.range(start::Date, len::Integer) = range(start, Day(1), len)
(::Type{StepRange{<:Dates.DatePeriod,<:Real}})(start, step, stop) =
throw(ArgumentError("must specify step as a Period when constructing Dates ranges"))
# Given a start and end date, how many steps/periods are in between
guess(a::DateTime, b::DateTime, c) = floor(Int64, (Int128(value(b)) - Int128(value(a))) / toms(c))
guess(a::Date, b::Date, c) = Int64(div(value(b - a), days(c)))
len(a::Time, b::Time, c) = Int64(div(value(b - a), tons(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 - 1
end
Base.length(r::StepRange{<:TimeType}) = isempty(r) ? Int64(0) : len(r.start, r.stop, r.step) + 1
# Period ranges hook into Int64 overflow detection
Base.length(r::StepRange{<:Period}) = length(StepRange(value(r.start), value(r.step), value(r.stop)))
# Used to calculate the last valid date in the range given the start, stop, and step
# last = stop - steprem(start, stop, step)
Base.steprem(a::T, b::T, c) where {T<:TimeType} = b - (a + c * len(a, b, c))
import Base.in
function in(x::T, r::StepRange{T}) where T<:TimeType
n = len(first(r), x, step(r)) + 1
n >= 1 && n <= length(r) && r[n] == x
end
Base.start(r::StepRange{<:TimeType}) = 0
Base.next(r::StepRange{<:TimeType}, i::Int) = (r.start + r.step*i, i + 1)
Base.done(r::StepRange{<:TimeType,<:Period}, i::Integer) = length(r) <= i
+(x::Period, r::Range{<:TimeType}) = (x + first(r)):step(r):(x + last(r))
+(r::Range{<:TimeType}, x::Period) = x + r
-(r::Range{<:TimeType}, x::Period) = (first(r)-x):step(r):(last(r)-x)

View File

@@ -0,0 +1,180 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# The epochs used for date rounding are based ISO 8601's "year zero" notation
const DATEEPOCH = value(Date(0))
const DATETIMEEPOCH = value(DateTime(0))
# According to ISO 8601, the first day of the first week of year 0000 is 0000-01-03
const WEEKEPOCH = value(Date(0, 1, 3))
"""
epochdays2date(days) -> Date
Takes the number of days since the rounding epoch (`0000-01-01T00:00:00`) and returns the
corresponding `Date`.
"""
epochdays2date(i) = Date(UTD(DATEEPOCH + Int64(i)))
"""
epochms2datetime(milliseconds) -> DateTime
Takes the number of milliseconds since the rounding epoch (`0000-01-01T00:00:00`) and
returns the corresponding `DateTime`.
"""
epochms2datetime(i) = DateTime(UTM(DATETIMEEPOCH + Int64(i)))
"""
date2epochdays(dt::Date) -> Int64
Takes the given `Date` and returns the number of days since the rounding epoch
(`0000-01-01T00:00:00`) as an [`Int64`](@ref).
"""
date2epochdays(dt::Date) = value(dt) - DATEEPOCH
"""
datetime2epochms(dt::DateTime) -> Int64
Takes the given `DateTime` and returns the number of milliseconds since the rounding epoch
(`0000-01-01T00:00:00`) as an [`Int64`](@ref).
"""
datetime2epochms(dt::DateTime) = value(dt) - DATETIMEEPOCH
function Base.floor(dt::Date, p::Year)
value(p) < 1 && throw(DomainError())
years = year(dt)
return Date(years - mod(years, value(p)))
end
function Base.floor(dt::Date, p::Month)
value(p) < 1 && throw(DomainError())
y, m = yearmonth(dt)
months_since_epoch = y * 12 + m - 1
month_offset = months_since_epoch - mod(months_since_epoch, value(p))
target_month = mod(month_offset, 12) + 1
target_year = div(month_offset, 12) - (month_offset < 0 && target_month != 1)
return Date(target_year, target_month)
end
function Base.floor(dt::Date, p::Week)
value(p) < 1 && throw(DomainError())
days = value(dt) - WEEKEPOCH
days = days - mod(days, value(Day(p)))
return Date(UTD(WEEKEPOCH + Int64(days)))
end
function Base.floor(dt::Date, p::Day)
value(p) < 1 && throw(DomainError())
days = date2epochdays(dt)
return epochdays2date(days - mod(days, value(p)))
end
Base.floor(dt::DateTime, p::DatePeriod) = DateTime(Base.floor(Date(dt), p))
function Base.floor(dt::DateTime, p::TimePeriod)
value(p) < 1 && throw(DomainError())
milliseconds = datetime2epochms(dt)
return epochms2datetime(milliseconds - mod(milliseconds, value(Millisecond(p))))
end
"""
floor(dt::TimeType, p::Period) -> TimeType
Returns the nearest `Date` or `DateTime` less than or equal to `dt` at resolution `p`.
For convenience, `p` may be a type instead of a value: `floor(dt, Dates.Hour)` is a shortcut
for `floor(dt, Dates.Hour(1))`.
```jldoctest
julia> floor(Date(1985, 8, 16), Dates.Month)
1985-08-01
julia> floor(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15))
2013-02-13T00:30:00
julia> floor(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day)
2016-08-06T00:00:00
```
"""
Base.floor(::Dates.TimeType, ::Dates.Period)
"""
ceil(dt::TimeType, p::Period) -> TimeType
Returns the nearest `Date` or `DateTime` greater than or equal to `dt` at resolution `p`.
For convenience, `p` may be a type instead of a value: `ceil(dt, Dates.Hour)` is a shortcut
for `ceil(dt, Dates.Hour(1))`.
```jldoctest
julia> ceil(Date(1985, 8, 16), Dates.Month)
1985-09-01
julia> ceil(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15))
2013-02-13T00:45:00
julia> ceil(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day)
2016-08-07T00:00:00
```
"""
function Base.ceil(dt::TimeType, p::Period)
f = floor(dt, p)
return (dt == f) ? f : f + p
end
"""
floorceil(dt::TimeType, p::Period) -> (TimeType, TimeType)
Simultaneously return the `floor` and `ceil` of a `Date` or `DateTime` at resolution `p`.
More efficient than calling both `floor` and `ceil` individually.
"""
function floorceil(dt::TimeType, p::Period)
f = floor(dt, p)
return f, (dt == f) ? f : f + p
end
"""
round(dt::TimeType, p::Period, [r::RoundingMode]) -> TimeType
Returns the `Date` or `DateTime` nearest to `dt` at resolution `p`. By default
(`RoundNearestTiesUp`), ties (e.g., rounding 9:30 to the nearest hour) will be rounded up.
For convenience, `p` may be a type instead of a value: `round(dt, Dates.Hour)` is a shortcut
for `round(dt, Dates.Hour(1))`.
```jldoctest
julia> round(Date(1985, 8, 16), Dates.Month)
1985-08-01
julia> round(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15))
2013-02-13T00:30:00
julia> round(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day)
2016-08-07T00:00:00
```
Valid rounding modes for `round(::TimeType, ::Period, ::RoundingMode)` are
`RoundNearestTiesUp` (default), `RoundDown` (`floor`), and `RoundUp` (`ceil`).
"""
function Base.round(dt::TimeType, p::Period, r::RoundingMode{:NearestTiesUp})
f, c = floorceil(dt, p)
return (dt - f) < (c - dt) ? f : c
end
Base.round(dt::TimeType, p::Period, r::RoundingMode{:Down}) = Base.floor(dt, p)
Base.round(dt::TimeType, p::Period, r::RoundingMode{:Up}) = Base.ceil(dt, p)
# No implementation of other `RoundingMode`s: rounding to nearest "even" is skipped because
# "even" is not defined for Period; rounding toward/away from zero is skipped because ISO
# 8601's year 0000 is not really "zero".
Base.round(::TimeType, ::Period, ::RoundingMode) = throw(DomainError())
# Default to RoundNearestTiesUp.
Base.round(dt::TimeType, p::Period) = Base.round(dt, p, RoundNearestTiesUp)
# Make rounding functions callable using Period types in addition to values.
Base.floor(dt::TimeType, p::Type{<:Period}) = Base.floor(dt, p(1))
Base.ceil(dt::TimeType, p::Type{<:Period}) = Base.ceil(dt, p(1))
function Base.round(dt::TimeType, p::Type{<:Period}, r::RoundingMode=RoundNearestTiesUp)
return Base.round(dt, p(1), r)
end

View File

@@ -0,0 +1,358 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
abstract type AbstractTime end
"""
Period
Year
Month
Week
Day
Hour
Minute
Second
Millisecond
Microsecond
Nanosecond
`Period` types represent discrete, human representations of time.
"""
abstract type Period <: AbstractTime end
abstract type DatePeriod <: Period end
abstract type TimePeriod <: Period end
for T in (:Year, :Month, :Week, :Day)
@eval struct $T <: DatePeriod
value::Int64
$T(v::Number) = new(v)
end
end
for T in (:Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
@eval struct $T <: TimePeriod
value::Int64
$T(v::Number) = new(v)
end
end
"""
Year(v)
Month(v)
Week(v)
Day(v)
Hour(v)
Minute(v)
Second(v)
Millisecond(v)
Microsecond(v)
Nanosecond(v)
Construct a `Period` type with the given `v` value. Input must be losslessly convertible
to an [`Int64`](@ref).
"""
Period(v)
"""
Instant
`Instant` types represent integer-based, machine representations of time as continuous
timelines starting from an epoch.
"""
abstract type Instant <: AbstractTime end
"""
UTInstant{T}
The `UTInstant` represents a machine timeline based on UT time (1 day = one revolution of
the earth). The `T` is a `Period` parameter that indicates the resolution or precision of
the instant.
"""
struct UTInstant{P<:Period} <: Instant
periods::P
end
# Convenience default constructors
UTM(x) = UTInstant(Millisecond(x))
UTD(x) = UTInstant(Day(x))
# Calendar types provide rules for interpretating instant
# timelines in human-readable form.
abstract type Calendar <: AbstractTime end
# 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 civil date and time parts
struct ISOCalendar <: Calendar end
abstract type TimeZone end
struct UTC <: TimeZone end
"""
TimeType
`TimeType` types wrap `Instant` machine instances to provide human representations of the
machine instant. `Time`, `DateTime` and `Date` are subtypes of `TimeType`.
"""
abstract type TimeType <: AbstractTime end
"""
DateTime
`DateTime` wraps a `UTInstant{Millisecond}` and interprets it according to the proleptic
Gregorian calendar.
"""
struct DateTime <: TimeType
instant::UTInstant{Millisecond}
DateTime(instant::UTInstant{Millisecond}) = new(instant)
end
"""
Date
`Date` wraps a `UTInstant{Day}` and interprets it according to the proleptic Gregorian calendar.
"""
struct Date <: TimeType
instant::UTInstant{Day}
Date(instant::UTInstant{Day}) = new(instant)
end
"""
Time
`Time` wraps a `Nanosecond` and represents a specific moment in a 24-hour day.
"""
struct Time <: TimeType
instant::Nanosecond
Time(instant::Nanosecond) = new(instant)
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 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 = 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))
### UTILITIES ###
# These are necessary because the type constructors for TimeType subtypes can
# throw, and we want to be able to use tryparse without requiring a try/catch.
# This is made easier by providing a helper function that checks arguments, so
# we can validate arguments in tryparse.
"""
validargs(::Type{<:TimeType}, args...) -> Nullable{ArgumentError}
Determine whether the given arguments consitute valid inputs for the given type.
Returns a `Nullable{ArgumentError}` where null signifies success.
"""
function validargs end
"""
argerror([msg]) -> Nullable{ArgumentError}
Construct a `Nullable{ArgumentError}` with the given message, or null if no message
is provided. For use by `validargs`.
"""
argerror(msg::String) = Nullable(ArgumentError(msg))
argerror() = Nullable{ArgumentError}()
### CONSTRUCTORS ###
# Core constructors
"""
DateTime(y, [m, d, h, mi, s, ms]) -> DateTime
Construct a `DateTime` type by parts. Arguments must be convertible to [`Int64`](@ref).
"""
function DateTime(y::Int64, m::Int64=1, d::Int64=1,
h::Int64=0, mi::Int64=0, s::Int64=0, ms::Int64=0)
err = validargs(DateTime, y, m, d, h, mi, s, ms)
isnull(err) || throw(unsafe_get(err))
rata = ms + 1000 * (s + 60mi + 3600h + 86400 * totaldays(y, m, d))
return DateTime(UTM(rata))
end
function validargs(::Type{DateTime}, y::Int64, m::Int64, d::Int64,
h::Int64, mi::Int64, s::Int64, ms::Int64)
0 < m < 13 || return argerror("Month: $m out of range (1:12)")
0 < d < daysinmonth(y, m) + 1 || return argerror("Day: $d out of range (1:$(daysinmonth(y, m)))")
-1 < h < 24 || return argerror("Hour: $h out of range (0:23)")
-1 < mi < 60 || return argerror("Minute: $mi out of range (0:59)")
-1 < s < 60 || return argerror("Second: $s out of range (0:59)")
-1 < ms < 1000 || return argerror("Millisecond: $ms out of range (0:999)")
return argerror()
end
"""
Date(y, [m, d]) -> Date
Construct a `Date` type by parts. Arguments must be convertible to [`Int64`](@ref).
"""
function Date(y::Int64, m::Int64=1, d::Int64=1)
err = validargs(Date, y, m, d)
isnull(err) || throw(unsafe_get(err))
return Date(UTD(totaldays(y, m, d)))
end
function validargs(::Type{Date}, y::Int64, m::Int64, d::Int64)
0 < m < 13 || return argerror("Month: $m out of range (1:12)")
0 < d < daysinmonth(y, m) + 1 || return argerror("Day: $d out of range (1:$(daysinmonth(y, m)))")
return argerror()
end
"""
Time(h, [mi, s, ms, us, ns]) -> Time
Construct a `Time` type by parts. Arguments must be convertible to [`Int64`](@ref).
"""
function Time(h::Int64, mi::Int64=0, s::Int64=0, ms::Int64=0, us::Int64=0, ns::Int64=0)
err = validargs(Time, h, mi, s, ms, us, ns)
isnull(err) || throw(unsafe_get(err))
return Time(Nanosecond(ns + 1000us + 1000000ms + 1000000000s + 60000000000mi + 3600000000000h))
end
function validargs(::Type{Time}, h::Int64, mi::Int64, s::Int64, ms::Int64, us::Int64, ns::Int64)
-1 < h < 24 || return argerror("Hour: $h out of range (0:23)")
-1 < mi < 60 || return argerror("Minute: $mi out of range (0:59)")
-1 < s < 60 || return argerror("Second: $s out of range (0:59)")
-1 < ms < 1000 || return argerror("Millisecond: $ms out of range (0:999)")
-1 < us < 1000 || return argerror("Microsecond: $us out of range (0:999)")
-1 < ns < 1000 || return argerror("Nanosecond: $ns out of range (0:999)")
return argerror()
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))
function Time(h::Hour, mi::Minute=Minute(0), s::Second=Second(0),
ms::Millisecond=Millisecond(0),
us::Microsecond=Microsecond(0), ns::Nanosecond=Nanosecond(0))
return Time(value(h), value(mi), value(s), value(ms), value(us), value(ns))
end
# To allow any order/combination of Periods
"""
DateTime(periods::Period...) -> DateTime
Construct 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(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
isa(p, Year) && (y = p::Year)
isa(p, Month) && (m = p::Month)
isa(p, Day) && (d = p::Day)
isa(p, Hour) && (h = p::Hour)
isa(p, Minute) && (mi = p::Minute)
isa(p, Second) && (s = p::Second)
isa(p, Millisecond) && (ms = p::Millisecond)
end
return DateTime(y, m, d, h, mi, s, ms)
end
"""
Date(period::Period...) -> Date
Construct 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(periods::Period...)
y = Year(1); m = Month(1); d = Day(1)
for p in periods
isa(p, Year) && (y = p::Year)
isa(p, Month) && (m = p::Month)
isa(p, Day) && (d = p::Day)
end
return Date(y, m, d)
end
"""
Time(period::TimePeriod...) -> Time
Construct a `Time` type by `Period` type parts. Arguments may be in any order. `Time` parts
not provided will default to the value of `Dates.default(period)`.
"""
function Time(periods::TimePeriod...)
h = Hour(0); mi = Minute(0); s = Second(0)
ms = Millisecond(0); us = Microsecond(0); ns = Nanosecond(0)
for p in periods
isa(p, Hour) && (h = p::Hour)
isa(p, Minute) && (mi = p::Minute)
isa(p, Second) && (s = p::Second)
isa(p, Millisecond) && (ms = p::Millisecond)
isa(p, Microsecond) && (us = p::Microsecond)
isa(p, Nanosecond) && (ns = p::Nanosecond)
end
return Time(h, mi, s, ms, us, ns)
end
# Fallback constructors
DateTime(y, m=1, d=1, h=0, mi=0, s=0, ms=0) = DateTime(Int64(y), Int64(m), Int64(d), Int64(h), Int64(mi), Int64(s), Int64(ms))
Date(y, m=1, d=1) = Date(Int64(y), Int64(m), Int64(d))
Time(h, mi=0, s=0, ms=0, us=0, ns=0) = Time(Int64(h), Int64(mi), Int64(s), Int64(ms), Int64(us), Int64(ns))
# Traits, Equality
Base.isfinite(::Union{Type{T}, T}) where {T<:TimeType} = true
calendar(dt::DateTime) = ISOCalendar
calendar(dt::Date) = ISOCalendar
"""
eps(::DateTime) -> Millisecond
eps(::Date) -> Day
eps(::Time) -> Nanosecond
Returns `Millisecond(1)` for `DateTime` values, `Day(1)` for `Date` values, and `Nanosecond(1)` for `Time` values.
"""
Base.eps
Base.eps(dt::DateTime) = Millisecond(1)
Base.eps(dt::Date) = Day(1)
Base.eps(t::Time) = Nanosecond(1)
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)
Base.typemax(::Union{Time, Type{Time}}) = Time(23, 59, 59, 999, 999, 999)
Base.typemin(::Union{Time, Type{Time}}) = Time(0)
# Date-DateTime promotion, isless, ==
Base.eltype(::Type{T}) where {T<:Period} = T
Base.promote_rule(::Type{Date}, x::Type{DateTime}) = DateTime
Base.isless(x::T, y::T) where {T<:TimeType} = isless(value(x), value(y))
Base.isless(x::TimeType, y::TimeType) = isless(Base.promote_noncircular(x, y)...)
(==)(x::T, y::T) where {T<:TimeType} = (==)(value(x), value(y))
function ==(a::Time, b::Time)
return hour(a) == hour(b) && minute(a) == minute(b) &&
second(a) == second(b) && millisecond(a) == millisecond(b) &&
microsecond(a) == microsecond(b) && nanosecond(a) == nanosecond(b)
end
(==)(x::TimeType, y::TimeType) = (===)(promote(x, y)...)
import Base: sleep, Timer, timedwait
sleep(time::Period) = sleep(toms(time) / 1000)
Timer(time::Period, repeat::Period=Second(0)) = Timer(toms(time) / 1000, toms(repeat) / 1000)
timedwait(testcb::Function, time::Period) = timedwait(testcb, toms(time) / 1000)
Base.TypeOrder(::Type{<:AbstractTime}) = Base.HasOrder()
Base.TypeArithmetic(::Type{<:AbstractTime}) = Base.ArithmeticOverflows()