Skip to content

Commit 14b0c20

Browse files
committed
feat: Relax UnixTime construction from String
1 parent 80c861b commit 14b0c20

2 files changed

Lines changed: 61 additions & 27 deletions

File tree

src/UnixTimes.jl

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ struct UnixTime <: Dates.AbstractDateTime
1212
end
1313

1414
function UnixTime(
15-
y::Integer,
16-
m::Integer = 1,
17-
d::Integer = 1,
18-
h::Integer = 0,
19-
mi::Integer = 0,
20-
s::Integer = 0,
21-
ms::Integer = 0,
22-
us::Integer = 0,
23-
ns::Integer = 0)
24-
convert(UnixTime, DateTime(y, m, d, h, mi, s, ms)) + Nanosecond(us * 1000 + ns)
15+
y::Integer,
16+
m::Integer = 1,
17+
d::Integer = 1,
18+
h::Integer = 0,
19+
mi::Integer = 0,
20+
s::Integer = 0,
21+
ms::Integer = 0,
22+
us::Integer = 0,
23+
ns::Integer = 0
24+
)
25+
return convert(UnixTime, DateTime(y, m, d, h, mi, s, ms)) + Nanosecond(us * 1000 + ns)
2526
end
2627

2728
const UNIX_EPOCH = UnixTime(Dates.UTInstant(Nanosecond(0)))
@@ -41,12 +42,12 @@ Base.:-(x::UnixTime, p::Period) = x + (-p)
4142

4243
function Base.:+(x::UnixTime, p::Union{Month, Year})
4344
trunc_ns = mod(Dates.value(x), 1_000_000)
44-
convert(UnixTime, convert(DateTime, x) + p) + Nanosecond(trunc_ns)
45+
return convert(UnixTime, convert(DateTime, x) + p) + Nanosecond(trunc_ns)
4546
end
4647

4748
function Dates.DateTime(x::UnixTime)
4849
instant_ms = Dates.UNIXEPOCH + div(x.instant.periods.value, 1_000_000)
49-
DateTime(Dates.UTM(instant_ms))
50+
return DateTime(Dates.UTM(instant_ms))
5051
end
5152

5253
Dates.Date(x::UnixTime) = Date(DateTime(x))
@@ -63,19 +64,39 @@ UnixTime(x::Date, y::Time) =
6364
const DATEFORMAT = dateformat"yyyy-mm-ddTHH:MM:SS.sss"
6465

6566
function UnixTime(s::AbstractString)
66-
try
67-
@assert length(s) == 29
68-
dt = DateTime(chop(s; tail=6), DATEFORMAT)
69-
ns = Nanosecond(parse(Int, last(s, 6)))
70-
UnixTime(dt) + ns
71-
catch
72-
throw(ArgumentError("Invalid UnixTime string"))
67+
# Find the decimal point if it exists
68+
dot_idx = findfirst('.', s)
69+
70+
# No fractional seconds, parse as DateTime
71+
isnothing(dot_idx) && return UnixTime(DateTime(s))
72+
73+
# Split into datetime and fractional parts
74+
dt_part = @view s[1:(dot_idx - 1)]
75+
frac_part = @view s[(dot_idx + 1):end]
76+
77+
# Parse the datetime part
78+
dt = DateTime(dt_part)
79+
80+
# Parse fractional seconds (can be 1-9 digits)
81+
if isempty(frac_part) || !all(isdigit, frac_part)
82+
throw(ArgumentError("Invalid fractional seconds in UnixTime string"))
7383
end
84+
85+
# Convert fractional seconds to nanoseconds
86+
# Pad or truncate to 9 digits (nanosecond precision)
87+
frac_digits = length(frac_part)
88+
ns_value = if frac_digits <= 9
89+
parse(Int, frac_part) * 10^(9 - frac_digits)
90+
else
91+
parse(Int, frac_part[1:9])
92+
end
93+
94+
return UnixTime(dt) + Nanosecond(ns_value)
7495
end
7596

7697
function Base.convert(::Type{UnixTime}, x::DateTime)
7798
instant_ns = (Dates.value(x) - Dates.UNIXEPOCH) * 1_000_000
78-
UnixTime(Dates.UTInstant(Nanosecond(instant_ns)))
99+
return UnixTime(Dates.UTInstant(Nanosecond(instant_ns)))
79100
end
80101

81102
# Promotion rules for comparing UnixTime with DateTime
@@ -90,11 +111,11 @@ function Base.show(io::IO, x::UnixTime)
90111
print(io, div(v, d) % 10 + '0')
91112
d = div(d, 10)
92113
end
93-
nothing
114+
return nothing
94115
end
95116

96117
function Base.floor(x::UnixTime, p::Union{DatePeriod, TimePeriod})
97-
convert(UnixTime, floor(convert(DateTime, x), p))
118+
return convert(UnixTime, floor(convert(DateTime, x), p))
98119
end
99120

100121
Dates.guess(a::UnixTime, b::UnixTime, c) = (Dates.value(b) - Dates.value(a)) ÷ Dates.tons(c)
@@ -111,12 +132,12 @@ if Sys.islinux()
111132
ts = Ref{LinuxTimespec}()
112133
ccall(:clock_gettime, Cint, (Cint, Ref{LinuxTimespec}), 0, ts)
113134
x = ts[]
114-
UnixTime(Dates.UTInstant(Nanosecond(x.seconds * 1_000_000_000 + x.nanoseconds)))
135+
return UnixTime(Dates.UTInstant(Nanosecond(x.seconds * 1_000_000_000 + x.nanoseconds)))
115136
end
116137
else
117138
@inline function unix_now()
118139
tv = Libc.TimeVal()
119-
UnixTime(Dates.UTInstant(Nanosecond(tv.sec * 1_000_000_000 + tv.usec * 1_000)))
140+
return UnixTime(Dates.UTInstant(Nanosecond(tv.sec * 1_000_000_000 + tv.usec * 1_000)))
120141
end
121142
end
122143

test/runtests.jl

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,22 @@ end
4848
x = UnixTime(2020, 1, 2, 3, 4, 5, 6, 7, 8)
4949
@test string(x) == "2020-01-02T03:04:05.006007008"
5050
@test UnixTime(string(x)) == x
51-
@test_throws ArgumentError UnixTime("2025-01-28T15:49:36.5901397023")
52-
@test_throws ArgumentError UnixTime("2025-01-28T15:49:36.59013970")
53-
@test_throws ArgumentError UnixTime(repeat("a", 29))
51+
52+
# Test various string formats
53+
@test UnixTime("2020-01-02T03:04:05") == UnixTime(2020, 1, 2, 3, 4, 5)
54+
@test UnixTime("2020-01-02T03:04:05.1") == UnixTime(2020, 1, 2, 3, 4, 5, 100)
55+
@test UnixTime("2020-01-02T03:04:05.12") == UnixTime(2020, 1, 2, 3, 4, 5, 120)
56+
@test UnixTime("2020-01-02T03:04:05.123") == UnixTime(2020, 1, 2, 3, 4, 5, 123)
57+
@test UnixTime("2020-01-02T03:04:05.1234") == UnixTime(2020, 1, 2, 3, 4, 5, 123, 400)
58+
@test UnixTime("2020-01-02T03:04:05.123456") == UnixTime(2020, 1, 2, 3, 4, 5, 123, 456)
59+
@test UnixTime("2020-01-02T03:04:05.123456789") == UnixTime(2020, 1, 2, 3, 4, 5, 123, 456, 789)
60+
61+
# Test truncation of extra digits
62+
@test UnixTime("2020-01-02T03:04:05.1234567890") == UnixTime(2020, 1, 2, 3, 4, 5, 123, 456, 789)
63+
64+
# Test invalid formats
65+
@test_throws ArgumentError UnixTime("2020-01-02T03:04:05.")
66+
@test_throws ArgumentError UnixTime("2020-01-02T03:04:05.abc")
5467
end
5568

5669
@testset "arithmetic" begin

0 commit comments

Comments
 (0)