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)
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
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.
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)
}