Skip to content

Commit 6b7223a

Browse files
committed
feat: more Datetime methods for Epoch
1 parent 5bf7246 commit 6b7223a

File tree

5 files changed

+108
-15
lines changed

5 files changed

+108
-15
lines changed

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
perf:
2-
#!/usr/bin/env -S julia --threads=auto --project=. -i
2+
#!/usr/bin/env -S julia --threads=auto --project=.
33
@time using CommonDataFormat
44
elx_file = "data/elb_l2_epdef_20210914_v01.cdf"
55
@time ds = CDFDataset(elx_file)

src/epochs.jl

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# 3. CDF_TIME_TT2000 (TT2000 as short) is nanoseconds since J2000 with leap seconds
77

88
import Base: promote_rule, -, +
9+
using Dates: value, toms, tons
910

1011
include("leap_second.jl")
1112

@@ -14,6 +15,20 @@ const EPOCH_OFFSET_SECONDS = 62167219200.0 # Seconds from year 0 to Unix epoch
1415

1516
abstract type CDFDateTime <: Dates.AbstractDateTime end
1617

18+
struct Picosecond <: Period
19+
value::Float64
20+
end
21+
22+
Picosecond(ns::Nanosecond) = convert(Picosecond, ns)
23+
Picosecond(p::Period) = Picosecond(Nanosecond(p))
24+
Base.convert(::Type{Nanosecond}, x::Picosecond) = Nanosecond(round(Int64, x.value / 1.0e3))
25+
Base.convert(::Type{Picosecond}, x::Nanosecond) = Picosecond(x.value * 1.0e3)
26+
Base.convert(::Type{Picosecond}, x::Dates.FixedPeriod) = Picosecond(Nanosecond(x))
27+
Base.promote_rule(::Type{Picosecond}, ::Type{<:Dates.FixedPeriod}) = Picosecond
28+
29+
Dates._units(x::Picosecond) = " picosecond" * (abs(value(x)) == 1 ? "" : "s")
30+
31+
1732
"""
1833
Epoch
1934
@@ -56,8 +71,11 @@ fillvalue(::Epoch16) = -1.0e31
5671
fillvalue(::TT2000) = 9999
5772

5873
(-)(epoch::Epoch, other::Epoch) = Millisecond(round(Int64, epoch.instant - other.instant))
59-
(+)(tt2000::TT2000, other::Period) = TT2000(tt2000.instant.value + Dates.tons(other))
60-
(+)(epoch::Epoch, other::Period) = Epoch(epoch.instant + Dates.toms(other))
74+
(-)(epoch::Epoch16, other::Epoch16) = Picosecond((epoch.seconds - other.seconds) * 1.0e12 + epoch.picoseconds - other.picoseconds)
75+
(+)(tt2000::TT2000, other::Period) = TT2000(value(tt2000) + tons(other))
76+
(-)(tt2000::TT2000, other::Period) = TT2000(value(tt2000) - tons(other))
77+
(+)(epoch::Epoch, other::Period) = Epoch(value(epoch) + toms(other))
78+
(-)(epoch::Epoch, other::Period) = Epoch(value(epoch) - toms(other))
6179

6280
# Conversion to DateTime
6381
function Dates.DateTime(epoch::Epoch)
@@ -66,8 +84,8 @@ end
6684

6785
function Dates.DateTime(epoch::Epoch16)
6886
s_since_unix = epoch.seconds - EPOCH_OFFSET_SECONDS
69-
total_ns = s_since_unix * 1.0e9 + epoch.picoseconds / 1000.0
70-
return DateTime(1970) + Nanosecond(round(Int64, total_ns))
87+
total_ms = s_since_unix * 1.0e3 + epoch.picoseconds / 1.0e9
88+
return DateTime(1970) + Millisecond(round(Int64, total_ms))
7189
end
7290

7391
function Dates.DateTime(epoch::TT2000)
@@ -79,10 +97,10 @@ end
7997

8098
# Conversion from TimeType
8199
function Epoch16(dt::DateTime)
82-
ns_since_unix = (dt - DateTime(1970, 1, 1)).value * 1_000_000 # DateTime precision is milliseconds
83-
s_since_unix = ns_since_unix / 1.0e9
100+
ms_since_unix = value(dt - DateTime(1970, 1, 1))
101+
s_since_unix = div(ms_since_unix, 1000)
84102
s_total = s_since_unix + EPOCH_OFFSET_SECONDS
85-
ps_component = (ns_since_unix % 1.0e9) * 1000.0 # Convert nanoseconds remainder to picoseconds
103+
ps_component = rem(ms_since_unix, 1000) * 1000000000 # Convert nanoseconds remainder to picoseconds
86104
return Epoch16(s_total, ps_component)
87105
end
88106

@@ -102,11 +120,12 @@ for f in (:year, :month, :day, :hour, :minute, :second, :millisecond)
102120
@eval Dates.$f(epoch::CDFDateTime) = Dates.$f(DateTime(epoch))
103121
end
104122

105-
Dates.value(epoch::CDFDateTime) = epoch.instant
123+
Dates.value(epoch::Epoch) = epoch.instant
124+
Dates.value(epoch::Epoch16) = ComplexF64(epoch.seconds, epoch.picoseconds)
106125
Dates.value(epoch::TT2000) = epoch.instant.value
107126

108127
function Base.floor(x::T, p::Union{DatePeriod, TimePeriod}) where {T <: CDFDateTime}
109-
convert(T, floor(convert(DateTime, x), p))
128+
return convert(T, floor(convert(DateTime, x), p))
110129
end
111130

112131
function Base.show(io::IO, epoch::CDFDateTime)
@@ -117,7 +136,12 @@ function Base.show(io::IO, epoch::CDFDateTime)
117136
print(io, DateTime(epoch))
118137
end
119138
end
139+
function Base.show(io::IO, epoch::Epoch16)
140+
return print(io, DateTime(epoch))
141+
end
142+
120143
Base.promote_rule(::Type{<:CDFDateTime}, ::Type{Dates.DateTime}) = Dates.DateTime
121144
Base.convert(::Type{Dates.DateTime}, x::CDFDateTime) = Dates.DateTime(x)
122-
Base.bswap(x::T) where {T <: CDFDateTime} = T(Base.bswap(x.instant))
145+
Base.bswap(x::Epoch) = Epoch(Base.bswap(x.instant))
146+
Base.bswap(x::Epoch16) = Epoch16(Base.bswap(x.seconds), Base.bswap(x.picoseconds))
123147
Base.bswap(x::TT2000) = TT2000(Base.bswap(x.instant.value))

test/epochs_test.jl

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
11
using Test
22
using CommonDataFormat
3+
import CommonDataFormat as CDF
34
using Dates
45

56
@testset "Epochs" begin
6-
@test Epoch(DateTime(0)) == Epoch(0)
7+
t = Epoch(DateTime(0))
8+
@test t == Epoch(0)
79
@test DateTime(Epoch(DateTime(0))) == DateTime(0)
810
@test Epoch(Epoch(0)) == Epoch(0)
911
@test Epoch(10) - Epoch(0) == Millisecond(10)
1012
@test string(Epoch(-1.0e31)) == "FILLVAL"
13+
@test Epoch(10) - Millisecond(10) == Epoch(0)
14+
@test Epoch(0) + Second(1) == Epoch(1000)
15+
@test ntoh(hton(t)) == t
1116
# @test Epoch16(DateTime(0)) == Epoch16(0, 0)
1217
end
1318

1419
@testset "TT2000" begin
15-
@test DateTime(TT2000(DateTime(2000))) == DateTime(2000)
20+
t = TT2000(DateTime(2000))
21+
@test DateTime(t) == DateTime(2000)
1622
@test TT2000(DateTime(TT2000(0))) == TT2000(0)
1723
@test TT2000(TT2000(0)) == TT2000(0)
1824
@test TT2000(10) - TT2000(0) == Nanosecond(10)
25+
@test t - Day(1) == DateTime(1999, 12, 31)
1926
@test floor(TT2000(0), Minute(1)) == DateTime(2000, 1, 1, 11, 58)
2027
@test TT2000(0) + Minute(1) == TT2000(60_000_000_000)
2128

2229
@test string(TT2000(0)) == "2000-01-01T11:58:55.816"
2330
@test TT2000(0) == TT2000(0) |> bswap
2431
@test TT2000(0) == DateTime("2000-01-01T11:58:55.816")
25-
end
32+
end
33+
34+
@testset "Epoch16" begin
35+
@test t == DateTime(2021, 1, 17, 11, 30, 40, 897)
36+
@test Epoch16(DateTime(t)) == t
37+
@test string(t) == "2021-01-17T11:30:40.897"
38+
@test ntoh(hton(t)) == t
39+
@test Epoch16(6.377810224e10, 8.97e11) - Epoch16(6.377810224e10, 0) == CDF.Picosecond(8.97e11)
40+
end
41+
42+
@testset "Picosecond" begin
43+
@test CDF.Picosecond(1) == CDF.Picosecond(1)
44+
@test Nanosecond(CDF.Picosecond(Nanosecond(1000))) == Nanosecond(1000)
45+
@test string(CDF.Picosecond(1)) == "1.0 picosecond"
46+
@test CDF.Picosecond(Millisecond(1)) == CDF.Picosecond(1.0e9)
47+
@test CDF.Picosecond(1.0e9) == Millisecond(1)
48+
end

test/perf_test_ntoh.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
a = rand(3, 100000)
2+
3+
f1(a) = map!(ntoh, a, a)
4+
f2(a) = a .= ntoh.(a)
5+
6+
using Polyester
7+
8+
function f3(a)
9+
return @inbounds @simd for i in eachindex(a)
10+
a[i] = ntoh(a[i])
11+
end
12+
end
13+
14+
function f4(a)
15+
@batch for i in eachindex(a)
16+
a[i] = ntoh(a[i])
17+
end
18+
return a
19+
end
20+
21+
using Base.Threads
22+
23+
function f5(a)
24+
Threads.@threads for i in eachindex(a)
25+
a[i] = ntoh(a[i])
26+
end
27+
return a
28+
end
29+
30+
a = rand(3, 10000000)
31+
32+
b1 = @b f1(a) evals = 10
33+
b2 = @b f2(a) evals = 10
34+
b3 = @b f3(a) evals = 10
35+
b4 = @b f4(a) evals = 10
36+
b5 = @b f5(a) evals = 10
37+
38+
39+
ds = CDFDataset("/Users/zijin/.cdaweb/data/THB_L2_FGM/thb_fgl_gseQ_thb_l2s_fgm_20210120000000_20210120235959_cdaweb.cdf")
40+
var = ds["thb_fgl_epoch16"]
41+
@b Array(var)

test/runtests.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,17 @@ end
7171
@test var[1:3] == Float32[6.7, 6.7, 7.3]
7272
@test var["UNITS"] == "nT"
7373
@test var["FIELDNAM"] == "BR (RTN)"
74+
75+
76+
@test ds["Epoch"][1] == DateTime(2024, 9, 1, 0, 0)
77+
@test ntoh(hton(ds["Epoch"][1])) == DateTime(2024, 9, 1, 0, 0)
78+
7479
@test @allocations(ds["BR"]) <= 50
7580
@info @allocated(ds.attrib)
7681
if VERSION >= v"1.12"
7782
@test @allocated(ds.attrib) <= 30000
7883
else
79-
@test @allocated(ds.attrib) <= 65000
84+
@test @allocated(ds.attrib) <= 70000
8085
end
8186
end
8287

0 commit comments

Comments
 (0)