AutoHotkey: Adding Months to an Arbitrary Date

After the third or fourth time typing date += 1,months in a script and coming up empty, I decided I needed to implement this apparently nonexistent functionality. I did few Google searches for this purpose and was met with a similar lack of success, so I figured I’d share my results here.

This function will take an arbitrary date and add (or subtract) a given number of months from it. If you want to return the date three months from today, throw today’s date and a positive 3 at the function. If you wanted to return what the date was 3 months ago, just make it a negative 3 instead. It’s fully fleshed out in the comments if you are interested.

Here’s the function, updated as of 01/2018 per an old (and wise) comment left on this article:

/*
Months(date,months):
This function accepts a date in the YYYYMMDD(...) format, goes forward or backward a
given number of months, and then returns the new date in the original format.
For example: 
date := Months(date,24)
date := Months(date,-24)
*/


Months(date,months) {
	FormatTime, year, %date%, yyyy ; Get the four-digit year
	FormatTime, month, %date%, MM ; Get the two-digit month

	; Cycle through the months an appropriate number of times
	Loop % abs(months) {
		i = 1
		if months < 0 
			i = -1	
		month += i ; Will add or subtract accordingly.
		
		; Whenever we hit a month of 0 or 13...
		if (month < 1 || month > 12) {
			month := abs(12 - month) ; Convert it, and...
			year += i ; ...add or subtract a year.
		}
	}
		
	if strLen(month) < 2 
		month := "0" . month ; Keep month in MM format
	
	; Return input date with modified year/month.
	return year . month . SubStr(date, 7)
} 

Be sure to throw it in your Program Files/AutoHotkey/lib folder to make it available for any future scripts.

Top of Page
Go Back



2 thoughts on “AutoHotkey: Adding Months to an Arbitrary Date

  1. There are several problems with this routine.

    First if your subtracting months then day doesn’t remain the same. I.E. 20151216 minus 1 month should be 20151116 instead the routine returned 20151115

    Second: there is no provision for dealing with the last day of the month, like if you subtract 2 months from 20151231 then I would expect to arrive at 20151031, instead the routine returns 20151030, this is even more noticeable when passing through February.

    Third: using a loop to iterate each month may be fast for short ranges, but the efficiency drops sharply when dealing with 100s or 1,000s of months.

    Fourth: the “if array contains” block costs more cpu time than if you had just used an object and asked for the days in the given month:
    ; this is declared at the start of the routine
    DaysInMonths := {1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31, 13:31}
    ; this is used in the loop
    DaysInMonths[2] := Leap = 0 ? 29 : 28
    Days := DaysInMonths[month]

    Fifth: the leap year test is not complete.
    if (year is not exactly divisible by 4) then (it is a common year)
    else
    if (year is not exactly divisible by 100) then (it is a leap year)
    else
    if (year is not exactly divisible by 400) then (it is a common year)
    else (it is a leap year)

    Sixth: after line 40 ‘date += days,days’ there is no need to further normalize months as these values are recreated on the next iteration, or are not returned.

    1. It may have taken me like, 2.5 years to think of a comeback, but you were spot on here. Embarrassed I bothered to share something so obviously broken, and poorly coded. Not the greatest turnaround time, I admit…. but it works now. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *