Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support named tuples and arrow serialization #41

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
style = "yas"
8 changes: 6 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
name = "TimeSpans"
uuid = "bb34ddd2-327f-4c4a-bfb0-c98fc494ece1"
authors = ["Beacon Biosignals, Inc."]
version = "0.3.1"
version = "0.4.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is breaking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the slack conversation we do want to be careful about how this is introduced re the pirated methods removed in beacon-biosignals/Onda.jl#126


[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"

[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. NamedTuples of the form `(;start=Nanosecond(1), stop=Nanosecond(2))` are supported in many cases (all those where the function belongs to `TimeSpans`).
15 changes: 14 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 @@ -38,6 +38,9 @@ struct TimeSpan
TimeSpan(start, stop) = TimeSpan(Nanosecond(start), Nanosecond(stop))
end

# you can treat named as time spans for many operations
const NamedTupleTimeSpan = NamedTuple{(:start, :stop),Tuple{Nanosecond,Nanosecond}}

"""
TimeSpan(x)

Expand Down Expand Up @@ -101,6 +104,7 @@ Types that overload `TimeSpans.start`/`TimeSpans.stop` should also overload `ist
istimespan(::Any) = false
istimespan(::TimeSpan) = true
istimespan(::Period) = true
istimespan(::NamedTupleTimeSpan) = true

"""
start(span)
Expand All @@ -109,6 +113,7 @@ Return the inclusive lower bound of `span` as a `Nanosecond` value.
"""
start(span::TimeSpan) = span.start
start(t::Period) = convert(Nanosecond, t)
start(x::NamedTupleTimeSpan) = x.start

"""
stop(span)
Expand All @@ -117,6 +122,7 @@ Return the exclusive upper bound of `span` as a `Nanosecond` value.
"""
stop(span::TimeSpan) = span.stop
stop(t::Period) = convert(Nanosecond, t) + Nanosecond(1)
stop(x::NamedTupleTimeSpan) = x.stop

#####
##### generic utilities
Expand Down Expand Up @@ -388,4 +394,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
35 changes: 34 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,36 @@ end
@test length(i_spans) == 6
@test all(duration.(i_spans) .== Second(8))
end

ntspan(a, b) = (;start=Nanosecond(a), stop=Nanosecond(b))
@testset "support named tuples" begin
t = (;start=Nanosecond(1), stop=Nanosecond(2))
@test index_from_time(100, (;start=Nanosecond(Second(3)), stop=Nanosecond(Second(6)))) == 301:600
@test istimespan(t)
@test start(t) == Nanosecond(1)
@test stop(t) == Nanosecond(2)
@test contains(t, t)
@test overlaps(t, t)
@test shortest_timespan_containing([t]) == TimeSpan(t)
@test duration(t) == Nanosecond(1)
by = Second(rand(1:10))
@test translate(t, by) === TimeSpan(start(t) + Nanosecond(by), stop(t) + Nanosecond(by))
spans = [ntspan(0, 10), ntspan(6, 12), ntspan(15, 20),
ntspan(21, 30), ntspan(29, 31)]
# this could be fixed by defining
# Base.convert(::Type{<:NamedTupleTimeSpan}, 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