Function Calculating Timespan in Years, Months, and Days

Post your working scripts, libraries and tools
User avatar
jackdunning
Posts: 38
Joined: 01 Aug 2016, 18:17

Function Calculating Timespan in Years, Months, and Days

28 Aug 2018, 17:48

HowLongYearsMonthsDays.ahk offers a rewrite of the age calculating function found in the GrandKids.ahk script.

After a Web search of the AutoHotkey sites for routines to determine timespans in years, months, and days, I found very little of help. A general search did find plenty of Web sites which offer Year, Month, Day calculators, but I wanted a function to build into any AutoHotkey app. I found a couple of examples which used pure math to calculate the variables, but, while those techniques get you close, the number of remaining days tends to go astray.

Rather than attempting to calculate the difference between the two dates by brute force (pretty much my original route), I decided to incrementally close in on the answer by comparing the two (start date and end date) standard computer date formats (YYYYMMDD). First, I would calculate the years and set them aside. Next, I would pull out the number of months leaving me with only a days calculation—one of the parameters available in the AutoHotkey EnvSub command (Var -= Value , TimeUnits) for calculating time differences.

As it turns out, this works great as long as the target day of the month is a higher number than the starting day of the month, and the target month of the year is later than the starting month of the year. In that case, you can directly figure the difference between each time increment:

Code: Select all

Years := SubStr(EndDate,1,4) - SubStr(StartDate,1,4)
Months := SubStr(EndDate,5,2) - SubStr(StartDate,5,2)
Days := SubStr(EndDate,7,2) - SubStr(StartDate,7,2)
If only the entire problem was that simple.

Issues arise when the starting day of the month is a higher number than the target day of the month and/or the starting month of the year is later than the target month of the year. The above calculation yields negative numbers. Plus, the fact that every four years we experience a February 29th, a number of months (five) contain less than 31 days, and every 12 months we start over with "01" for January only increases the confusion. When either of these negative calculation conditions occurs, we must do our arithmetic with either the previous year and/or previous month.

The Year, Month, Day Calculator
Image
Acting as a demonstration app, two DateTime GUI controls appear in the pop-up window.

Select a Start Date and Stop Date, then hit the Calculate button. AutoHotkey feeds the two dates into the HowLong(StartDate,StopDate) function, then a pop-up MsgBox displays the exact number of years, months, and days from the StartDate to the StopDate.
Image
You can use this function to calculate ages by providing the birthday in YYYYMMDD format and today's date (A_Now) as function parameters (HowLong(Birthday,A_Now)). Maybe you want to use it as a countdown for a major event? Or, possibly, you want to keep track of how much time has elapsed since a major turning point in your life (HowLong(CriticalDate,A_Now)).

As it turns out, I used much of the same code as in the original age calculating function—just in a different order. It uses a couple more techniques to make the app a little more robust and user-friendly:

1. The timespan function trims both of the DateTime stamps removing the time portion. This makes any date comparisons less susceptible to error.
2. Test for leap year to set February to 29 days. [Removed as unneeded in the current version, although remains intact, yet inactive, in the commented version at the free scripts page].
3. The calculation for the previous month uses the AutoHotkey Format() function rather than my original SubStr() function kludge to left pad single digit months with a zero.
4. The #If directive converts the mouse scroll wheel to Up/Down arrows whenever hovering over an AutoHotkey GUI pop-up window. This facilitates using the mouse wheel to increment the fields in the DateTime GUI controls without resorting to the cursor arrows

Plus, I've added more detailed comments to the script which hopefully make it easier to understand the logic.

You can get the AutoHotkey code for HowLongYearsMonthsDays.ahk at the ComputorEdge AutoHotkey Free Scripts page.

For a detailed explanation of the steps used in the HowLong() function, see "Calculating Timespans in Years, Months, Days in AutoHotkey (Understanding the HowLong() Function)" at my AutoHotkey blog.

August 29, 2018—I've made some of the changes I mention below.

October 9, 2018—Changed function to swap the two dates when the first date occurs after the second date. Calculation remains the same.

Here is the code for testing without all of the verbose comments found in the version at the site:

Code: Select all

Gui, SpanCalc:Add, Text, , Enter Date One:
Gui, SpanCalc:Add, DateTime, vDate1, LongDate
Gui, SpanCalc:Add, Text, , Enter Date Two:
Gui, SpanCalc:Add, DateTime, vDate2, LongDate
Gui, SpanCalc:Add, Button, , Calculate
Gui,SpanCalc:Show, , Calculate How Long

Return

SpanCalcButtonCalculate:
  Gui, Submit, NoHide
  HowLong(Date1,Date2)
  MsgBox, , TimeSpan Calculation, Years %Years%`rMonths %Months%`rDays %Days% %Past%
Return

HowLong(FromDay,ToDay)
  {
   Global Years,Months,Days,Past
   Past := ""

    FromDay := SubStr(FromDay,1,8)
    ToDay := SubStr(ToDay,1,8)

; If two dates identical

   If (ToDay = FromDay)
   {
     Years := 0, Months := 0, Days := 0
     Return
   }
   
 ; Added to swap dates if in reverse order (looking back). The calculation remains the same.

   If (ToDay < FromDay)
   {
     Temp := Today
     ToDay := FromDay
     FromDay := Temp
     Past := "Ago"
   }
   
    Years  := % SubStr(ToDay,5,4) - SubStr(FromDay,5,4) < 0 ? SubStr(ToDay,1,4)-SubStr(FromDay,1,4)-1 
            : SubStr(ToDay,1,4)-SubStr(FromDay,1,4)

     FromYears := Substr(FromDay,1,4)+years . SubStr(FromDay,5,4)

    If (Substr(FromYears,5,2) <= Substr(ToDay,5,2)) and (Substr(FromYears,7,2) <= Substr(ToDay,7,2))
       Months := Substr(ToDay,5,2) - Substr(FromYears,5,2)
    Else If (Substr(FromYears,5,2) < Substr(ToDay,5,2)) and (Substr(FromYears,7,2) > Substr(ToDay,7,2))
       Months := Substr(ToDay,5,2) - Substr(FromYears,5,2) - 1
    Else If (Substr(FromYears,5,2) > Substr(ToDay,5,2)) and (Substr(FromYears,7,2) <= Substr(ToDay,7,2))
       Months := Substr(ToDay,5,2) - Substr(FromYears,5,2) +12
    Else If (Substr(FromYears,5,2) >= Substr(ToDay,5,2)) and (Substr(FromYears,7,2) > Substr(ToDay,7,2))
       Months := Substr(ToDay,5,2) - Substr(FromYears,5,2) +11
 
     If (Substr(FromYears,7,2) <= Substr(ToDay,7,2))
         FromMonth := Substr(ToDay,1,4) . SubStr(ToDay,5,2) . Substr(FromDay,7,2)
     Else If Substr(ToDay,5,2) = "01"
         FromMonth := Substr(ToDay,1,4)-1 . "12" . Substr(FromDay,7,2)
     Else
        FromMonth := Substr(ToDay,1,4) . Format("{:02}", SubStr(ToDay,5,2)-1) . Substr(FromDay,7,2)

    Date1 := Substr(FromMonth,1,6) . "01"
    Date2 := Substr(ToDay,1,6) . "01"
    Date2 -= Date1, Days
    If (Date2 < Substr(FromDay,7,2)) and (Date2 != 0)
        FromMonth := Substr(FromMonth,1,6) . Date2

       ToDay -= %FromMonth% , d
       Days := ToDay
 }

; Enable mousewheel in AutoHotkey GUIs

#If MouseIsOver("ahk_class AutoHotkeyGUI")
   WheelUp::Send {Up}
   WheelDown::Send {Down}
#If

MouseIsOver(WinTitle)
{
   MouseGetPos,,, Win
   Return WinExist(WinTitle . " ahk_id " . Win)
}
Last edited by jackdunning on 10 Oct 2018, 12:42, edited 11 times in total.
Jack Dunning

I have a passion for helping new users grow maniacal about AutoHotkey Windows scripting.

Jack's AutoHotkey Blog
Free AutoHotkey Scripts and Apps for Learning Script Writing and Generating Ideas
AutoHotkey E-Books
r2997790
Posts: 23
Joined: 02 Feb 2017, 02:46

Re: Function Calculating Timespan in Years, Months, and Days

28 Aug 2018, 22:59

Jack,

This is gold. I've been looking for something like this for ages. Thank you so much for sharing it with us. It's a simple thing that is more difficult than it first appears I know.

For my needs I'm going to need to modify to use drop-downs for each of DD MM YYYY as I need to show the month like this... 06 JUNE, 07 JULY etc.

Will see if there's a clever way to detect leap year and month lengths so I can master updating the drop-down lists.

Thanks again for sharing this.

Super helpful.
User avatar
jackdunning
Posts: 38
Joined: 01 Aug 2016, 18:17

Re: Function Calculating Timespan in Years, Months, and Days

29 Aug 2018, 00:53

I merely used a set of conditionals for shorter month lengths, but you could calculate it with the EnvSub command:

Code: Select all

Date1 := 20200201
Date2 := 20200301
Date2 -= Date1, Days
MsgBox, % Date2    ; Returns 29


This code also checks for leap years (as shown) if you use February (0201) and March (0301) for any given year. I might put this in a function to replace the current leap year test. However, if I use this approach, I no longer need to test for leap years and eliminate a number of lines of code. Just calculate the month length. Humm. [August 29, 2018—Done! See code in the code in the first post.]
Jack Dunning

I have a passion for helping new users grow maniacal about AutoHotkey Windows scripting.

Jack's AutoHotkey Blog
Free AutoHotkey Scripts and Apps for Learning Script Writing and Generating Ideas
AutoHotkey E-Books
User avatar
jackdunning
Posts: 38
Joined: 01 Aug 2016, 18:17

Re: Function Calculating Timespan in Years, Months, and Days

09 Oct 2018, 13:20

After discussions, I realized that I had no reason to insist upon the order of the dates. If they appear in the wrong order, I merely swap the two and add the word "Ago" to the Past variable.
Jack Dunning

I have a passion for helping new users grow maniacal about AutoHotkey Windows scripting.

Jack's AutoHotkey Blog
Free AutoHotkey Scripts and Apps for Learning Script Writing and Generating Ideas
AutoHotkey E-Books
User avatar
SKAN
Posts: 320
Joined: 29 Sep 2013, 16:58

Re: Function Calculating Timespan in Years, Months, and Days

10 Oct 2018, 05:10

Very nice! :)

There is typo ( dot instead of comma ) in following line

Code: Select all

HowLong(FromDay,ToDay)
  {
   Global Years,Months,Days.Past
I tried this 11 years ago and wasn't successful in creating something fast enough.
I did have a accurate slow version though: Age()

Seemingly your current version is off by a year: ( 8398 11 30 )
The difference between max permissible dates should be:

9998 12 31 23:59:59
1601 01 01 00:00:00
--------------------
8397 11 30 23:59:59

I will update my Age() function and post it in current forum.
Thanks
:)
Guest

Re: Function Calculating Timespan in Years, Months, and Days

10 Oct 2018, 05:21

Related but very useful:

Time() - Count Days, hours, minutes, seconds between dates by HotKeyIt
https://autohotkey.com/board/topic/4266 ... een-dates/
User avatar
jackdunning
Posts: 38
Joined: 01 Aug 2016, 18:17

Re: Function Calculating Timespan in Years, Months, and Days

10 Oct 2018, 12:49

Thanks, SKAN,

I fixed the error in the code.
Seemingly your current version is off by a year: ( 8398 11 30 )
I think I see what you're saying, SKAN. I never tested it at the limits. However, it seems to be accurate in other tests.

Just tested again and it returned:

Years 8397
Months 11
Days 30

Seems to work.
Jack Dunning

I have a passion for helping new users grow maniacal about AutoHotkey Windows scripting.

Jack's AutoHotkey Blog
Free AutoHotkey Scripts and Apps for Learning Script Writing and Generating Ideas
AutoHotkey E-Books

Return to “Scripts and Functions”

Who is online

Users browsing this forum: DRocks and 30 guests