Skip to content

proc -(: DateTime, : Duration): DateTime SIGSEGV runtime error when used on a deserialised DateTime value #25533

@incansvl

Description

@incansvl

Nim Version

Nim Compiler Version 2.2.6 [Linux: amd64]

Description

I have a program that saves state information including a start timestamp DateTime in a small tuple, which is serialised with marshal and written to file. The next execution loads+deserialises the data from the last run. When this code uses the overloaded - proc on a timestamp which is a DateTime value, it hits a SIGSEGV.

It has taken me a while to narrow down the trigger for the error, and come up with a repeatable example. The demo code below demonstrates (I think)-

  1. Various "shapes" of data, that include a DateTime field, work ok
  2. Serialising + deserialising a DateTime within a single process invocation works ok
  3. Serialising in one process, then reading+deserialising a DateTime in a separate invocation fails

Demonstrating the Error (test program)
Compile the code below to test_marshal_external
Note- serialised output is written to multiple files (one per data structure) in /tmp/marshal_*.json

Usage: test_marshal_external -[wrd]
w- Write serialised data to /tmp/marshal_.json
r- Read and deserialise data from files
d- Cleanup (delete files in /tmp)

Then try-

  1. test_marshal_external -wrd- Serialises, deserialises, and operates on the DateTime values in one process.
  2. test_marshal_external -w- Write data to marshal-formatted json files
  3. test_marshal_external -r- Reads and operates on the data. Triggers SIGSEGV

Demo Code

import std / [ cmdline, strutils, times, marshal, strformat, os, files ]

type TestDT = DateTime
type TestSimpleTuple = tuple[timestamp : DateTime]
type TestComplexTuple = tuple[
  intValue : int,
  floatValue : float,
  timestamp : DateTime,
  stringValue : string,
  intArray : array[0..2, int],
  floatSeq : seq[float]
]
type TestComplexTuple2 = tuple[
  Interactive : bool,
  ScanMode : string,
  RunStart : DateTime,
  ExecuteTime_s : Duration,
  TotalProcessorTime_s : float
]

let args = commandLineParams().join("").toLower
echo fmt "{args=}"
echo fmt "\nTime now= {now()}"

if args.contains("w"):
  echo "\n****************Serialising + Writing****************"
  block:
    echo "----Simple DateTime----"
    let sourceDT : TestDT = now()
    let fPath = "/tmp/marshal_simpleDT.json"
    var f = open(fPath, fmWrite)
    f.write($$sourceDT)
    f.close()

  block:
    echo "----Single element tuple----"
    let sourceTuple : TestSimpleTuple = (timestamp : now())
    let fPath = "/tmp/marshal_simpleTuple.json"
    var f = open(fPath, fmWrite)
    f.write($$sourceTuple)
    f.close()

  block:
    echo "----Multi element tuple----"
    let sourceTuple : TestComplexTuple = (
      intValue : 1,
      floatValue : 1.0,
      timestamp : now(),
      stringValue : "this is the string value",
      intArray : [10,11,12],
      floatSeq : @[1.0,1.1,1.2]
    )
    let fPath = "/tmp/marshal_complexTuple.json"
    var f = open(fPath, fmWrite)
    f.write($$sourceTuple)
    f.close()

  block:
    echo "----Another Multi element tuple----"
    let sourceTuple : TestComplexTuple2 = (
      Interactive : true,
      ScanMode : "Not specified",
      RunStart : now(),
      ExecuteTime_s : initDuration(seconds = 4),
      TotalProcessorTime_s : 2.82
    )
    let fPath = "/tmp/marshal_complexTuple2.json"
    var f = open(fPath, fmWrite)
    f.write($$sourceTuple)
    f.close()

if args.contains("r"):
  echo "\n****************Reading + Deserialising****************"
  block:
    echo "----Simple DateTime----"
    let fPath = "/tmp/marshal_simpleDT.json"
    var f = open(fPath, fmRead)
    var deserialised = f.readAll().to[:TestDT]
    f.close()
    echo fmt "{deserialised=}"

    let earlierBy5s : DateTime = deserialised - initDuration(seconds = 5)
    echo fmt "{earlierBy5s=}"

  block:
    echo "----Single element tuple----"
    let fPath = "/tmp/marshal_simpleTuple.json"
    var f = open(fPath, fmRead)
    var deserialised = f.readAll().to[:TestSimpleTuple]
    f.close()
    echo fmt "{deserialised=}"

    let earlierBy5s : DateTime = deserialised.timestamp - initDuration(seconds = 5)
    echo fmt "{earlierBy5s=}"

  block:
    echo "----Complex tuple----"
    let fPath = "/tmp/marshal_complexTuple.json"
    var f = open(fPath, fmRead)
    var deserialised = f.readAll().to[:TestComplexTuple]
    f.close()
    echo fmt "{deserialised=}"

    let earlierBy5s : DateTime = deserialised.timestamp - initDuration(seconds = 5)
    echo fmt "{earlierBy5s=}"

  block:
    echo "----Another Complex tuple----"
    let fPath = "/tmp/marshal_complexTuple2.json"
    var f = open(fPath, fmRead)
    var deserialised = f.readAll().to[:TestComplexTuple2]
    f.close()
    echo fmt "{deserialised=}"

    let earlierBy5s : DateTime = deserialised.RunStart - initDuration(seconds = 5)
    echo fmt "{earlierBy5s=}"

if args.contains("d"):
  echo "\n****************Cleanup - Deleting tmp files****************"
  write(stdout, "\nCleaning up")
  for f in [
    "/tmp/marshal_simpleDT.json",
    "/tmp/marshal_simpleTuple.json",
    "/tmp/marshal_complexTuple.json",
    "/tmp/marshal_complexTuple2.json"
  ]:
    f.removeFile()
    write(stdout, ".")

write(stdout, "\n\n")

Current Output

test_marshal_external' -r [Exec]
args=-r

Time now= 2026-02-20T11:29:01+00:00

****************Reading + Deserialising****************
----Simple DateTime----
deserialised=2026-02-20T11:28:58+00:00
Traceback (most recent call last)
~/Documents/Development/Nim test/test_marshal_external.nim(82) test_marshal_external
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1470) -
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1268) inZone
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1239) zonedTimeFromTime
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

Expected Output

$ ./test_marshal_external' -wrd

args=-wrd

Time now= 2026-02-19T18:39:21+00:00

****************Serialising + Writing****************
----Simple DateTime----
----Single element tuple----
----Multi element tuple----
----Another Multi element tuple----

****************Reading + Deserialising****************
----Simple DateTime----
deserialised=2026-02-19T18:39:21+00:00
earlierBy5s=2026-02-19T18:39:16+00:00
----Single element tuple----
deserialised=(timestamp: 2026-02-19T18:39:21+00:00)
earlierBy5s=2026-02-19T18:39:16+00:00
----Complex tuple----
deserialised=(intValue: 1, floatValue: 1.0, timestamp: 2026-02-19T18:39:21+00:00, stringValue: "this is the string value", intArray: [10, 11, 12], floatSeq: @[1.0, 1.1, 1.2])
earlierBy5s=2026-02-19T18:39:16+00:00
----Another Complex tuple----
deserialised=(Interactive: true, ScanMode: "Not specified", RunStart: 2026-02-19T18:39:21+00:00, ExecuteTime_s: 4 seconds, TotalProcessorTime_s: 2.82)
earlierBy5s=2026-02-19T18:39:16+00:00

****************Cleanup - Deleting tmp files****************

Cleaning up....

Known Workarounds

UPDATE

Contrary to (my) expectations, it seems you cannot "un-poison" a DateTime that has been poisoned in this manner by "round-tripping" through serialisation within the current process. I.e. this does NOT work-

let shiftedTimestamp : DateTime = $$poisonedTimestamp.to[:DateTime] - initDuration(seconds = 5)

However you can un-poison the value by (in effect) printing the DateTime to a string and then building a new DateTime value by parsing the string. It's ugly, but it works-

# Un-poison the deserialised DateTime value before attempting to use it for calculation-
const defaultFormat = "yyyy-MM-dd'T'HH:mm:sszzz"
let shiftedTimestamp : DateTime = ($poisonedTimestamp).parse(defaultFormat) - 
                                  initDuration(seconds = 5)

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions