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

JuliaLang / julia / #37997

29 Jan 2025 02:08AM UTC coverage: 17.283% (-68.7%) from 85.981%
#37997

push

local

web-flow
bpart: Start enforcing min_world for global variable definitions (#57150)

This is the analog of #57102 for global variables. Unlike for consants,
there is no automatic global backdate mechanism. The reasoning for this
is that global variables can be declared at any time, unlike constants
which can only be decalared once their value is available. As a result
code patterns using `Core.eval` to declare globals are rarer and likely
incorrect.

1 of 22 new or added lines in 3 files covered. (4.55%)

31430 existing lines in 188 files now uncovered.

7903 of 45728 relevant lines covered (17.28%)

98663.7 hits per line

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

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

3
abstract type AbstractTime end
4

5
"""
6
    Period
7
    Year
8
    Quarter
9
    Month
10
    Week
11
    Day
12
    Hour
13
    Minute
14
    Second
15
    Millisecond
16
    Microsecond
17
    Nanosecond
18

19
`Period` types represent discrete, human representations of time.
20
"""
21
abstract type Period     <: AbstractTime end
22

23
"""
24
    DatePeriod
25
    Year
26
    Quarter
27
    Month
28
    Week
29
    Day
30

31
Intervals of time greater than or equal to a day.
32
Conventional comparisons between `DatePeriod`s are not all valid.
33
(eg `Week(1) == Day(7)`, but `Year(1) != Day(365)`)
34
"""
35
abstract type DatePeriod <: Period end
36

37
"""
38
    TimePeriod
39
    Hour
40
    Minute
41
    Second
42
    Millisecond
43
    Microsecond
44
    Nanosecond
45

46
Intervals of time less than a day.
47
Conversions between all `TimePeriod`s are permissible.
48
(eg `Hour(1) == Minute(60) == Second(3600)`)
49
"""
50
abstract type TimePeriod <: Period end
51

52
for T in (:Year, :Quarter, :Month, :Week, :Day)
53
    @eval struct $T <: DatePeriod
54
        value::Int64
55
        $T(v::Number) = new(v)
2✔
56
    end
57
end
58
for T in (:Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
59
    @eval struct $T <: TimePeriod
60
        value::Int64
61
        $T(v::Number) = new(v)
25✔
62
    end
63
end
64

65
"""
66
    Year(v)
67
    Quarter(v)
68
    Month(v)
69
    Week(v)
70
    Day(v)
71
    Hour(v)
72
    Minute(v)
73
    Second(v)
74
    Millisecond(v)
75
    Microsecond(v)
76
    Nanosecond(v)
77

78
Construct a `Period` type with the given `v` value. Input must be losslessly convertible
79
to an [`Int64`](@ref).
80
"""
81
Period(v)
82

83
"""
84
    Instant
85

86
`Instant` types represent integer-based, machine representations of time as continuous
87
timelines starting from an epoch.
88
"""
89
abstract type Instant <: AbstractTime end
90

91
"""
92
    UTInstant{T}
93

94
The `UTInstant` represents a machine timeline based on UT time (1 day = one revolution of
95
the earth). The `T` is a `Period` parameter that indicates the resolution or precision of
96
the instant.
97
"""
98
struct UTInstant{P<:Period} <: Instant
99
    periods::P
25✔
100
end
101

102
# Convenience default constructors
103
UTM(x) = UTInstant(Millisecond(x))
25✔
UNCOV
104
UTD(x) = UTInstant(Day(x))
×
105

106
# Calendar types provide rules for interpreting instant
107
# timelines in human-readable form.
108
abstract type Calendar <: AbstractTime end
109

110
# ISOCalendar implements the ISO 8601 standard (en.wikipedia.org/wiki/ISO_8601)
111
# Notably based on the proleptic Gregorian calendar
112
# ISOCalendar provides interpretation rules for UTInstants to civil date and time parts
113
struct ISOCalendar <: Calendar end
114

115
"""
116
    TimeZone
117

118
Geographic zone generally based on longitude determining what the time is at a certain location.
119
Some time zones observe daylight savings (eg EST -> EDT).
120
For implementations and more support, see the [`TimeZones.jl`](https://github.com/JuliaTime/TimeZones.jl) package
121
"""
122
abstract type TimeZone end
123

124
"""
125
    UTC
126

127
`UTC`, or Coordinated Universal Time, is the [`TimeZone`](@ref) from which all others are measured.
128
It is associated with the time at 0° longitude. It is not adjusted for daylight savings.
129
"""
130
struct UTC <: TimeZone end
131

132
"""
133
    TimeType
134

135
`TimeType` types wrap `Instant` machine instances to provide human representations of the
136
machine instant. `Time`, `DateTime` and `Date` are subtypes of `TimeType`.
137
"""
138
abstract type TimeType <: AbstractTime end
139

140
abstract type AbstractDateTime <: TimeType end
141

142
"""
143
    DateTime
144

145
`DateTime` represents a point in time according to the proleptic Gregorian calendar.
146
The finest resolution of the time is millisecond (i.e., microseconds or
147
nanoseconds cannot be represented by this type). The type supports fixed-point
148
arithmetic, and thus is prone to underflowing (and overflowing). A notable
149
consequence is rounding when adding a `Microsecond` or a `Nanosecond`:
150

151
```jldoctest
152
julia> dt = DateTime(2023, 8, 19, 17, 45, 32, 900)
153
2023-08-19T17:45:32.900
154

155
julia> dt + Millisecond(1)
156
2023-08-19T17:45:32.901
157

158
julia> dt + Microsecond(1000) # 1000us == 1ms
159
2023-08-19T17:45:32.901
160

161
julia> dt + Microsecond(999) # 999us rounded to 1000us
162
2023-08-19T17:45:32.901
163

164
julia> dt + Microsecond(1499) # 1499 rounded to 1000us
165
2023-08-19T17:45:32.901
166
```
167
"""
168
struct DateTime <: AbstractDateTime
169
    instant::UTInstant{Millisecond}
170
    DateTime(instant::UTInstant{Millisecond}) = new(instant)
25✔
171
end
172

173
"""
174
    Date
175

176
`Date` wraps a `UTInstant{Day}` and interprets it according to the proleptic Gregorian calendar.
177
"""
178
struct Date <: TimeType
179
    instant::UTInstant{Day}
UNCOV
180
    Date(instant::UTInstant{Day}) = new(instant)
×
181
end
182

183
"""
184
    Time
185

186
`Time` wraps a `Nanosecond` and represents a specific moment in a 24-hour day.
187
"""
188
struct Time <: TimeType
189
    instant::Nanosecond
UNCOV
190
    Time(instant::Nanosecond) = new(mod(instant, 86400000000000))
×
191
end
192

193
# Convert y,m,d to # of Rata Die days
194
# Works by shifting the beginning of the year to March 1,
195
# so a leap day is the very last day of the year
196
const SHIFTEDMONTHDAYS = (306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275)
197
function totaldays(y, m, d)
198
    # If we're in Jan/Feb, shift the given year back one
199
    z = m < 3 ? y - 1 : y
25✔
200
    mdays = SHIFTEDMONTHDAYS[m]
25✔
201
    # days + month_days + year_days
202
    return d + mdays + 365z + fld(z, 4) - fld(z, 100) + fld(z, 400) - 306
25✔
203
end
204

205
# If the year is divisible by 4, except for every 100 years, except for every 400 years
UNCOV
206
isleapyear(y::Integer) = (y % 4 == 0) && ((y % 100 != 0) || (y % 400 == 0))
×
207

208
# Number of days in month
209
const DAYSINMONTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
210
daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleapyear(y))
25✔
211

212
### UTILITIES ###
213

214
# These are necessary because the type constructors for TimeType subtypes can
215
# throw, and we want to be able to use tryparse without requiring a try/catch.
216
# This is made easier by providing a helper function that checks arguments, so
217
# we can validate arguments in tryparse.
218

219
"""
220
    validargs(::Type{<:TimeType}, args...) -> Union{ArgumentError, Nothing}
221

222
Determine whether the given arguments constitute valid inputs for the given type.
223
Returns either an `ArgumentError`, or [`nothing`](@ref) in case of success.
224
"""
225
function validargs end
226

227
# Julia uses 24-hour clocks internally, but user input can be AM/PM with 12pm == noon and 12am == midnight.
228
@enum AMPM AM PM TWENTYFOURHOUR
229
function adjusthour(h::Int64, ampm::AMPM)
230
    ampm == TWENTYFOURHOUR && return h
25✔
UNCOV
231
    ampm == PM && h < 12 && return h + 12
×
UNCOV
232
    ampm == AM && h == 12 && return Int64(0)
×
UNCOV
233
    return h
×
234
end
235

236
### CONSTRUCTORS ###
237
# Core constructors
238
"""
239
    DateTime(y, [m, d, h, mi, s, ms]) -> DateTime
240

241
Construct a `DateTime` type by parts. Arguments must be convertible to [`Int64`](@ref).
242
"""
243
function DateTime(y::Int64, m::Int64=1, d::Int64=1,
25✔
244
                  h::Int64=0, mi::Int64=0, s::Int64=0, ms::Int64=0, ampm::AMPM=TWENTYFOURHOUR)
245
    err = validargs(DateTime, y, m, d, h, mi, s, ms, ampm)
44✔
246
    err === nothing || throw(err)
25✔
247
    h = adjusthour(h, ampm)
25✔
248
    rata = ms + 1000 * (s + 60mi + 3600h + 86400 * totaldays(y, m, d))
25✔
249
    return DateTime(UTM(rata))
25✔
250
end
251

252
function validargs(::Type{DateTime}, y::Int64, m::Int64, d::Int64,
25✔
253
                   h::Int64, mi::Int64, s::Int64, ms::Int64, ampm::AMPM=TWENTYFOURHOUR)
254
    0 < m < 13 || return ArgumentError("Month: $m out of range (1:12)")
25✔
255
    0 < d < daysinmonth(y, m) + 1 || return ArgumentError("Day: $d out of range (1:$(daysinmonth(y, m)))")
25✔
256
    if ampm == TWENTYFOURHOUR # 24-hour clock
25✔
257
        -1 < h < 24 || (h == 24 && mi==s==ms==0) ||
25✔
258
            return ArgumentError("Hour: $h out of range (0:23)")
259
    else
UNCOV
260
        0 < h < 13 || return ArgumentError("Hour: $h out of range (1:12)")
×
261
    end
262
    -1 < mi < 60 || return ArgumentError("Minute: $mi out of range (0:59)")
25✔
263
    -1 < s < 60 || return ArgumentError("Second: $s out of range (0:59)")
25✔
264
    -1 < ms < 1000 || return ArgumentError("Millisecond: $ms out of range (0:999)")
25✔
265
    return nothing
25✔
266
end
267

UNCOV
268
DateTime(dt::Base.Libc.TmStruct) = DateTime(1900 + dt.year, 1 + dt.month, dt.mday, dt.hour, dt.min, dt.sec)
×
269

270
"""
271
    Date(y, [m, d]) -> Date
272

273
Construct a `Date` type by parts. Arguments must be convertible to [`Int64`](@ref).
274
"""
275
function Date(y::Int64, m::Int64=1, d::Int64=1)
UNCOV
276
    err = validargs(Date, y, m, d)
×
UNCOV
277
    err === nothing || throw(err)
×
UNCOV
278
    return Date(UTD(totaldays(y, m, d)))
×
279
end
280

UNCOV
281
function validargs(::Type{Date}, y::Int64, m::Int64, d::Int64)
×
UNCOV
282
    0 < m < 13 || return ArgumentError("Month: $m out of range (1:12)")
×
UNCOV
283
    0 < d < daysinmonth(y, m) + 1 || return ArgumentError("Day: $d out of range (1:$(daysinmonth(y, m)))")
×
UNCOV
284
    return nothing
×
285
end
286

UNCOV
287
Date(dt::Base.Libc.TmStruct) = Date(1900 + dt.year, 1 + dt.month, dt.mday)
×
288

289
"""
290
    Time(h, [mi, s, ms, us, ns]) -> Time
291

292
Construct a `Time` type by parts. Arguments must be convertible to [`Int64`](@ref).
293
"""
294
function Time(h::Int64, mi::Int64=0, s::Int64=0, ms::Int64=0, us::Int64=0, ns::Int64=0, ampm::AMPM=TWENTYFOURHOUR)
UNCOV
295
    err = validargs(Time, h, mi, s, ms, us, ns, ampm)
×
UNCOV
296
    err === nothing || throw(err)
×
UNCOV
297
    h = adjusthour(h, ampm)
×
UNCOV
298
    return Time(Nanosecond(ns + 1000us + 1000000ms + 1000000000s + 60000000000mi + 3600000000000h))
×
299
end
300

UNCOV
301
function validargs(::Type{Time}, h::Int64, mi::Int64, s::Int64, ms::Int64, us::Int64, ns::Int64, ampm::AMPM=TWENTYFOURHOUR)
×
UNCOV
302
    if ampm == TWENTYFOURHOUR # 24-hour clock
×
UNCOV
303
        -1 < h < 24 || return ArgumentError("Hour: $h out of range (0:23)")
×
304
    else
UNCOV
305
        0 < h < 13 || return ArgumentError("Hour: $h out of range (1:12)")
×
306
    end
UNCOV
307
    -1 < mi < 60 || return ArgumentError("Minute: $mi out of range (0:59)")
×
UNCOV
308
    -1 < s < 60 || return ArgumentError("Second: $s out of range (0:59)")
×
UNCOV
309
    -1 < ms < 1000 || return ArgumentError("Millisecond: $ms out of range (0:999)")
×
UNCOV
310
    -1 < us < 1000 || return ArgumentError("Microsecond: $us out of range (0:999)")
×
UNCOV
311
    -1 < ns < 1000 || return ArgumentError("Nanosecond: $ns out of range (0:999)")
×
UNCOV
312
    return nothing
×
313
end
314

UNCOV
315
Time(dt::Base.Libc.TmStruct) = Time(dt.hour, dt.min, dt.sec)
×
316

317
# Convenience constructors from Periods
UNCOV
318
function DateTime(y::Year, m::Month=Month(1), d::Day=Day(1),
×
319
                  h::Hour=Hour(0), mi::Minute=Minute(0),
320
                  s::Second=Second(0), ms::Millisecond=Millisecond(0))
UNCOV
321
    return DateTime(value(y), value(m), value(d),
×
322
                    value(h), value(mi), value(s), value(ms))
323
end
324

UNCOV
325
Date(y::Year, m::Month=Month(1), d::Day=Day(1)) = Date(value(y), value(m), value(d))
×
326

UNCOV
327
function Time(h::Hour, mi::Minute=Minute(0), s::Second=Second(0),
×
328
              ms::Millisecond=Millisecond(0),
329
              us::Microsecond=Microsecond(0), ns::Nanosecond=Nanosecond(0))
UNCOV
330
    return Time(value(h), value(mi), value(s), value(ms), value(us), value(ns))
×
331
end
332

333
# To allow any order/combination of Periods
334

335
"""
336
    DateTime(periods::Period...) -> DateTime
337

338
Construct a `DateTime` type by `Period` type parts. Arguments may be in any order. DateTime
339
parts not provided will default to the value of `Dates.default(period)`.
340
"""
UNCOV
341
function DateTime(period::Period, periods::Period...)
×
UNCOV
342
    y = Year(1); m = Month(1); d = Day(1)
×
UNCOV
343
    h = Hour(0); mi = Minute(0); s = Second(0); ms = Millisecond(0)
×
UNCOV
344
    for p in (period, periods...)
×
UNCOV
345
        isa(p, Year) && (y = p::Year)
×
UNCOV
346
        isa(p, Month) && (m = p::Month)
×
UNCOV
347
        isa(p, Day) && (d = p::Day)
×
UNCOV
348
        isa(p, Hour) && (h = p::Hour)
×
UNCOV
349
        isa(p, Minute) && (mi = p::Minute)
×
UNCOV
350
        isa(p, Second) && (s = p::Second)
×
UNCOV
351
        isa(p, Millisecond) && (ms = p::Millisecond)
×
UNCOV
352
    end
×
UNCOV
353
    return DateTime(y, m, d, h, mi, s, ms)
×
354
end
355

356
"""
357
    Date(period::Period...) -> Date
358

359
Construct a `Date` type by `Period` type parts. Arguments may be in any order. `Date` parts
360
not provided will default to the value of `Dates.default(period)`.
361
"""
UNCOV
362
function Date(period::Period, periods::Period...)
×
UNCOV
363
    y = Year(1); m = Month(1); d = Day(1)
×
UNCOV
364
    for p in (period, periods...)
×
UNCOV
365
        isa(p, Year) && (y = p::Year)
×
UNCOV
366
        isa(p, Month) && (m = p::Month)
×
UNCOV
367
        isa(p, Day) && (d = p::Day)
×
UNCOV
368
    end
×
UNCOV
369
    return Date(y, m, d)
×
370
end
371

372
"""
373
    Time(period::TimePeriod...) -> Time
374

375
Construct a `Time` type by `Period` type parts. Arguments may be in any order. `Time` parts
376
not provided will default to the value of `Dates.default(period)`.
377
"""
UNCOV
378
function Time(period::TimePeriod, periods::TimePeriod...)
×
UNCOV
379
    h = Hour(0); mi = Minute(0); s = Second(0)
×
UNCOV
380
    ms = Millisecond(0); us = Microsecond(0); ns = Nanosecond(0)
×
UNCOV
381
    for p in (period, periods...)
×
UNCOV
382
        isa(p, Hour) && (h = p::Hour)
×
UNCOV
383
        isa(p, Minute) && (mi = p::Minute)
×
UNCOV
384
        isa(p, Second) && (s = p::Second)
×
UNCOV
385
        isa(p, Millisecond) && (ms = p::Millisecond)
×
UNCOV
386
        isa(p, Microsecond) && (us = p::Microsecond)
×
UNCOV
387
        isa(p, Nanosecond) && (ns = p::Nanosecond)
×
UNCOV
388
    end
×
UNCOV
389
    return Time(h, mi, s, ms, us, ns)
×
390
end
391

392
# Convenience constructor for DateTime from Date and Time
393
"""
394
    DateTime(d::Date, t::Time)
395

396
Construct a `DateTime` type by `Date` and `Time`.
397
Non-zero microseconds or nanoseconds in the `Time` type will result in an
398
`InexactError`.
399

400
!!! compat "Julia 1.1"
401
    This function requires at least Julia 1.1.
402

403
```jldoctest
404
julia> d = Date(2018, 1, 1)
405
2018-01-01
406

407
julia> t = Time(8, 15, 42)
408
08:15:42
409

410
julia> DateTime(d, t)
411
2018-01-01T08:15:42
412
```
413
"""
UNCOV
414
function DateTime(dt::Date, t::Time)
×
UNCOV
415
    (microsecond(t) > 0 || nanosecond(t) > 0) && throw(InexactError(:DateTime, DateTime, t))
×
UNCOV
416
    y, m, d = yearmonthday(dt)
×
UNCOV
417
    return DateTime(y, m, d, hour(t), minute(t), second(t), millisecond(t))
×
418
end
419

420
# Fallback constructors
421
DateTime(y, m=1, d=1, h=0, mi=0, s=0, ms=0, ampm::AMPM=TWENTYFOURHOUR) = DateTime(Int64(y), Int64(m), Int64(d), Int64(h), Int64(mi), Int64(s), Int64(ms), ampm)
12✔
UNCOV
422
Date(y, m=1, d=1) = Date(Int64(y), Int64(m), Int64(d))
×
UNCOV
423
Time(h, mi=0, s=0, ms=0, us=0, ns=0, ampm::AMPM=TWENTYFOURHOUR) = Time(Int64(h), Int64(mi), Int64(s), Int64(ms), Int64(us), Int64(ns), ampm)
×
424

425
# Traits, Equality
426
Base.isfinite(::Union{Type{T}, T}) where {T<:TimeType} = true
×
UNCOV
427
calendar(dt::DateTime) = ISOCalendar
×
UNCOV
428
calendar(dt::Date) = ISOCalendar
×
429

430
"""
431
    eps(::Type{DateTime}) -> Millisecond
432
    eps(::Type{Date}) -> Day
433
    eps(::Type{Time}) -> Nanosecond
434
    eps(::TimeType) -> Period
435

436
Return the smallest unit value supported by the `TimeType`.
437

438
# Examples
439
```jldoctest
440
julia> eps(DateTime)
441
1 millisecond
442

443
julia> eps(Date)
444
1 day
445

446
julia> eps(Time)
447
1 nanosecond
448
```
449
"""
450
Base.eps(::Union{Type{DateTime}, Type{Date}, Type{Time}, TimeType})
451

UNCOV
452
Base.eps(::Type{DateTime}) = Millisecond(1)
×
UNCOV
453
Base.eps(::Type{Date}) = Day(1)
×
UNCOV
454
Base.eps(::Type{Time}) = Nanosecond(1)
×
UNCOV
455
Base.eps(::T) where T <: TimeType = eps(T)::Period
×
456

457
# zero returns dt::T - dt::T
UNCOV
458
Base.zero(::Type{DateTime}) = Millisecond(0)
×
UNCOV
459
Base.zero(::Type{Date}) = Day(0)
×
UNCOV
460
Base.zero(::Type{Time}) = Nanosecond(0)
×
UNCOV
461
Base.zero(::T) where T <: TimeType = zero(T)::Period
×
462

463

UNCOV
464
Base.typemax(::Union{DateTime, Type{DateTime}}) = DateTime(146138512, 12, 31, 23, 59, 59)
×
UNCOV
465
Base.typemin(::Union{DateTime, Type{DateTime}}) = DateTime(-146138511, 1, 1, 0, 0, 0)
×
UNCOV
466
Base.typemax(::Union{Date, Type{Date}}) = Date(252522163911149, 12, 31)
×
UNCOV
467
Base.typemin(::Union{Date, Type{Date}}) = Date(-252522163911150, 1, 1)
×
UNCOV
468
Base.typemax(::Union{Time, Type{Time}}) = Time(23, 59, 59, 999, 999, 999)
×
UNCOV
469
Base.typemin(::Union{Time, Type{Time}}) = Time(0)
×
470
# Date-DateTime promotion, isless, ==
UNCOV
471
Base.promote_rule(::Type{Date}, x::Type{DateTime}) = DateTime
×
UNCOV
472
Base.isless(x::T, y::T) where {T<:TimeType} = isless(value(x), value(y))
×
UNCOV
473
Base.isless(x::TimeType, y::TimeType) = isless(promote(x, y)...)
×
UNCOV
474
(==)(x::T, y::T) where {T<:TimeType} = (==)(value(x), value(y))
×
UNCOV
475
(==)(x::TimeType, y::TimeType) = (===)(promote(x, y)...)
×
UNCOV
476
Base.min(x::AbstractTime) = x
×
UNCOV
477
Base.max(x::AbstractTime) = x
×
UNCOV
478
Base.minmax(x::AbstractTime) = (x, x)
×
UNCOV
479
Base.hash(x::Time, h::UInt) =
×
480
    hash(hour(x), hash(minute(x), hash(second(x),
481
        hash(millisecond(x), hash(microsecond(x), hash(nanosecond(x), h))))))
482

483
Base.sleep(duration::Period) = sleep(seconds(duration))
×
484

485
function Base.Timer(delay::Period; interval::Period=Second(0))
×
486
    Timer(seconds(delay), interval=seconds(interval))
×
487
end
488

UNCOV
489
function Base.timedwait(testcb, timeout::Period; pollint::Period=Millisecond(100))
×
UNCOV
490
    timedwait(testcb, seconds(timeout), pollint=seconds(pollint))
×
491
end
492

493
Base.OrderStyle(::Type{<:AbstractTime}) = Base.Ordered()
×
494
Base.ArithmeticStyle(::Type{<:AbstractTime}) = Base.ArithmeticWraps()
×
495

496
# minimal Base.TOML support
497
Date(d::Base.TOML.Date) = Date(d.year, d.month, d.day)
×
498
Time(t::Base.TOML.Time) = Time(t.hour, t.minute, t.second, t.ms)
×
499
DateTime(dt::Base.TOML.DateTime) = DateTime(Date(dt.date), Time(dt.time))
×
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

© 2026 Coveralls, Inc