Commit b293b6c4 authored by MichelJuillard's avatar MichelJuillard
Browse files

WIP intermediary update

parent 50300cdb
Pipeline #5903 failed with stage
in 37 seconds
......@@ -2,6 +2,7 @@ module Periods
using Dates
abstract type AbstractPeriod end
include("io.jl")
include("SimplePeriods.jl")
export Frequency, Period, Year, Semester, Quarter, Month, Week, Day, Undated
......
function week_per_year()
weeks = Dict()
thisyear = year(today())
cummulativeweeks = 0
for y in 1970:thisyear
w = week(Date(y, 12, 31))
if w == 1
w = week(Date(y, 12, 24))
end
cummulativeweeks += w
weeks[y] = Dict([("max_weeks", w),
("cum_weeks", cummulativeweeks)])
end
cummulativeweeks = 0
for y in 1969:-1:1900
w = week(Date(y, 12, 31))
if w == 1
w = week(Date(y, 12, 24))
end
cummulativeweeks -= w
weeks[y] = Dict([("max_weeks", w),
("cum_weeks", cummulativeweeks)])
end
return weeks
end
const weeks = week_per_year()
function year(p::Period)
freq = p.frequency
if freq == Year
y = p.ordinal
elseif freq == Semester
y = div(p.ordinal, 2)
elseif freq == Quarter
y = div(p.ordinal, 4)
elseif freq == Month
y = div(p.ordinal, 12)
elseif freq == Week
d = Date(Dates.UTInstant(Day(7*p.ordinal)))
y = year(d)
elseif freq == Day
d = Date(Dates.UTInstant(Day(p.ordinal)))
y = year(d)
else
return nothing
end
return y + 1970
end
function semester(p::Period)
freq = p.frequency
if freq == Year
return nothing
elseif freq == Semester
return rem(p.ordinal, 2)
elseif freq == Quarter
return rem(rem(p.ordinal, 4), 2)
elseif freq == Month
return rem(rem(p.ordinal, 12), 6)
elseif freq == Week
return week(7*p.ordinal)
elseif freq == Day
return week(p.ordinal)
else
return nothing
end
end
function quarter(p::Period)
freq = p.frequency
if freq == Year
return nothing
elseif freq == Semester
return nothing
elseif freq == Quarter
return rem(p.ordinal, 4)
elseif freq == Month
return rem(p.ordinal, 12)
elseif freq == Week
return week(7*p.ordinal)
elseif freq == Day
return week(p.ordinal)
else
return nothing
end
end
# 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
function quarter(days)
m = month(days)
return m < 4 ? 1 : m < 7 ? 2 : m < 10 ? 3 : 4
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))
quarter(dt::TimeType) = quarter(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, :quarter)
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.
# Examples
```jldoctest
julia> Dates.week(Date(1989, 6, 22))
25
julia> Dates.week(Date(2005, 1, 1))
53
julia> Dates.week(Date(2004, 12, 31))
53
```
"""
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
......@@ -53,7 +53,7 @@ for c in "ysqmwd"
end
end
for (tok, fn) in zip("uU", Any[monthabbr_to_value, monthname_to_value, dayabbr_to_value, dayname_to_value])
for (tok, fn) in zip("uU", Any[Dates.monthabbr_to_value, Dates.monthname_to_value])
@eval @inline function tryparsenext(d::PeriodPart{$tok}, str, i, len, locale)
next = tryparsenext_word(str, i, len, locale, max_width(d))
next === nothing && return nothing
......@@ -182,14 +182,19 @@ const CONVERSION_DEFAULTS = IdDict{Type, Any}(
# Specifies the required fields in order to parse a TimeType
# Note: Allows for addition of new TimeTypes
const CONVERSION_TRANSLATIONS = IdDict{Type, Any}(
Date => (Year, Month, Day),
PeriodYear => (Year),
PeriodSemester => (Year, Semester),
PeriodQuarter => (Year, Quarter),
PeriodMonth => (Year, Month),
PeriodWeek => (Year, Week),
PeriodDay => (Year, Month, Day),
)
"""
DateFormat(format::AbstractString, locale="english") -> DateFormat
PeriodFormat(format::AbstractString) -> PeriodFormat
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`
Construct a period formatting object that can be used for parsing period strings or
formatting a period object as a string. The following character codes can be used to construct the `format`
string:
| Code | Matches | Comment |
......@@ -217,7 +222,7 @@ Common-Period-Formatters), listed later.
See [`DateTime`](@ref) and [`format`](@ref) for how to use a DateFormat object to parse and write Date strings
respectively.
"""
function DateFormat(f::AbstractString)
function PeriodFormat(f::AbstractString)
tokens = AbstractDateToken[]
prev = ()
prev_offset = 1
......@@ -258,238 +263,157 @@ function DateFormat(f::AbstractString)
end
tokens_tuple = (tokens...,)
return DateFormat{Symbol(f),typeof(tokens_tuple)}(tokens_tuple, locale)
return PeriodFormat{Symbol(f),typeof(tokens_tuple)}(tokens_tuple)
end
function DateFormat(f::AbstractString, locale::AbstractString)
DateFormat(f, LOCALES[locale])
end
function Base.show(io::IO, df::DateFormat)
print(io, "dateformat\"")
for t in df.tokens
function Base.show(io::IO, pf::PeriodFormat)
print(io, "periodformat\"")
for t in pf.tokens
_show_content(io, t)
end
print(io, '"')
end
Base.Broadcast.broadcastable(x::DateFormat) = Ref(x)
Base.Broadcast.broadcastable(x::PeriodFormat) = Ref(x)
"""
dateformat"Y-m-d H:M:S"
periodformat"Y-m-d"
Create a [`DateFormat`](@ref) object. Similar to `DateFormat("Y-m-d H:M:S")`
Create a [`PeriodFormat`](@ref) object. Similar to `PeriodFormat("Y-m-d")`
but creates the DateFormat object once during macro expansion.
See [`DateFormat`](@ref) for details about format specifiers.
See [`PeriodFormat`](@ref) for details about format specifiers.
"""
macro dateformat_str(str)
DateFormat(str)
macro periodformat_str(str)
PeriodFormat(str)
end
# Standard formats
"""
Dates.ISODateTimeFormat
Describes the ISO8601 formatting for a date and time. This is the default value for `Dates.format`
of a `DateTime`.
Periods.YearFormat
# Example
```jldoctest
julia> Dates.format(DateTime(2018, 8, 8, 12, 0, 43, 1), ISODateTimeFormat)
"2018-08-08T12:00:43.001"
julia> Periods.format(Periods(2018, Year), YearFormat )
"2018"
```
"""
const ISODateTimeFormat = DateFormat("yyyy-mm-dd\\THH:MM:SS.s")
default_format(::Type{DateTime}) = ISODateTimeFormat
const YearFormat = PeriodFormat("yyyy")
"""
Dates.ISODateFormat
Describes the ISO8601 formatting for a date. This is the default value for `Dates.format` of a `Date`.
Periods.SemesterFormat
# Example
```jldoctest
julia> Dates.format(Date(2018, 8, 8), ISODateFormat)
"2018-08-08"
julia> Periods.format(Periods(2018, 1, Semester), SemesterFormat )
"2018-S1"
```
"""
const ISODateFormat = DateFormat("yyyy-mm-dd")
default_format(::Type{Date}) = ISODateFormat
const SemesterFormat = PeriodFormat("yyyy-Ss")
"""
Dates.ISOTimeFormat
Describes the ISO8601 formatting for a time. This is the default value for `Dates.format` of a `Time`.
Periods.QuarterFormat
# Example
```jldoctest
julia> Dates.format(Time(12, 0, 43, 1), ISOTimeFormat)
"12:00:43.001"
julia> Periods.format(Periods(2018, 2, Quarter), QuarterFormat )
"2018-Q2"
```
"""
const ISOTimeFormat = DateFormat("HH:MM:SS.s")
default_format(::Type{Time}) = ISOTimeFormat
const QuarterFormat = PeriodFormat("yyyy-Qq")
"""
Dates.RFC1123Format
Describes the RFC1123 formatting for a date and time.
Periods.MonthFormat
# Example
```jldoctest
julia> Dates.format(DateTime(2018, 8, 8, 12, 0, 43, 1), RFC1123Format)
"Wed, 08 Aug 2018 12:00:43"
julia> Periods.format(Periods(2018, 3, Month), MonthFormat )
"2018-03"
```
"""
const RFC1123Format = DateFormat("e, dd u yyyy HH:MM:SS")
### API
const Locale = Union{DateLocale, String}
const MonthFormat = PeriodFormat("yyyy-mm")
"""
DateTime(dt::AbstractString, format::AbstractString; locale="english") -> DateTime
Construct a `DateTime` by parsing the `dt` date time string following the
pattern given in the `format` string (see [`DateFormat`](@ref) for syntax).
!!! note
This method creates a `DateFormat` object each time it is called. It is recommended
that you create a [`DateFormat`](@ref) object instead and use that as the second
argument to avoid performance loss when using the same format repeatedly.
Periods.WeekFormat
# Example
```jldoctest
julia> DateTime("2020-01-01", "yyyy-mm-dd")
2020-01-01T00:00:00
julia> a = ("2020-01-01", "2020-01-02");
julia> [DateTime(d, dateformat"yyyy-mm-dd") for d ∈ a] # preferred
2-element Vector{DateTime}:
2020-01-01T00:00:00
2020-01-02T00:00:00
julia> Periods.format(Periods(2018, 4, Week), WeekFormat )
"2018-W04"
```
"""
function DateTime(dt::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
return parse(DateTime, dt, DateFormat(format, locale))
end
const WeekFormat = PeriodFormat("yyyy-Www")
"""
Periods.DayFormat
# Example
```jldoctest
julia> Periods.format(Periods(2018, 3, 11, Day), DayFormat )
"2018-03-11"
```
"""
DateTime(dt::AbstractString, df::DateFormat=ISODateTimeFormat) -> DateTime
const DayFormat = PeriodFormat("yyyy-mm-dd")
Construct a `DateTime` by parsing the `dt` date time string following the
pattern given in the [`DateFormat`](@ref) object, or $ISODateTimeFormat if omitted.
### API
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(d::AbstractString, format::AbstractString; locale="english") -> Date
Period(p::AbstractString, format::AbstractString) -> Period
Construct a `Date` by parsing the `d` date string following the pattern given
in the `format` string (see [`DateFormat`](@ref) for syntax).
Construct a `Period` by parsing the `p` period string following the pattern given
in the `format` string (see [`PeriodFormat`](@ref) for syntax).
!!! note
This method creates a `DateFormat` object each time it is called. It is recommended
that you create a [`DateFormat`](@ref) object instead and use that as the second
This method creates a `PeriodFormat` object each time it is called. It is recommended
that you create a [`PeriodFormat`](@ref) object instead and use that as the second
argument to avoid performance loss when using the same format repeatedly.
# Example
```jldoctest
julia> Date("2020-01-01", "yyyy-mm-dd")
julia> Period("2020-01-01", "yyyy-mm-dd")
2020-01-01
julia> a = ("2020-01-01", "2020-01-02");
julia> [Date(d, dateformat"yyyy-mm-dd") for d ∈ a] # preferred
2-element Vector{Date}:
julia> [Period(p, dateformat"yyyy-mm-dd") for p ∈ a] # preferred
2-element Vector{Period}:
2020-01-01
2020-01-02
```
"""
function Date(d::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Date, d, DateFormat(format, locale))
function Period(p::AbstractString, format::AbstractString)
parse(Period, d, PeriodFormat(format))
end
"""
Date(d::AbstractString, df::DateFormat=ISODateFormat) -> Date
Construct a `Date` by parsing the `d` date string following the
pattern given in the [`DateFormat`](@ref) object, or $ISODateFormat if omitted.
Similar to `Date(::AbstractString, ::AbstractString)` but more efficient when
repeatedly parsing similarly formatted date strings with a pre-created
`DateFormat` object.
"""
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 (see [`DateFormat`](@ref) for syntax).
!!! note
This method creates a `DateFormat` object each time it is called. It is recommended
that you create a [`DateFormat`](@ref) object instead and use that as the second
argument to avoid performance loss when using the same format repeatedly.
Period(p::AbstractString, pf::PeriodFormat) -> Period
# Example
```jldoctest
julia> Time("12:34pm", "HH:MMp")
12:34:00
julia> a = ("12:34pm", "2:34am");
julia> [Time(d, dateformat"HH:MMp") for d ∈ a] # preferred
2-element Vector{Time}:
12:34:00
02:34:00
```
"""
function Time(t::AbstractString, format::AbstractString; locale::Locale=ENGLISH)
parse(Time, t, DateFormat(format, locale))
end
Construct a `Period` by parsing the `p` period string following the
pattern given in the [`PeriodFormat`](@ref) object.
Similar to `Period(::AbstractString, ::AbstractString)` but more efficient when
repeatedly parsing similarly formatted period strings with a pre-created
`PeriodFormat` object.
"""
Time(t::AbstractString, df::DateFormat=ISOTimeFormat) -> Time
Construct a `Time` by parsing the `t` date time string following the
pattern given in the [`DateFormat`](@ref) object, or $ISOTimeFormat if omitted.
Period(p::AbstractString, pf::DateFormat) = parse(Period, p, pf)
Similar to `Time(::AbstractString, ::AbstractString)` but more efficient when
repeatedly parsing similarly formatted time strings with a pre-created
`DateFormat` object.
"""
Time(t::AbstractString, df::DateFormat=ISOTimeFormat) = parse(Time, t, df)
@generated function format(io::IO, dt::TimeType, fmt::DateFormat{<:Any,T}) where T
@generated function format(io::IO, p::Period, fmt::DateFormat{<:Any,T}) where T
N = fieldcount(T)
quote
ts = fmt.tokens
loc = fmt.locale
Base.@nexprs $N i -> format(io, ts[i], dt, loc)
Base.@nexprs $N i -> format(io, ts[i], p)
end
end
function format(dt::TimeType, fmt::DateFormat, bufsize=12)
function format(p::Period, fmt::DateFormat, bufsize=10)
# preallocate to reduce resizing
io = IOBuffer(Vector{UInt8}(undef, bufsize), read=true, write=true)
format(io, dt, fmt)
format(io, p, fmt)
String(io.data[1:io.ptr - 1])
end
"""
format(dt::TimeType, format::AbstractString; locale="english") -> AbstractString
format(p::Period, format::AbstractString) -> AbstractString
Construct a string by using a `TimeType` object and applying the provided `format`. The
Construct a string by using a `Period` object and applying the provided `format`. The
following character codes can be used to construct the `format` string:
| Code | Examples | Comment |
......@@ -500,13 +424,9 @@ following character codes can be used to construct the `format` string:
| `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 |
| `s` | 1, 2 | Semester of the year |
| `q` | 1, 4 | Quarter of the year |