(the H is for "holidays")
Information
Spoiler
Code: Select all
#SingleInstance, Force
SetTitleMatchMode, RegEx
;################################################################
;################################################################
;################ DATEPICKER - H ###############
;################################################################
;################################################################
; A simple popup calendar that has US Holidays in bold font.
; For AHK v1.
; Holiday names shown under/beside MonthCal.
; Alternate Month/Quarter/Year view (uses ini file for this).
; For hotstring date entry, see triggers, explanation below.
; Original calendar-with-bolded-dates code by PhiLho.
; https://www.autohotkey.com/board/topic/13441-monthcal-setdaystate/
; IsHoliday function by TibBit.
; https://www.autohotkey.com/boards/viewtopic.php?t=77312
; The two were integrated by Just Me.
; https://www.autohotkey.com/boards/viewtopic.php?f=76&t=117399&p
; Then kunkel321 added his customizations.
; Script expects DateFormat.txt in same folder, creates if not found.
;################################################################
+!d:: ; Alt+Shift+D
DateToolHandler:
;===== User Options Set Here ==================
;calColor = Gray
;FontColor = Black
;TypedDateFormat <---- Variable must be assigned below hotstring triggers. See DateFormat: label, below.
UnderCalDateFormat := "MM-dd" ; (keep short) ; Change according to your locale.
BesideCalDateFormat := "MMM-dd ddd" ; Change according to your locale.
;==============================================
ShowMulti =
menu, tray, icon, wmploc.DLL,101 ; A nice calendar icon. wmploc.dll is for Windows Media Player, I think.
IniRead, ShowMulti, DateFormat.txt, DateTool, Rows, 3 ; Must stay below calendar hotkey.
UsedGUI = 1
WinGetActiveTitle, myWinTarget
myWinTarget := StrReplace(myWinTarget, "*", "") ; Astrisks mess up regex matching... I think.
WinGetPos, X, Y, W, H, A ; "A" to get the active window's pos.
X := X + (W * 0.30) ; Use these with GUI Show, below.
Y := Y + (H * 0.04) ; TxtPos near top/left of current window.
If !myWinTarget ; If no active window is found, use these generic coordinates.
X := 600, Y := 30
FaveDate := A_Now
tics:=0 ; for below double-click code
global Year, Month, MonthCount, holiFunLoopNum ; Global because they are used in below functions.
OnMessage(0x4E, "WM_NOTIFY") ; Not sure how this works. Voodoo maybe? Uses Just Me function.
If (ShowMulti = 3) { ; Muli-month view
MultiButton1 = Month ; Button will show action-upon-press, not 'current view.'
MultiButton2 = Year
FaveDate += -30, D ; Start 30 in past, so last month appears too.
HolisForThisDays = 93
CalDateFormat := UnderCalDateFormat
CalNameW = w208
caltxtloops = 5 ; Show this many holidays in multi-month view.
TxtPos =
CalPos =
}
If (ShowMulti = 1) { ; Single month view
MultiButton1 = Quartr
MultiButton2 = Year
HolisForThisDays = 31
CalDateFormat := UnderCalDateFormat
CalNameW = w208
caltxtloops = 2 ; Show this many holidays under single-month calendar.
TxtPos =
CalPos =
}
If (ShowMulti = "3 w-3") { ; 12-month view
MultiButton1 = Month
MultiButton2 = Quartr
FaveDate := A_Year "0101000000" ; Default to January of this year.
HolisForThisDays = 366
CalDateFormat := BesideCalDateFormat
CalNameW =
caltxtloops = 29 ; Show this many holidays to right of 12-month calendar.
TxtPos = xm ym ; Put invisible placeholder text in top/left.
CalPos = x250 ; Move calendar over to make room for text.
X = 100 ; Don't put GUI over active window, just put in top/left of screen.
Y = 100
}
;=========== Popup calendar ===============
Gui, dp:Destroy ; Clear the DatePicked variable to prevent an error.
Gui, dp:color, %calColor%,
Gui, dp:-MinimizeBox +LastFound ; +Toolwindow ; Toolwindow prevents icon from showing.
Gui, dp:Font, s10 c%FontColor%,
Gui, dp:Add, MonthCal, R%ShowMulti% %CalPos% +0x01 +AltSubmit vDatePicked gDblClick, %FaveDate%
HolidayList(30, HolisForThisDays, CalDateFormat) ; This has to appear after MonthCal creation.
Gui, dp:Add, Text, %TxtPos% ; Invisible item to affect TxtPos of below text items.
HoliArr := StrSplit(theseHolis, "`n")
Loop, %caltxtloops% {
If (HoliArr[A_Index]) && (A_Index = 1)
Gui, dp:Add, Text, y+-19 %CalNameW% vHoliTxt%A_Index%, % HoliArr[A_Index] ; First holi overlaps above invisible item.
else If (HoliArr[A_Index]) ; might not show if date+holidate is >= StrLen(33)
Gui, dp:Add, Text, y+-1 %CalNameW% vHoliTxt%A_Index%, % HoliArr[A_Index] ; ToDo enforce nowrap.
else
Gui, dp:Add, Text, y+-1 %CalNameW% vHoliTxt%A_Index%,
}
Gui, dp:Add, Button, y+-1 x13 w50 h20 Section , Submit
Gui, dp:Add, Button, ys X+2 w50 h20 , %MultiButton1%
Gui, dp:Add, Button, ys X+2 w50 h20 , %MultiButton2%
Gui, dp:Add, Button, ys X+2 w50 h20 , Esc
Gui, dp:Show, x%X% y%Y%, DatePicker-H (Alt+Shift+D)
If (ShowMulti != 1) ; More than one month being shown. FaveDate was 30 days ago,
GuiControl, dp:, DatePicked, %A_Now% ; so move selection to today.
return
#IfWinActive, DatePicker-H ; If name is changed at Gui, Show; change here too.
; Causes Enter key to enter date; but only if DatePicker is open.
; SetTitleMatchMode might need to be changed for this to work.
$Enter::
SoundBeep
ControlGetFocus, FocusedControl, A
If (FocusedControl = "SysMonthCal321")
gosub, dpButtonSubmit ; Only if the calendar (not buttons) focused.
Else
Send {Enter} ; Otherwise do normal Enter key behavior.
Return
#IfWinActive
DblClick:
Gui, dp:submit, noHide
; Below several lines adapted from TidBit's 2015 Get A Date.
If (A_GuiControlEvent=1 && (A_TickCount-tics)<=DllCall("GetDoubleClickTime") )
gosub, dpButtonSubmit ; Normal double click.
Else If (A_GuiControlEvent=1) ; Single click.
tics:=A_TickCount
Else If (A_GuiControlEvent="Normal") { ; Arrow keys or mouse wheel.
forHoliFunLoopNum := (MonthCount - 2) * 31
HolidayList(30, forHoliFunLoopNum, CalDateFormat)
HoliArr := StrSplit(theseHolis, "`n")
Loop, %caltxtloops% {
If (HoliArr[A_Index]) ; might not show if date+holidate is >= StrLen(33)
GuiControl,, HoliTxt%A_Index%, % HoliArr[A_Index]
else
GuiControl,, HoliTxt%A_Index%,
}
}
Return
dpButtonSubmit:
Gui, dp:Submit
Gui, dp:Destroy
gosub, DateFormat
return
dpButtonMonth:
IniWrite, 1, DateFormat.txt, DateTool, Rows
gosub, DateToolHandler
return
dpButtonQuartr:
IniWrite, 3, DateFormat.txt, DateTool, Rows
gosub, DateToolHandler
return
dpButtonYear:
IniWrite, "3 w-3", DateFormat.txt, DateTool, Rows
gosub, DateToolHandler
return
dpButtonEsc:
dpGuiClose:
dpGuiEscape:
Gui, dp:Destroy
DatePicked =
Gosub EndingPart
Return
;=========== Hotstrings ===============
; Warning: The below StrReplace expects these hotstrings to have THESE names. Edit with caution.
:?*:;dd9:: ; For entering dates.
:?*:;dd8:: ; all start with {semicolon}
:?*:;dd7:: ; ddn = in the past, n days.
:?*:;dd6:: ; dn = future by n days.
:?*:;dd5::
:?*:;dd4::
:?*:;dd3::
:?*:;dd2::
:?*:;dd1:: ; yesterday
:?*:;d0:: ; ;d0 = today
:?*:;d1:: ; tomorrow
:?*:;d2:: ; day after tomorrow
:?*:;d3:: ; etc.
:?*:;d4::
:?*:;d5::
:?*:;d6::
:?*:;d7::
:?*:;d8::
:?*:;d9::
nOffset := StrReplace(StrReplace(A_ThisHotkey, ":?*:;d", ""), "d", "-")
DatePicked += %nOffset%, days ; Puts offset into date format.
usedGUI = 0
DateFormat:
;======= User Option =================
TypedDateFormat := "M-d-yyyy" ; Change according to your locale.
;====================================
FormatTime, MyDate, %DatePicked%, %TypedDateFormat%
If (usedGUI = 1) ; Only need to wait if MonthCal GUI was used.
WinWaitActive, %myWinTarget%
SendInput, %MyDate% ; This types out the date.
;------------- Tooltip Section --------------
dateOffset := DatePicked
vNow := subStr(A_Now, 1, 8)
EnvSub, dateOffset, vNow, days
WdayArr := [6,5,4,3,2,1,0] ; Determine days until Saturday, for suffixes below.
daysTillSat := WdayArr[A_Wday]
fromSat := -1 * (daysTillSat - dateOffset)
MySuffix =
if (nOffset = 0)
MySuffix := " -- Today"
; Use Saturday this week as a constant in determining the following suffixes.
if (fromSat > 0)
MySuffix := " next week"
if (fromSat > 8)
MySuffix := ", week after next"
if (fromSat > 15)
MySuffix := " in three weeks"
if (fromSat > 22)
MySuffix := " in four weeks"
if (fromSat > 29)
MySuffix := " in five weeks"
if (fromSat > 36)
MySuffix := " in six or more weeks"
if (fromSat < -7)
MySuffix := " last week"
if (fromSat < -14)
MySuffix := ", week before last"
if (fromSat < -21)
MySuffix := " three weeks ago"
if (fromSat < -28)
MySuffix := " four weeks ago"
if (fromSat < -35)
MySuffix := " five weeks ago"
if (fromSat < -42)
MySuffix := " six or more weeks ago"
myToolTipX := A_CaretX + 10 ; For TxtPos of tooltip.
myToolTipY := A_CaretY + 25
DatePicked += 0, days ; Puts it in proper date format.
HoliTip := (isHoliday(DatePicked) <> "")? isHoliday(DatePicked) . " ---> " : ""
FormatTime, DayOfWeek, %DatePicked%, dddd
WeekEndTip := (DayOfWeek = "Saturday") || (DayOfWeek = "Sunday")? "Weekend ---> " : ""
ToolTip, %HoliTip%%WeekEndTip%%DayOfWeek%%MySuffix%, %myToolTipX%, %myToolTipY%
SetTimer, RemoveToolTip, 2000 ; Number of milliseconds for the tooltip to show.
DatePicked =
nOffset =
;usedGUI = 0
gosub EndingPart
return
RemoveToolTip:
SetTimer, RemoveToolTip, Off
ToolTip
return ;----------- End of Tooltip section -------------
EndingPart:
; Reassign previous systray icon here, if there was one...
;Menu, Tray, Icon, %A_ScriptDir%/Icons/Psicon.ico ; Specific to Steve's setup. If you see this, he forgot to remove it.
If (ShowMulti = "3 w-3") ; 12 months shown.
IniWrite, 3, DateFormat.txt, DateTool, Rows
Return ; Replace this with ExitApp to kill each time. Use Return, for it to stay in RAM.
; ---------------------------------------------------------------------------------
HolidayList(offset, holiFunLoopNum, holiListDateForm)
; Note: If e.g. "May" is shown, Windows will gather notification data for April, May, June, when creating MonthCal.
; That is the reason for varam1 (offset). So we only show holi names for e.g. May.
{ ;This function written by kunkel321 (but it calls Tidbit's function, and uses global vars from Just Me's function).
global theseHolis =
Month := SubStr("0" . Month, strlen(Month), 2)
thisLoopDate := Year . Month . "01000000"
thisLoopDate += %offset%, D
loop, %holiFunLoopNum% {
HoliThisLoop := isHoliday(thisLoopDate)
;MsgBox, loop %A_index% `nhisLoopDate is: %thisLoopDate% `nHoliThisLoop is: %HoliThisLoop%
If (HoliThisLoop = "") || (HoliThisLoop = "-1") || (HoliThisLoop = " ") {
thisLoopDate += 1, D
continue
}
FormatTime, thisLoopFormDate, %thisLoopDate%, %holiListDateForm%
thisHoli := isHoliday(thisLoopDate)
theseHolis .= thisLoopFormDate . "`t" . thisHoli . "`n"
thisLoopDate += 1, D
}
theseHolis := SubStr(theseHolis, 1, StrLen(theseHolis)-1) ; Remove last `n.
Return, theseHolis
}
; ---------------------------------------------------------------------------------
; Process the MCN_GETDAYSTATE notification
; https://learn.microsoft.com/en-us/windows/win32/controls/mcn-getdaystate
; The first notification is sent while the GuiControl is created.
; ---------------------------------------------------------------------------------
WM_NOTIFY(W, L, M, H) {
; This function, and LDOM(), written by user: Just Me.
Static OffHwnd := 0
, OffCode := OffHwnd + (A_PtrSize * 2)
, OffYear := OffCode + A_PtrSize
, OffMonth := OffYear + 2
, OffCount := OffMonth + 14
, OffArray := OffCount + A_PtrSize
If (NumGet(L + OffCode, "Int") = -747) { ; MCN_GETDAYSTATE
Year := NumGet(L + OffYear, "UShort")
Month := NumGet(L + OffMonth, "UShort")
MonthCount := NumGet(L + OffCount, "Int")
Addr := NumGet(L + OffArray, "UPtr")
CurrentDate := Format("{:}{:02}01000000", Year, Month)
Loop, %MonthCount% {
LastDay := LDOM(CurrentDate)
BoldDays := 0
Loop, %LastDay% {
I := A_Index - 1
If (isHoliday(CurrentDate) <> "") {
BoldDays |= 1 << I
}
CurrentDate += 1, D
}
NumPut(BoldDays, Addr + 0, "UInt")
Addr += 4
CurrentDate := SubStr(CurrentDate, 1, 6) . "01000000"
}
Return 1
}
}
; ---------------------------------------------------------------------------------
LDOM(Date) { ; get the number of last day of the month
Date += 31, D
Date := SubStr(Date, 1, 6)
Date += -1, D
Return (SubStr(Date, 7, 2) + 0)
}
; ---------------------------------------------------------------------------------
isHoliday(yyyymmddhhmiss:="", businessOnly:=0, stopAtFirst:=0)
; This function written by user: Tidbit.
{
tstamp:=(yyyymmddhhmiss="") ? A_Now : yyyymmddhhmiss
; not a valid timestamp
if (strLen(yyyymmddhhmiss)!=14)
return -1
if yyyymmddhhmiss is not number
return -2
date={}
out:="" ; return a string of all possible events today
; grab more data than needed. safety first.
formatTime, ttt, %tstamp%, yyyy|MM|dd|MMMM|dddd
date:=strSplit(ttt, "|")
formatTime, ttt, %tstamp%, YWeek
date.push(substr(ttt, 5))
formatTime, ttt, %tstamp%, YDay
date.push(ttt)
date:={year: date[1], mon: date[2], day: date[3], monN: date[4], dayN: date[5], dayY: date[7], weekY: date[6]}
; Leap-year
isLeap:=0
if ((mod(date.year, 4)=0 && mod(date.year, 100)!=0) || mod(date.year, 400)=0)
isLeap:=1
; Easter... Amazing. Thank you, "Nature" Journal - 1876
a:=mod(date.year, 19), b:=floor(date.year/100), c:=mod(date.year, 100)
d:=floor(b/4), e:=mod(b, 4), f:=floor((b+8)/25)
g:=floor((b-f+1)/3), h:=mod(((19*a)+b-d-g+15), 30), i:=floor(c/4),
k:=mod(c, 4), l:=mod((32+(2*e)+(2*i)-h-k), 7), m:=floor((a+(11*h)+(22*L))/451)
emonth:=format("{:02}", (floor((h+l+(7*m)+114)/31)))
eday:=format("{:02}", mod((h+l-(7*m)+114), 31)+1)
; single space delimited, strictly
; ["month day-day dayName", "Day Text", federal/business day]
; if "dayName" = "absolute", "month" becomes "isLeapYear", and "day-day" is a number between 1-366
dates:=[["01 01", "New Year's Day", 1]
, ["01 15-21 Monday", "Martin Luther King Jr. Day", 1]
, ["02 15-21 Monday", "Presidents Day", 1]
, [ emonth " " eday, "Easter Sunday", 0]
; , [ emonth " " eday+1, "Easter Monday", 1]
, ["05 25-31 Monday", "Memorial Day", 1]
, ["07 04", "Fourth of July", 1]
, ["09 01-07 Monday", "Labor Day", 1]
; , ["10 08-14 Monday", "Columbus Day", 1]
, ["11 11", "Veterans Day", 1]
, ["11 22-28 Thursday", "Thanksgiving Day", 1]
; , ["12 24", "Christmas Eve", 0]
, ["12 25", "Christmas Day", 1]
, ["02 14", "Valentines Day", 0]
, ["05 08-14 Sunday", "Mother's Day", 0]
, ["06 15-21 Sunday", "Father's Day", 0]
, ["03 17", "St. Patrick's Day", 0]
, ["10 31", "Halloween", 0]
; , ["01 23", "0123 Day", 0]
, ["03 14", "Pi Day", 0]
; , ["06 28", "Tau Day", 0]
, ["06 20 Monday", "Juneteenth", 1]
, ["06 19 Monday", "Juneteenth", 1]
, ["06 19 Tuesday", "Juneteenth", 1]
, ["06 19 Wednesday", "Juneteenth", 1]
, ["06 19 Thursday", "Juneteenth", 1]
, ["06 19 Friday", "Juneteenth", 1]
, ["06 18 Friday", "Juneteenth", 1]] ; Added extra ] since below is commented-out.
; , ["05 04", "May the fourth be with you", 0]
; , ["11 11", "1111 Day", 0]
; , ["0 182 absolute", "50% through the year", 0]
; , ["1 183 absolute", "50% through the year", 0]]
stop:=0
for k, day in dates
{
if (day[3]=0 && businessOnly=1)
continue
holiday:=day[2] ; give it a nicer name
stamp:=strSplit(day[1], " ")
range:=strSplit(stamp[2], "-")
range[2]:=(range[2]="") ? range[1] : range[2]
if (stamp[3]!="absolute")
{
; set a temp var to blank if a weekday wasn't specified.
; Otherwise check if the specified day is today
ttt:=(stamp[3]="") ? "" : date.dayN
isBetween:=(date.day>=range[1] && date.day<=range[2]) ? 1 : 0
if (date.mon=stamp[1] && isBetween=1 && ttt=stamp[3])
out.=holiday "`n", stop:=1
}
else
{
if (isLeap=stamp[1] && date.dayY=stamp[2])
out.=holiday "`n", stop:=1
}
if (stopAtFirst=1 && stop=1) ; sometimes you'll want to be a nanosecond faster.
return trim(out, "`r`n `t")
}
return trim(out, "`r`n `t")
}
;#########################################################################
;############# END OF DATEPICKER - H #####################################
;#########################################################################
There are a couple of new features
-press 1-5 to quickly change the number of dates shown.
-I got rid of year view because I never used it.
-clicking a bold day gives a tooltip showing the name of the holiday.
-the gui is now minimalist (no more space for holidays. no more buttons.)
-see post for more info.