-
-
Save sairus7/7a3f2ea6d3e0c34b4ea973d3b80105e8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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