Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><code>map</code> - change each element</p> <p><code>fold</code> - combine all the elements</p> <p><code>scan</code> - combine all the elements keeping a running "total" - this is what you need</p> <p>It's going to be easier to keep everything as an Integer until the very end:</p> <pre><code>type PosixOffset = Integer </code></pre> <p>A string in your <code>listOfTimes</code> could be a unix time, an increment or an erroneous value. We could represent that by <code>Maybe (Either PosixOffset Integer)</code> but that could get annoying. Let's roll our own:</p> <pre><code>data Time = Unix PosixOffset | Inc Integer | Error String deriving Show </code></pre> <p>This allows me to be flexible about what we do later with an error: crash the program with an <code>error</code>, show the <code>Error</code> message to the user but somehow allow them to resume, or ignore the bad value.</p> <p>Let's make safe version to replace <code>read :: String -&gt; Integer</code>, which returns <code>Nothing</code> instead of crashing. We'll need to <code>import Data.Char (isDigit)</code></p> <pre><code>readInteger :: String -&gt; Maybe Integer readInteger "" = Nothing readInteger xs | all isDigit xs = Just (read xs) | otherwise = Nothing </code></pre> <p>Now we can use that to <code>readTime</code> with some helpful <code>Error</code> messages.</p> <pre><code>readTime :: String -&gt; Time readTime ('u':xs) = case readInteger xs of Just i -&gt; Unix i Nothing -&gt; Error $ "readTime: there should be an integer after the u, but I got: " ++ 'u':xs readTime [] = Error "readTime: empty time" readTime xs = case readInteger xs of Just i -&gt; Inc i Nothing -&gt; Error $ "readTime: " ++ xs ++ " is neither a unix time nor an increment." </code></pre> <p>The plan is to convert our list of Strings to a list of pairs <code>(PosixOffset,Integer)</code>, with the last known <code>PosixOffset</code> from a unix time, and the current increment. We'll then need to be able to convert these pairs to a <code>UTCTime</code> </p> <pre><code>toUTC :: (PosixOffset,Integer) -&gt; UTCTime toUTC (p,i) = psUTC (p+i) </code></pre> <p>Now we need to know how to combine the running total of the <code>Time</code>s with the next <code>Time</code>. We'll keep hold of the last unix time for reference.</p> <pre><code>addTime :: (PosixOffset,Integer) -&gt; Time -&gt; (PosixOffset,Integer) addTime (oldunix,oldinc) time = case time of Unix new -&gt; (new,0) -- If there's a new unix time, replace and reset the inc to 0. Inc inc -&gt; (oldunix,inc) -- If there's a new increment, replace the old one. Error msg -&gt; error msg -- If there's an error, crash showing it. </code></pre> <p>or you could use </p> <pre><code>addTimeTolerant :: (PosixOffset,Integer) -&gt; Time -&gt; (PosixOffset,Integer) addTimeTolerant (oldunix,oldinc) time = case time of Unix new -&gt; (new,0) -- If there's a new unix time, replace and reset the inc to 0. Inc inc -&gt; (oldunix,inc) -- If there's a new increment, replace the old one. Error msg -&gt; (oldunix,oldinc) -- If there's an error, ignore it and keep the time the same. </code></pre> <p>Now we can stick it together: turn the <code>String</code>s into <code>Time</code>s, then combine them into <code>(PosixOffset,Integer)</code> pairs by <code>scan</code>ning with <code>addTime</code>, then turn all the resulting pairs into <code>UTCTime</code>s.</p> <pre><code>runningTotal :: [String] -&gt; [UTCTime] runningTotal [] = [] runningTotal xss = let (t:ts) = map readTime xss in -- turn Strings to Times case t of Error msg -&gt; error msg Inc _ -&gt; error "runningTotal: list must start with a unix time" Unix po -&gt; map toUTC $ scanl addTime (po,0) ts -- scan the list adding times, -- starting with an initial unix time -- then convert them all to UTC </code></pre> <p>or if you like the keep calm and carry on approach of <code>addTimeTolerant</code>, you could use</p> <pre><code>isn't_UnixTime :: Time -&gt; Bool isn't_UnixTime (Unix _) = False isn't_UnixTime _ = True runningTotalTolerant :: [String] -&gt; [UTCTime] runningTotalTolerant xss = let ts = dropWhile isn't_UnixTime (map readTime xss) in -- cheerily find the first unix time if null ts then [] else -- if there wasn't one, there are no UTCTimes let (Unix po) = head ts in -- grab the first time map toUTC $ scanl addTimeTolerant (po,0) (tail ts) -- scan the list adding times, -- starting with an initial unix time -- then convert them all to UTC </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload