TimeDiff(), TimeStr()

Share your advanced PureBasic knowledge/code with the community.
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

TimeDiff(), TimeStr()

Post by Little John »

Express the difference between two given dates in wanted units.
  • TimeDiff() populates a structure with the numbers of years, weeks, days, etc.
    This function is so to say a counterpart of PB's built-in function AddDate() and is compatible with it (see validation in the code).
    You can control in which units the result will be expressed.
    So e.g. for a time span of 1 week, TimeDiff() can also return e.g. 7 days or 336 hours, if you want (see examples in the code).
  • TimeStr() converts the values in the structure returned by TimeDiff() into a human friendly readable string.
This is not earth-shattering, but sometimes quite useful. :)

Disclaimer: This code does not magically add support for daylight saving time to PureBasic. It also cannot repair broken washing machines.

//edit 2021-11-02: New version 1.00

Code: Select all

; <https://www.purebasic.fr/english/viewtopic.php?f=12&t=78150>
; Author: Little John
; License: Feel free to do with this code what you want. Credits are welcome, but not required.
; Version 1.00, 2021-11-02
; tested with PB 5.73 LTS x64 on Windows (should be cross-platform)

EnableExplicit

Structure TimeSpan
   years.i
   months.i
   weeks.i
   days.i
   hours.i
   minutes.i
   seconds.i
EndStructure


#TS_en    = " year,s, month,s, week,s, day,s, hour,s, minute,s, second,s"
#TS_en_ab = " yr,, mo,, wk,, dy,, h,, m,, s,"     ; English, abbreviated (just an example)
#TS_de    = " Jahr,e, Monat,e, Woche,n, Tag,e, Stunde,n, Minute,n, Sekunde,n"     ; German

#SECONDS_PER_MINUTE = 60
#SECONDS_PER_HOUR   = 60 * #SECONDS_PER_MINUTE
#SECONDS_PER_DAY    = 24 * #SECONDS_PER_HOUR
#SECONDS_PER_WEEK   =  7 * #SECONDS_PER_DAY


Procedure.i TimeDiff (startTime.q, endTime.q, *diff.TimeSpan)
   ; in : startTime: point in time in PureBasic format
   ;      endTime  : point in time in PureBasic format
   ;      *diff    : set field values to -1 for units that shall be ignored
   ; out: *diff       : duration from startTime to endTime expressed in wanted units;
   ;                    The values are compatible with PB's built-in AddDate() function
   ;                    (see validation below).
   ;      return value: 1 on success, 0 on error
   Protected rest.i, tmp.q = startTime
   
   If startTime < 0 Or endTime < 0 Or startTime > endTime
      ProcedureReturn 0               ; error
   EndIf
   
   With *diff
      If \years < 0 And \months < 0 And \weeks < 0 And \days < 0 And
         \hours < 0 And \minutes < 0 And \seconds < 0
         ProcedureReturn 0            ; error
      EndIf
      
      If \years > -1
         \years = Year(endTime) - Year(startTime)
         If AddDate(tmp, #PB_Date_Year, \years) > endTime
            \years - 1
         EndIf
         tmp = AddDate(tmp, #PB_Date_Year, \years)
      Else
         \years = 0
      EndIf
      
      If \months > -1
         \months = (Month(endTime) - Month(startTime) + 12) % 12
         If AddDate(tmp, #PB_Date_Month, \months) > endTime
            \months - 1
         EndIf
         tmp = AddDate(tmp, #PB_Date_Month, \months)
      Else
         \months = 0
      EndIf
      
      rest = endTime - tmp
      
      If \weeks > -1
         \weeks = Int(rest / #SECONDS_PER_WEEK)
         rest % #SECONDS_PER_WEEK
      Else
         \weeks = 0
      EndIf
      
      If \days > -1
         \days = Int(rest / #SECONDS_PER_DAY)
         rest % #SECONDS_PER_DAY
      Else
         \days = 0
      EndIf
      
      If \hours > -1
         \hours = Int(rest / #SECONDS_PER_HOUR)
         rest % #SECONDS_PER_HOUR
      Else
         \hours = 0
      EndIf
      
      If \minutes > -1
         \minutes = Int(rest / #SECONDS_PER_MINUTE)
         rest % #SECONDS_PER_MINUTE
      Else
         \minutes = 0
      EndIf
      
      If \seconds > -1
         \seconds = rest
      Else
         \seconds = 0
      EndIf
   EndWith
   
   ProcedureReturn 1           ; success
EndProcedure


Procedure.s TimeStr (*diff.TimeSpan, units$=#TS_en)
   ; in : *diff : time span expressed in appropriate units
   ;      units$: seven time units in the language of your choice (default: English)
   ; out: return value: given time span expressed in given units as a string,
   ;                    or "" on error
   Protected ret$=""
   
   If *diff = #Null Or CountString(units$, ",") <> 13
      ProcedureReturn ""          ; error
   EndIf
   
   With *diff
      If \years < 0 Or \months < 0 Or \weeks < 0 Or \days < 0 Or
         \hours < 0 Or \minutes < 0 Or \seconds < 0
         ProcedureReturn ""       ; error
      EndIf
      
      If \years > 0
         ret$ + ", " + \years + StringField(units$, 1, ",")
         If \years > 1
            ret$ + StringField(units$, 2, ",")
         EndIf
      EndIf
      
      If \months > 0
         ret$ + ", " + \months + StringField(units$, 3, ",")
         If \months > 1
            ret$ + StringField(units$, 4, ",")
         EndIf
      EndIf
      
      If \weeks > 0
         ret$ + ", " + \weeks + StringField(units$, 5, ",")
         If \weeks > 1
            ret$ + StringField(units$, 6, ",")
         EndIf
      EndIf
      
      If \days > 0
         ret$ + ", " + \days + StringField(units$, 7, ",")
         If \days > 1
            ret$ + StringField(units$, 8, ",")
         EndIf
      EndIf
      
      If \hours > 0
         ret$ + ", " + \hours + StringField(units$, 9, ",")
         If \hours > 1
            ret$ + StringField(units$, 10, ",")
         EndIf
      EndIf
      
      If \minutes > 0
         ret$ + ", " + \minutes + StringField(units$, 11, ",")
         If \minutes > 1
            ret$ + StringField(units$, 12, ",")
         EndIf
      EndIf
      
      If \seconds > 0 Or Asc(ret$) = ''
         ret$ + ", " + \seconds + StringField(units$, 13, ",")
         If \seconds <> 1
            ret$ + StringField(units$, 14, ",")
         EndIf
      EndIf
   EndWith
   
   ProcedureReturn Mid(ret$, 3)
EndProcedure


Macro TimeDiffS (_startTime_, _endTime_, _units_=#TS_en)
   If TimeDiff(_startTime_, _endTime_, diff)
      Debug TimeStr(diff, _units_)
   Else
      Debug "Error"
   EndIf
EndMacro


Macro DateX (_year_, _month_, _day_, _hour_=0, _min_=0, _sec_=0)
   ; This can be used instead of PB's Date() function for convenience,
   ; when hours, minutes and seconds are often not of interest.
   Date(_year_, _month_, _day_, _hour_, _min_, _sec_)
EndMacro


CompilerIf #PB_Compiler_IsMainFile
   #Mask$ = "%yyyy-%mm-%dd %hh:%ii:%ss"
   
   Define startTime.q, endTime.q, endTime_c.q, i.i, n.i=100
   Define diff.TimeSpan
   
   ; -- Validation of TimeDiff()
   For i = 1 To n
      startTime = Date(Random(2037,1970), Random(12,1), Random(28,1), Random(23,0), Random(59,0), Random(59,0))
      endTime   = Date(Random(2037,1970), Random(12,1), Random(28,1), Random(23,0), Random(59,0), Random(59,0))
      
      If startTime > endTime
         Swap startTime, endTime
      EndIf
      
      ; randomly switch usage of individual units ON (0) or OFF (-1)
      ; (Seconds are always ON in this test so as not to lose precision.)
      With diff
         \years   = Random(1) - 1
         \months  = Random(1) - 1
         \weeks   = Random(1) - 1
         \days    = Random(1) - 1
         \hours   = Random(1) - 1
         \minutes = Random(1) - 1
      EndWith
      
      If TimeDiff(startTime, endTime, diff) = 0
         Debug "Fatal error: TimeDiff() = 0"
         End
      EndIf
      
      With diff
         endTime_c = AddDate(startTime, #PB_Date_Year,   \years)
         endTime_c = AddDate(endTime_c, #PB_Date_Month,  \months)
         endTime_c = AddDate(endTime_c, #PB_Date_Week,   \weeks)
         endTime_c = AddDate(endTime_c, #PB_Date_Day,    \days)
         endTime_c = AddDate(endTime_c, #PB_Date_Hour,   \hours)
         endTime_c = AddDate(endTime_c, #PB_Date_Minute, \minutes)
         endTime_c = AddDate(endTime_c, #PB_Date_Second, \seconds)
      EndWith
      
      If endTime <> endTime_c
         Debug "-- Error"
         Debug "startTime = " + FormatDate(#Mask$, startTime)
         Debug "endTime   = " + FormatDate(#Mask$, endTime)
         Debug "endTime_c = " + FormatDate(#Mask$, endTime_c)
         Debug "TimeStr() = " + TimeStr(diff)
         Debug ""
      EndIf
   Next
   
   
   ; -- Demo of TimeStr(), TimeDiffS(), and DateX()
   Debug TimeStr(diff)             ; default language: English
   Debug TimeStr(diff, #TS_en_ab)  ; use some abbreviations
   Debug TimeStr(diff, #TS_de)     ; German
   
   Debug "--------"
   
   ; You can control in which units the result will be expressed:
   startTime = DateX(2021, 4, 10)
   endTime   = DateX(2021, 4, 24)
   
   TimeDiffS(startTime, endTime)   ; => 2 weeks
   
   diff\weeks = -1                 ; Don't use weeks in the result:
   TimeDiffS(startTime, endTime)   ; => 14 days
   
   ; Note that now all negative fields in the 'diff' structure are automatically set to zero.
   ; There is no need for you to do that in your code.
   
   diff\weeks = -1                 ; Neither use weeks ...
   diff\days  = -1                 ; ... nor days in the result.
   TimeDiffS(startTime, endTime)   ; => 336 hours
   
   Debug ""
   
   ; Another example
   startTime = DateX(1975, 5, 29)
   endTime   = Date()
   
   ; If you want to get *precise* results you should do without years and months,
   ; because their lengths can vary ...
   diff\years  = -1
   diff\months = -1
   TimeDiffS(startTime, endTime)
   
   ; ... however, when I e.g. want to calculate the age of a person, I do want
   ; to know the number of years and months, but normally am not interested in
   ; hours, minutes and seconds.
   diff\hours   = -1
   diff\minutes = -1
   diff\seconds = -1
   TimeDiffS(startTime, endTime)
CompilerEndIf
Last edited by Little John on Tue Nov 02, 2021 7:04 pm, edited 3 times in total.
User avatar
Caronte3D
Addict
Addict
Posts: 1362
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: TimeDiff(), TimeDiffS()

Post by Caronte3D »

Thanks! :wink:
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: TimeDiff(), TimeDiffS()

Post by davido »

@Little John,
This will be more than 'quite' useful to me. Thank you. :D

I note, with interest, that you opt for requesting the number of 'leap days' rather than calculating. Why is this?
DE AA EB
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: TimeDiff(), TimeDiffS()

Post by Little John »

Hi,

you are both welcome! I'm glad that the code is useful for you.

@davido:
Because my above function only receives the length of a time span as parameter, it cannot calculate whether or not that time span contains a leap day.
Say a span of 400 days is given: If this span is e.g. from January 1st 2020 to February 4th 2021, then it contains a leap day. But if a span of exactly the same duration is e.g. from January 1st 2021 to February 5th 2022, then it does not contain a leap day.

I can write another function that recives a time period in the form of concrete start and end dates. Such a function can calculate the number of leap days in the given period automatically. I'll be happy to do so, if you want.
User avatar
Sicro
Enthusiast
Enthusiast
Posts: 560
Joined: Wed Jun 25, 2014 5:25 pm
Location: Germany
Contact:

Re: TimeDiff(), TimeDiffS()

Post by Sicro »

Little John wrote: Sat Oct 30, 2021 8:33 pm I can write another function that recives a time period in the form of concrete start and end dates.
Please do it! Otherwise, the code is too difficult to use, because we have to write our own leap year calculator in the current form.
Image
Why OpenSource should have a license :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (syntax color scheme) :: RegEx-Engine (compiles RegExes to NFA/DFA)
Manjaro Xfce x64 (Main system) :: Windows 10 Home (VirtualBox) :: Newest PureBasic version
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: TimeDiff(), TimeDiffS()

Post by Little John »

Sicro wrote: Sun Oct 31, 2021 9:59 amPlease do it!
Done. :-)
The first post here now contains completely new code:
As suggested, this code works differently than the old one. Also I've split the old function into two. The function TimeDiff() does the calculations and returns pure numbers for further usage, and the function TimeDiffS() yields a string that is good readable by humans.
I made sure that the results of TimeDiff() are compatible with PB's built-in AddDate() function.

Thanks to Davido and Sicro for motivating me to significantly improve the code!
User avatar
Tenaja
Addict
Addict
Posts: 1959
Joined: Tue Nov 09, 2010 10:15 pm

Re: TimeDiff(), TimeDiffS()

Post by Tenaja »

Sicro wrote: Sun Oct 31, 2021 9:59 am
Little John wrote: Sat Oct 30, 2021 8:33 pm I can write another function that recives a time period in the form of concrete start and end dates.
Please do it! Otherwise, the code is too difficult to use, because we have to write our own leap year calculator in the current form.
There are several already in the forum, like this one
viewtopic.php?f=13&t=75481&p=555876&hil ... ar#p555876
juergenkulow
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 25, 2019 10:18 am

Re: TimeDiff(), TimeDiffS()

Post by juergenkulow »

Code: Select all

Debug TimeDiffS(Date(2012, 6,30,23,59,59), Date(2012, 7, 1, 0, 0, 0)) ; Leap second is missing
Debug TimeDiffS(Date(2017, 3,26, 1,59,59), Date(2017, 3,26, 3, 0, 0)) ; Changeover to daylight saving time
Debug TimeDiffS(Date(2017,10,29, 2, 0, 1), Date(2017,10,29, 2, 0, 1)) ; No 2:00:01B 
Debug TimeDiffS(Date(2017,10,29, 2, 0, 0), Date(2017,10,29, 3, 0, 0)) ; Changeover to winter time
Debug TimeDiffS(Date(2038, 1,19, 3,14, 7), Date(2038, 1,19, 3,14, 8)) ; #MaxLONG -1 All programmers worldwide are awakened. 
End
; 1 second
; 1 hour, 1 second
; 0 seconds
; 1 hour
;       <-- No response 
Please ask your questions, because switch on the cognition apparatus decides on the only known life in the universe.Wersten :DDüsseldorf NRW Germany Europe Earth Solar System Flake Bubble Orionarm
Milky Way Local_Group Virgo Supercluster Laniakea Universe
User avatar
Sicro
Enthusiast
Enthusiast
Posts: 560
Joined: Wed Jun 25, 2014 5:25 pm
Location: Germany
Contact:

Re: TimeDiff(), TimeDiffS()

Post by Sicro »

Little John wrote: Sun Oct 31, 2021 2:26 pm Done. :-)
Thanks!
Tenaja wrote: Sun Oct 31, 2021 2:30 pm There are several already in the forum, like this one
viewtopic.php?f=13&t=75481&p=555876&hil ... ar#p555876
Sure, but I think it makes more sense to do the leap year calculation already in this code and not separately.
Image
Why OpenSource should have a license :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (syntax color scheme) :: RegEx-Engine (compiles RegExes to NFA/DFA)
Manjaro Xfce x64 (Main system) :: Windows 10 Home (VirtualBox) :: Newest PureBasic version
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: TimeDiff(), TimeDiffS()

Post by Little John »

juergenkulow wrote: Sun Oct 31, 2021 5:04 pm

Code: Select all

Debug TimeDiffS(Date(2012, 6,30,23,59,59), Date(2012, 7, 1, 0, 0, 0)) ; Leap second is missing
Debug TimeDiffS(Date(2017, 3,26, 1,59,59), Date(2017, 3,26, 3, 0, 0)) ; Changeover to daylight saving time
Debug TimeDiffS(Date(2017,10,29, 2, 0, 1), Date(2017,10,29, 2, 0, 1)) ; No 2:00:01B 
Debug TimeDiffS(Date(2017,10,29, 2, 0, 0), Date(2017,10,29, 3, 0, 0)) ; Changeover to winter time
Debug TimeDiffS(Date(2038, 1,19, 3,14, 7), Date(2038, 1,19, 3,14, 8)) ; #MaxLONG -1 All programmers worldwide are awakened. 
End
; 1 second
; 1 hour, 1 second
; 0 seconds
; 1 hour
;       <-- No response 
  1. Yes, leap seconds are missing.
    Do PB's built-in Date functions take leap seconds into account??
  2. Yes, my functions do not support daylight saving time. PB's built-in functions don't do so, too. And as I wrote repeatedly, I want the functions to be compatible with PB's AddDate() function.
    DST depends on the country where someone lives. For instance, in Russia there is no daylight saving time at all. And I'm certainly not going to maintain a list of countries that apply daylight saving time, and the dates when they switch it on and off.
  3. Date( 2038, 1,19, 3,14, 8 ) yields -1, and thus TimeDiffS() returns an empty string, as expected. "No response" is wrong. When you add e.g. "#" at the beginning and at the end of your Debug statement, then you can see that.
Last edited by Little John on Tue Nov 02, 2021 4:55 pm, edited 1 time in total.
AZJIO
Addict
Addict
Posts: 2191
Joined: Sun May 14, 2017 1:48 am

Re: TimeDiff(), TimeDiffS()

Post by AZJIO »

davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: TimeDiff(), TimeDiffS()

Post by davido »

@Little John,
Thank you for the improved code; an unexpected bonus!
Your inclusion multi-languages is a nice touch.
DE AA EB
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: TimeDiff(), TimeDiffS()

Post by Little John »

Davido, you are welcome!
I'm already working on another improvement, so stay tuned. :-)
Little John
Addict
Addict
Posts: 4789
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: TimeDiff(), TimeStr()

Post by Little John »

New version 1.00, 2021-11-02
  • Main improvement:
    You can now control in which units a result is expressed.
    So e.g. for a time span of 1 week, TimeDiff() now can also return e.g. 7 days or 336 hours, if you want (see examples in the code).

    This new functionality entailed other changes.
    • The function TimeDiffS() now is named TimeStr(), and does not call TimeDiff() anymore, but is independent from that function.
    • There is the new macro TimeDiffS(), which is a "wrapper" for TimeDiff() and TimeStr().
  • Also new:
    • support for months
    • macro DateX() for convenience
    • extended demo code and comments
Post Reply