diff --git a/base/dates/parse.jl b/base/dates/parse.jl index e638319f10b09..4c2c360c63c0e 100644 --- a/base/dates/parse.jl +++ b/base/dates/parse.jl @@ -200,3 +200,58 @@ function Base.parse(::Type{DateTime}, s::AbstractString, df::typeof(ISODateTimeF @label error throw(ArgumentError("Invalid DateTime string")) end + +@generated function tryparse_internal{S, F}(::Type{Array{Period}}, str::AbstractString, df::DateFormat{S, F}, raise::Bool=false) + token_types = Type[dp <: DatePart ? SLOT_RULE[first(dp.parameters)] : Void for dp in F.parameters] + N = length(token_types) + + field_order = sizehint!(Int[], N) + field_types = sizehint!(Type[], N) + for (i, typ) in enumerate(token_types) + if typ <: Period + push!(field_order, i) + push!(field_types, typ) + end + end + field_parsers = collect(zip(field_order, field_types)) + + quote + periods = sizehint!(Period[], $(length(field_parsers))) + t = df.tokens + l = df.locale + pos, len = start(str), endof(str) + + err_idx = 1 + Base.@nexprs $N i->val_i = 0 + Base.@nexprs $N i->(begin + pos > len && @goto done + nv, next_pos = tryparsenext(t[i], str, pos, len, l) + isnull(nv) && @goto error + val_i, pos = unsafe_get(nv), next_pos + err_idx += 1 + end) + pos <= len && @goto error + + @label done + parts = Base.@ntuple $N val + for (i, P) in $field_parsers + if i < err_idx + push!(periods, P(parts[i])) + end + end + return periods + + @label error + # Note: Keeping exception generation in separate function helps with performance + raise && throw(gen_exception(t, err_idx, pos)) + return periods + end +end + +function Base.tryparse{T<:Array{Period}}(::Type{T}, str::AbstractString, df::DateFormat) + tryparse_internal(T, str, df, false) +end + +function Base.parse{T<:Array{Period}}(::Type{T}, str::AbstractString, df::DateFormat) + tryparse_internal(T, str, df, true) +end diff --git a/base/deprecated.jl b/base/deprecated.jl index 9ecfd1843f90f..ad4013e87dd80 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1278,6 +1278,14 @@ end @deprecate_binding LinearSlow IndexCartesian false @deprecate_binding linearindexing IndexStyle false +# #20876 +@eval Base.Dates begin + function Base.Dates.parse(x::AbstractString, df::DateFormat) + Base.depwarn("`Dates.parse(x::AbstractString, df::DateFormat)` is deprecated, use `sort!(parse(Array{Dates.Period}, x, df), rev=true, lt=Dates.periodisless)` instead.", :parse) + sort!(parse(Array{Period}, x, df), rev=true, lt=periodisless) + end +end + # END 0.6 deprecations # BEGIN 1.0 deprecations diff --git a/test/dates/io.jl b/test/dates/io.jl index 7a3790478c2fc..a773ba7635d2e 100644 --- a/test/dates/io.jl +++ b/test/dates/io.jl @@ -29,10 +29,18 @@ # DateTime parsing # Useful reference for different locales: http://library.princeton.edu/departments/tsd/katmandu/reference/months.html -let str = "1996/02/15 24:00", format = "yyyy/mm/dd HH:MM" - expected = (1996, 2, 15, 24, 0, 0, 0) - @test get(Dates.tryparse_internal(DateTime, str, Dates.DateFormat(format))) == expected +# Using parse(::Array{Period}, ...) allows for more flexibility. +let str = "02/15/1996 24:00", format = Dates.DateFormat("mm/dd/yyyy HH:MM"), T = Array{Dates.Period} @test_throws ArgumentError Dates.DateTime(str, Dates.DateFormat(format)) + + expected = [Dates.Month(2), Dates.Day(15), Dates.Year(1996), Dates.Hour(24), Dates.Minute(0)] + @test Dates.parse(T, str, Dates.DateFormat(format)) == expected +end + +let T = Array{Dates.Period} + expected = Dates.Period[Dates.Year(2017), Dates.Month(3)] + @test Dates.parse(T, "2017-03", DateFormat("yyyy-mm-dd")) == expected + @test_throws ArgumentError Dates.parse(T, "2017-03-03", DateFormat("yyyy-mm")) end # DateFormat printing