Using Parts to Build a New AutoHotkey Script (HowLongInstant.ahk)

While Many Users Find the Original GUI Based HowLong Script Valuable, Combining Snippets of Code Creates a New Instant HowLong Script

Last time in “Extracting Multiple Dates from Text Using AutoHotkey RegEx,” I wrote a Regular Expressions (RegEx) that copied the first and last date (in a variety of formats) found in a selection from a document or Web page. (I recently updated that RegEx to make it more robust.) That represented the first step in building an instant HowLongYearsMonthsDay.ahk script. The goal, as defined by the reader, included highlighting a section of text which bounds two dates, pressing a Hotkey combination, then immediately calculating and displaying the timespan—no delaying the process with an input GUI or clicking a calculate button. As with many new scripts, I took pieces of it from other scripts and integrated them to produce a new one.

The chunks I used to produce the new script included:

  1. The Standard Clipboard Routine for capturing the selected text.
  2. The RegEx for identifying and capturing the target dates. (Discussed in my last blog.)
  3. The DateConvert() function found in the DateStampConvert.ahk script for formatting the parsed dates as the standard TimeDate stamp (YYYYMMDD).
  4. The HowLong() function found in the HowLongYearsMonthsDays.ahk script for calculating the timespan between the two TimeDate stamp parameters.
  5. A MsgBox for instantly displaying the results.

The HowLongInstant.ahk Script Core

The primary section of the HowLongInstant.ahk script (shown below) includes routines and calls for the following:

  1. Uses the Windows clipboard to copy the selected text. (Start Line 4—End Line 40)
  2. Extracts the first and last date from the clipboard. (Line 17)
  3. Converts each date format into a TimeDate stamp by calling the DateConvert() function. (Line 27 and Line 28)
  4. Calculates the timespan between the two dates by calling the HowLong() function. (Line 31)
  5. Displays the results in a Message Box. (Line 34)
^!#h::

; Standard Clipboard Routine
OldClipboard := ClipboardAll
Clipboard := ""
Send, ^c ;copies selected text
ClipWait 0
If ErrorLevel
{
	MsgBox, No Text Selected!
	Return
}

StartingPos := 1

; Two-date parsing (first and last dates in selection)
RegExMatch(Clipboard
, "sx)(\b[[:alpha:]]+.?\s\d\d?,?\s\d?\d?\d\d
      |\b\d\d?[-\s]?[[:alpha:]]+[-\s]?\d\d\d?\d?
      |\d\d?[-/]\d\d?[-/]\d\d\d?\d?)
    .*(\b[[:alpha:]]+.?\s\d\d?,?\s\d?\d?\d\d
      |\b\d\d?[-\s]?[[:alpha:]]+[-\s]?\d\d\d?\d?
      |\b\d\d?[-/]\d\d?[-/]\d\d\d?\d?)"
, OutputVar, StartingPos)

; Convert two dates to DateTime stamp
DateStamp1 := DateConvert(OutputVar1)
DateStamp2 := DateConvert(OutputVar2)

; Calculate timespan in years, month, and days
HowLong(DateStamp1,DateStamp2)

; Display dates, datetime stamp, and timespan
MsgBox,,Date Match, % OutputVar1 . "`r" . DateStamp1
. "`r" . OutputVar2 . "`r" . DateStamp2 
. "`rYears:`t" Format("{:6}",Years) 
. "`rMonths:`t" Format("{:6}",Months) 
. "`rDays:`t" Format("{:6}",Days) . " " . Past

Clipboard := OldClipboard

Return

For this script to run, you must include the functions DateConvert() (and its supporting functions) and HowLong() either in the script or in a function library. (See “Guidelines for AutoHotkey Function Libraries.”) You can find the complete code including all of the functions in the HowLongInstant.ahk script.

Use the DateConvert() Function to Produce the DateTime Stamp

The DateConvert() function, originally discussed in “Using Regular Expressions to Convert Most Formatted Dates into DateTime Stamps,” identifies various worldwide date formats converting them into the equivalent DateTime stamp format (YYYYMMDD) required by many date controls and functions.

Note: I removed the Standard Clipboard Routine from the original DateConvert() function found in the DateStampConvert.ahk script making it a more universal function for validating and reformatting various date formats.

DateConvert(DateFormat)
{
	; MsgBox %DateFormat%
	; Regular Expression to identify US date format with alphabetic month Octob 10 2018
	
	If (RegExMatch(DateFormat, "^([[:alpha:]]+).*?(\d\d?).*?(\d\d\d?\d?)$" , Date))
	{
		
		; Convert alphabetic month to numeric month
		NewMonth := MonthConvert(Date1)
		If NewMonth = "Not found!"
		{
			MsgBox Not a valid date!
			Return
		}
		Else
			Return MakeStamp(Date3,NewMonth,Date2)
		
	}
	
	; British date formats  1 October 2018
	
	Else If (RegExMatch(DateFormat, "(\d\d?).*?([[:alpha:]]+).*?(\d\d\d?\d?)", Date))
	{
		; Convert alphabetic month to numeric month
		;    MsgBox in euro %DateFormat% => %DateTemp%
		NewMonth := MonthConvert(Date2)
		If NewMonth = "Not found!"
		{
			MsgBox Not a valid date!
		}
		Else
			Return MakeStamp(Date3,NewMonth,Date1)
		
	}
	Else If (RegExMatch(DateFormat, "^(\d\d?).*?(\d\d?).*?(\d\d\d?\d?)$" , Date))
	{
		If (Date1 > 12)
			Return MakeStamp(Date3,Date2,Date1)    ; 10-13-18
		Else If (Date2 > 12)
			Return MakeStamp(Date3,Date1,Date2)    ; 13-10-2018
		Else
		{
			; ambiguous date format
			MsgBox, 4,, US date format?        (press Yes)`rEuropean date format? (press No)
			IfMsgBox Yes
				Return MakeStamp(Date3,Date1,Date2)    ;  10-1-18
			else
				Return MakeStamp(Date3,Date2,Date1)    ;  1-10-18
		}
		
	}
	Else
		MsgBox %DateFormat% No date found!
}

; This function uses the Switch/Case command statement to convert text months into numbers.

MonthConvert(month)
{
; MsgBox % SubStr(month,1,3)
	Switch SubStr(month,1,3)
	{	
	Case "jan","ene","jän","gen":Return "01" 
	Case "feb","fév","fev": Return "02"
	Case "mar","mär": Return "03"
	Case "apr","abr","avr": Return "04"
	Case "may","mai","mag": Return "05"
	Case "jun","jui","gui":
		If (SubStr(month,1,4) = "juil")
		 Return "07"
		Else
		 Return "06"
	Case "jul","lug": Return "07"
	Case "aug","ago","aoû","aou","ag": Return "08"
	Case "sep","set": Return "09"
	Case "oct","okt","ott": Return "10"
	Case "nov": Return "11"
	Case "dec","dic","dez","déc": Return "12"
  	}
}

; This function is a little arbitrary since it cuts off at year 1940 before jumping to the 2000s

YearCheck(ByRef Year)
{
	If Year > 40
		Year := "19" . Year
	Else
		Year := "20" . Year
}

MakeStamp(Year,Month,Day)
{
	; if two-digit year, convert to four-digit year
	
	If StrLen(Year) = 2
		YearCheck(Year)
	
	; concatenate DateTime Stamp
	
	DateStamp := Year . Format("{:02}", Month) . Format("{:02}", Day)
	; MsgBox %DateStamp%
	; Check for valid date
	
	If DateStamp is not Date
	{
		MsgBox Invalid date! %DateStamp% %DateFormat%
	}
	Else
		Return DateStamp
}

Note: This version of the function implements the fairly new Switch/Case command in the MonthConvert() function (see Line 62) called by DateConvert() to evaluate non-English month names. (See “A Look at the New Switch/Case Command.” The MsgBox image at right shows two Spanish dates.) This creates a more flexible routine than the original pseudo-case statements built using the Ternary operator. (See “Use the Ternary Operator to Create Conditional Case Statements or Switches.”)

The code found above and the posted HowLongInstant.ahk script ask for user input whenever encountering an ambiguous numeric date format (all numeric with the day 12 or less). The DateStampConvertSwitch.ahk script version of the DateConvert() function saves the default format key (American or British) in the Windows Registry for resolving ambiguous date formats. See “Save AutoHotkey Script Settings in Your Windows Registry.”

The HowLong() Function for Calculating Timespans

The HowLong() function calculates the timespan in years, months, and days between two dates. I posted a discussion of the function at “Understanding the HowLong() Function.

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

; Trim any time component from the input dates
    FromDay := SubStr(FromDay,1,8)
    ToDay := SubStr(ToDay,1,8)

;   For proper date order before calculation
/*
   If (ToDay <= FromDay)
   {
     Years := 0, Months := 0, Days := 0
     MsgBox, Start date after end date!
     Return
   }
*/

   If (ToDay = FromDay)
   {
     Years := 0, Months := 0, Days := 0
     Return
   }

   If (ToDay < FromDay)
   {
     Temp := Today
     ToDay := FromDay
     FromDay := Temp
     Past := "Ago"
   }

/*
The function no longer requires this Leap Year test since when needed
AutoHotkey now calculates the length of the previous month in the target
year. I've left this commented-out code in here for people looking for a different
approach to  identifying a Leap Year but it does not run in this script.

;   Test for Stop date Leap Year. Invalid date calc returns blank.

  Feb29 := SubStr(ToDay,1,4) . "0229"
  Feb29 += 1, days      ; Test for Leap Year

  If (Feb29 != "")
      Feb := 29
  Else
      Feb := 28

*/

;   Calculate years

    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)

;   Remove years from the calculation

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


/*
Calculate the number of months between the Start date (Years removed)
and the Stop date. If the day of the month in the Start date is greater than
the day of the month in the Stop date, then add 11 or 12 months to the 
calculation depending upon the comparison between month days.
*/

    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 the start day of the month is less than the stop day of the month use the same month
;   Otherwise use the previous month, (If Jan "01" use Dec "12")
 
     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)

;   FromMonth := Substr(ToDay,1,4) . Substr("0" . SubStr(ToDay,5,2)-1,-1) . Substr(FromDay,7,2)
;   "The Format("{:02}",  SubStr(ToDay,5,2)-1)" function replaces the original "Substr("0" . SubStr(ToDay,5,2)-1,-1)"
;   function found in the line of code above. Both serve the same purpose, although the original function
;   uses sleight of hand to pad single digit months with a zero (0).

;   Adjust for previous months with fewer days than the target day

    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


; Calculate remaining days. This operation (EnvSub) changes the value of the original 
; ToDay variable, but, since this completes the function, we don't need to save ToDay 
; in its original form. 

     Days := ToDay

     Days -= %FromMonth% , d
 }

You can use the DateConvert() function and HowLong() function independently in other scripts although the DateConvert() function requires the presence of the MonthConvert(), YearCheck(), and MakeStamp() functions to operate.

Click the Follow button at the top of the sidebar on the right of this page for e-mail notification of new blogs. (If you’re reading this on a tablet or your phone, then you must scroll all the way to the end of the blog—pass any comments—to find the Follow button.)

jack

This post was proofread by Grammarly
(Any other mistakes are all mine.)

(Full disclosure: If you sign up for a free Grammarly account, I get 20¢. I use the spelling/grammar checking service all the time, but, then again, I write a lot more than most people. I recommend Grammarly because it works and it’s free.)

Find my AutoHotkey books at ComputorEdge E-Books!

Find quick-start AutoHotkey classes at “Robotic Desktop Automation with AutoHotkey“!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s