QB64 Phoenix Edition
Extended Timer and TimeStamp - Printable Version

+- QB64 Phoenix Edition (https://staging.qb64phoenix.com)
+-- Forum: QB64 Rising (https://staging.qb64phoenix.com/forumdisplay.php?fid=1)
+--- Forum: Prolific Programmers (https://staging.qb64phoenix.com/forumdisplay.php?fid=26)
+---- Forum: SMcNeill (https://staging.qb64phoenix.com/forumdisplay.php?fid=29)
+---- Thread: Extended Timer and TimeStamp (/showthread.php?tid=65)



Extended Timer and TimeStamp - SMcNeill - 04-20-2022

Code: (Select All)
SHELL "https://www.epochconverter.com/"
PRINT "Compare to time stamps generated at the website which popped up in your browser.https://www.epochconverter.com/"

CONST MyTimeZone## = 4 * 3600
DO
   _LIMIT 1
   CLS
   PRINT TimeStamp(DATE$, TIMER + MyTimeZone) 'Timezone difference with GMT, which is what the webpage sometimes points to.
   '                                           If the times seem off from the website, you'll want to change the timezone
   '                                           offset to match your current time zone.
   PRINT ExtendedTimer 'Unix Epoch Timer based on local time.
   _DISPLAY
LOOP


FUNCTION TimeStamp## (d$, t##) 'date and timer
   'Based on Unix Epoch time, which starts at year 1970.
   DIM l AS _INTEGER64, l1 AS _INTEGER64, m AS _INTEGER64
   DIM d AS _INTEGER64, y AS _INTEGER64, i AS _INTEGER64
   DIM s AS _FLOAT

   l = INSTR(d$, "-")
   l1 = INSTR(l + 1, d$, "-")
   m = VAL(LEFT$(d$, l))
   d = VAL(MID$(d$, l + 1))
   y = VAL(MID$(d$, l1 + 1))
   IF y < 1970 THEN 'calculate shit backwards
       SELECT CASE m 'turn the day backwards for the month
           CASE 1, 3, 5, 7, 8, 10, 12: d = 31 - d '31 days
           CASE 2: d = 28 - d 'special 28 or 29.
           CASE 4, 6, 9, 11: d = 30 - d '30 days
       END SELECT
       IF y MOD 4 = 0 AND m < 3 THEN 'check for normal leap year, and we're before it...
           d = d + 1 'assume we had a leap year, subtract another day
           IF y MOD 100 = 0 AND y MOD 400 <> 0 THEN d = d - 1 'not a leap year if year is divisible by 100 and not 400
       END IF

       'then count the months that passed after the current month
       FOR i = m + 1 TO 12
           SELECT CASE i
               CASE 2: d = d + 28
               CASE 3, 5, 7, 8, 10, 12: d = d + 31
               CASE 4, 6, 9, 11: d = d + 30
           END SELECT
       NEXT

       'we should now have the entered year calculated.  Now lets add in for each year from this point to 1970
       d = d + 365 * (1969 - y) '365 days per each standard year
       FOR i = 1968 TO y + 1 STEP -4 'from 1968 onwards,backwards, skipping the current year (which we handled previously in the FOR loop)
           d = d + 1 'subtract an extra day every leap year
           IF (i MOD 100) = 0 AND (i MOD 400) <> 0 THEN d = d - 1 'but skipping every year divisible by 100, but not 400
       NEXT
       s## = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
       TimeStamp## = -(s## + 24 * 60 * 60 - t##)
       EXIT FUNCTION
   ELSE
       y = y - 1970
   END IF

   FOR i = 1 TO m 'for this year,
       SELECT CASE i 'Add the number of days for each previous month passed
           CASE 1: d = d 'January doestn't have any carry over days.
           CASE 2, 4, 6, 8, 9, 11: d = d + 31
           CASE 3 'Feb might be a leap year
               IF (y MOD 4) = 2 THEN 'if this year is divisible by 4 (starting in 1972)
                   d = d + 29 'its a leap year
                   IF (y MOD 100) = 30 AND (y MOD 400) <> 30 THEN 'unless..
                       d = d - 1 'the year is divisible by 100, and not divisible by 400
                   END IF
               ELSE 'year not divisible by 4, no worries
                   d = d + 28
               END IF
           CASE 5, 7, 10, 12: d = d + 30
       END SELECT
   NEXT
   d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
   FOR i = 2 TO y - 1 STEP 4 'from 1972 onwards, skipping the current year (which we handled previously in the FOR loopp)
       d = d + 1 'add an extra day every leap year
       IF (i MOD 100) = 30 AND (i MOD 400) <> 30 THEN d = d - 1 'but skiping every year divisible by 100, but not 400
   NEXT
   s## = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
   TimeStamp## = (s## + t##)
END FUNCTION

FUNCTION ExtendedTimer##
   'Simplified version of the TimeStamp routine, streamlined to only give positive values based on the current timer.
   'Note:  Only good until the year 2100, as we don't do all the fancy calculations for leap years.
   'A timer should work quickly and efficiently in the background; and the less we do, the less lag we might insert
   'into a program.

   DIM m AS INTEGER, d AS INTEGER, y AS INTEGER
   DIM s AS _FLOAT, day AS STRING
   day = DATE$
   m = VAL(LEFT$(day, 2))
   d = VAL(MID$(day, 4, 2))
   y = VAL(RIGHT$(day, 4)) - 1970
   SELECT CASE m 'Add the number of days for each previous month passed
       CASE 2: d = d + 31
       CASE 3: d = d + 59
       CASE 4: d = d + 90
       CASE 5: d = d + 120
       CASE 6: d = d + 151
       CASE 7: d = d + 181
       CASE 8: d = d + 212
       CASE 9: d = d + 243
       CASE 10: d = d + 273
       CASE 11: d = d + 304
       CASE 12: d = d + 334
   END SELECT
   IF (y MOD 4) = 2 AND m > 2 THEN d = d + 1 'add a day if this is leap year and we're past february
   d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
   d = d + (y + 2) \ 4 'add in days for leap years passed
   s = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
   ExtendedTimer## = (s + TIMER)
END FUNCTION



RE: Extended Timer and TimeStamp - dcromley - 04-22-2022

@SMcNeill ,
You can replace 13 lines of code with 2 lines (see code).  And running the code will show it works.
EDIT: .. with 1 line of code .. (code was changed)

Re "What Happened?":  
  I don't see how you can get so much done in one day!  
  But thanks!

Code: (Select All)
Option _Explicit
DefLng A-Z
Dim d, m, t
If 0 Then

  ' ---- you can replace the following: ----
  Select Case m 'Add the number of days for each previous month passed
    Case 2: d = d + 31
    Case 3: d = d + 59
    Case 4: d = d + 90
    Case 5: d = d + 120
    Case 6: d = d + 151
    Case 7: d = d + 181
    Case 8: d = d + 212
    Case 9: d = d + 243
    Case 10: d = d + 273
    Case 11: d = d + 304
    Case 12: d = d + 334
  End Select

  ' ---- with the following: ----
  d = d + Int((295 + 153 * ((m + 9) Mod 12) + 2) / 5) Mod 365

End If

' -- proof:
Print "Date", "Method1", "Method2", "Difference"
For m = 1 To 12
  Print Str$(m) + "/1", Method1(m, 1), Method2(m, 1), Method1(m, 1) - Method2(m, 1)
Next m
Print "Dec 31", Method1(12, 31), Method2(12, 31), Method1(12, 31) - Method2(12, 31)

Function Method1 (m, d)
  Select Case m 'Add the number of days for each previous month passed
    Case 2: d = d + 31
    Case 3: d = d + 59
    Case 4: d = d + 90
    Case 5: d = d + 120
    Case 6: d = d + 151
    Case 7: d = d + 181
    Case 8: d = d + 212
    Case 9: d = d + 243
    Case 10: d = d + 273
    Case 11: d = d + 304
    Case 12: d = d + 334
  End Select
  Method1 = d
End Function

Function Method2 (m, d)
  Method2 = d + Int((295 + 153 * ((m + 9) Mod 12) + 2) / 5) Mod 365
End Function



RE: Extended Timer and TimeStamp - SMcNeill - 01-02-2023

And here's a nice human readable timestamp:

Code: (Select All)
Type SYSTIME
    year As Integer
    month As Integer
    weekday As Integer
    day As Integer
    hour As Integer
    minute As Integer
    second As Integer
    millis As Integer
End Type
Declare Dynamic Library "Kernel32"
    Sub GetSystemTime (lpSystemTime As SYSTIME)
    Sub GetLocalTime (lpSystemTime As SYSTIME)
End Declare

Dim st As SYSTIME

GetSystemTime st
Print st.year, st.month, st.day, st.weekday
Print st.hour, st.minute, st.second, st.millis

GetLocalTime st
Print st.year, st.month, st.day, st.weekday
Print st.hour, st.minute, st.second, st.millis

Do
    _Limit 100
    Locate 10, 1: Print " "; LocalTimeStamp$
    Print LocalTimeStamp64
Loop Until _KeyHit = 27

Function LocalTimeStamp$
    Dim st As SYSTIME
    GetLocalTime st
    t$ = AddString(t$, st.year, 4)
    t$ = AddString(t$, st.month, 2)
    t$ = AddString(t$, st.day, 2)
    t$ = AddString(t$, st.weekday, 1)
    t$ = AddString(t$, st.hour, 2)
    t$ = AddString(t$, st.minute, 2)
    t$ = AddString(t$, st.second, 2)
    t$ = AddString(t$, st.millis, 3)
    LocalTimeStamp = t$
End Function

Function LocalTimeStamp64~&&
    Dim st As SYSTIME: GetLocalTime st
    LocalTimeStamp64 = st.year * 1E14 + st.month * 1E12 + st.day * 1E10 + st.weekday * 1E9 + st.hour * 1E7 + st.minute * 1E5 + st.second * 1E3 + st.millis
End Function

Function AddString$ (what$, value As _Integer64, minDigits As Integer)
    v$ = Right$(String$(minDigits, "0") + _Trim$(Str$(value)), minDigits)
    AddString = what$ + v$
End Function


Comes in both a string and an integer64 version.  Reading it is:

year
month
day
weekday (0 for sunday, 6 for saturday)
hour
minute
second
millisecond

Personally, I think this is a better method than the UTC Timestamps just because I -- as a human being -- can read and understand them with just one quick glance at them.   Wink


RE: Extended Timer and TimeStamp - SMcNeill - 01-02-2023

Note:  Strings are inherently slow, so if you need a time stamp for use inside a loop, use the integer64 version above.  For example, try the code here:

Code: (Select All)
Type SYSTIME
    year As Integer
    month As Integer
    weekday As Integer
    day As Integer
    hour As Integer
    minute As Integer
    second As Integer
    millis As Integer
End Type
Declare Dynamic Library "Kernel32"
    Sub GetSystemTime (lpSystemTime As SYSTIME)
    Sub GetLocalTime (lpSystemTime As SYSTIME)
End Declare

Dim st As SYSTIME
Dim As _Integer64 count, count1

GetSystemTime st
Print st.year, st.month, st.day, st.weekday
Print st.hour, st.minute, st.second, st.millis

GetLocalTime st
Print st.year, st.month, st.day, st.weekday
Print st.hour, st.minute, st.second, st.millis

Do
    _Limit 100
    Locate 10, 1: Print " "; LocalTimeStamp$
    Print LocalTimeStamp64
Loop Until _KeyHit

Print "Timing routines for 5 seconds each:"
t# = Timer + 5
Do Until Timer > t#
    count = count + 1
    test$ = LocalTimeStamp$
Loop

t# = Timer + 5
Do Until Timer > t#
    count1 = count1 + 1
    t = LocalTimeStamp64
Loop

Print "In 5 seconds, each routine ran:"
Print Using "###,###,###,### (String)"; count
Print Using "###,###,###,### (int64)"; count1


Function LocalTimeStamp$
    Dim st As SYSTIME
    GetLocalTime st
    t$ = AddString(t$, st.year, 4)
    t$ = AddString(t$, st.month, 2)
    t$ = AddString(t$, st.day, 2)
    t$ = AddString(t$, st.weekday, 1)
    t$ = AddString(t$, st.hour, 2)
    t$ = AddString(t$, st.minute, 2)
    t$ = AddString(t$, st.second, 2)
    t$ = AddString(t$, st.millis, 3)
    LocalTimeStamp = t$
End Function

Function LocalTimeStamp64~&&
    Dim st As SYSTIME: GetLocalTime st
    LocalTimeStamp64 = st.year * 1E14 + st.month * 1E12 + st.day * 1E10 + st.weekday * 1E9 + st.hour * 1E7 + st.minute * 1E5 + st.second * 1E3 + st.millis
End Function



Function AddString$ (what$, value As _Integer64, minDigits As Integer)
    v$ = Right$(String$(minDigits, "0") + _Trim$(Str$(value)), minDigits)
    AddString = what$ + v$
End Function


I get about 3 million calls to the string version of the timestamp, and about 56 million calls to the integer64 version.  Numbers are just faster than excessive amounts of string manipulation like this.  Wink