convert DIFF_TIME_STR parameter to a readable / usable value

We are using the DTE block to trigger an event, but we would also like to show the remaining time on an Operate graphic in a more traditional DD/MM/YYYY HH:MM:SS format instead of the ISO style and we would also like to have the value in the control module converted to a numerical value so that it can be used with a RET block.  Is there a way to convert the DIFF_TIME_STR to our desired time format as well as read the value in seconds?

Thanks

15 Replies

  • I have the exact same question. I want to use the remaining time from the DIFF_TIME_STR converted to minutes, so can multiply it times a flow rate to predict a usage. But cant figure out how to take the ISO format into total minutes. Did you ever figure this out?
  • In reply to David Stevenson:

    Hi,

    Check if this helps ..

    Senthilkrishnan

  • In reply to Senthilkrishnan M:

    The DTE block provides the TE_TIME_STR as a "future event time" relative to the local Time. 

    The DIFF_TIME_STR will be the time remaining. which is equal to TE_TIME_STR - LOCAL_TIME_STR.

    Using the STR_TO_TIME function, you can convert the TE_TIME_STR to Epoch time and Subract current Local Time to get the number of seconds remaining:

    In the HMI:

    The DIFF_TIME_STR is in the P0000T00:00:00 format giving you days hours, minutes, and seconds.  You can display this directly, and use a function in the HMI to return the Hours: Minutes : Seconds portion of the string rather than do that in the controller.

    F_DIFF_TIME  The following is psuedo code.  I have not created this function, but parsing the DIFF_TIME_STR into various formats can be done at the console, avoiding string manipulation and such in the controller.  By defining a function, you can resuse this anywhere you need to format a time string into something else.  Skies the limit, but also common sense here.

    Input:

    DIFF_TIME, which is a path or variable holding the P0000T00:00:00 formated string

    FORMAT, An integer that can be used to return different formats of the time value.  Or a Letter.  The script would use this to decide which format to use (Total seconds?, Hours:Min, Min:seconds.  Hours with decimal, etc.

    Use a Script Function that returns the desired format.

    Here's what I'm thinking:

    TIME_array == Split and format DIFF_TIME string into an array to give Days and Time, two strings one with T0001 and the other with 01:02:03

    Values_array == Plit time portion into three separate integers

    If format ==1 then {

       Time == ((Days * 24) + Value_array(1)) + ":" + Value_Array(2) + ":" + Value_array(3)

        Return Time  //This returns total time in Hous: Min : Sec as a string.

    end if

    }

    If format == 2  then {

      Time == (Days +86400) + (Value_array(1) * 3600)) + (Value_arry(2) * 60) + Value_array(3))

       Return Time   // This returns a string representing total seconds.

    endif

    }

    (Note:  I likely don't have the syntax correct or the Array element reference right.  But this is just an idea at this point)

    By creating a function with multiple format options, you have one function instead of many.  How you choose to define the output Format options you want, you have one place to manage them.  You can also use the Calculations in the function to extract the raw values as integers, which simplifies the overall script.  

    Andre Dicaire

  • In reply to Andre Dicaire:

    Thanks to both of you for the suggestions.  I was able to make it work like you suggested Andre.  Below is the finished product; thought I should show it for future reference.

  • In reply to David Stevenson:

    David, one issue with your solution. Time to string returns a 32 bit unsigned Int. Out1 of Calc block is a float. The float cannot accurately portray the epoch time value. You need to make your epoch tome parameters 32 UNSIGNED and write directly to them and avoid passing throughCALC/OUT or internal variables of the CALC block

    Andre Dicaire

  • In reply to Andre Dicaire:

    Thanks Andre, I didn't realize that. It is actually working to produce the correct values and results. I guess it must be because a 32 bit unsigned integer value, when read as a floating point, just produces a whole number with zero decimals. And since the time is in seconds without decimals it produces the correct result. I think I will rewrite the code to write direct to the blocks and skip using the Calc block outputs to clean it up. Thanks. .
  • In reply to David Stevenson:

    When you say it works, I'm curious as I would not expect it to provide the precision needed on the Seconds remaining.



    In your expression, you have OUT3 := Out1-Out2; for the Time remaining in seconds.

    The result is currently 58000 sec remaining. The Float values are still different. But as Local time approaches Event Time, the resolution of the float will make these two numbers equal with 1000 or so seconds to go. The math will work until more or less, but the result will lack precision as you approach the time event.

    Are you seeing the Seconds remaining update continually? Or every 20 minutes or so?

    If you set OUT3 := STR_TO_TIME ( '^/DTE1/TE_TIME_STR') - Time('$time_format:Local') the resulting value a much smaller number and well in the range of a Float to represent accurately. As the difference approaches 0, the result is always correct.

    Another aspect of this is if you have a Float that is 1,000,000,000 and try to add or substract a number like 15, the result will be 1,000,000,000. The larger float value does not allow that parameter to register such a small change and stays the same. The resolution is not there. If you store 32UINT in a float, and than read the value back as a 32UINT, the value will be different. At the resolution of the float, the values are the same. but you loose the lower bits due to the float's 22 bit mantissa, which limits the resolution.

    Since you really are dealing with Integers, having your Parameters defined as Integers and writing directly to them in the expression will preserve the 32UINT resolution. The HOUR, MIN and SEC can be 16 or 8 bit integers, but Epoch time values must be stored as 32UINT types.

    Also, your REMAIN_EPOCH variable holds the Event Time, not remaining time.

    '^/REMAIN_EPOCH' := STR_TO_TIME('^/DTE1/TE_TIME_STR');
    '^/LOCAL_EPOCH' := TIME('$time_format:LOCAL');
    '^/REMAIN_SECS' := STR_TO_TIME('^/DTE1/TE_TIME_STR')-TIME('$time_format:LOCAL');

    These will give you precise and accurate values. The rest of your math using REMAINS_SECS will be valid in Float as the results are small.

    I would use UINT values for the other parameters and write directly from CALC. Main reason is wired connections take CPU to move the value from the OUTx parameter to the module parameter. Writing directly skips that and is more efficient. Not a big deal on a one of. My rule is if the wire helps describe the logic, use it. In this case, I would likely create a composite and expose only the parameters needed in the instance. With everything under the hood, composite wires aren't visible unless you drill down, so at the module level, you explain the use of the composite. Makes for cleaner module layouts and more efficient code. And of course, we don't lose precision using 32UINT directly.

    Andre Dicaire

  • In reply to Andre Dicaire:

    I spoke wrong.  When I checked it the first time it appeared to be correct, but i was checking when it was live.  When i took a snapshot and did the math it was off by a percent or two.  So, i did an experiment like you described, and sure enough the error is present.   The bottom calc block keeps the values as 32 UINT.  and the 11 hours, 52 minutes, and 7 seconds remaining, is exactly 42667 second as depicted in the lower sections; not the 42624 seconds as in the upper section.  Thanks Andre

  • In reply to Andre Dicaire:

    Andre,

    I have one query here, why does the time() & time_to_str() expression work for "UTC_TIME_STR" (string) but it's not working for "TE_TIME_STR"(string)?

    Also, if we can change the "TE_TIME_STR" to Epoch time using STR_TO_TIME(), then can we use TIME_TO_STR again on this result to find the seconds, minutes & hours as the code below I have shared ??

    Only the Year is displaying as Year + 1972, Month & day is added +1  !! I wonder why ??..

    Thank you..

    Senthilkrishnan

  • In reply to Senthilkrishnan M:

    Questions of "why" are not something I can address.

    had to re-read your question several times to realize what was at the heart of it. Your expression is calculating "NEEDED_TIME", which is an interval in seconds. Not a date or EPOCH time. When you Time_TO_STR that interval you get a relatively small inteber, which corresponds to the date in 1974.

    You cannot use TIME_TO_STR on an interval value. The interval is not a date time. TIME_TO_STR is not applicable for this number of seconds.

    The DIFF_TIME_STR provides a Day HH:MM:SS formatted string of the time remaining. If you want to extract years, months, days, hours, mins, seconds remaining you woul need to do that on the "NEEDED_TIME" value, which is your Time left before the event..

    '/^YEAR' = '^/NEEDED_TIME' / (86400*365) - MOD('^/NEEDED_TIME'/86400 *365)

    Or something like that, and also need to take into account Leap Years and such.

    My approach would be to pass the DIFF_TIME_STR to the console and let DeltaV Operate or Live extract the information you want. It would be easy in VBA or Typescript to extract the Day value, and the HH:MM:SS portion of the string. To convert the Days remaining to Years, Months and Days, and assemple as YY-MM-DD as a string, that becomes trivial at the console.

    Controllers don't work with strings. The math works with Epoch times and intervals as seconds, but not as strings. The Operator benefits from time strings. If the DTE time strings are not sufficient, the best place to condition these for viewing is in a function at the console.

    I suppose if there was a function of Interval_to_String, and you could convert an Integer to the P00000T00:00:00 format, and it applied a set of conditions like "%d", "%h", "%m", "%s", you would be able to easily determine the Years and Months and days remaining from the Day value. But that doesn't exist.

    So TIME_TO_STR does not work for an interval as an interval measured in seconds is not an Epoch Time. (An Epoch Time value is an interval since 1971. It's are relative interval, and the Time() function returns that interval.)

    Andre Dicaire

  • In reply to Andre Dicaire:

    Andre,

    Thanks.. Will pass to DeltaV Operate to get the information.

    Senthilkrishnan
  • In reply to Senthilkrishnan M:

    So I looked at this some more.  Found that the some of the TIME_TO_STR formats work with what is called a Period time, or an Interval.  TIME_TO_STR handles the time integer based on the start time of 1971JAN01.  If you use the %T, %h, %i, and %s formats, you will get the hh:mm:ss string or the individual Hour, Minute or Second strings.  No need to do math to extract those.

    If the interval is less than a day, you can use these to extract the time and individual values.  If you wanted to show the Time remaining in hh:mm:ss at the console, you need to convert the DIFF_TIME_STR to seconds and then use the TIME_TO_STR format of %T and send that to a string parameter.  But there is no function to convert DIFF_TIME_STR.  But You can get seconds remaining as we showed earlier by converting LOCAL_TIME and TE_TIME_STR to seconds and subtracting to get the difference.  

    If the default P00000T02:15:01 time is desired as 02:15:01, and we have already found the Time Remaining in seconds, and this value is less than one day (86400 seconds), then TIME_TO_STR("%T", '^/PERIOD_SEC') is your friend.

    Note that the %D format returns a string starting with day 1.  For a date, that is correct, but for a period, the value should be 0 days if seconds is less than 86400.  So  If I want to convert the seconds into Days, hours, minutes and seconds, I can find Days with some math:  ROUND(('^?PERIOD_SEC - 43200) / 86400), which gives me an integer.  (If you store this in an Integer parameter, I think you can must use :=('^?PERIOD_SEC  / 86400); because the Float to Integer will truncate the fraction.  I'd have to check)

    If you want each value of time, the formats shown will give these.  If your store in a String, the two digit string will persist with leading 0.  If you store into an INT parameter, leading 0 will be lost.  If the %T and the Days Remaining are all you need.  You can concatenate these into a string in the module:

    "^/TIME_REMAIN' := " " + '^/DAYS_REMAINING + " " +   TIME_TO_STR("%T", '^/PERIOD_SEC');

    That should produce something like " 2 03:02:01".  That could be cleaner than moving for integers to the HMI and building a time string.  

    As a result of this, I can see a need to enhance STR_TO_TIME to convert the Period time of DIFF_TIME_STR into a time integer.  And also enhance TIME_TO_STR to create a Period format to be used to set the DTE/INTERVAL_TIME.  Programmatically setting the INTERVAL_TIME requires the controller to build a string of format "P00000T00:00:00."  This is possible, but would require math for each portion and padding zeroes to have exactly the right number of characters.  It would be nice to have a function that takes an interval in seconds and returns the Period string for the DTE block.  

    Anyway, thanks for asking the question because it spurred me to explore the DTE and time functions more thoroughly.

    Cheers.

    Andre Dicaire

  • In reply to Andre Dicaire:

    Andre,

    Thanks for exploring the options available.

    I tried your codes & it work perfectly to display the Days & "hh:mm:ss" detail.

    One observation I found, in addition, is the STR_TO_TIME code on DTE/DIFF_TIME_STR provides the same result as we get by converting LOCAL_TIME and TE_TIME_STR to seconds and subtracting them. So we can use this if we need only hh:mm:ss details.

    Thanks 

    Senthilkrishnan

  • In reply to Senthilkrishnan M:

    Thanks for all the discussion and research here. This was super helpful and what makes this a great forum!!!

    Dave
  • In reply to Senthilkrishnan M:

    Just to finish this up, here is a sample implementation to extract Difference time or time remaining.  I used the Modulus operator to get the extra seconds after dividing by 86400 seconds.  I use this to trim the time in seconds and again divide by 86400 to get number of days.

    At the top of the expression I check if the DTE is disabled and if so, I initialize the TE_TIME_STR to be newer than local time, which arms the DTE block.  I also set an interval time by converting number of seconds to a Period format string.  This allows me to programmatically set the desired Interval time.  I noticed that during an active period, the Interval Time can be changed but that does not take effect until the current period ends, as long as the DTE is enabled.  If you disable the DTE, change the Interval and enable, it sets the new TE_TIME_STR value and starts the period.

    TIME_TO_STR returns the same HOUR, MIN, SEC if you run in on the Total Seconds remaining or on the PERIOD_SEC value which is the left over seconds beyond the number of days.  The highlighted expression statements show a way to get the period in number of days and seconds and to convert the seconds to hh:mm:ss.  Fairly easy from their to pass this up to the console as Days hh:mm:ss remaining, which matches up with the DTE1/DIFF_TIME_STR with format P00001T03:45:17.

    On initial download the DTE1/TE_TIME_STR sits at EPOCH 0 time resulting in a negative interval or period.  To Arm the DTE, you need to set this time to a future time.  That's what I'm doing in the top part of the expression.  When DTE is disabled I force TE_TIME_STR to a future time (1 second in the future).  and also set a desired interval.  When I Enable DTE, it is armed and automatically sets the event time to the correct time.  I am tracking the DTE OUT_D transitions in the CTR1 as this is set to 1 for one scan only with the automatic Interval value.  

    Anyway, BOL covers several additional use cases for the DTE block.  Glad the discussion was helpful.

    Andre Dicaire