See additional links below.
This version has a more minimalist gui for the popup month calendar, but the hotstrings work exactly the same.
FYI Several non-US holidays here: viewtopic.php?f=82&t=123895&p=552309#p552302
Information and Gif
Code: Select all
#SingleInstance
#Requires AutoHotkey v2+
; ======== DatePicker-H =========================================================
; The 'H' is for 'Holidays.' Version: 12-19-2023.
; A simple popup calendar that has US Holidays in bold font.
; Original calendar-with-bolded-dates v1 code by PhiLho
; https://www.autohotkey.com/board/topic/13441-monthcal-setdaystate/
; IsHoliday function (v1) by TidBit.
; https://www.autohotkey.com/boards/viewtopic.php?t=77312
; The two were integrated (v1) by Just Me.
; https://www.autohotkey.com/boards/viewtopic.php?f=76&t=117399&p
; Entire code converted to AHK v2 by Just Me.
; https://www.autohotkey.com/boards/viewtopic.php?f=82&t=123895
; ======== Directions for Use: Date HotStrings ==================================
; For today, type ";d0" (semicolon dee zero) into edit field.
; For future dates, type ";dX" where X is 1-9.
; For previous dates, type ";ddX" where X is 1-9.
; Information ToolTip shows for X seconds.
; ======== Directions for Use: Popup Calendar ===================================
; Ctrl+Alt+Shift+D ----> Show calendar.
; Press h (while calendar is active) ----> toggle list of holidays.
; Press t (while calendar is active) ----> go to today.
; Press 1-5 (while calendar is active) ----> Show this many months.
; Double-click a date, or press Enter, to type it.
; Calendar appears placed over active window.
; Waits for window to be active again before typing date.
; ===================================================================
^Esc::ExitApp ; Ctrl+Esc to just kill the whole ding dang script.
;======== User Options ===========================================
guiTitle := "DateTool-H" ; change title if desired
TypeOutFormat := "M-d-yyyy" ; preferred date format for typing date in edit field
HolidayListFormat := "MMM-dd" ; preferred date format for popup list
ShowSingleHoliday := 1 ; show holiday when click on day (1 = yes / 0 = no)
BorderColor := "FF9966" ; GUI background color
MonthCount := 2 ; default number of months to display vertically
;======== Global Variables ========================================
TargetWindow := 0
MCGUI := 0
hlYear := A_YYYY
hlMonth := A_MM
HolidayList := ""
toggle := false ; Variable to keep track of tooltip state
;=========== 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::
{ Global nOffset := StrReplace(StrReplace(A_ThisHotkey, ":?*:;d", ""), "d", "-")
Global DatePicked := DateAdd(A_Now, nOffset, "D") ; Puts offset into date format.
MyDate := FormatTime(DatePicked, TypeOutFormat)
ShowToolTip()
SendInput(MyDate) ; This types out the date.
}
;======= Date HotString ToolTip =======================================
ShowToolTip(*)
{ Global nOffset
Global DatePicked
dateOffset := DatePicked
vNow := SubStr(A_Now, 1, 8)
dateOffset := DateDiff(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"
DatePicked := DateAdd(DatePicked, 0, "D") ; Puts it in proper date format.
HoliTip := (isHoliday(DatePicked) != "")? isHoliday(DatePicked) . " ---> " : ""
DayOfWeek := FormatTime(DatePicked, "dddd")
WeekEndTip := (DayOfWeek = "Saturday") || (DayOfWeek = "Sunday")? "Weekend ---> " : ""
If CaretGetPos(&ttx, &tty)
ToolTip(HoliTip "" WeekEndTip "" DayOfWeek "" MySuffix, ttx+10, tty+25, "3") ; Set location of tooltip here.
SetTimer () => ToolTip(,,,"3"), -2500 ; Disable tooltip in this many milisecs.
DatePicked := ""
nOffset := ""
}
; ===================================================================
!^+d::
MCRemake(*)
{ ; Hotkey is Alt+Ctrl+Shift+D
Local X, Y, W, H
Global MCGUI, TargetWindow
If (MCGUI)
Return
If (TargetWindow := WinExist("A")) {
WinGetPos(&X, &Y, &W, &H, TargetWindow)
X := X + (W * 0.30)
Y := Y + (H * 0.04)
MCGUI := Gui("-MinimizeBox +LastFound +ToolWindow +Owner" TargetWindow, guiTitle)
;MCGUI.SetFont("s14") ; <--- optional thicker border
MCGUI.OnEvent("Close", MCGUIClose)
MCGUI.OnEvent("Escape", MCGUIClose)
MCGUI.BackColor := BorderColor
OnMessage(0x004E, WM_NOTIFY) ; needs to be called on control creation
MCGUI.AddMonthCal("r" MonthCount " +0x01 vMC")
OnMessage(0x004E, WM_NOTIFY, 0)
MCGUI["MC"].OnNotify(-747, MCN_GETDAYSTATE)
If (ShowSingleHoliday = 1)
MCGUI["MC"].OnEvent("Change", ShowOneHoliday)
Set_CS_DBLCLKS(MCGUI["MC"].Hwnd) ; needed to get WM_LBUTTONDBLCLK notifications for clicks on a MonthCal
MCGUI.Show("x" X " y" Y)
OnMessage(0x0203, WM_LBUTTONDBLCLK)
OnMessage(0x0100, WM_KEYDOWN)
}
}
; ===================================================================
#HotIf WinActive(guiTitle)
h::doToggle() ; Calls function to popup list of holidays.
t::MCGUI["MC"].Value := A_Now ; Hotkey for 'Go to today.'
1::
2::
3::
4::
5::ChangeMCNumber() ; Shows this many months. (1-5)
#HotIf
; ===================================================================
ChangeMCNumber(*) ; Reopens calendar with specified number of months.
{ Global MonthCount := A_ThisHotKey
Global DefaultDate := MCGUI["MC"].Value ; Remember selected day, when changing monthcount.
MCGuiClose()
MCRemake()
}
; ===================================================================
IsMouseOverMC(*) {
mX := 0, mY := 0
Global isOver := 0
WinGetPos(&cX, &cY, &cW, &cH, guiTitle)
MouseGetPos(&mX, &mY)
IsOver := (mX > 0 and mX < cW and mY > 0 and mY < cH)? 1 : 0
}
ShowOneHoliday(*)
{ If isHoliday(MCGUI["MC"].Value) != "" {
IsMouseOverMC()
If IsOver = 1
tooltip(isHoliday(MCGUI["MC"].Value),,,"2")
Else
tooltip(isHoliday(MCGUI["MC"].Value),60,-10,"2")
}
Else tooltip(,,,"2")
}
; ===================================================================
doToggle(*) ; Toggles popup list of holidays.
{ global toggle
if (toggle)
ToolTip(,,,"1") ; Hide the tooltip
else HolidayNames()
toggle := !toggle ; Switch the toggle state
}
; ===================================================================
HolidayNames(*) ; Generate list of dates/names of holidays for shown months.
{ thisDay := hlYear hlMonth "02000000"
monIndex := 0, rStart := 0, rEnd := 0
loop MonthCount+1 {
While monIndex < MonthCount+1 {
thisDay := DateAdd(thisDay, 1, "D")
If SubStr(thisDay, 7, 2) = "01" {
monIndex++
if monIndex = 1 and SubStr(thisDay, 7, 2) = "01"
rStart := thisDay
if monIndex = MonthCount+1 and SubStr(thisDay, 7, 2) = "01"
rEnd := thisDay
}
}
}
thisDay := rStart
Global HolidayList := ""
Loop DateDiff(rEnd, rStart, "Days") {
If IsHoliday(thisDay) != ""
HolidayList .= FormatTime(thisDay, HolidayListFormat) "`t" IsHoliday(thisDay) "`n"
thisDay := DateAdd(thisDay, 1, "D")
} ToolTip(HolidayList, 275, 100, "1") ; <--- Adjust position of tooltip here.
}
; ===================================================================
MCGuiClose(*) {
Global MCGUI
MCGUI.Destroy()
MCGUI := 0
OnMessage(0x0203, WM_LBUTTONDBLCLK, 0)
OnMessage(0x0100, WM_KEYDOWN, 0)
ToolTip(,,,"1") ; close H List tooltip if it's showing
ToolTip(,,,"2") ; close one day tooltip if it's showing
Global toggle := false
}
; ===================================================================
WM_KEYDOWN(W, *) {
If (W = 0x0D) { ; VK_RETURN => ENTER key
SendDate()
Return 0
}
}
; ===================================================================
WM_LBUTTONDBLCLK(*) {
Local CtrlHwnd := 0
MouseGetPos( , , , &CtrlHwnd, 2)
If (CtrlHwnd = MCGUI["MC"].Hwnd) {
SendDate()
Return 0
}
}
; ===================================================================
; 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, *) { ; first notification
If (NumGet(L, A_PtrSize * 2, "Int") = -747)
Return MCN_GETDAYSTATE("DUMMY", L)
}
; -------------------------------------------------------------------
MCN_GETDAYSTATE(MC, L) {
Static OffHwnd := 0,
OffCode := OffHwnd + (A_PtrSize * 2),
OffYear := OffCode + A_PtrSize,
OffMonth := OffYear + 2,
OffCount := OffMonth + 14,
OffArray := OffCount + A_PtrSize
Local Year, Month, MonthCount, Addr, CurrentDate, BoldDays, I
Year := NumGet(L + OffYear, "UShort")
Month := NumGet(L + OffMonth, "UShort")
MonthCount := NumGet(L + OffCount, "Int")
Global hlYear := Year
Global hlMonth := SubStr("0" . Month, -2) ; pad
Addr := NumGet(L + OffArray, "UPtr")
CurrentDate := Format("{:}{:02}01000000", Year, Month)
Loop MonthCount {
BoldDays := 0
Loop DIM(CurrentDate) {
I := A_Index - 1
If (IsHoliday(CurrentDate) != "") {
BoldDays |= 1 << I
}
CurrentDate := DateAdd(CurrentDate, 1, "D")
}
NumPut("UInt", BoldDays, Addr)
Addr += 4
CurrentDate := SubStr(CurrentDate, 1, 6) . "01000000"
}
if (!toggle) {
ToolTip(,,,"1") ; Hide the tooltip
}
else {
HolidayNames()
} ; Updates tooltip when scrolling months.
Return 1
}
; ===================================================================
Set_CS_DBLCLKS(HWND) {
; GCL_STYLE = -26, CS_DBLCLKS = 0x0008
If (A_PtrSize = 8)
Return DllCall("SetClassLongPtrW", "Ptr", HWND, "Int", -26, "Ptr", 0x0008)
Return DllCall("SetClassLongW", "Ptr", HWND, "Int", -26, "Ptr", 0x0008)
}
; ===================================================================
DIM(Date) { ; get the number of days in the month of Date
Date := DateAdd(SubStr(Date, 1, 6), 31, "D")
Date := DateAdd(SubStr(Date, 1, 6), -1, "D")
Return (SubStr(Date, 7, 2) + 0)
}
; ===================================================================
SendDate() {
Local Date := FormatTime(MCGUI["MC"].Value, TypeOutFormat)
MCGUIClose()
WinActivate(TargetWindow)
If !WinWaitActive(TargetWindow, , 1)
Return
SendInput(Date)
SoundBeep ; Temporary send notification.
}
; ===================================================================
; Name: isHoliday; Author: tidbit; Created: Thursday, June 11, 2020
IsHoliday(YYYYMMDDHHMISS := "", BusinessOnly := 0, StopAtFirst := 0) {
Static Eastern := Map()
Local TStamp:=(YYYYMMDDHHMISS = "") ? A_Now : YYYYMMDDHHMISS
; not a valid timestamp
If !IsTime(TStamp)
Return -1
Local Out := "" ; return a string of all possible events today
; grab more data than needed. safety first.
Local Date := StrSplit(FormatTime(TStamp, "yyyy|MM|dd|MMMM|dddd"), "|")
Date.Push(Substr(FormatTime(TStamp, "YWeek"), 5))
Date.Push(FormatTime(TStamp, "YDay"))
Date := {Year: Date[1], Mon: Date[2], Day: Date[3], MonN: Date[4], DayN: Date[5], DayY: Date[7], WeekY: Date[6]}
; Leap-year
Local IsLeap := (((Mod(Date.Year, 4) = 0) && (Mod(Date.Year, 100) != 0)) || (Mod(Date.Year, 400) = 0))
; Easter... Amazing. Thank you, "Nature" Journal - 1876
Local EDay, EMon
If Eastern.Has(Date.Year) {
EDay := Eastern[Date.Year].EDay
EMon := Eastern[Date.Year].EMon
}
Else {
EasterSunday(Date.Year, &EMon, &EDay)
Eastern[Date.Year] := {EDay: EDay, EMon: EMon}
}
; 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
Local Dates := [["01 01", "New Year's Day", 1],
["01 15-21 Monday", "Martin Luther King Jr. Day", 1],
["02 14", "Valentines Day", 0],
["02 15-21 Monday", "Presidents Day", 1],
["02 29", "Leap Day", 0],
["03 17", "St. Patrick's Day", 0],
["03 14", "Pi Day", 0],
[ EMon " " EDay, "Easter Sunday", 0],
["05 08-14 Sunday", "Mother's Day", 0],
["05 25-31 Monday", "Memorial Day", 1],
["0 182 absolute", "50% through the year", 0],
["1 183 absolute", "50% through the year", 0],
["06 19", "Juneteenth", 1],
["06 15-21 Sunday", "Father's Day", 0],
["07 04", "Fourth of July", 1],
["09 01-07 Monday", "Labor Day", 1],
["10 31", "Halloween", 0],
["11 11", "Veterans Day", 1],
["11 22-28 Thursday", "Thanksgiving Day", 1],
["12 25", "Christmas Day", 1]] ; <-- Note double bracket ]]
Local Stop := 0, Holiday, Stamp, Range, TTT, IsBetween
For Day In Dates {
If (Day[3] = 0 && BusinessOnly = 1)
Continue
Holiday := Day[2] ; give it a nicer name
Stamp := StrSplit(Day[1], " ")
While Stamp.Length < 3
Stamp.Push("")
Range := StrSplit(Stamp[2], "-")
If Range.Length = 1
Range.Push(Range[1])
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])
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")
}
; ===================================================================
EasterSunday(Year, &Month, &Day) {
Local A, B, C, D, E, F, G, H, I, K, L, M
Month := Day := 0
A := Mod(Year, 19),
B := Floor(Year / 100),
C := Mod(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),
Month := Format("{:02}", Floor((H + L +(7 * M) + 114) / 31)),
Day := Format("{:02}", Mod((H + L - (7 * M) + 114), 31) + 1)
}
; ===================================================================