# Calculate elapsed time (or countdown to future time)

This popped up on the OptoNews email so I though I would comment.

Mary,

Your leap year check won’t work for they year 2100 and every 100 years after that, unless of course it is divisible by 400 … why can’t the earth go around the sun in a nice even interval???

Another way of doing date/time arithmetic:

You can use the DateTimeToNtpTimestamp() on Opto DateTime data types (the integer table used for all the date time commands). Then perform the math on the NTP timestamp and convert back when done - just have to watch out for the fractional seconds on the timestamp. Good for up to a 68 year difference, but I haven’t tested it on anything that large.

Hopefully the DateTimeToNtpTimestamp() doesn’t have any leap year bugs

Thanks, philip! Yes, I wish we could fix that pesky ol’ earth revolving around sun problem. Thanks for catching that error! At least it won’t show up in any strategies in our lifetime(s) (well, maybe?) although that’s a little sooner than how long it took the Vatican ([U][B]350 years[/B][/U]) to admit Galileo was right about that whole earth moving around the sun thing, but I digress.

In any case, I’ve not done much with the Ntp stuff (don’t worry, I didn’t write that code!) – but perhaps there’s a better way to do this elapsed time calculation using those commands? Although, I notice the help for “Convert Date & Time to NTP Timestamp” says it’s only good from Jan 1, 2001 through Dec 31, [B]2135[/B] so you’d only be okay for another 35 extra years anyway.

heavy sigh Hopefully we’ll all be sipping half-full margaritas by then and letting the next generations sort out all these little details.

Looking at your code more closely this calculation: // (365.25 * 24) would cause the result of a date difference between different calendar years to be incorrect. Try Jan 1. 2015 0:00:00 from Dec 31. 2014 12:59:59. I get strange results on that one.

Revised DateTimeInSeconds subroutine code - fixes leap year issues and a bug in the nDaysSinceYearStart for loop:

``````
// Get date/time in terms of seconds since year 0.

// Add up all the seconds since the start of year 0
nnWholeYearsInSeconds = ((nYear - 1) * 365 + ((nYear - 1) / 4 - (nYear - 1) / 100 + (nYear - 1) / 400)) * 86400i64;

// Now the number of days that have passed in the current year. For that we need to know the # of days
// per month, which breaks down like this:
ntDaysPerMonth[1] = 31;
ntDaysPerMonth[2] = 28;
ntDaysPerMonth[3] = 31;
ntDaysPerMonth[4] = 30;
ntDaysPerMonth[5] = 31;
ntDaysPerMonth[6] = 30;
ntDaysPerMonth[7] = 31;
ntDaysPerMonth[8] = 31;
ntDaysPerMonth[9] = 30;
ntDaysPerMonth[10] = 31;
ntDaysPerMonth[11] = 30;
ntDaysPerMonth[12] = 31;

// Figure out how many days have gone by so far this year, not counting this month/day
nDaysSinceYearStart = 0;

if (nMonth > 1) then
for nMonthIndex = 1 to (nMonth - 1) step 1
nDaysSinceYearStart = nDaysSinceYearStart + ntDaysPerMonth[nMonthIndex];
next

if (nMonth > 2) then // we need to figure out if this is a leap year, and if so, add an extra day for the 29th
if (nYear % 4 == 0 and (nYear % 100 <> 0 or nYear % 400 == 0)) then //a leap year
nDaysSinceYearStart = nDaysSinceYearStart + 1;
endif
endif
endif

// Now add the number of days (except for the current one, which is not done yet, so shouldn't be counted
// as a full day)
nDaysSinceYearStart = nDaysSinceYearStart + (nDay - 1);

nnWholeDaysInSeconds = (nDaysSinceYearStart * 86400i64); //24 * 60 * 60 // how much of this year passed, in whole days, units of seconds

// Figure out how many seconds have passed since the beginning of the day in question
nSecondsSinceDayStart = (nHour * 60 * 60) + (nMinute * 60) + nSecond;

nnTotalSeconds = nnWholeYearsInSeconds + nnWholeDaysInSeconds + nSecondsSinceDayStart;

``````

One last thing is on the human readable form:

Do you want the difference between 2/28/2012 and 2/28/2013 to be the same as 2/28/2013 and 2/28/2014? That is a pretty big challenge.

If we divide by the fractional year - 365.2425 as Mary’s strategy is sort of doing (using the less precise 365.25), then you’re going to get a result that is not equal to one exact year in either case.

I would probably just stick to total days and not worry about displaying years, but it will of course depend on the application.

Perhaps this is a solution looking for a problem?

Mary,

This is great! I have been reading lots of posts and bookmarking and saving posts. This is a great start to my problem, and I hope you can help.

My ultimate goal is to create a UUID, https://en.wikipedia.org/wiki/Universally_unique_identifier. I am trying to use the standard to create a version 1 UUID using the date-time and MAC address. I got the MAC address from the “MAC Address from PAC Controller” post, MAC Address of PAC Controller. With this post, I believe I can get very close with the date.

My issue is, how do I calculate, and I quote from the specs:

“…a 60-bit timestamp, being the number of 100-nanosecond intervals since midnight 15 October 1582 Coordinated Universal Time (UTC), the date on which the Gregorian calendar was first adopted.”

This code provides the elapsed years, days, and HH:MM:SS, from a given date. How would you calculate the needed 100-nanosecond intervals from 1582???

Thank you.

Dwayne (new-to-opto22) Sawyer

PS
Loved the training class and meeting you this past Oct. Tell Norma I said hello!

Intriguing!

The time function resolution is 1 msec. That doesn’t mean it won’t work, you just have to either make sure you don’t build two UUID during the same system time interval (using a delay?) or you can sequentially add to the time stamp (up to 10,000 UUIDs per ms time interval). (Section 4.2.1.2 of RFC 4122)

If you are not generating a lot, just put in a delay of 1 msec after each UUID generation. Are you putting this in a subroutine?

Thank you Phillip for a quick reply.

I would like to put this in a subroutine. I am creating records that I am writing to a file, each record will have a UUID. So, calling a subroutine is the way to go.

The problem is, I do not know how I would calculate the time component for the UUID.

What and where is “Section 4.2.1.2 of RFC 4122”?

https://tools.ietf.org/html/rfc4122

Do a search for the word ‘Gregorian’ in that doc and you will see your spec.

Do a search for ‘4.2.1.2’ and you will see the bit that Philip is talking about.

So yeah, between those two, just follow the spec and use 1ms resolution and put a delay between creation of UUID’s and you are all done.

Feel free to ask any questions, pretty interesting stuff, but I’m a time nut; http://blog.opto22.com/optoblog/how-to-set-the-clock-on-a-pac-controller

Beno,

Thanks for the clarification. I see now what Phillip was talking about. I am still unclear, and do not know how to code in opto-script to get the 60-bit value from the timestamp. Is it possible for you to provide a code sample?

This is an epoch problem. (Hopefully you will catch the humor in that)

Opto has a command to give you an NTP timestamp - the UUID version 1 algorithm has its own kind of timestamp (UUID timestamp).

NTP timestamp is a 64 bit value - top 32 bits are seconds since 01 January 1900 (called the NTP epoch) and the lower 32 bits are in fractional seconds (they will run through the entire 32 bit number space every second - yikes).

UUID timestamps are 100ns intervals since 15 October 1582 (UUID epoch and the start of the Gregorian calendar) stored in 60 bits.

So with a bit of bit shufflin’ you can convert from one to the other:

``````GetDateTime(ntDateTime); //Current controller time (local time)
DateTimeToNtpTimestamp(ntDateTime, n64NTP); // 1/(2^32) seconds since 1/1/1900 (NTP epoch)
n64TimeZoneOffset = GetTimeZoneOffset(0); //time zone offset in seconds
n64TimeZoneOffset = -25200; //Using softpac, so I'm adjusting manually for this test - remove before flight
n64TimeZoneOffset = n64TimeZoneOffset << 32; //Shift over to NTP sized timestamp
n64NTP = n64NTP - n64TimeZoneOffset; //NTP timestamp in UTC (This is what an SNTP server should give you)
//Convert from NTP timestamp to UUID timestamp
//First get from the NTP fractional seconds to 100ns intervals, then we can adjust for the epoch difference
//Separate top 32 from bottom 32
//n64Top = GetHighBitsOfInt64(n64NTP); //This extends the sign bit, poo!
n64Top = (n64NTP bitand 0xFFFFFFFF00000000i64) >> 32; //Do it this way

//n64Top is in seconds, we need to get them into 100ns intervals now
n64Top = n64Top * 10000000i64;
//n64Bottom = GetLowBitsOfInt64(n64NTP); //this will cause issues with the sign bit too
n64Bottom = n64NTP bitand 0x00000000FFFFFFFFi64;
//n64 is in fractional seconds, convert to 100ns (n64Bottom * 10^7)/2^32
n64Bottom = (n64Bottom * 1000000i64) / 0x100000000i64;
//Add the two 100ns values together
n64NTP100ns = n64Top + n64Bottom;
//Adjust from NTP epoch (1/1/1900) to UUID epoch (10/15/1582)
//(115,860 days = 10,010,304,000 seconds = 100103040000000000 * 10^-7 seconds)
n64UUIDTimestamp = n64NTP100ns + 100103040000000000i64;
//Add the timestamp RFC 4122 version 1 to the top 4 bits
n64UUIDTimestamp = (0x0FFFFFFFFFFFFFFFi64 bitand n64UUIDTimestamp) + 0x1000000000000000i64;
``````

From there you can convert the n64UUIDTimestamp to a hex string and tear it apart to build the top 64 bits of the UUID.

You will need to store a copy of this timestamp in persistant storage along with a clock sequence number that is initially generated randomly. The next timestamp you generate, if it is less than (or equal in my opinion) than the one stored, then you need to increment the clock sequence #. This is to ensure uniqueness after a clock change. The clock sequence is part of the UUID as well along with the ethernet MAC address which you already retrieved. This is all documented in the RFC.

1 Like

That is outstanding! At this point in my opto22 development, I could have never figured this out. Thank you for the help, and documentation.

Hi all,

In the various programs I have seen on this forum, I didn’t see where you manage the changeover from summer time to winter time or vice versa?
This is an important point because there may be a delay of one hour or an extra hour in the recovered data.

Thank you in advance,

The answer is here;

I gave things a good try at understanding before jumping in with my question, but here goes

I want to estimate when a test, that contains many sub-steps, of which I know how many (int32-scope) seconds each will take, will end, and be able to display the time and date the test is predicted to end.

We are talking in the neighborhood of 1,000 to 2,000 hours per test. Each test has either 40 or 80-ish cycles, each cycle happens to always add up to a days worth of time.

What I have done so far:

1. Taken the available subs here, and changed to accept date and time string input
2. I pass the current date and time (in strings) to the existing sub, it spits out (int64) seconds since the Epoch (year zero)
3. I calculate how many seconds (int64) the remainder of the test will take (easily), and add it to the result from #2
4. I want to be able to send this new date in the future (in seconds) to a similar function, and have it give me back a date and time
5. I have started a reverse function to do exactly that, but I am getting myself stuck in writing it.
6. In an attempt to not re-invent the wheel, I am searching for code that can guide me into making this reverse function. I have found https://www.epochconverter.com/seconds-days-since-y0 which, its first demo on that page is what I need to create in Opto.
7. Any thoughts (anyone?) on if I’m making this harder than it need be, or anywhere I can find the appropriate math to guide me?

Have you considered just using the DateTimeToNtpTimestamp and NtpTimestampToDateTime built-in commands? You would need to convert your date/time string to the Opto date/time table format, but then you could use a built-in function that is supported.

If you already have a lot of invested code written based on the above, then you could also convert time format to an Ntp Timestamp and then use the NtpTimestampToDateTime. You will need to perform an epoch conversion by adding the seconds from your epoch of year 0 to the NTP epoch of 1/1/1900 (just pass 1/1/1900 into your subroutine and use that result), and then bit shift that value over 32 bits to get it into a Ntp Timestamp (you will lose some of the bits in the upper 32 bits, because of NTP timestamp rollover - but that is okay in this case). You can then pass that to the NtpTimestampToDateTime and build your date/time string from the opto time table. If it were me, I would just use the Ntp commands all together and not the subroutine above.

No charge extra interesting information:

An Ntp Timestamp roll over happens every ~136 years. The next 64 bit unsigned overflow will happen on 2/7/2036 6:28:16 which won’t affect Opto since the int64s are signed (the timestamp will change from -1 to 0). A signed overflow will happen on 2/26/2104 (at 9:42:24 if you care). This will break in Opto if you add a number that goes passed that - you will get an overflow error. So you will want to get a firmware update before then, so mark your calendar.

On another note, the PAC Control help says that NTP time stamps are supported from 1/1/2001 through 12/31/2135, in my testing I was able to go through 2/7/2137 6:28:15.999 (0xBDFA46FFFFFFFFFF). 1/1/2001 is 0xBDFA470000000000.

2 Likes

Thank you Philip I’d do the above with NTP stamps as suggested. At first I thought using them would require a major re-write of some stuff, so I started in on the subroutines I outlined above. I’m realizing now I can make much shorter work of the task with your suggestion, so away we go!

Philip,

Just to be clear, I’ve dropped my own subs since I wasn’t THAT far into things, and I am picking up the idea of using the NTP to/from functions, and adding my calculated number of seconds to the current/start times to get the predicted end times.

I would have to take an int64 with a number of seconds in it (that I’ve made) and bit-shift it 32 bits over still, correct? THEN I could add it to my int64 output from the NTP function of the start time. If I didn’t I’d be adding my seconds to the lower (fractions of a second) portion of the NTP seconds, correct?

SetInt32TablesEqual(itCurrentDateTime,itTestStartTime);
DateTimeToNtpTimestamp(itTestStartTime,iiTestStartTime);
iiTestStartTime = iiTestStartTime >> 32;

repeat

iiTestEndTime = iiTestStartTime + ((iCycles_Ideal - iCycles) * fSecondsInDay);
iiTestEndTime = iiTestEndtime << 32;
NtpTimestampToDateTime(itTestEndTime,iiTestEndTime);

iCycles = iCycles + 1;

The above sets my start time once from the current time/date string, then permanently shifts the start integer over 32 bits

Each time I start a cycle (24 hours), I am calculating how many seconds away the end is, then shifting the result back over 32 bits before sending it into the function

Looks reasonable - you may want to use the GetHighBitsOfInt64 command instead of the >> operator to preserve the sign.

1 Like