Greetings, fellow geeks!  I hope to occasionally find time to write blog posts that are hopefully meaningful to others.  Like many other vCampus customers, I consider myself to be working deep in the trenches where we oftentimes must supplement the OSIsoft offerings with some of our own patchwork code to provide a more comprehensive solution, or even just a simple workaround to our own customers.  It’s in the spirit of finding such solutions that I would dedicate my own blogs.




My inaugural blog will cover the topic of time.  Or more so, the resolution of time as it may be on a client front-end and how that may differ from what might be coming from a backend PI server.  This topic may be particularly relevant to anyone needing to work with sub-second data.




A couple years back I had a gnarly 3-day bug hunt trying to find out why the AFTime value coming back from a PI Point didn’t exactly match the start time from a GetValues call.  Some info about my environment: I was testing on my 64-bit Windows 7 desktop against an AFAttribute using the PI Point data reference.  The PI Point was interpolated, i.e. not stepped.  I would set the end of the time range to AFTime.Now, and set the start to 8 hours before the end.  And for reasons that shall remain undisclosed, I was checking that the passed start time (now minus 8 hours) was exactly equal to the first returned timestamp for the PI Point over that time range.




I was quite surprised to find out that they weren’t exactly the same – when I thought they should be.  In fact, it took me over 2.5 days to find that much out.  The fact that they weren’t equal was slightly obscured as I initially dumped out the UtcSeconds for the timestamps.  What I first discovered was something like:




AFTime.Now.UtcSeconds      1334259544.15274


Returned.UtcSeconds        1334259544.15274




Hey, those look equal to me.  How about you?  But something still wasn’t right, so I had to dig deeper.  I decided to chop off the whole number portion of UtcSeconds and focus on the decimal portion.




AFTime.Now.UtcSeconds      1334259544.15274


              Whole        1334259544


              Decimal      0.152743101119995




Returned.UtcSeconds        1334259544.15274


              Whole        1334259544


              Decimal      0.152740240097046




Ahhhh, now my interest got peaked.  This times are indeed different staring in the 6th decimal place.  In this example, I show a difference of:




Delta  2.86102294921875E-06 of one second




I began to ponder just how could that be?  At first I wondered if that’s attributable to the difference between AFTime versus PITime.  But, no, it’s wasn’t  that.  It soon dawned on me that the problem was my development environment had a high resolution timer for when AFTime.Now is invoked.  And while a sub-second PI server can serve up sub-second data, that sub-second resolution will be in intervals of 65536 sub-second slices per second.  That was my problem: AFTime.Now had a finer sub-second resolution and wasn’t guaranteed to be on a ‘whole’ PI sub-second slice (if I have explained that adequately) and therefore differed from the returned PI server time’s UtcSeconds starting in the 6th decimal place.  I did mention gnarly, right?




For those of us who can live with whole second data, OSIsoft has 2 methods worth mentioning.  Ever since AF 2.3, the AFSDK offers both the AFTime.NowInWholeSeconds property, compliments of yours truly.  Not that I wrote it, but I did request it.  And there is also the AFTime.TruncateToWholeSeconds method.  These hopefully should satisfy many of you, except perhaps those in Electrical or Power industries where sub-second timings are critical.  For those, I offer a bit of code to compliment the TruncateToWholeSeconds method.




I call the extension method TruncateToPISubseconds where again I define a “PI server sub-second” as 1/65536th of a second.




C# Extension Method




using OSIsoft.AF.Time;




public static class SomeTimeExtensions




    public static AFTime TruncateToPISubseconds(this AFTime inputTime)




        AFTime outputTime = new AFTime(inputTime);


        double Seconds = outputTime.UtcSeconds;


        double WholeSeconds = System.Math.Truncate(Seconds);


        if (Seconds != WholeSeconds)




            const int SlicesPerSecond = 65536;


            double Slices = (Seconds - WholeSeconds) * SlicesPerSecond;


            double WholeSlices = System.Math.Truncate(Slices);


            if (Slices != WholeSlices)




                double Subseconds = WholeSlices / SlicesPerSecond;


                outputTime = new AFTime(WholeSeconds + Subseconds);






        return outputTime;








VB.NET Extension Method




Imports OSIsoft.AF.Time




Public Module SomeTimeExtensions




    <System.Runtime.CompilerServices.Extension()> _


    Public Function TruncateToPISubseconds(inputTime As AFTime) As AFTime


        Dim outputTime As New AFTime(inputTime)


        Dim Seconds As Double = outputTime.UtcSeconds


        Dim WholeSeconds As Double = System.Math.Truncate(Seconds)


        If Seconds <> WholeSeconds Then


            Const SlicesPerSecond As Integer = 65536


            Dim Slices As Double = (Seconds - WholeSeconds) * SlicesPerSecond


            Dim WholeSlices As Double = System.Math.Truncate(Slices)


            If Slices <> WholeSlices Then


                Dim Subseconds As Double = WholeSlices / SlicesPerSecond


                outputTime = New AFTime(WholeSeconds + Subseconds)


            End If


        End If


        Return outputTime


    End Function




End Module




In summary, there are 2 bits of advice that I give to every developer new to time series data:




First, is to never just use AFTime.Now as is, and especially not in a loop.  Since Now changes with every invocation, you run the risk that data returned is not at the same event horizon, even if they are mere milliseconds apart.  Rather you should freeze AFTime.Now into a variable, and use that variable’s value for the duration of your procedure.




And second, you should consider truncating AFTime.Now to an acceptable resolution as required by your application.  If your PI data is saved to whole seconds, then use one of the AFSDK methods to truncate Now to whole seconds.  If you are using sub-second PI, that is 1/65536th of a second data, you may find the above extension methods useful.  If you work with whole minutes, there’s nothing stopping you from writing your own extension method!