• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37527

pending completion
#37527

push

local

web-flow
make `IRShow.method_name` inferrable (#49607)

18 of 18 new or added lines in 3 files covered. (100.0%)

68710 of 81829 relevant lines covered (83.97%)

33068903.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.87
/stdlib/Dates/src/periods.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
#Period types
4
"""
5
    Dates.value(x::Period) -> Int64
6

7
For a given period, return the value associated with that period.  For example,
8
`value(Millisecond(10))` returns 10 as an integer.
9
"""
10
value(x::Period) = x.value
1,008,641✔
11

12
# The default constructors for Periods work well in almost all cases
13
# P(x) = new((convert(Int64,x))
14
# The following definitions are for Period-specific safety
15
for period in (:Year, :Quarter, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
16
    period_str = string(period)
17
    accessor_str = lowercase(period_str)
18
    # Convenience method for show()
19
    @eval _units(x::$period) = " " * $accessor_str * (abs(value(x)) == 1 ? "" : "s")
20✔
20
    # AbstractString parsing (mainly for IO code)
21
    @eval $period(x::AbstractString) = $period(Base.parse(Int64, x))
12✔
22
    # The period type is printed when output, thus it already implies its own typeinfo
23
    @eval Base.typeinfo_implicit(::Type{$period}) = true
×
24
    # Period accessors
25
    typs = period in (:Microsecond, :Nanosecond) ? ["Time"] :
26
           period in (:Hour, :Minute, :Second, :Millisecond) ? ["Time", "DateTime"] : ["Date", "DateTime"]
27
    reference = period === :Week ? " For details see [`$accessor_str(::Union{Date, DateTime})`](@ref)." : ""
28
    for typ_str in typs
29
        @eval begin
30
            @doc """
31
                $($period_str)(dt::$($typ_str)) -> $($period_str)
32

33
            The $($accessor_str) part of a $($typ_str) as a `$($period_str)`.$($reference)
34
            """ $period(dt::$(Symbol(typ_str))) = $period($(Symbol(accessor_str))(dt))
80✔
35
        end
36
    end
37
    @eval begin
38
        @doc """
39
            $($period_str)(v)
40

41
        Construct a `$($period_str)` object with the given `v` value. Input must be
42
        losslessly convertible to an [`Int64`](@ref).
43
        """ $period(v)
44
    end
45
end
46

47
#Print/show/traits
48
Base.print(io::IO, x::Period) = print(io, value(x), _units(x))
16✔
49
Base.show(io::IO, ::MIME"text/plain", x::Period) = print(io, x)
×
50
Base.show(io::IO, p::P) where {P<:Period} = print(io, P, '(', value(p), ')')
×
51
Base.zero(::Union{Type{P},P}) where {P<:Period} = P(0)
171,118✔
52
Base.one(::Union{Type{P},P}) where {P<:Period} = 1  # see #16116
5,149✔
53
Base.typemin(::Type{P}) where {P<:Period} = P(typemin(Int64))
1✔
54
Base.typemax(::Type{P}) where {P<:Period} = P(typemax(Int64))
2✔
55
Base.isfinite(::Union{Type{P}, P}) where {P<:Period} = true
2✔
56

57
# Default values (as used by TimeTypes)
58
"""
59
    default(p::Period) -> Period
60

61
Return a sensible "default" value for the input Period by returning `T(1)` for Year,
62
Month, and Day, and `T(0)` for Hour, Minute, Second, and Millisecond.
63
"""
64
function default end
65

66
default(p::Union{T,Type{T}}) where {T<:DatePeriod} = T(1)
5✔
67
default(p::Union{T,Type{T}}) where {T<:TimePeriod} = T(0)
6✔
68

69
(-)(x::P) where {P<:Period} = P(-value(x))
303✔
70
==(x::P, y::P) where {P<:Period} = value(x) == value(y)
50,715✔
71
Base.isless(x::P, y::P) where {P<:Period} = isless(value(x), value(y))
167,377✔
72

73
# Period Arithmetic, grouped by dimensionality:
74
for op in (:+, :-, :lcm, :gcd)
75
    @eval ($op)(x::P, y::P) where {P<:Period} = P(($op)(value(x), value(y)))
91,510✔
76
end
77

78
/(x::P, y::P) where {P<:Period} = /(value(x), value(y))
12✔
79
/(x::P, y::Real) where {P<:Period} = P(/(value(x), y))
13✔
80
div(x::P, y::P, r::RoundingMode) where {P<:Period} = div(value(x), value(y), r)
13✔
81
div(x::P, y::Real, r::RoundingMode) where {P<:Period} = P(div(value(x), Int64(y), r))
2✔
82

83
for op in (:rem, :mod)
84
    @eval begin
85
        ($op)(x::P, y::P) where {P<:Period} = P(($op)(value(x), value(y)))
203✔
86
        ($op)(x::P, y::Real) where {P<:Period} = P(($op)(value(x), Int64(y)))
37,423✔
87
    end
88
end
89

90
(*)(x::P, y::Real) where {P<:Period} = P(value(x) * y)
213,950✔
91
(*)(y::Real, x::Period) = x * y
167,876✔
92

93
(*)(A::Period, B::AbstractArray) = Broadcast.broadcast_preserving_zero_d(*, A, B)
1✔
94
(*)(A::AbstractArray, B::Period) = Broadcast.broadcast_preserving_zero_d(*, A, B)
1✔
95

96
for op in (:(==), :isless, :/, :rem, :mod, :lcm, :gcd)
97
    @eval ($op)(x::Period, y::Period) = ($op)(promote(x, y)...)
269✔
98
end
99
div(x::Period, y::Period, r::RoundingMode) = div(promote(x, y)..., r)
×
100

101
# intfuncs
102
Base.gcdx(a::T, b::T) where {T<:Period} = ((g, x, y) = gcdx(value(a), value(b)); return T(g), x, y)
9✔
103
Base.abs(a::T) where {T<:Period} = T(abs(value(a)))
181✔
104
Base.sign(x::Period) = sign(value(x))
5✔
105

106
# return (next coarser period, conversion factor):
107
coarserperiod(::Type{P}) where {P<:Period} = (P, 1)
1,735✔
108
coarserperiod(::Type{Nanosecond})  = (Microsecond, 1000)
2✔
109
coarserperiod(::Type{Microsecond}) = (Millisecond, 1000)
2✔
110
coarserperiod(::Type{Millisecond}) = (Second, 1000)
278✔
111
coarserperiod(::Type{Second}) = (Minute, 60)
906✔
112
coarserperiod(::Type{Minute}) = (Hour, 60)
500✔
113
coarserperiod(::Type{Hour}) = (Day, 24)
541✔
114
coarserperiod(::Type{Day}) = (Week, 7)
544✔
115
coarserperiod(::Type{Month}) = (Year, 12)
745✔
116

117
# Stores multiple periods in greatest to least order by type, not values,
118
# canonicalized to eliminate zero periods, merge equal period types,
119
# and convert more-precise periods to less-precise periods when possible
120
"""
121
    CompoundPeriod
122

123
A `CompoundPeriod` is useful for expressing time periods that are not a fixed multiple of
124
smaller periods. For example, "a year and a  day" is not a fixed number of days, but can
125
be expressed using a `CompoundPeriod`. In fact, a `CompoundPeriod` is automatically
126
generated by addition of different period types, e.g. `Year(1) + Day(1)` produces a
127
`CompoundPeriod` result.
128
"""
129
struct CompoundPeriod <: AbstractTime
130
    periods::Vector{Period}
131
    function CompoundPeriod(p::Vector{Period})
1,476✔
132
        n = length(p)
1,476✔
133
        if n > 1
1,476✔
134
            # We sort periods in decreasing order (rev = true) according to the length of
135
            # the period's type (by = tons ∘ oneunit). We sort by type, not value, so that
136
            # we can merge equal types.
137
            #
138
            # This works by computing how many nanoseconds are in a single period, and sorting
139
            # by that. For example, (tons ∘ oneunit)(Week(10)) = tons(oneunit(Week(10))) =
140
            # tons(Week(1)) ≈ 6.0e14, which is less than (tons ∘ oneunit)(Month(-2)) ≈ 2.6e15
141
            sort!(p, rev = true, by = tons ∘ oneunit)
1,339✔
142
            # canonicalize p by merging equal period types and removing zeros
143
            i = j = 1
1,282✔
144
            while j <= n
4,894✔
145
                k = j + 1
3,555✔
146
                while k <= n && typeof(p[j]) == typeof(p[k])
3,627✔
147
                    p[j] += p[k]
72✔
148
                    k += 1
72✔
149
                end
72✔
150
                if !iszero(p[j])
3,555✔
151
                    p[i] = p[j]
3,524✔
152
                    i += 1
3,524✔
153
                end
154
                j = k
3,555✔
155
            end
3,555✔
156
            n = i - 1 # new length
1,339✔
157
            p = resize!(p, n)
2,678✔
158
        elseif n == 1 && value(p[1]) == 0
266✔
159
            p = Period[]
26✔
160
        end
161

162
        return new(p)
1,476✔
163
    end
164
end
165

166
"""
167
    Dates.periods(::CompoundPeriod) -> Vector{Period}
168

169
Return the `Vector` of `Period`s that comprise the given `CompoundPeriod`.
170

171
!!! compat "Julia 1.7"
172
    This function requires Julia 1.7 or later.
173
"""
174
periods(x::CompoundPeriod) = x.periods
1✔
175

176
"""
177
    CompoundPeriod(periods) -> CompoundPeriod
178

179
Construct a `CompoundPeriod` from a `Vector` of `Period`s. All `Period`s of the same type
180
will be added together.
181

182
# Examples
183
```jldoctest
184
julia> Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13))
185
25 hours
186

187
julia> Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1))
188
-1 hour, 1 minute
189

190
julia> Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2))
191
1 month, -2 weeks
192

193
julia> Dates.CompoundPeriod(Dates.Minute(50000))
194
50000 minutes
195
```
196
"""
197
CompoundPeriod(p::Vector{<:Period}) = CompoundPeriod(Vector{Period}(p))
28✔
198

199
CompoundPeriod(t::Time) = CompoundPeriod(Period[Hour(t), Minute(t), Second(t), Millisecond(t),
×
200
                                                Microsecond(t), Nanosecond(t)])
201

202
CompoundPeriod(p::Period...) = CompoundPeriod(Period[p...])
78✔
203

204

205
"""
206
    canonicalize(::CompoundPeriod) -> CompoundPeriod
207

208
Reduces the `CompoundPeriod` into its canonical form by applying the following rules:
209

210
* Any `Period` large enough be partially representable by a coarser `Period` will be broken
211
  into multiple `Period`s (eg. `Hour(30)` becomes `Day(1) + Hour(6)`)
212
* `Period`s with opposite signs will be combined when possible
213
  (eg. `Hour(1) - Day(1)` becomes `-Hour(23)`)
214

215
# Examples
216
```jldoctest
217
julia> canonicalize(Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13)))
218
1 day, 1 hour
219

220
julia> canonicalize(Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1)))
221
-59 minutes
222

223
julia> canonicalize(Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2)))
224
1 month, -2 weeks
225

226
julia> canonicalize(Dates.CompoundPeriod(Dates.Minute(50000)))
227
4 weeks, 6 days, 17 hours, 20 minutes
228
```
229
"""
230
canonicalize(x::Period) = canonicalize(CompoundPeriod(x))
15✔
231
function canonicalize(x::CompoundPeriod)
519✔
232
    # canonicalize Periods by pushing "overflow" into a coarser period.
233
    p = x.periods
519✔
234
    n = length(p)
519✔
235
    if n > 0
519✔
236
        pc = sizehint!(Period[], n)
506✔
237
        P = typeof(p[n])
506✔
238
        v = value(p[n])
1,010✔
239
        i = n - 1
506✔
240
        while true
1,373✔
241
            Pc, f = coarserperiod(P)
1,373✔
242
            if i > 0 && typeof(p[i]) == P
1,373✔
243
                v += value(p[i])
18✔
244
                i -= 1
9✔
245
            end
246
            v0 = f == 1 ? v : rem(v, f)
2,274✔
247
            v0 != 0 && push!(pc, P(v0))
1,373✔
248
            if v != v0
1,373✔
249
                P = Pc
41✔
250
                v = div(v - v0, f)
46✔
251
            elseif i > 0
1,327✔
252
                P = typeof(p[i])
821✔
253
                v = value(p[i])
1,642✔
254
                i -= 1
821✔
255
            else
256
                break
506✔
257
            end
258
        end
867✔
259
        p = reverse!(pc)
506✔
260
        n = length(p)
506✔
261
    else
262
        return x
13✔
263
    end
264

265
    # reduce the amount of mixed positive/negative Periods.
266
    if n > 0
506✔
267
        pc = sizehint!(Period[], n)
503✔
268
        i = n
501✔
269
        while i > 0
1,385✔
270
            j = i
880✔
271

272
            # Determine sign of the largest period in this group which
273
            # can be converted into via coarserperiod.
274
            last = Union{}
880✔
275
            current = typeof(p[i])
882✔
276
            while i > 0 && current != last
3,401✔
277
                if typeof(p[i]) == current
2,519✔
278
                    i -= 1
1,330✔
279
                end
280
                last, current = current, coarserperiod(current)[1]
2,519✔
281
            end
2,519✔
282
            s = sign(value(p[i + 1]))
1,762✔
283

284
            # Adjust all the periods in the group based upon the
285
            # largest period sign.
286
            P = typeof(p[j])
882✔
287
            v = 0
880✔
288
            while j > i
1,740✔
289
                Pc, f = coarserperiod(P)
1,361✔
290
                if j > 0 && typeof(p[j]) == P
1,361✔
291
                    v += value(p[j])
2,658✔
292
                    j -= 1
1,330✔
293
                end
294
                v0 = f == 1 ? v : mod(v, f * s)
2,250✔
295
                v0 != 0 && push!(pc, P(v0))
1,361✔
296
                if v != v0
1,361✔
297
                    P = Pc
86✔
298
                    v = div(v - v0, f)
86✔
299
                elseif j > 0
1,275✔
300
                    P = typeof(p[j])
772✔
301
                    v = 0
772✔
302
                else
303
                    break
503✔
304
                end
305
            end
858✔
306
        end
882✔
307
        p = reverse!(pc)
503✔
308
    end
309

310
    return CompoundPeriod(p)
506✔
311
end
312

313
Base.convert(::Type{CompoundPeriod}, x::Period) = CompoundPeriod(Period[x])
3✔
314
function Base.string(x::CompoundPeriod)
4✔
315
    if isempty(x.periods)
4✔
316
        return "empty period"
1✔
317
    else
318
        s = ""
×
319
        for p in x.periods
3✔
320
            s *= ", " * string(p)
12✔
321
        end
15✔
322
        return s[3:end]
3✔
323
    end
324
end
325
Base.show(io::IO,x::CompoundPeriod) = print(io, string(x))
1✔
326

327
Base.convert(::Type{T}, x::CompoundPeriod) where T<:Period =
9✔
328
    isconcretetype(T) ? sum(T, x.periods) : throw(MethodError(convert,(T,x)))
329

330
# E.g. Year(1) + Day(1)
331
(+)(x::Period,y::Period) = CompoundPeriod(Period[x, y])
341✔
332
(+)(x::CompoundPeriod, y::Period) = CompoundPeriod(vcat(x.periods, y))
274✔
333
(+)(y::Period, x::CompoundPeriod) = x + y
42✔
334
(+)(x::CompoundPeriod, y::CompoundPeriod) = CompoundPeriod(vcat(x.periods, y.periods))
63✔
335
# E.g. Year(1) - Month(1)
336
(-)(x::Period, y::Period) = CompoundPeriod(Period[x, -y])
88✔
337
(-)(x::CompoundPeriod, y::Period) = CompoundPeriod(vcat(x.periods, -y))
74✔
338
(-)(x::CompoundPeriod) = CompoundPeriod(-x.periods)
48✔
339
(-)(y::Union{Period, CompoundPeriod}, x::CompoundPeriod) = (-x) + y
47✔
340

341
GeneralPeriod = Union{Period, CompoundPeriod}
342
(+)(x::GeneralPeriod) = x
14✔
343

344
(==)(x::CompoundPeriod, y::Period) = x == CompoundPeriod(y)
12✔
345
(==)(x::Period, y::CompoundPeriod) = y == x
×
346
(==)(x::CompoundPeriod, y::CompoundPeriod) = canonicalize(x).periods == canonicalize(y).periods
248✔
347

348
Base.isequal(x::CompoundPeriod, y::Period) = isequal(x, CompoundPeriod(y))
41✔
349
Base.isequal(x::Period, y::CompoundPeriod) = isequal(y, x)
20✔
350
Base.isequal(x::CompoundPeriod, y::CompoundPeriod) = isequal(x.periods, y.periods)
58✔
351

352
# Capture TimeType+-Period methods
353
(+)(a::TimeType, b::Period, c::Period) = (+)(a, b + c)
10✔
354
(+)(a::TimeType, b::Period, c::Period, d::Period...) = (+)((+)(a, b + c), d...)
4✔
355

356
function (+)(x::TimeType, y::CompoundPeriod)
54✔
357
    for p in y.periods
54✔
358
        x += p
118✔
359
    end
172✔
360
    return x
54✔
361
end
362
(+)(x::CompoundPeriod, y::TimeType) = y + x
10✔
363

364
function (-)(x::TimeType, y::CompoundPeriod)
19✔
365
    for p in y.periods
19✔
366
        x -= p
38✔
367
    end
57✔
368
    return x
19✔
369
end
370

371
# Fixed-value Periods (periods corresponding to a well-defined time interval,
372
# as opposed to variable calendar intervals like Year).
373
const FixedPeriod = Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond}
374

375
# like div but throw an error if remainder is nonzero
376
function divexact(x, y)
81✔
377
    q, r = divrem(x, y)
81✔
378
    r == 0 || throw(InexactError(:divexact, Int, x/y))
99✔
379
    return q
63✔
380
end
381

382
# TODO: this is needed to prevent undefined Period constructors from
383
# hitting the deprecated construct-to-convert fallback.
384
(::Type{T})(p::Period) where {T<:Period} = convert(T, p)::T
275✔
385

386
# Conversions and promotion rules
387
function define_conversions(periods)
388
    for i = eachindex(periods)
389
        T, n = periods[i]
390
        N = Int64(1)
391
        for j = (i - 1):-1:firstindex(periods) # less-precise periods
392
            Tc, nc = periods[j]
393
            N *= nc
394
            vmax = typemax(Int64) ÷ N
395
            vmin = typemin(Int64) ÷ N
396
            @eval function Base.convert(::Type{$T}, x::$Tc)
619✔
397
                $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, $T, x))
689✔
398
                return $T(value(x) * $N)
627✔
399
            end
400
        end
401
        N = n
402
        for j = (i + 1):lastindex(periods) # more-precise periods
403
            Tc, nc = periods[j]
404
            @eval Base.convert(::Type{$T}, x::$Tc) = $T(divexact(value(x), $N))
98✔
405
            @eval Base.promote_rule(::Type{$T}, ::Type{$Tc}) = $Tc
330✔
406
            N *= nc
407
        end
408
    end
409
end
410
define_conversions([(:Week, 7), (:Day, 24), (:Hour, 60), (:Minute, 60), (:Second, 1000),
411
                    (:Millisecond, 1000), (:Microsecond, 1000), (:Nanosecond, 1)])
412
define_conversions([(:Year, 4), (:Quarter, 3), (:Month, 1)])
413

414
# fixed is not comparable to other periods, except when both are zero (#37459)
415
const OtherPeriod = Union{Month, Quarter, Year}
416
(==)(x::FixedPeriod, y::OtherPeriod) = iszero(x) & iszero(y)
106✔
417
(==)(x::OtherPeriod, y::FixedPeriod) = y == x
53✔
418

419
const zero_or_fixedperiod_seed = UInt === UInt64 ? 0x5b7fc751bba97516 : 0xeae0fdcb
420
const nonzero_otherperiod_seed = UInt === UInt64 ? 0xe1837356ff2d2ac9 : 0x170d1b00
421
otherperiod_seed(x) = iszero(value(x)) ? zero_or_fixedperiod_seed : nonzero_otherperiod_seed
96✔
422
# tons() will overflow for periods longer than ~300,000 years, implying a hash collision
423
# which is relatively harmless given how infrequently such periods should appear
424
Base.hash(x::FixedPeriod, h::UInt) = hash(tons(x), h + zero_or_fixedperiod_seed)
528✔
425
# Overflow can also happen here for really long periods (~8e17 years)
426
Base.hash(x::Year, h::UInt) = hash(12 * value(x), h + otherperiod_seed(x))
44✔
427
Base.hash(x::Quarter, h::UInt) = hash(3 * value(x), h + otherperiod_seed(x))
26✔
428
Base.hash(x::Month, h::UInt) = hash(value(x), h + otherperiod_seed(x))
26✔
429

430
function Base.hash(x::CompoundPeriod, h::UInt)
72✔
431
    isempty(x.periods) && return hash(0, h + zero_or_fixedperiod_seed)
72✔
432
    for p in x.periods
54✔
433
        h = hash(p, h)
72✔
434
    end
126✔
435
    return h
54✔
436
end
437

438
Base.isless(x::FixedPeriod, y::OtherPeriod) = throw(MethodError(isless, (x, y)))
1✔
439
Base.isless(x::OtherPeriod, y::FixedPeriod) = throw(MethodError(isless, (x, y)))
1✔
440

441
Base.isless(x::Period, y::CompoundPeriod) = CompoundPeriod(x) < y
3✔
442
Base.isless(x::CompoundPeriod, y::Period) = x < CompoundPeriod(y)
2✔
443
Base.isless(x::CompoundPeriod, y::CompoundPeriod) = tons(x) < tons(y)
9✔
444
# truncating conversions to milliseconds, nanoseconds and days:
445
# overflow can happen for periods longer than ~300,000 years
446
toms(c::Nanosecond)  = div(value(c), 1000000)
1✔
447
toms(c::Microsecond) = div(value(c), 1000)
1✔
448
toms(c::Millisecond) = value(c)
930✔
449
toms(c::Second)      = 1000 * value(c)
37,723✔
450
toms(c::Minute)      = 60000 * value(c)
134,460✔
451
toms(c::Hour)        = 3600000 * value(c)
2,761✔
452
toms(c::Period)      = 86400000 * days(c)
5,753✔
453
toms(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(toms, c.periods))
2✔
454
tons(x)              = toms(x) * 1000000
42,133✔
455
tons(x::Microsecond) = value(x) * 1000
92✔
456
tons(x::Nanosecond)  = value(x)
113✔
457
tons(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(tons, c.periods))
19✔
458
days(c::Millisecond) = div(value(c), 86400000)
2✔
459
days(c::Second)      = div(value(c), 86400)
2✔
460
days(c::Minute)      = div(value(c), 1440)
2✔
461
days(c::Hour)        = div(value(c), 24)
2✔
462
days(c::Day)         = value(c)
2,273✔
463
days(c::Week)        = 7 * value(c)
1,112✔
464
days(c::Year)        = 365.2425 * value(c)
1,239✔
465
days(c::Quarter)     = 91.310625 * value(c)
×
466
days(c::Month)       = 30.436875 * value(c)
1,274✔
467
days(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(days, c.periods))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc