Search code examples
c#.netgps

Calculating a DateTime in a Specific Encoding in C#


I have a project that receives GPS reports from devices. It works, but I'm in the middle of overhauling my unit tests and adding integration tests so I have less panic attacks when publishing to production. Once specific device model uses a very tightly packed binary for the report and I've spent the last three weeks reverse engineering my own parser so I can generate random or specific reports for the unit and integration tests.

I'm pretty sure I have every part figured out, except for the timestamp. I can't seem to be able to generate a correct timestamp, so I've decided to ask for help.

The documentation for the device specifies the timestamp as:

Timestamp is a 4-byte field identifying when the report was generated (which may be different from when the report was transmitted). The value of the field is an unsigned integer representing Coordinated Universal Time (UTC) in the following encoding:

<seconds> = <timestamp> % 60
<minutes> = int( <timestamp> / 60 ) % 60
<hours> = int( <timestamp> / (60*60) ) % 24
<days> = 1 + int( <timestamp> / (60*60*24) ) % 31
<month> = 1 + int( <timestamp> / (60*60*24*31) ) % 12 (where 1 = Jan, 2 = Feb, etc.)
<year> = 2000 + int( <timestamp> / (60*60*24*31*12) )

From the one report from a real device that I've used for testing all this time, I know that 2020-06-03 22:01:52 +00:00 is 656460112, but I can't figure out how to pass the timestamp to my method and generate that value. This is my method so far:

private const int _millennium = 946684800;

private static byte[] GetLocatedAtUtcBytes(
    DateTimeOffset locatedAtUtc) {
    var bytes = new byte[4];

    BinaryPrimitives.WriteInt32BigEndian(bytes, (int)(locatedAtUtc.ToUnixTimeSeconds() - _millennium));

    return bytes;
}

I think I have to somehow deduct the millennium, since their formula wants me to explicitly add 2000 to get the year, but that doesn't seem to get me to where I need to be. My method generates 644536912 as the value, which happens to be 138 days off. Adding 138 blindly, doesn't solve it for other dates.

I'd appreciate some help on generating the correct value because I've spent too many days and I'm just running around in circles and getting cross-eyed at this point. It's probably something simple, but I can't see it anymore. Any help will be much appreciated!


Solution

  • Alright, after 10+ attempts at it, I finally got it, and it was actually simpler than I was making it to be. I guess I was expecting it to be a complicated calculation because everything else about this device's payload has been complicated. Anyway, here's the code:

    public static readonly int SecondsInDay = 86400;        //  60 * 60 * 24
    public static readonly int SecondsInHour = 3600;        //  60 * 60
    public static readonly int SecondsInMinute = 60;        //  60
    public static readonly int SecondsInMonth = 2678400;    //  60 * 60 * 24 * 31
    public static readonly int SecondsInYear = 32140800;    //  60 * 60 * 24 * 31 * 12
    
    private static byte[] GetLocatedAtUtcBytes(
        DateTimeOffset locatedAtUtc) {
        var bytes = new byte[4];
    
        var years = (locatedAtUtc.Year - 2000) * SecondsInYear;
        var months = (locatedAtUtc.Month - 1) * SecondsInMonth;
        var days = (locatedAtUtc.Day - 1) * SecondsInDay;
        var hours = locatedAtUtc.Hour * SecondsInHour;
        var minutes = locatedAtUtc.Minute * SecondsInMinute;
        var seconds = locatedAtUtc.Second;
    
        BinaryPrimitives.WriteInt32BigEndian(bytes, years + months + days + hours + minutes + seconds);
    
        return bytes;
    }
    

    Thanks to everyone that commented!