Epoch time.

Windows specific questions.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Epoch time.

Post by deltarho[1859] »

I thought it would take a while to check this but after a few hours trying to break it I reckon that it is passing muster. Of course, authors are not the best testers, potential users are; they can break code that an author would never dream of.

We have Unix time in FB libraries and José Roca's WinFBX framework and other sources I should imagine. However, they all have one thing in common, an epoch of 1 January 1970. I wanted a user-defined epoch. Unix time generally has a resolution of one second, I wanted milliseconds. To that end I used the Windows APIs.

There are many uses for epoch time and, it seems to me, the number is only limited to our imagination. We have epoch in satellite-based time systems, for example.

The code relies heavily on two structures: SystemTime and FileTime. FileTime is calibrated to 100ns intervals but its resolution is that of SystemTime. 'Out of the box' SystemTime has a resolution of 15.625ms. If we changed the timer interval to 1ms, say, via timeBeginPeriod, for example, then FileTime's resolution would also be 1ms. However, FileTime can also be associated with the Performance Counter and here we use GetSystemTimePreciseAsFileTime which was introduced in Windows 8. The resolution is more than we need for milliseconds and will be clipped when we move from FileTime to SystemTime, but we can dispense with timeBeginPeriod. If you want to use the following code with an OS older than Windows 8 then use:-

Code: Select all

Declare Function SetTimerInterval Lib "winmm" Alias "timeBeginPeriod"(As Ulong=1) As Long
SetTimerInterval
and use GetSystemTime() as opposed to GetSystemTimeEx().

I could check the host OS but I'm not going to get into that. If you want to distribute an exe using this code then use GetSystemTime().

The function EpochTimeToSystemTime() outputs according to DateFormat and TimeFormat in the 'Select/End Select' construct in EpochTimeToSystemTime(). These may be customized and a good place to see what we can do is to go to Control Panel>Region, with Windows 10 for sure, with drop down menus and an assortment of choices.

The code was tested using Notable events in Unix time and the function Main() covers them using, obviously, the epoch 1 January 1970; which can be changed in SystemTimeToEpochTime() and EpochTimeToSystemTime().

As a point of interest if you use the epoch 1 January 1900 then that will precede the oldest living person's date of birth so a date of birth epoch date can be calculated for any living person. With that epoch my date of birth epoch time is 1512518400000. I may change the epoch to 1 January 1920 making folk think that I am younger than I really am. Image

Final note: If anything untoward happens with SystemTimeToEpochTime() it will return zero like most APIs so check for a zero in your code; I haven't in the following code but then I know it works.

Code: Select all

'#Console on
#include once "windows.bi"
#include once "string.bi"

' Courtesy of Mr Swiss at the FreeBASIC forums
#Define IsFalse(e)  ( Not CBool(e) )
#Define IsTrue(e)  ( CBool(e) )

Union ulEpochtime
  ft As FILETIME
  ulDateTime as ulongint
End Union

Declare Function SystemTimeToEpochTime( Byval As SystemTime, ByVal As Long ) As UlongInt
Declare Function EpochTimeToSystemTime( ByVal As UlongInt, ByVal As Long, ByVal As Long, ByVal As Boolean ) As String
Declare Sub GetSystemTimeEx( ByRef As SystemTime )

Function Main () As Long
Dim As UlongInt EpochTime
Dim As ulEpochTime DateTime
Dim As Long i

  ' Test data from Notable events in Unix time ( https://en.wikipedia.org/wiki/Unix_time#Notable_events_in_Unix_time )
  ' The test data is in UTC so we use False for the second parameter of SystemTimeToEpochTime
  ' Note that the third parameter, 'WORD wDayOfWeek', is ignored by the following procedures
  
  Dim As SystemTime TestTime = (1973, 10, 0, 17, 18, 36, 57, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2001, 9, 0, 9, 1, 46, 40, 0)
  print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2009, 2, 0, 13, 23, 31, 30, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2033, 5, 0, 18, 3, 33, 20, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2038, 1, 0, 19, 3, 14, 8, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2065, 1, 0, 24, 5, 20, 0, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  TestTime = Type(2106, 2, 0, 7, 6, 28, 15, 0)
  Print SystemTimeToEpochTime( TestTime, False )
 
  Print

  ' Get the current epoch time with high precision
  Dim As SystemTime TimeNow
  GetSystemTimeEx( TimeNow )
  ' Since we are using local time the second parameter is True
  Epochtime = SystemTimeToEpochTime( TimeNow, True )
  Print "Epoch time now: ";Epochtime
  Print
  ' Output in local time
  print "Milliseconds included" : Print
  For i = 1 To 6
    print EpochTimeToSystemTime( Epochtime, True, i, True)
  Next
  Print
  Print "Milliseconds excluded" : Print
  For i = 1 To 6
    Print EpochTimeToSystemTime( Epochtime, True, i, False)
  Next

  Sleep
  return 0

End Function

End Main()

Function SystemTimeToEpochTime( ByVal stGivenTime As SystemTime, ByVal Flag As Long ) As UlongInt
' Flag:
' If true then the given time is treated as local and converted to UTC stripping
' out local time and daylight savings otherwise it is treated as UTC
 
Dim As SystemTime stBaseTime, stUTCTime
Dim As FILETIME ftBaseTime, ftGivenTime
Dim As ulEpochTime DateTime
Dim as UlongInt GT, BT

  ' Change to whatever you want and in EpochTimeToSystemTime()
  ' This is Unix time and used by Microsoft
  stBaseTime.wYear = 1970
  stBaseTime.wMonth = 1
  stBaseTime.wDay = 1
  
  SystemTimeToFileTime @stBaseTime, @ftBaseTime
  If IsTrue(Flag) Then
    ' Convert given time to UTC
    TzSpecificLocalTimeToSystemTime ByVal 0, @stGivenTime, @stUTCTime
    SystemTimeToFileTime @stUTCTime, @ftGivenTime
  Else
    ' use given time as UTC
    SystemTimeToFileTime @stGivenTime, @ftGivenTime
  End If 
  DateTime.ft = ftGivenTime : GT = DateTime.ulDateTime
  DateTime.ft = ftBaseTime : BT = DateTime.ulDateTime
  If GT < BT Then
    Function = 0
  Else
    Function = ( GT - BT )\10000 ' 10000000 = 10^9/100
  End If

End Function

Sub GetSystemTimeEx( byref Result as SystemTime )
Dim ftFileTme As FILETIME

  GetSystemTimePreciseAsFileTime ByVal VarPtr( ftFileTme )
  FileTimeToSystemTime ByVal @ftFileTme, @Result
 
End Sub

Function EpochTimeToSystemTime( ByVal ulGivenEpochTime As UlongInt, ByVal Flag As Long, _
  Byval FormatNum as Long, Byval ms As Boolean ) As String
' String return is Epoch time in readable Date/Time format
' Flag:
' If true then string return is local time and includes daylight
' savings according to PC's setup otherwise UTC.
' FormatNum - see 'Select/End Select' below
' ms - displays milliseconds if True and if TimeFormat <> ""

Dim As FILETIME ftBaseTime
dim as ulongint ulEpochTimetoSystemTime
Dim As SystemTime stBaseTime, stEpochTimetoSystemTime, stEpochTimeToLocalSystemTime, stDummyTime
Dim As ZString * 40 szFormat, szTemp
Dim As String sDateResult, sTimeResult
Dim As ulEpochTime DateTime
Dim As string DateFormat, TimeFormat

  stBaseTime.wYear = 1970
  stBaseTime.wMonth = 1
  stBaseTime.wDay = 1

  SystemTimeToFileTime @stBaseTime, @ftBaseTime
 
  DateTime.ft = ftBaseTime
  ulEpochTimetoSystemTime =  DateTime.ulDateTime + ulGivenEpochTime*10000
  DateTime.ulDateTime = ulEpochTimetoSystemTime
  FileTimeToSystemTime @DateTime.ft, @stEpochTimetoSystemTime

  If IsTrue(Flag) Then ' Output to local time and include daylight savings
    SystemTimeToTzSpecificLocalTime ByVal 0, @stEpochTimetoSystemTime, @stEpochTimeToLocalSystemTime
    stDummyTime = stEpochTimeToLocalSystemTime
  Else ' output as UTC
    stDummyTime = stEpochTimetoSystemTime
  End If
  
  Select  Case As Const FormatNum
    Case 1
      DateFormat = "dddd',' d MMMM yyyy"
      TimeFormat = "H':'mm':'ss"
    Case 2
       DateFormat = "dd'/'MM'/'yyyy"
       TimeFormat = ""
    Case 3
      DateFormat = "d'/'M'/'yy"
      TimeFormat = ""
    Case 4
       DateFormat = "dd'/'MM'/'yyyy"
       TimeFormat = "HH':'mm"
    Case 5
       DateFormat = "d'/'M'/'yy"
       TimeFormat = "H':'m"
    Case 6
        DateFormat = "dddd"
        TimeFormat = ""
    Case Else
       DateFormat = "d'/'M'/'yy"
       TimeFormat = ""
  End Select

  szFormat = DateFormat
  GetDateFormat LOCALE_USER_DEFAULT, 0, @stDummyTime, @szFormat, @szTemp, SizeOf( szTemp )
  sDateResult = szTemp
  if TimeFormat <> "" Then
    szFormat = TimeFormat
    GetTimeFormat LOCALE_USER_DEFAULT, 0, @stDummyTime, @szFormat, @szTemp, SizeOf( szTemp )
    sTimeResult = szTemp
  End If

  If TimeFormat <> "" Then
    If IsTrue(ms) then
      Function = sDateResult + " " + sTimeResult + ":" + format(stDummyTime.wMilliseconds, "000")
    Else
      Function = sDateResult + " " + sTimeResult
    End If
  Else
    Function = sDateResult  
  End If
 
End Function
Last edited by deltarho[1859] on Mar 10, 2020 19:16, edited 14 times in total.
grindstone
Posts: 862
Joined: May 05, 2015 5:35
Location: Germany

Re: Epoch time.

Post by grindstone »

deltarho[1859] wrote:With that epoch my date of birth epoch time is 1512518400000. I may change the epoch to 1 January 1920 making folk think that I am younger than I really am. Image
And I assumed you were born in 1859... *grin*
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

Image
srvaldez
Posts: 3373
Joined: Sep 25, 2005 21:54

Re: Epoch time.

Post by srvaldez »

Hello deltarho[1859]
good job, but for fun try February 12, 1809
SystemTimeToEpochTime fails, however, if I get the Epochtime from https://www.epochconverter.com/ -> -5077000999000 and plug that value in EpochTimeToSystemTime( Epochtime, True ) it correctly prints the date Sunday, 12 February 1809
if you change stBaseTime.wYear to 1800 it does not work, the output is Saturday 11 February 1809
btw, perhaps you should use Longint instead of Ulongint and allow negative Epochtime
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

@srvaldez

I am reminded of the joke: "Doctor, it hurts when I do this." and the doctor replies "Well, don't do it then.".

The reason why SystemTimeToEpochTime fails with February 12, 1809 using Unix time is that my code is designed to fail at the line 'If GT < BT Then ...', that is if 'Given time < Base time'.

I can control what my code does but I cannot control what the APIs do given dates earlier than a chosen epoch, so I decided to treat them as invalid dates.

When I change the epoch to 1800, February 12, 1809 works for me so I don't know how you get 11 February 1809. With an epoch at 1800 the following

Code: Select all

TestTime = Type(1809, 2, 0, 12, 0 ,0 ,0 ,0)
EpochTime = SystemTimeToEpochTime( TestTime )
Print EpochTime
Print EpochTimeToSystemTime( Epochtime, True )
gives me

Code: Select all

287625600000
Sunday, 12 February 1809 0:00:00:000
Allowing negative epoch times seems a pointless exercise to me. If we want to use dates before a given epoch then simply change the epoch. We could use an epoch of January 1, 1601 in line with FILETIME. FILETIME could be made to go back a little further but not much further as the Gregorian calendar was introduced in October 1582 before which we have the Julian calendar which took effect in 45 BC.

I did actually look at Longint but I then asked myself, rhetorically : "Why am I doing this when I can use an earlier epoch."

So, if we use the same epoch as FILETIME then we are good to go with any later dates.

If anyone wants to go into Julian time, in addition, then they have the code so be my guest but it is of no interest to me.

There is no disadvantage, speed wise, in using the epoch January 1,1601 - SystemTimeToEpochTime takes about half a microsecond on my machine. EpochTimeToSystemTime takes much longer, about 130 microseconds but then it has the formatting to work out.

Image
srvaldez
Posts: 3373
Joined: Sep 25, 2005 21:54

Re: Epoch time.

Post by srvaldez »

there's probably some kind time-zone adjustment made which throws off the date and time
if I change the base-year to 1800 then the following input
TestTime = Type(1809, 2, 0, 12, 0 ,0 ,0 ,0)
gives
287625600000
Saturday, 11 February 1809 18:00:00:000
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

My local time and UTC this time of year is the same because I live in UTC 0. In fact, I live only 0.42 degrees west of Greenwich. You live in UTC -6 so there is your answer. If you used Flag = 0 in EpochTimeToSystemTime then you should get Sunday, 12 February 1809 0:00:00:000 because Flag = 0 gives UTC whereas Flag = 1 gives local time.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

@srvaldez

You made a good point in your last post and then 'pulled it' - you may have thought that it didn't make sense but it did.

As my code stands if two people, one living in Germany and one living in America, say, both put their local time into SystemTimeToEpochTime at the very same time then they would get different epoch times because their local times would be different. They should of course get the same epoch time because epoch time is UTC.

In the function SystemTimeToFileTime we have these two lines:

Code: Select all

SystemTimeToFileTime @stBaseTime, @ftBaseTime
SystemTimeToFileTime @stGivenTime, @ftGivenTime
Change them to
ADDED The opening post has been corrected

Code: Select all

SystemTimeToFileTime @stBaseTime, @ftBaseTime
TzSpecificLocalTimeToSystemTime ByVal 0, @stGivenTime, @stUTCTime
SystemTimeToFileTime @stUTCTime, @ftGivenTime
The first line is the same for both blocks.

The new second line converts the local given time to UTC time.

At this time of year it makes no difference to me, living in UTC 0. However, our friends in Germany and America should now get the same epoch time when they both use their local times simultaneously.

@srvaldez

If you use the 1970 epoch and the following

Code: Select all

GetSystemTimeEx( TimeNow )
Epochtime = SystemTimeToEpochTime( TimeNow )
you should get an epoch time not a great deal more than this: 1583681671382

It is, of course, impossible for me to test the above, living in UTC 0.
Last edited by deltarho[1859] on Mar 08, 2020 20:29, edited 2 times in total.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

BTW, I mention Flag at the top of the function EpochTimeToSystemTime but it was remiss of me not to give it a few words in the opening post.
srvaldez
Posts: 3373
Joined: Sep 25, 2005 21:54

Re: Epoch time.

Post by srvaldez »

@deltarho[1859]
in addition to the lines of code you mention, you also need to add
dim as SystemTime stUTCTime
after that, it gives the expected result
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

srvaldez wrote:...you also need to add
dim as SystemTime stUTCTime
I realized that I'd forgotten to mention that after I posted but then thought: "What the hell, the compiler will tell them." Image

Thanks for testing by the way, I needed someone in a different time zone to me.

So, what we now have is no matter where we live if we use local time then we will get the same epoch time. If we then use that epoch time and Flag = 0 we will get the same readable time but if we use Flag = 1 we will get local time and you will still get -6 hours to me.

Thanks, again.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

I have just added TzSpecificLocalTimeToSystemTime to my WinFBE Keywords because I haven't used it before and it wasn't getting highlighted. We have to change to lowercase throughout otherwise it won't work.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

Just as a side note times on this forum are in UTC. The PowerBASIC forum is described as GMT-5. That should read UTC-5. Why -5? PB's HQ is in Florida. Image
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

We have a problem. Some Wikipedia data now give incorrect results. Three of the dates fall within the UK's daylight savings period. Wikipedia specifically notes that the dates are in UTC. The first test, for example, is "At 18:36:57 UTC on Wednesday, 17 October 1973". Of hand, I cannot think of a reason why anyone would want to use a given date in UTC other than to replicate the Wikipedia results. However, unless an adjustment is made someone may come back to me and say my code is not working. The solution is to use a Flag as EpochTimeToSystemTime() does.

I am using Flag such that if True then the given date is treated as UTC otherwise it is treated as local and converted to UTC. The opening post has been edited and has comments when Flag is used and why it is True or False.

This project turned out to be more complicated than I anticipated. Image

Added: I spent sometime thinking about Flag being the other way around and ended up with what made sense to me. I had a similar problem with EpochTimeToSystemTime's Flag. Now, it may make better sense for you to change one or both in which case go ahead - they are not carved in stone.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Epoch time.

Post by deltarho[1859] »

The more I test the more I reckon that my logic for the Flag used in SystemTimeToEpochTime is wrong, so I have reversed it; making it easier to remember.

What we now have is:

Code: Select all

SystemTimeToEpochTime
  If the given time is local then use Flag = True otherwise,
  when the given time is in UTC, then use Flag = False
 
EpochTimeToSystemTime
  If the output is required to be local then use Flag = True otherwise,
  when the output is required to be in UTC, then use Flag = False
That is better, I reckon. Image

Opening post uses new logic.
Post Reply