Skip to content

Commit

Permalink
Time string parsing for Time type (#22163)
Browse files Browse the repository at this point in the history
Adds:

- `parse(::Type{Time}, ...)` methods
- constructors for creating `Time` from a string
- default ISO time format (`HH:MM:SS.s`)
- conversion defaults for `Microsecond` and `Nanosecond` (`0`)
  • Loading branch information
joshbode authored and quinnj committed Jun 11, 2017
1 parent 2f72030 commit 6caaad8
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 19 deletions.
2 changes: 1 addition & 1 deletion base/dates/Dates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,6 @@ export Period, DatePeriod, TimePeriod,
firstdayofquarter, lastdayofquarter,
adjust, tonext, toprev, tofirst, tolast,
# io.jl
ISODateTimeFormat, ISODateFormat, DateFormat, RFC1123Format, @dateformat_str
ISODateTimeFormat, ISODateFormat, ISOTimeFormat, DateFormat, RFC1123Format, @dateformat_str

end # module
66 changes: 48 additions & 18 deletions base/dates/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,16 @@ const CONVERSION_DEFAULTS = Dict{Type, Any}(
Minute => Int64(0),
Second => Int64(0),
Millisecond => Int64(0),
Microsecond => Int64(0),
Nanosecond => 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),
Time => (Hour, Minute, Second, Millisecond, Microsecond, Nanosecond),
)

"""
Expand Down Expand Up @@ -388,10 +391,12 @@ end
# Standard formats
const ISODateTimeFormat = DateFormat("yyyy-mm-dd\\THH:MM:SS.s")
const ISODateFormat = DateFormat("yyyy-mm-dd")
const ISOTimeFormat = DateFormat("HH:MM:SS.s")
const RFC1123Format = DateFormat("e, dd u yyyy HH:MM:SS")

default_format(::Type{DateTime}) = ISODateTimeFormat
default_format(::Type{Date}) = ISODateFormat
default_format(::Type{Time}) = ISOTimeFormat

### API

Expand All @@ -400,12 +405,12 @@ 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.
Construct a `DateTime` by parsing the `dt` date time 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.
This method creates a `DateFormat` object each time it is called. If you are
parsing many date time 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))
Expand All @@ -414,30 +419,55 @@ 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.
Construct a `DateTime` by parsing the `dt` date time string following the
pattern given in the [`DateFormat`](@ref) object. Similar to
`DateTime(::AbstractString, ::AbstractString)` but more efficient when
repeatedly parsing similarly formatted date time 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
Date(d::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)`.
Construct a `Date` by parsing the `d` 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 Date(d::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Date, d, DateFormat(format, locale))
end

"""
Date(d::AbstractString, df::DateFormat) -> Date
Parse a date from a date string `d` using a `DateFormat` object `df`.
"""
Date(d::AbstractString, df::DateFormat=ISODateFormat) = parse(Date, d, df)

"""
Time(t::AbstractString, format::AbstractString; locale="english") -> Time
Construct a `Time` by parsing the `t` time 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 time strings of the same format, consider creating a
[`DateFormat`](@ref) object once and using that as the second argument instead.
"""
function Date(dt::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Date, dt, DateFormat(format, locale))
function Time(t::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Time, t, DateFormat(format, locale))
end

"""
Date(dt::AbstractString, df::DateFormat) -> Date
Time(t::AbstractString, df::DateFormat) -> Time
Parse a date from a date string `dt` using a `DateFormat` object `df`.
Parse a time from a time string `t` using a `DateFormat` object `df`.
"""
Date(dt::AbstractString,df::DateFormat=ISODateFormat) = parse(Date, dt, df)
Time(t::AbstractString, df::DateFormat=ISOTimeFormat) = parse(Time, t, df)

@generated function format(io::IO, dt::TimeType, fmt::DateFormat{<:Any,T}) where T
N = nfields(T)
Expand Down
39 changes: 39 additions & 0 deletions test/dates/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,42 @@ end
# Ensure that no overflow occurs when using Int32 literals: Int32(10)^10
@test Dates.parse_components("." * rpad(999, 10, '0'), Dates.DateFormat(".s")) == [Dates.Millisecond(999)]
end

# Time Parsing
let
time_tuple(t::Dates.Time) = (
Dates.hour(t), Dates.minute(t), Dates.second(t),
Dates.millisecond(t), Dates.microsecond(t), Dates.nanosecond(t)
)

## default ISOTimeFormat
t = Dates.Time("01")
@test time_tuple(t) == (1, 0, 0, 0, 0, 0)
t = Dates.Time("01:23")
@test time_tuple(t) == (1, 23, 0, 0, 0, 0)
t = Dates.Time("01:23:45")
@test time_tuple(t) == (1, 23, 45, 0, 0, 0)
t = Dates.Time("01:23:45.678")
@test time_tuple(t) == (1, 23, 45, 678, 0, 0)

## string format
t = Dates.Time("23:56:12.1", "HH:MM:SS.s")
@test time_tuple(t) == (23, 56, 12, 100, 0, 0)

## precomputed DateFormat
t = Dates.Time("04:09:45.012", DateFormat("HH:MM:SS.s"))
@test time_tuple(t) == (4, 9, 45, 12, 0, 0)
t = Dates.Time("21 07", DateFormat("HH MM"))
@test time_tuple(t) == (21, 7, 0, 0, 0, 0)
t = Dates.Time("4.02", DateFormat("H.MM"))
@test time_tuple(t) == (4, 2, 0, 0, 0, 0)
t = Dates.Time("1725", DateFormat("HHMM"))
@test time_tuple(t) == (17, 25, 0, 0, 0, 0)

## exceptions
@test_throws ArgumentError Dates.Time("24:00") # invalid hours
@test_throws ArgumentError Dates.Time("00:60") # invalid minutes
@test_throws ArgumentError Dates.Time("00:00:60") # invalid seconds
@test_throws ArgumentError Dates.Time("20:03:20", DateFormat("HH:MM")) # too much precision
@test_throws ArgumentError Dates.Time("10:33:51", DateFormat("YYYY-MM-DD HH:MM:SS")) # Time can't hold year/month/day
end

0 comments on commit 6caaad8

Please sign in to comment.