0

I am using the following code snippet

lxDate= #1/1/1970#
GetUnixDate = CType(DateDiff("S", lxDate, pDate), Int32)

where pDate is the date entered by user and its in the format mm/dd/yyyy e.g. #12/24/2014# This retrieves unix date correctly.However on one particular machine the output is one sec less than the required date. That is the unix timestamp when converted results in the previous date. For e.g Sat, 18 Dec 2004 23:59:59 GMT is retrieved when the desired result is
Sun, 19 Dec 2004 00:00:00 GMT

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
Yasha
  • 161
  • 1
  • 4
  • 13
  • I would personally suggest subtracting one `DateTime` from the other and using the `TotalSeconds` property of the resulting `TimeSpan`. Things like `DateDiff` are more for backwards compatibility with VB6 than as a modern way of doing things in .NET. That said, your question is somewhat unclear - the result isn't a `DateTime`, it's an integer... so how can you say the result is a particular `DateTime`? It would be helpful if you could show a short but complete program demonstrating the problem (on that one machine). – Jon Skeet Dec 24 '14 at 09:04
  • Well you are right the output is an integer, It gives the unixtimestamp. For the particular case the unix timestamp retreived was 1418860799 this can be convereted to date .You can use the following link http://www.onlineconversion.com/unix_time.htm it results in 17-dec-2014 23:59:59 however the required timestamp is 1418860800 it results in 18-dec-2014 00:00:00 Thus a lag of 1 sec is observed – Yasha Dec 24 '14 at 09:32
  • Right. That makes more sense, and you should include that information in your question, along with a short but complete program showing it. – Jon Skeet Dec 24 '14 at 09:38

1 Answers1

2

Yes, this is technically possible. DateDiff() uses Math.Round() to produce the return value from TimeSpan.TotalSeconds. Floating point math like that is vulnerable to misbehaving code that runs on the machine which alters the FPU control word. Two settings in the control word can cause an off-by-one bug like this, the precision setting and the rounding mode.

There are various ways such code can infect your program. The usual troublemakers are a printer driver, some Hewlett Packard drivers are known to have this bug. Or a shell extension, they get loaded into your process with, say, an OpenFileDialog. More obscure ways are DirectX, it can alter the precision setting. A version of the Microsoft ACE data provider is known to screw up the rounding mode.

By far the best way to tackle this problem is to re-image the machine. The DLL can be pretty hard to find if you don't have a debugger available on the machine. And it can cause other programs to misbehave in very hard to diagnose ways. This post has debugging tips if you do have a debugger.

But you probably just want to get the bug off your plate. You can do so by avoiding floating point math with this code, put it in a Module:

Public Function UnixTimestamp(ByVal dt As DateTime) As Integer
    Dim span = dt.ToUniversalTime() - New DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
    Return CInt(span.Ticks \ 10000000)
End Function

A sample program that demonstrates this problem:

Imports System.Runtime.InteropServices

Module Module1
    Sub Main()
        SetFpuRoundingMode(RoundingMode.Down)  '' or Chop
        Dim lxDate = #1/1/1970#
        Dim pDate = #12/24/2014#
        Dim unix = CType(DateDiff("S", lxDate, pDate), Int32)
        System.Diagnostics.Debug.Assert(unix = 1419379200)
    End Sub

    Enum RoundingMode
        Near
        Down
        Up
        Chop
    End Enum

    Sub SetFpuRoundingMode(mode As RoundingMode)
        _controlfp(mode << 8, &H300)
    End Sub

    <DllImport("msvcrt.dll", CallingConvention:=CallingConvention.Cdecl)> _
    Friend Function _controlfp(bits As Integer, mask As Integer) As Integer
    End Function
End Module

Which gives you another workaround, you could use SetFpuRoundingMode(RoundingMode.Near) to restore the FPU. Where exactly to put that call is however what you'd have to figure out.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Is there a method to check if the FPU control has been altered? – Yasha Dec 24 '14 at 12:50
  • The return value of _controlfp(0, 0) should be &H8001F in a .NET program. – Hans Passant Dec 24 '14 at 13:08
  • Note that only Direct3D 9 and prior alter the x87 FP control word. Direct3D 10 and later never do that. With Direct3D 9, you can prevent this behavior using ``D3DCREATE_FPU_PRESERVE``. Changing the fp control word globally is general bad behavior and highly likely to lead to problems. See this [article](http://msdn.microsoft.com/en-us/library/windows/desktop/ee418872(v=vs.85).aspx#manipulation_of_the_floating-point_control_word) for some details. – Chuck Walbourn Dec 24 '14 at 21:28