How can I parse timestamps in ISO 8601 format?

I use an api that returns timestamps in this format: 

2025-04-10T18:21:47.779Z
Is there a way to parse this into a Moment or unix timestamp?
The best idea I could come up with is to read the parts with substring, then convert to Number, then create a dictionary and then 
var options = {
    :year   => 2025,
    :month  => 4,
    :day    => 10,
    :hour   => 18,
    :minute => 21,
:second => 47
}; var timestamp = Gregorian.moment(options);


But all this would almost double the datafield.... so the best I could come up with is to store the timestamps as a string,
and compare them as a string.

  • TL;DR if memory usage is a concern and you're storing lots of timestamps, just store them as Numbers (parse and convert to Moment, then call Moment.value()). You can also store the millisecond value as a separate Number field if you need that extra precision.

    Quick memory usage comparison:

    - "2025-04-10T18:21:47.779Z" => 33 bytes

    - Moment object => 96 bytes

    - Number (Moment.value()) => 5 bytes

    - 2 Numbers = 10 bytes (Moment.value() + milliseconds as Number) (ofc there could be some additional overhead depending on whether you need to add an additional field to your dictionary or class container)

    But all this would almost double the datafield

    Double what? Memory usage?

    I'm going to assume:

    - you do mean double the memory usage (due to the storage requirements of the timestamps themselves)

    - you are comparing the case of storing the timestamps as Strings as opposed to storing them as Moments

    Indeed, a string like "2025-04-10T18:21:47.779Z" uses 33 bytes, while a Moment object uses 96 bytes. [*]

    However, if the above guesses are correct, then I will also guess you are disregarding the option where you simply store each timestamp as a Number (Moment.value()). A Number takes 5 bytes (1 byte for type, 4 bytes for value), making it by far the most efficient choice here, in terms of memory.

    If you need millisecond-level precision, you could also store milliseconds in a separate Number field, at the cost of doubling memory usage (10 bytes is still far less than 33 or 96 though).

    [*] It is crazy how much overhead there is to using classes in Monkey C. Even an instance of a completely empty class (no methods or variables) takes up 84 bytes.

    Honestly, if you need to save memory, rewriting part of your code to *not* use classes (when lots of data needs to be stored) is still a viable hand-optimization strategy.

    If you actually meant that the code for parsing the timestamps "would almost double the datafield" (memory usage?), well I can't really think of a better way of parsing them than you've already outlined. I would kind of find it hard to believe that tho. Your data field would have to be very simple indeed if a simple timestamp parsing function alone doubles the size. And if so, then the size of your code must not be a problem at all.

    (Btw, as written, it really does sound like you are talking about the code for parsing timestamps doubling your data field's memory usage, as opposed to the storage of the timestamps itself)

    so the best I could come up with is to store the timestamps as a string,
    and compare them as a string.

    Up until this point (the very end of your post), you didn't really specify *why* you need to parse timestamps.

    If the only purpose is to compare (and maybe sort) timestamps, then storing them in their original string format would be the "easiest" solution (in terms of code), especially if you only support CIQ 5.0.0 devices (so you get string comparison for free, although it's not hard to write).

    However, as noted above, storing timestamps as numbers is more efficient memory-wise, and it will also be more efficient CPU-wise when you compare timestamps. Then again, any CPU usage savings may be offset by the need to parse all of the timestamps in the first place. (Ofc there's other considerations which impact CPU usage, like whether any of these timestamps will be persisted to storage or not; and if so, whether they will need to be compared/sorted again after they're loading from storage)

    If you need to support devices with less than CIQ 5, you also save a little bit of memory by not having to write your own string compare function.

    I suppose that if you also need to display the timestamps in a human-friendly format, then you have no choice but to parse timestamps at some point, which would really make this question more about what's the best way to *store* timestamps (in memory/storage).

  • If you get a unix/epoch time stamp it's really easy as it's a count of seconds, and isn't based on time zone/DST, etc.  And small vs saving it in ascii

    You may find it in the data you get, but at times the name may be unclear - with OWM for example it's called "dt"

    It's easy to do math on that. A difference of 60*60 is an hour for example, even if DST changed during that hour.

    For example, for one site I use I get this for time:

    obsTimeLocal=>2019-03-29 09:47:00,
    epoch=>1553878020,
    and can then convert epoch to ascii for display with
     
    var ts1=new Time.Moment(epochValue);
    var ts2=Calendar.info(ts1, Time.FORMAT_MEDIUM);
  • Sorry, I wasn't specific enough.

    I meant to save memory by saving in the code size. My use case is that I am calling a REST api that returns the data plus a timestamp. I am saving this timestamp "in some format" to storage (together with the returned data) in order to know later when I get to the same part of the code whether the stored data is expired and I need to call the api again. So without a better solution I save the string, and I generate the timestamp for "now" in ISO 8601 format (still lot of code, but I estimated this to be less then writing the parser). And yes, I also wrote compareStr for older devices... Maybe I'll write the parser and compare the code size...

  • I meant to save memory by saving in the code size.

    Hmm that's actually what I originally understood you to mean, but I guess I just had a hard time believing that the code for the parser "doubles your data field" (code size? total memory size? prg size?).

    Sorry for second guessing you.

    I would've understood if your data field was already close to the memory limit and writing the parser simply pushes it over the edge.

  • I took a stab at writing an ISO8601 parsing function and found that it takes up 188 bytes of code and 9 bytes of data, at optimization level 1. Ofc you would have slightly more overhead for the code that actually has to call it. Maybe I'm missing something, but I find it hard to believe that 197 bytes "doubles your datafield" (again, not quite sure what that means). Especially given that you can forgo the compareStr function for older devices, which means you save a bit of memory there.

    function parse8601(timestamp as String) as Number {
        var options = {
            :year   => timestamp.substring(0, 4).toNumber(),
            :month  => timestamp.substring(5, 7).toNumber(),
            :day    => timestamp.substring(8, 10).toNumber(),
            :hour   => timestamp.substring(11, 13).toNumber(),
            :minute => timestamp.substring(14, 16).toNumber(),
            :second => timestamp.substring(17, 19).toNumber(),
        };
        return Gregorian.moment(options).value();
    }

  • So without a better solution I save the string, and I generate the timestamp for "now" in ISO 8601 format (still lot of code, but I estimated this to be less then writing the parser).

    I wrote a function for converting a Moment to ISO8601 format. I found that with optimization level 1, it takes 176 bytes of code and 30 bytes of data - slightly more total memory (code+data = 206) than the function for parsing the timestamp. Ofc the code to pass in Time.now() would add a slight amount of overhead.

    function momentTo8601(m as Moment) as String {
        var info = Gregorian.utcInfo(m, Time.FORMAT_SHORT);
        var f = "%02d"; // this hand optimization saves 8 bytes at all optimization levels (including 0)
        return
            info.year.format("%04d") + "-" + 
            info.month.format(f) + "-" +
            info.day.format(f) + "T" +
            info.hour.format(f) + ":" +
            info.min.format(f) + ":" +
            info.sec.format(f) + ".000Z";
    }

  • I wrote the code both ways, and compared. It's a bit more difference in the code around, but alltogether it turns out that I was wrong, and it's worth to use the parser in my case.

    With strict typecheck, if one doesn't mind errors in case the input is incorrect:

    function parseISO8601(dateTime as String) as Number {
        return Gregorian.moment({
            :year => (dateTime.substring(0, 4) as String).toNumber() as Number,
            :month => (dateTime.substring(5, 7) as String).toNumber() as Number,
            :day => (dateTime.substring(8, 10) as String).toNumber() as Number,
            :hour => (dateTime.substring(11, 13) as String).toNumber() as Number,
            :minute => (dateTime.substring(14, 16) as String).toNumber() as Number,
            :second => (dateTime.substring(17, 19) as String).toNumber() as Number
        }).value();
    }

    maybe it's better to add some more code to check for nulls in the substring (maybe also in toNumber), though it adds quiet a bit of code, and doesn't really have a good solution, maybe it could then return null to indicate that the parsing was unsuccessful. 

  • maybe it's better to add some more code to check for nulls in the substring (maybe also in toNumber), though it adds quiet a bit of code, and doesn't really have a good solution, maybe it could then return null to indicate that the parsing was unsuccessful. 

    If memory is so tight to that you were reluctant to implement a ~200 byte timestamp parser function, it sounds like comprehensive error handling won't be feasible.

    You could add a sanity check for the length of the dateTime string without *too* much impact - it should be either 20 or 24 chars:

    - 20: "2025-04-10T18:21:47Z"

    - 24: "2025-04-10T18:21:47.779Z"

    You could also check that it ends in "Z".

  • Maybe the trick would be to offload the work. You did say you were using an API, do you have access to your own server? If so then you could use it to get the data for you, then have it do the conversion and send you back the unix epoch time.

  • Not my server :) it's a public API. Otherwise I would send the Unix timestamp (seconds since 1970) as a number and not in ISO 8601 format.