Skip to content

Instantly share code, notes, and snippets.

@sairus7
Last active April 18, 2021 06:04
Show Gist options
  • Save sairus7/7a3f2ea6d3e0c34b4ea973d3b80105e8 to your computer and use it in GitHub Desktop.
Save sairus7/7a3f2ea6d3e0c34b4ea973d3b80105e8 to your computer and use it in GitHub Desktop.
module Timestamps
using Dates
export TimeGrid, RegularTimestamps, IrregularTimestamps, SparseTimestamps
"""
TimeGrid represents discrete time axes for regularly sampled signal,
starting from `timestart` and sampling rate `freq`
It behaves just like a StepRange of discrete times, but with infinite length.
"""
struct TimeGrid
timestart::DateTime
freq::Float64 # samplerate (Hz), or instead timestep = 1/freq (s)
end
function TimeGrid(timestart::DateTime, freq)
TimeGrid(timestart, Float64(freq))
end
# internal reinterpret functions
@inline time2ms(time::Period) = Dates.value(Millisecond(time)) # time Period to integer milliseconds (may error for nanoseconds)
@inline ms2time(msec) = Millisecond(msec) # integer milliseconds to time Period
# @inline ms2time(msec) = DateTime(Dates.UTM(msec)) - transforms to absolute DateTime
# from index - to floating-point milliseconds
@inline index2ms(freq::Float64, index::Real) = (index - 1) * 1000 / freq
@inline ms2index(freq::Float64, ms::Real) = ms * freq / 1000 + 1
"""
`ind = timegrid[time]`
transform relative time (from `timestart`) to index
"""
function Base.getindex(timegrid::TimeGrid, time::Period)
i = ms2index(timegrid.freq, time2ms(time))
ind = trunc(Int, i)
end
"""
`ind = timegrid[time]`
transform absolute time to index
"""
function Base.getindex(timegrid::TimeGrid, time::DateTime)
timegrid[time - timegrid.timestart]
end
"""
`time = timegrid[ind, Period]`
transform index to relative time (from `timestart`)
"""
function Base.getindex(timegrid::TimeGrid, index::Real, ::Type{Period})
ms = index2ms(timegrid.freq, index)
time = ms2time(floor(Int, ms))
end
"""
`time = timegrid[ind]`
transform index to absolute time
"""
function Base.getindex(timegrid::TimeGrid, index::Real)
timegrid[index, Period] + timegrid.timestart
end
## =================================================
"""
Based on TimeGrid discrete times, we can create:
- regularly-sampled discrete timestamps, that are computed on getindex,
- irregularly-sampled discrete timestamps, with inner index vector
"""
# regularly-sampled timestamps
struct RegularTimestamps <: AbstractVector{DateTime}
timegrid::TimeGrid
range::UnitRange{Int}
end
index2time(vec::RegularTimestamps, index::Int) = vec.timegrid[index]
time2index(vec::RegularTimestamps, time::DateTime) = vec.timegrid[time]
Base.size(vec::RegularTimestamps) = (length(vec.range),)
function Base.getindex(vec::RegularTimestamps, i::Int)
@boundscheck i in vec.range || throw(BoundsError(vec, i))
index2time(vec, i)
end
## =================================================
# irregularly-sampled timestamps has index column (for events position on time grid)
struct IrregularTimestamps <: AbstractVector{DateTime}
timegrid::TimeGrid
indices::Vector{Int}
end
index2time(vec::IrregularTimestamps, index::Int) = vec.timegrid[vec.indices[index]]
function time2index(vec::IrregularTimestamps, time::DateTime)
i = vec.timegrid[time]
index = searchsorted(vec.indices, i)
return i
end
Base.size(vec::IrregularTimestamps) = size(vec.indices)
function Base.getindex(vec::IrregularTimestamps, index::Int)
index2time(vec, index)
end
## =================================================
"""
Or we can even create regular timestamps that have intervals of missing data,
e.g. due to sensor malfunction or lost connection
"""
struct SparseTimestamps <: AbstractVector{DateTime}
timegrid::TimeGrid
restarts::Vector{UnitRange{Int}} # a list of increasing index ranges of valid data
offsets::Vector{Int} # cumulative offset for points after restart
len::Int # number of all included points
function SparseTimestamps(timegrid::TimeGrid, restarts::Vector{UnitRange{Int}})
offsets = Vector{Int}(undef, length(restarts)) #
r_stop = 0
sum = 0
len = 0
for (i, r) in enumerate(restarts)
sum += first(r) - r_stop - 1
offsets[i] = sum
r_stop = last(r)
len += length(r)
end
new(timegrid, restarts, offsets, len)
end
end
function sparse2denseindex(vec::SparseTimestamps, index::Int)
for (i, r) in enumerate(vec.restarts)
if index <= last(r)
@boundscheck first(r) <= index || throw(BoundsError(vec, i))
return index - vec.offsets[i]
end
end
throw(BoundsError(vec, i))
end
function dense2sparseindex(vec::SparseTimestamps, index::Int)
for (i, r) in enumerate(vec.restarts)
if index <= last(r) - vec.offsets[i]
index += vec.offsets[i]
@boundscheck first(r) <= index || throw(BoundsError(vec, i))
return index
end
end
throw(BoundsError(vec, i))
end
index2time(vec::SparseTimestamps, index::Int) = vec.timegrid[dense2sparseindex(vec, index)]
time2index(vec::SparseTimestamps, time::DateTime) = sparse2denseindex(vec, vec.timegrid[time])
Base.size(vec::SparseTimestamps) = (vec.len,)
function Base.getindex(vec::SparseTimestamps, i::Int)
@boundscheck 1 <= i <= vec.len || throw(BoundsError(vec, i))
index2time(vec, i)
end
end # module
## =================================================
using Dates
using .Timestamps
# TimeGrid examples
timestart = parse(DateTime, "2021-02-13T23:34:42")
timegrid = TimeGrid(timestart, 250) # 250 Hz (or 4 ms step)
timegrid[1] == timestart + Millisecond(0) # get absolute time
timegrid[2] == timestart + Millisecond(4)
timegrid[2, Period] == Millisecond(4) # get relative time from time start
timegrid[101] - timegrid[1] == Millisecond(400)
# all values between two discrete timestamps are truncated to nearest previous value
timegrid[Millisecond(0)] == 1
timegrid[Millisecond(3)] == 1
timegrid[Millisecond(4)] == 2
## =================================================
t1 = RegularTimestamps(timegrid, 1:100)
t1[1] == timestart
t1[2] == timestart + Millisecond(4)
t1[end] == timestart + Millisecond(396)
## =================================================
indices = [1, 15, 23, 55, 1000]
t2 = IrregularTimestamps(timegrid, indices)
t2[1] == timestart
t2[2] == timestart + Millisecond(4*14) # timestep * index
t2[end] == timestart + Millisecond(3996)
## =================================================
t3 = SparseTimestamps(timegrid, [1:5, 25:100])
t3[5] == t1[5]
t3[6] == t1[25]
length(t3) == 81
t3[end] == t1[100]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment