Function Calculating Timespan in Years, Months, and Days

Post your working scripts, libraries and tools
User avatar
jackdunning
Posts: 46
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: 26
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: 46
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: 46
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: 333
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: 46
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
User avatar
jackdunning
Posts: 46
Joined: 01 Aug 2016, 18:17

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

20 Nov 2018, 18:54

I've written a DateConvert() function for use in conjunction with this function.

Image
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
AlphaBravo
Posts: 428
Joined: 29 Sep 2013, 22:59

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

21 Nov 2018, 00:05

Jack,
from the last post, I would have to say I disagreed with the results, from Jan 24,2019 to March 18, 2021 in my mind is 2 years, 1 month and 25 days.
from Jan 24 to Jan 31 (end of month) is 7 days + 18 days in March = 25 Days.

I didn't follow your algorithm closely but I think you did your calculation like so:
from Jan 24 to Feb 24 is 1 month and from Feb 24 to Feb 28 (end of month) is 4 Days + 18 Days in March = 22 Days

so I checked few online Age Calculators, some said 25 days others said 22 days.
www.calculator.net
www.calculatorsoup.com
http://www.calculconversion.com/age-calculator.html
www.timeanddate.com
www.calculator.net wrote:In some situations, the months and days result of this age calculator may be confusing, especially when the starting date is the end of a month. For example, we all count Feb. 20 to March 20 to be one month. However, there are two ways to calculate the age from Feb. 28, 2015 to Mar. 31, 2015. If thinking Feb. 28 to Mar. 28 as one month, then the result is one month and 3 days. If thinking both Feb. 28 and Mar. 31 as the end of the month, then the result is one month. Both calculation results are reasonable. Similar situations exist for dates like Apr. 30 to May 31, May 30 to June 30, etc. The confusion comes from the uneven number of days in different months. In our calculation, we used the former method.

I'll post this function here as an alternative way of calculation, also taking in consideration Leap Years.

Code: Select all

HowLong(Date1,Date2){
	year1 := SubStr(Date1, 1, 4), 	month1 := SubStr(Date1, 5, 2), 	day1 := SubStr(Date1, 7, 2)
	year2 := SubStr(Date2, 1, 4), 	month2 := SubStr(Date2, 5, 2), 	day2 := SubStr(Date2, 7, 2)
	month := leapyear(year1) ? [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
	if (day1 > day2)
		day2 := day2 + month[month1], month2 := month2 - 1
	if (month1 > month2) 
		year2 := year2 - 1, month2 := month2 + 12
	D:= day2 - day1, 	M:= month2 - month1, 	Y := year2 - year1
	;~ MsgBox % Y " Years`n" M " Months`n" D " Days"
	return Y " Years`n" M " Months`n" D " Days"
}
leapyear(year){
    if (Mod(year, 100) = 0)
        return (Mod(year, 400) = 0)
    return (Mod(year, 4) = 0)
}
User avatar
jackdunning
Posts: 46
Joined: 01 Aug 2016, 18:17

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

21 Nov 2018, 02:38

For example, we all count Feb. 20 to March 20 to be one month.
AlphaBravo, as you stated, from Jan 24, 2021 to Feb 24, 2021 is one month. Since we can now bank that full month, we can ignore the number of days in January. From Feb 24, 2021 to March 18, 2021 is 22 days.

Also, my function does account for Leap Years.

The function you included generates the answer, 25 days, but I think that reasonable people can disagree on which solution is more appropriate. I prefer counting days from the day of the month that marks the end of the last complete month (e.g. Feb 24).
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
carno
Posts: 133
Joined: 20 Jun 2014, 16:48

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

25 Nov 2018, 04:06

Excellent function. Added to my personal library including Date Counter below:

Code: Select all

#SingleInstance Force ; Ensures that only the last executed instance of script is running
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases

Gui, Add, Text,, Prior Date:
Gui, Add, Text,, Post Date:
Gui, Add, Edit, vPr ym ; The ym option starts a new column of controls
Gui, Add, Edit, vpo
Gui, Add, Button, default, OK ; The label ButtonOK (if it exists) will run when the button is pressed
Gui, Show, w350, Enter Date in Format: YYYYMMDD
Return ; End of auto-execute section. The script is idle pending user's action

GuiClose:
ButtonOK:
Gui, Submit ; Save input from the user to each control's associated variable
EnvSub, po, %pr%, days

years := po // 365
remain_days := mod(po, 365)

months := remain_days // 30 ; Month taken as 30 days
remain_days := mod(remain_days, 30)

fortnights := remain_days // 15 ; Fortnight taken as 15 days
remain_days := mod(remain_days, 15)

weeks := remain_days // 7
days := mod(remain_days, 7 )

MsgBox % "Total days in between: " . po . "`n`n" . "Years: " . years . "`n" . "Months: " . months . "`n" . "Fortnights: " . fortnights . "`n" . "Weeks: " . weeks . "`n" . "Days: " . days

Clipboard := po

ExitApp

Esc::ExitApp
User avatar
jeeswg
Posts: 5411
Joined: 19 Dec 2016, 01:58
Location: UK

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

Yesterday, 01:15

Hello, I just shared a similar function here. Thanks.
DateAdd/DateDiff with friendly format - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=59825

Return to “Scripts and Functions”

Who is online

Users browsing this forum: [Shambles], Bing [Bot], Drugwash, Google [Bot] and 27 guests