Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/MAT_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,11 @@ function from_datetime(obj::MatlabOpaque)
return map_or_not(ms_to_datetime, dat)
end

# is the complex part the submilliseconds?
# The imaginary part is the low half of a double-double: smaller than half an ulp of the
# real part, so real + imag == real in Float64 and nothing is lost at DateTime resolution.
ms_to_datetime(ms::Complex) = ms_to_datetime(real(ms))
function ms_to_datetime(ms::Real)
isfinite(ms) || return missing # NaT is stored as NaN; DateTime has no ±Inf either
s, ms_rem = fldmod(ms, 1_000) # whole seconds and remainder milliseconds
return DateTime(1970, 1, 1) + Second(s) + Millisecond(round(Int, ms_rem))
end
Expand Down Expand Up @@ -561,9 +563,13 @@ function from_duration(obj::MatlabOpaque)
if isnothing(dat) || isempty(dat)
return Millisecond[]
end
return map_or_not(Millisecond, dat)
return map_or_not(ms_to_millisecond, dat)
end

# milliseconds are stored as Float64: rounded to whole ones, as datetimes are, and a
# missing (NaN) or infinite duration is missing
ms_to_millisecond(ms::Real) = isfinite(ms) ? Millisecond(round(Int, ms)) : missing

to_matlab_millis(d::Millisecond) = Float64(Dates.value(d))

function MatlabOpaque(d::ScalarOrArray{Millisecond})
Expand Down
16 changes: 16 additions & 0 deletions test/missing_gen.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
% Generates test/v7/missing_times.mat and test/v7.3/missing_times.mat: datetime and
% duration arrays holding missing (NaT, NaN), infinite, or fractional-millisecond values.
% Run from the test directory: matlab -batch "missing_gen"

dt_nat = [datetime(2022, 7, 20, 12, 0, 0), NaT];
dt_nat_scalar = NaT;
dt_inf = [datetime(2022, 7, 20, 12, 0, 0), datetime(Inf, 'ConvertFrom', 'posixtime'), ...
datetime(-Inf, 'ConvertFrom', 'posixtime')];
dt_nat_zoned = [datetime(2022, 7, 20, 12, 0, 0, 'TimeZone', 'Europe/London'), NaT('TimeZone', 'Europe/London')];
dur_nan = [seconds(1.5), seconds(NaN)];
dur_nan_scalar = seconds(NaN);
dur_frac = milliseconds([1.5; 2.25; 2.75]);

vars = {'dt_nat', 'dt_nat_scalar', 'dt_inf', 'dt_nat_zoned', 'dur_nan', 'dur_nan_scalar', 'dur_frac'};
save(fullfile('v7', 'missing_times.mat'), vars{:}, '-v7');
save(fullfile('v7.3', 'missing_times.mat'), vars{:}, '-v7.3');
24 changes: 24 additions & 0 deletions test/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,30 @@ for format in ["v7", "v7.3"]
end
end

# missing_times.mat is written by missing_gen.m: datetime and duration arrays holding
# missing (NaT, NaN), infinite or fractional-millisecond values
@testset "missing and fractional times $format" begin
filepath = joinpath(dirname(@__FILE__), format, "missing_times.mat")
if isfile(filepath)
# dt_nat_zoned has a time zone, which is dropped with a warning
vars = @test_logs (:warn, r"timezone") match_mode=:any matread(filepath)
noon = DateTime(2022, 7, 20, 12)

@test isequal(vars["dt_nat"], [noon missing]) # NaT is missing
@test eltype(vars["dt_nat"]) == Union{Missing,DateTime}
@test ismissing(vars["dt_nat_scalar"])
@test isequal(vars["dt_inf"], [noon missing missing]) # so are ±Inf
@test isequal(vars["dt_nat_zoned"], [noon - Hour(1) missing]) # the UTC instant

@test isequal(vars["dur_nan"], [Millisecond(1500) missing])
@test ismissing(vars["dur_nan_scalar"])
@test vec(vars["dur_frac"]) == Millisecond.([2, 2, 3]) # 1.5, 2.25, 2.75 ms
else
# generated in MATLAB by test/missing_gen.m; skipped until it is committed
@test_skip isfile(filepath)
end
end

@testset "user defined classdef $format" begin
let objtestfile = "user_defined_classdefs.mat"
filepath = joinpath(dirname(@__FILE__), format, objtestfile)
Expand Down
Loading