Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
style = "yas"
9 changes: 7 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
name = "TimeSpans"
uuid = "bb34ddd2-327f-4c4a-bfb0-c98fc494ece1"
authors = ["Beacon Biosignals, Inc."]
version = "0.3.1"
version = "0.3.2"

[deps]
ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
julia = "1.6"
Arrow = "1.6"
ArrowTypes = "1.1.0"

[extras]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
test = ["Arrow", "Test", "Tables"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://beacon-biosignals.github.io/TimeSpans.jl/stable)
[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://beacon-biosignals.github.io/TimeSpans.jl/dev)

TimeSpans.jl provides a simple `TimeSpan` type for representing a continuous span between two points in time, along with generic utility functions for common operations on `TimeSpan`-like types. Importantly, the package exposes a minimal interface (`TimeSpans.start` and `TimeSpans.stop`) that any type can implement to enable support for the TimeSpans API.
TimeSpans.jl provides a simple `TimeSpan` type for representing a continuous span between two points in time, along with generic utility functions for common operations on `TimeSpan`-like types. Importantly, the package exposes a minimal interface (`TimeSpans.start` and `TimeSpans.stop`) that any type can implement to enable support for the TimeSpans API. In addition to `TimeSpan`, this package implements the TimeSpans API for `Period`s and `NamedTuple`s that have `:start` and `:stop` keys.
24 changes: 23 additions & 1 deletion src/TimeSpans.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module TimeSpans
using Base.Iterators
using Dates
using Statistics
using ArrowTypes

export TimeSpan, start, stop, istimespan, translate, overlaps,
shortest_timespan_containing, duration, index_from_time,
time_from_index, merge_spans!, merge_spans, invert_spans


const NS_IN_SEC = Dates.value(Nanosecond(Second(1))) # Number of nanoseconds in one second

#####
Expand Down Expand Up @@ -102,6 +102,11 @@ istimespan(::Any) = false
istimespan(::TimeSpan) = true
istimespan(::Period) = true

function istimespan(::T) where {T<:NamedTuple}
return hasfield(T, :start) && fieldtype(T, :start) <: Period &&
hasfield(T, :stop) && fieldtype(T, :stop) <: Period
end

"""
start(span)

Expand All @@ -110,6 +115,11 @@ Return the inclusive lower bound of `span` as a `Nanosecond` value.
start(span::TimeSpan) = span.start
start(t::Period) = convert(Nanosecond, t)

function start(x::NamedTuple)
istimespan(x) || throw(ArgumentError("input is not a valid timespan"))
return Nanosecond(x.start)
end

"""
stop(span)

Expand All @@ -118,6 +128,11 @@ Return the exclusive upper bound of `span` as a `Nanosecond` value.
stop(span::TimeSpan) = span.stop
stop(t::Period) = convert(Nanosecond, t) + Nanosecond(1)

function stop(x::NamedTuple)
istimespan(x) || throw(ArgumentError("input is not a valid timespan"))
return Nanosecond(x.stop)
end

#####
##### generic utilities
#####
Expand Down Expand Up @@ -388,4 +403,11 @@ function invert_spans(spans, parent_span)
return gaps
end

# Support arrow serialization of timespans

const TIME_SPAN_ARROW_NAME = Symbol("JuliaLang.TimeSpan")

ArrowTypes.arrowname(::Type{TimeSpan}) = TIME_SPAN_ARROW_NAME
ArrowTypes.JuliaType(::Val{TIME_SPAN_ARROW_NAME}) = TimeSpan

end # module
44 changes: 43 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Test, TimeSpans, Dates
using Test, TimeSpans, Dates, Arrow, Tables

using TimeSpans: contains, nanoseconds_per_sample
using Statistics
Expand Down Expand Up @@ -221,3 +221,45 @@ end
@test length(i_spans) == 6
@test all(duration.(i_spans) .== Second(8))
end

ntspan(a, b) = (; start=Nanosecond(a), other=1.0, stop=Nanosecond(b))
@testset "support named tuples" begin
@test index_from_time(100, (;start=Second(3), stop=Second(6))) == 301:600
@test_throws ArgumentError stop((;stop = 0))
@test_throws ArgumentError start((;start = 0))
@test !istimespan((;stop=0, start=1))
@test !istimespan((;stop=Nanosecond(0)))

for t in [(; start=Nanosecond(1), stop=Nanosecond(2)),
(; stop=Second(1), start=Second(0))]
@test istimespan(t)
@test start(t) == Nanosecond(t.start)
@test stop(t) == Nanosecond(t.stop)
@test contains(t, t)
@test overlaps(t, t)
@test shortest_timespan_containing([t]) == TimeSpan(t)
@test duration(t) == Nanosecond(t.stop - t.start)
by = Second(rand(1:10))
@test translate(t, by) === TimeSpan(start(t) + Nanosecond(by),
stop(t) + Nanosecond(by))
end

spans = [ntspan(0, 10), ntspan(6, 12), ntspan(15, 20),
ntspan(21, 30), ntspan(29, 31)]
# NOTE: this could be fixed by defining
# Base.convert(::Type{<:NamedTuple}, x::TimeSpan) = (; start=start(x), stop=stop(x))
@test_broken merge_spans!(overlaps, spans) == [ntspan(0, 12), ntspan(15, 20), ntspan(21, 31)]

spans = [ntspan(Second(x), Second(x + 1)) for x in 0:10:59]
parent_span = ntspan(Second(0), Second(60))
i_spans = invert_spans(spans, parent_span)
@test length(i_spans) == 6
end

@testset "Arrow serialization" begin
cols = (; span=TimeSpan(1, 2))
io = Arrow.tobuffer([cols])
spancol = first(Tables.columns(Arrow.Table(io)))
@test spancol isa AbstractVector{<:TimeSpan}
@test spancol == [cols.span]
end