0

I'm working with FIX Timestamp 20180605-03:10:33.0240756 that I need to parse to DA.Time to persist as part of Trade information. I also need to get the microseconds back as part of the response message (ACK/NACK).

Ideally I would like to have a simple way of doing it.

Currently I'm only storing up to seconds using the DA.Time time constructor. Looking at the https://docs.daml.com/daml/stdlib/base.html#data-prelude-time-24894, seems like the way to retrieve the microseconds is by:

  1. creating another time sans microseconds
  2. subTime both to get the RelTime
  3. call microseconds in RelTime

Please advise on whether this is correct, if there is a better way of doing it. Thank you.

blu35ky
  • 7
  • 4

1 Answers1

0

Under usual circumstances, I would recommend doing the string parsing off-ledger and passing dates and times in as Date and Time types, but it sounds like you have a requirement to do this on-ledger in DAML.

Your first question concerns parsing a string to a DA.Time. There is currently no inbuilt general purpose date/time parsing function so you'll have to build your own. For a fixed format like yours, pattern matching makes that quite straightforward:

daml 1.2
module ParseTime where

import DA.Assert
import DA.Optional
import DA.Text
import DA.Time
import DA.Date

-- | `parseUTC` takes a string in the format "yyyyMMdd-HHmmss.SSS"
-- and converts it to `Time`, assuming the time is in UTC
-- Time is truncated to whole microseconds.
parseDateTimeUTC utc = datetime yy mon dd h m s `addRelTime` micros
  where
    -- Split out the micro seconds to stay under
    -- pattern match complexity limit
    [dt, ms] = splitOn "." utc
    -- Pattern match on `explode` to check
    -- the format and assign names to characters
    [ y1, y2, y3, y4, mon1, mon2, d1, d2, "-",
      h1, h2, ":", m1, m2, ":", s1, s2
      ] = explode dt
    -- The function will fail on `fromSome` if any of the
    -- integer strings don't parse
    unsafeParse = fromSome . parseInt . implode
    -- Map the parse function over grouped characters and
    -- pattern match the result to give names to the Ints
    [yy, imon, dd, h, m, s, imic] = map unsafeParse
      [[y1, y2, y3, y4], [mon1, mon2], [d1, d2],
      [h1, h2], [m1, m2], [s1, s2], explode $ T.take 6 ms]
    -- Process month and microseconds into `Month` and
    -- `Reltime` types
    mon = toEnum $ imon - 1
    mic = 10 ^ (max 0 (6 - T.length ms)) * imic
    micros = convertMicrosecondsToRelTime mic

t = scenario do
  let t = time (date 2018 Jun 05) 03 10 33 `addRelTime` convertMicrosecondsToRelTime 24075
  parseDateTimeUTC "20180605-03:10:33.0240756" === t

To transition between DA.Time and microseconds, we need to make a choice on time zone. Assuming UTC, we can define

epochUTC = datetime 1970 Jan 1 0 0 0
microToTimeUTC n = epoch `addRelTime` convertMicrosecondsToRelTime n
timeToMicroUTC t = convertRelTimeToMicroseconds $ t `subTime` epoch

t2 = scenario do
  microToTimeUTC 1557733583000000 === datetime 2019 May 13 07 46 23
  timeToMicroUTC (datetime 2019 May 13 07 46 23) === 1557733583000000

to transition between DA.Time and Int representations of dates.

If you want the microsecond component of an existing DA.Time, you just want the last six digits of the microsecond representation:

micros t = timeToMicroUTC t % 1000000

t3 = scenario do
  micros (parseDateTimeUTC "20180605-03:10:33.0240756") === 24075

Note the truncation to whole microseconds here, that's also mentioned in the description of parseDateTimeUTC.

bame
  • 960
  • 5
  • 7