jeeswg's dates tutorial

Helpful script writing tricks and HowTo's
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

jeeswg's dates tutorial

Post by jeeswg » 19 Jun 2019, 08:56

==================================================

jeeswg's dates tutorial

==================================================

CONTENTS

AUTOHOTKEY FUNCTIONALITY INTRO
EXCEL FUNCTIONALITY INTRO
ADD/SUBTRACT: DATEADD / 'DATESUB' (DAYS/HOURS/MINUTES/SECONDS)
ADD/SUBTRACT: YEARS
ADD/SUBTRACT: MONTHS (EXCEL: EDATE)
ADD/SUBTRACT: WORKDAYS (EXCLUDE WEEKENDS/HOLIDAYS) (EXCEL: WORKDAY)
DIFFERENCE: DATEDIFF (DAYS/HOURS/MINUTES/SECONDS)
DIFFERENCE: WORKDAYS (EXCEL: NETWORKDAYS)
DIFFERENCE: WORK HOURS
DIFFERENCE: FRIENDLY FORMAT
MONTHS: MONTH TO YEAR QUARTER
MONTHS: DATE TO START/END OF MONTH (EXCEL: EOMONTH)
MONTHS: DATE TO PREVIOUS/NEXT MONTH
WEEKDAYS: DATE TO WEEKDAY NUMBER/STRING (MONDAY, SUNDAY ETC)
WEEKS: DATE TO START/END OF WEEK
WEEKS: NTH WEEK OF YEAR (WEEK NUMBER) (EXCEL: WEEKNUM/ISOWEEKNUM)
YEARS: IS LEAP YEAR
YEARS: CALCULATE HOLIDAYS
YEARS: CALCULATE EASTER
DAYS: DATE TO NTH DAY OF YEAR
TIME ZONES: NOW
TIME ZONES: DAYLIGHT SAVING TIME
DATE SYSTEMS: AUTOHOTKEY, EXCEL, FILETIME, SYSTEMTIME, UNIX
TIMESTAMPS/DURATIONS: HH:MM:SS TO/FROM SECONDS, DAYS
DATE FORMAT: TWO-DIGIT YEARS TO FOUR-DIGIT YEARS
DATE FORMAT: IS DATE VALID
DATE FORMAT: SPLIT/COMBINE
DATE FORMAT: FORMATTIME (YYYYMMDDHH24MISS TO OTHER FORMATS)
DATE FORMAT: LANGUAGES
DATE FORMAT: CONVERT DATES (OTHER FORMATS TO YYYYMMDDHH24MISS)
DATE FORMAT: DECIMAL TIME ('METRIC TIME')
GET DATE: PC SWITCHED ON / PROCESS OPENED / FILE MODIFIED
DATE LIST: DATE RANGE (EXPAND/CONTRACT LIST)
DATE LIST: SORT BY DATE
LINKS (JULIAN/GREGORIAN CALENDARS)
LINKS (OTHER)

==================================================

AUTOHOTKEY FUNCTIONALITY INTRO

- AutoHotkey has the following date functions/commands (to modify/compare/format dates):
- date increase/decrease: DateAdd [AHK v1: EnvAdd and +=]
- date difference: DateDiff [AHK v1: EnvSub and -=]
- date format: FormatTime
- note: use DateAdd with a negative number to decrease a date

- Some further details regarding the functions:
- DateAdd: add/subtract days/hours/minutes/seconds to/from a date
- DateDiff: find the difference between two dates (in days/hours/minutes/seconds)
- FormatTime: display a date in a friendly format

- AutoHotkey also has various built-in date variables:
- date now: A_Now [current time zone]
- date now: A_NowUTC [UTC, also known as GMT]
- week now: A_YWeek [ISO 8601 week number: year and week]
- also:
- A_DD(/A_MDay), A_MM(/A_Mon), A_YYYY(/A_Year) [current date (numeric)]
- A_Hour, A_Min, A_Sec, A_MSec [current time]
- and:
- A_MMM, A_MMMM [current month (string)]
- A_WDay [current weekday (numeric), Sun:1, Sat:7]
- A_DDD, A_DDDD [current weekday (string)]
- A_YDay [current day: nth day of the year]
- A_TickCount [milliseconds since PC switched on, wraps around to 0 after 49.7 days]

- Also, 'is' can be used to check if a date is a valid:
If Var is Type - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/IfIs.htm

- AutoHotkey uses the 14-digit YYYYMMDDHH24MISS format.
- This can also be described as 'yyyyMMddHHmmss'.
- E.g. '20060504030201' is 03:02:01 04 May 2006.

- Note: the latter parts of a date can be omitted.
- Hours/minutes/seconds default to 0.
- Months/days default to 1.
- E.g. '200605040302' is equivalent to '20060504030200'.
- E.g. '2006050403' is equivalent to '20060504030000'.
- E.g. '20060504' is equivalent to '20060504000000'.
- E.g. '200605' is equivalent to '20060501000000'.
- E.g. '2006' is equivalent to '20060101000000'.

- Here are some custom functions to replicate the AHK v2 functions in AHK v1.

Code: Select all

;commands as functions (AHK v2 functions for AHK v1) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=37&t=29689

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
DateDiff(DateTime1, DateTime2, TimeUnits)
{
	EnvSub, DateTime1, % DateTime2, % TimeUnits
	return DateTime1
}
FormatTime(YYYYMMDDHH24MISS:="", Format:="")
{
	local OutputVar
	FormatTime, OutputVar, % YYYYMMDDHH24MISS, % Format
	return OutputVar
}
- Note: there are various example date functions in this tutorial.
- Date functions are fiddly and may be subject to change.
- I will release a proper function library containing these functions, and others, at some point.

- Links:
ISO week date - Wikipedia
https://en.wikipedia.org/wiki/ISO_week_date
Variables and Expressions - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Variables.htm#date

==================================================

EXCEL FUNCTIONALITY INTRO

- MS Excel is useful as a point of reference for common date functionality.
- The following Excel function names appear in section headings below:

- EDATE: date, add/subtract months
- EOMONTH: date, add/subtract months, to last day of month
- WEEKNUM/ISOWEEKNUM: date to week number
- WORKDAY: date, add/subtract workdays, exclude weekends/holidays, specify list of holidays
- NETWORKDAYS: date difference (in workdays), exclude weekends/holidays, specify list of holidays

- Links:
EDATE function - Office Support
https://support.office.com/en-us/article/edate-function-3c920eb2-6e66-44e7-a1f5-753ae47ee4f5
EOMONTH function - Office Support
https://support.office.com/en-us/article/eomonth-function-7314ffa1-2bc9-4005-9d66-f49db127d628
WEEKNUM function - Office Support
https://support.office.com/en-us/article/weeknum-function-e5c43a03-b4ab-426c-b411-b18c13c75340
ISOWEEKNUM function - Office Support
https://support.office.com/en-us/article/isoweeknum-function-1c2d0afe-d25b-4ab1-8894-8d0520e90e0e
WORKDAY function - Office Support
https://support.office.com/en-us/article/workday-function-f764a5b7-05fc-4494-9486-60d494efbf33
NETWORKDAYS function - Office Support
https://support.office.com/en-us/article/networkdays-function-48e717bf-a7a3-495f-969e-5005e3eb18e7

- Links (further):
Excel functions (by category) - Office Support
https://support.office.com/en-us/article/excel-functions-by-category-5f91f4e9-7b42-46d2-9bd1-63f26a86c0eb
Excel functions (alphabetical) - Office Support
https://support.office.com/en-us/article/excel-functions-alphabetical-b3944572-255d-4efb-bb96-c6d90033e188
Excel Functions List
https://www.excelfunctions.net/excel-functions-list.html#DateTimeFunctions

==================================================

ADD/SUBTRACT: DATEADD / 'DATESUB' (DAYS/HOURS/MINUTES/SECONDS)

- Some examples to increase/decrease a date (by n days/hours/minutes/seconds):
- Note: to *decrease* a date, you *add* a negative number.
- Note: DateAdd is a built-in function in AHK v2.
- Note: DateAdd expects a YYYYMMDDHH24MISS number (14 digits or fewer).
- Note: the return value is always a 14-digit YYYYMMDDHH24MISS number.

Code: Select all

;e.g. increase a date by 7 days
vDate := 20060101
vDate += 7, Days
MsgBox, % vDate ;20060108000000

vDate := 20060101
EnvAdd, vDate, 7, Days
MsgBox, % vDate ;20060108000000

vDate := 20060101
vDate := DateAdd(vDate, 7, "Days")
MsgBox, % vDate ;20060108000000

;==============================

;e.g. decrease a date by 7 days
vDate := 20060101
vDate += -7, Days
MsgBox, % vDate ;20051225000000

vDate := 20060101
EnvAdd, vDate, -7, Days
MsgBox, % vDate ;20051225000000

vDate := 20060101
vDate := DateAdd(vDate, -7, "Days")
MsgBox, % vDate ;20051225000000

;==============================

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
==================================================

ADD/SUBTRACT: YEARS

- To add/subtract years, use regular addition/subtraction to adjust the date.
- Some form of handling is needed for leap days. '29 Feb' is only valid for leap years. Check if the date is valid, and adjust accordingly to '28 Feb' or '1 Mar'.
- Examples of adding/subtracting years:

Code: Select all

;assuming there are no leap days (29 Feb):

vDate := 20060504030201
vDate += 10000000000
MsgBox, % vDate ;20070504030201

vDate := 20060504030201
vDate -= 10000000000
MsgBox, % vDate ;20050504030201

;==============================

;add some years, if date is invalid, subtract a day (29 Feb to 28 Feb):

vDate := 20040229000000
vDate += 40000000000
if vDate is not date
	vDate -= 1000000
MsgBox, % vDate

vDate := 20040229000000
vDate += 50000000000
if vDate is not date
	vDate -= 1000000
MsgBox, % vDate

;==============================

;add some years, if date is invalid, add a day (29 Feb to 1 Mar):
;to go from '0229' to '0301' is a move of 301-229=72

vDate := 20040229000000
vDate += 40000000000
if vDate is not date
	vDate += 72000000
MsgBox, % vDate

vDate := 20040229000000
vDate += 50000000000
if vDate is not date
	vDate += 72000000
MsgBox, % vDate
- Alternatively, you can add/subtract a multiple of 12 months, using the function below.

==================================================

ADD/SUBTRACT: MONTHS (EXCEL: EDATE)

- There is a function, here, to add/subtract months from a date:
Subtract a month from a date? - Page 2 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=27910&p=161729#p161729
- Essentially you can just adjust the year/month numbers, but you must double-check the day.
- It handles the following invalid dates like so:
Feb 29 -> Feb 29 or Feb 28 [note: sometimes Feb 29 is valid]
Feb 30 -> Feb 29 or Feb 28
Feb 31 -> Feb 29 or Feb 28
Apr 31 -> Apr 30
Jun 31 -> Jun 30
Sep 31 -> Sep 30
Nov 31 -> Nov 30
- Note: if there were exactly 31 days in a month, a year would last 31*12=372 days. 372-365=7, so there are 7 potentially invalid dates.

==================================================

ADD/SUBTRACT: WORKDAYS (EXCLUDE WEEKENDS/HOLIDAYS) (EXCEL: WORKDAY)

- To find the next working day, i.e. to add 1 working day, check the weekday:
Fri + 3 -> Mon
Sat + 2 -> Mon
every other day + 1:
Sun + 1 -> Mon
Mon + 1 -> Tue
Tue + 1 -> Wed
Wed + 1 -> Thu
Thu + 1 -> Fri

- To find the previous working day, i.e. to subtract 1 working day, check the weekday:
Sun - 2 -> Fri
Mon - 3 -> Fri
every other day - 1:
Tue - 1 -> Mon
Wed - 1 -> Tue
Thu - 1 -> Wed
Fri - 1 -> Thu
Sat - 1 -> Fri

- To add n working days:
- Since 5 working days is equivalent to 1 week:
- To add n working days = to add 7*Floor(n/5) days + Mod(n, 5) working days.
- E.g. to add 23=20+3 working days:
equivalent to + 4 weeks + 3 working days
equivalent to + 28 days + 3 working days

- Note: using FormatTime with WDay returns the weekday, where Sun=1, Mon=2, Tue=3, Wed=4, Thu=5, Fri=6, Sat=7.
FormatTime, vWDay, % vDate, WDay

- A simple function to add/subtract workdays, might look like this:

Code: Select all

vDate1 := A_Now
vDate1 := 20191201
vOutput := ""
vHoliday1 := Format("{:i}1225", SubStr(vDate1, 1, 4))
vHoliday2 := Format("{:i}1226", SubStr(vDate1, 1, 4))
oHolidays := {(vHoliday1):1, (vHoliday2):1}
vIndex := -21
Loop 41
{
	vIndex++
	vDate2 := DateAddWorkdays(vDate1, vIndex, oHolidays)
	FormatTime, vDate2, % vDate2, ddd yyyy-MM-dd
	vOutput .= vIndex "`t" vDate2 "`r`n"
}
Clipboard := vOutput
MsgBox, % "done"

;where oHolidays is an associative array with integer key names of the form 'yyyyMMdd'
DateAddWorkdays(vDate, vWorkdays, oHolidays:="")
{
	if !vWorkdays
		return vDate
	vSign := (vWorkdays > 0) ? 1 : -1
	vCount := Abs(vWorkdays)
	if !IsObject(oHolidays)
		oHolidays := {}
	while vCount
	{
		EnvAdd, vDate, % vSign, Days
		FormatTime, vWDay, % vDate, WDay
		vDate2 := 0+SubStr(vDate, 1, 8)
		if (vWDay >= 2 && vWDay <= 6) ;between Mon and Fri
		&& !oHolidays.HasKey(vDate2)
			vCount--
	}
	return vDate
}
- Link:
workday - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=62984

==================================================

DIFFERENCE: DATEDIFF (DAYS/HOURS/MINUTES/SECONDS)

- Some examples of comparing dates:
- Note: DateDiff is a built-in function in AHK v2.
- Note: DateDiff expects two YYYYMMDDHH24MISS numbers (14 digits or fewer).

- To calculate the time between dates:
- The mathematics is like this:
- date 1 'take away' date 2 = date difference
- Thus, if date 1 is bigger than date 2, the date difference is positive.
- And, if date 1 is smaller than date 2, the date difference is negative.
- Bigger = newer, smaller = older.

Code: Select all

;e.g. compare dates: bigger (later) date take away smaller (earlier) date
vDate1 := 20060101, vDate2 := 20050101
vDate1 -= vDate2, Days
MsgBox, % vDate1 ;365

vDate1 := 20060101, vDate2 := 20050101
EnvSub, vDate1, % vDate2, Days
MsgBox, % vDate1 ;365

vDate1 := 20060101, vDate2 := 20050101
vDate1 := DateDiff(vDate1, vDate2, "Days")
MsgBox, % vDate1 ;365

;==============================

;e.g. compare dates: smaller (earlier) date take away bigger (later) date
vDate1 := 20050101, vDate2 := 20060101
vDate1 -= vDate2, Days
MsgBox, % vDate1 ;-365

vDate1 := 20050101, vDate2 := 20060101
EnvSub, vDate1, % vDate2, Days
MsgBox, % vDate1 ;-365

vDate1 := 20050101, vDate2 := 20060101
vDate1 := DateDiff(vDate1, vDate2, "Days")
MsgBox, % vDate1 ;-365

;==============================

DateDiff(DateTime1, DateTime2, TimeUnits)
{
	EnvSub, DateTime1, % DateTime2, % TimeUnits
	return DateTime1
}
==================================================

DIFFERENCE: WORKDAYS (EXCEL: NETWORKDAYS)

- A simple function to count the workdays between two days (inclusive), might look like this:

Code: Select all

vDate1 := A_Now
vDate2 := vDate1
EnvAdd, vDate2, 31, Days
vDate1 := SubStr(vDate1, 1, 8)
vDate2 := SubStr(vDate2, 1, 8)
vHoliday1 := Format("{:i}1225", SubStr(vDate1, 1, 4))
vHoliday2 := Format("{:i}1226", SubStr(vDate1, 1, 4))
oHolidays := {(vHoliday1):1, (vHoliday2):1}
MsgBox, % vDate1 "`r`n" vDate2 "`r`n" DateGetWorkdayCount(vDate1, vDate2, oHolidays)

DateGetWorkdayCount(vDate1, vDate2, oHolidays:="")
{
	FormatTime, vDate1, % vDate1, yyyyMMdd000000
	FormatTime, vDate2, % vDate2, yyyyMMdd000000
	if (vDate1 > vDate2)
		vTemp := vDate1, vDate1 := vDate2, vDate2 := vTemp
	if !IsObject(oHolidays)
		oHolidays := {}
	vCount := 0
	while (vDate1 <= vDate2)
	{
		FormatTime, vWDay, % vDate1, WDay
		vDate3 := 0+SubStr(vDate1, 1, 8)
		if (vWDay >= 2 && vWDay <= 6) ;between Mon and Fri
		&& !oHolidays.HasKey(vDate3)
			vCount++
		EnvAdd, vDate1, 1, Days
	}
	return vCount
}
==================================================

DIFFERENCE: WORK HOURS

- A function based on the DateGetWorkdayCount function above, to count the work hours between two dates (inclusive):

Code: Select all

vDate1 := 20060504110000 ;Thu
vDate2 := 20060504170000 ;Thu
MsgBox, % DateGetWorkHoursCount(vDate1, vDate2)

;requires the DateGetWorkdayCount function
;warning: not thoroughly tested
DateGetWorkHoursCount(vDate1, vDate2, oHolidays:="")
{
	if (vDate1 > vDate2)
		vTemp := vDate1, vDate1 := vDate2, vDate2 := vTemp
	FormatTime, vDate1, % vDate1, yyyyMMddHHmmss
	FormatTime, vDate2, % vDate2, yyyyMMddHHmmss
	FormatTime, vWDay1, % vDate1, WDay
	FormatTime, vWDay2, % vDate2, WDay
	vDate1X := 0+SubStr(vDate1, 1, 8)
	vDate2X := 0+SubStr(vDate2, 1, 8)
	EnvSub, vDate1, % Min(0+SubStr(vDate1, 1, 8) "170000", vDate2), Hours
	EnvSub, vDate2, % SubStr(vDate2, 1, 8) "090000", Hours
	if (vWDay1 >= 2 && vWDay1 <= 6) ;between Mon and Fri
		vHours := Min((vDate1 < 0) ? -vDate1 : 0, 8)
	else
		vHours := 0
	if !(vDate1X = vDate2X)
	&& (vWDay2 >= 2 && vWDay2 <= 6) ;between Mon and Fri
		vHours += Min((vDate2 > 0) ? vDate2 : 0, 8)
	if IsObject(oHolidays)
		oHolidays2 := ObjClone(oHolidays)
	else
		oHolidays2 := {}
	oHolidays2[0+vDate1X] := 1
	oHolidays2[0+vDate2X] := 1
	vHours += 8*DateGetWorkdayCount(vDate1X, vDate2X, oHolidays2)
	return vHours
}
==================================================

DIFFERENCE: FRIENDLY FORMAT

- Some code for calculating years since an event:
- Year born and current year to age in years.
- Age in years and current year to year born.

Code: Select all

;date to age (year born to age):

vYearBorn := 1970
vYear := 2010
vAge1 := vYear-vYearBorn-1
vAge2 := vYear-vYearBorn
MsgBox, % "I was born in " vYearBorn ", so in " vYear " I was " vAge1 "/" vAge2 " years old"

;==============================

;age to date (age to year born):

vAge := 39
vYear := 2010
vYearBorn1 := vYear-vAge-1
vYearBorn2 := vYear-vAge
MsgBox, % "I was " vAge " during part of " vYear ", so I was born in " vYearBorn1 "/" vYearBorn2

vAge := 40
vYear := 2010
vYearBorn1 := vYear-vAge-1
vYearBorn2 := vYear-vAge
MsgBox, % "I was " vAge " during part of " vYear ", so I was born in " vYearBorn1 "/" vYearBorn2
- Links:
Time() - Count Days, hours, minutes, seconds between dates - Scripts and Functions - AutoHotkey Community
http://www.autohotkey.com/board/topic/42668-time-count-days-hours-minutes-seconds-between-dates/
Function Calculating Timespan in Years, Months, and Days - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=54796
DateAdd/DateDiff with friendly format - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=59825

==================================================

MONTHS: MONTH TO YEAR QUARTER

- Some code to convert a month to a quarter:

Code: Select all

MsgBox, % MonthToQuarter(A_MM)

vOutput := ""
Loop 12
	vOutput .= A_Index "`t" MonthToQuarter(A_Index) "`r`n"
MsgBox, % vOutput

;1/2/3 -> 1, 4/5/6 -> 2, 7/8/9 -> 3, 10/11/12 -> 4
MonthToQuarter(vMonth)
{
	return Floor((vMonth + 2)/3)
}
- Link:
[some more versatile code re. months to quarters]
date calculation - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=55696

==================================================

MONTHS: DATE TO START/END OF MONTH (EXCEL: EOMONTH)

- Some code to get the first/last days of a month.
- To get the first day of the month, replace the day of the month with '01'.
- To get the last day of the month, get a date in the next month, replace the day of the month with '01', subtract one day.
- ... To get a date in the next month: get the day of the month, subtract it, that gives you the last day of the previous month, add 32 days.

Code: Select all

vDate := 20060504
vMonthStart := SubStr(vDate, 1, 6) "01"
vDay := SubStr(vDate, 7, 2)
vMonthEnd := DateAdd(vDate, -vDay+32, "Days")
vMonthEnd := SubStr(vMonthEnd, 1, 6)
vMonthEnd := DateAdd(vMonthEnd, -1, "Days")
vMonthEnd := SubStr(vMonthEnd, 1, 8)
MsgBox, % vMonthStart "`r`n" vDate "`r`n" vMonthEnd

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
==================================================

MONTHS: DATE TO PREVIOUS/NEXT MONTH

- To get the previous/next month, in the form 'yyyyMM', where the day of the month (1 to 31) is irrelevant.

- To get the previous month:
- Use DateAdd, subtract n days, where n is the day of the month, to give the last day of the previous month:

Code: Select all

;date get previous month as 'yyyyMM'

vDate := 20060504
vDay := SubStr(vDate, 7, 2)
vDate := DateAdd(vDate, -vDay, "Days")
vYearMonth := SubStr(vDate, 1, 6)
MsgBox, % vYearMonth

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
- To get the next month:
- Use DateAdd, subtract n days, where n is the day of the month, to give the last day of the previous month, then add 32 days:

Code: Select all

e.g. last day of previous month + 32:
Jan -> 31 Dec + 32 = 1 Feb
Feb -> 31 Jan + 32 = 4 Mar [on a 365-day year]
Feb -> 31 Jan + 32 = 3 Mar [on a 366-day year]
Mar -> 28 Feb + 32 = 1 Apr [on a 365-day year]
Mar -> 29 Feb + 32 = 1 Apr [on a 366-day year]
Apr -> 31 Mar + 32 = 2 May
May -> 30 Apr + 32 = 1 Jun
Jun -> 31 May + 32 = 2 Jul
Jul -> 30 Jun + 32 = 1 Aug
Aug -> 31 Jul + 32 = 1 Sep
Sep -> 31 Aug + 32 = 2 Oct
Oct -> 31 Sep + 32 = 1 Nov
Nov -> 31 Oct + 32 = 2 Dec
Dec -> 31 Nov + 32 = 1 Jan

Code: Select all

;date get next month as 'yyyyMM'

vDate := 20060504
vDay := SubStr(vDate, 7, 2)
vDate := DateAdd(vDate, -vDay+32, "Days")
vYearMonth := SubStr(vDate, 1, 6)
MsgBox, % vYearMonth

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
==================================================

WEEKDAYS: DATE TO WEEKDAY NUMBER/STRING (MONDAY, SUNDAY ETC)

- FormatTime can be used to get the weekday for a date (as a number or string):

Code: Select all

vDate := 20060504
;vDate := A_Now
FormatTime, vWDay, % vDate, WDay ;5 (Sun:1, Sat:7)
MsgBox, % vWDay
FormatTime, vWDay, % vDate, ddd ;Thu ;this can vary based on the locale
MsgBox, % vWDay
FormatTime, vWDay, % vDate, dddd ;Thursday ;this can vary based on the locale
MsgBox, % vWDay

;==============================

;examples for dates that Excel gets wrong:
;(the mistake occurs in Excel 2003/2007, and perhaps later versions)
;to maintain compatibility with Lotus 1-2-3,
;Excel assumes that 29 Feb 1900 existed (there was no such day)
;(1900 was not a leap year, so Feb 1900 had 28 days only)
;note: Excel gets the weekday wrong for 1 Jan-28 Feb 1900

;here are examples with the correct weekdays:

vDate := 19000101
FormatTime, vWDay, % vDate, WDay ;2
MsgBox, % vWDay
FormatTime, vWDay, % vDate, ddd ;Mon
MsgBox, % vWDay

vDate := 19000201
FormatTime, vWDay, % vDate, WDay ;5
MsgBox, % vWDay
FormatTime, vWDay, % vDate, ddd ;Thu
MsgBox, % vWDay

vDate := 19000301
FormatTime, vWDay, % vDate, WDay ;5
MsgBox, % vWDay
FormatTime, vWDay, % vDate, ddd ;Thu
MsgBox, % vWDay
- Some notes on weekday numbers in different date systems:

Code: Select all

AutoHotkey (Sun:1, Sat:7)

Excel (Sun:1, Sat:7)

WEEKDAY function - Office Support
https://support.office.com/en-gb/article/weekday-function-60e44483-2ed1-439f-8bd0-e404c190949a
Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default.

FILETIME (N/A)

SYSTEMTIME (Sun:0, Sat:6)

Unix (N/A)
- See also 'DATE FORMAT: LANGUAGES' to display the words for weekdays/months in different languages.

==================================================

WEEKS: DATE TO START/END OF WEEK

- To get the start/end of the week for a date, you can use Mod on the weekday of the date, to work out how many days to add/subtract to reach the week's start/end.

Code: Select all

;vDate := 20060504
vDate := A_Now
vDate1 := DateGetWeekStart(vDate,, "ddd yyyy-MM-dd")
vDate2 := DateGetWeekEnd(vDate,, "ddd yyyy-MM-dd")
FormatTime, vDate, % vDate, ddd yyyy-MM-dd
MsgBox, % vDate1 "`r`n" vDate "`r`n" vDate2

;vWDayStart: day defined as first day of week (Sun:1, Sat:7)
DateGetWeekStart(vDate, vWDayStart:=2, vFormat:="yyyyMMddHHmmss")
{
	if (vDate = "")
		vDate := A_Now
	FormatTime, vWDay, % vDate, WDay
	vNum := Mod(vWDayStart+7-vWDay, 7)
	if vNum
		EnvAdd, vDate, % vNum-7, Days
	vDate := SubStr(vDate, 1, 8) "000000"
	if !(vFormat == "yyyyMMddHHmmss")
	{
		FormatTime, vDate, % vDate, % vFormat
		return vDate
	}
	return vDate
}

;vWDayEnd: day defined as last day of week (Sun:1, Sat:7)
DateGetWeekEnd(vDate, vWDayEnd:=1, vFormat:="yyyyMMddHHmmss")
{
	if (vDate = "")
		vDate := A_Now
	FormatTime, vWDay, % vDate, WDay
	vNum := Mod(vWDayEnd+7-vWDay, 7)
	if vNum
		EnvAdd, vDate, % vNum, Days
	vDate := SubStr(vDate, 1, 8) "235959"
	if !(vFormat == "yyyyMMddHHmmss")
	{
		FormatTime, vDate, % vDate, % vFormat
		return vDate
	}
	return vDate
}
==================================================

WEEKS: NTH WEEK OF YEAR (WEEK NUMBER) (EXCEL: WEEKNUM/ISOWEEKNUM)

- Two classic systems for defining a week.
- One has 7-day weeks, but some weeks span two years.
- The other has weeks restricted to a specific year, but the 'weeks' may have fewer than 7 days.

- A description of YWeek:
FormatTime - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/FormatTime.htm
The ISO 8601 full year and week number. For example: 200453. If the week containing January 1st has four or more days in the new year, it is considered week 1. Otherwise, it is the last week of the previous year, and the next week is week 1. Consequently, both January 4th and the first Thursday of January are always in week 1.
- Numbers of the form 'yyyyww'.
- A week where: Monday is the first day of the week, and Sunday is the last day of the week.
- The ISO week numbers for 1 Jan-3 Jan, may contain the previous year.
- The ISO week numbers for 29 Dec-31 Dec, may contain the next year.
- The week number ranges from 1-53.

- Some examples of retrieving the ISO 8601 week number:

Code: Select all

;the current week number:
MsgBox, % A_YWeek

vDate := 20060504030201
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek ;200618

vDate := 20060101
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek ;200552

vDate := 20061231
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek ;200652

vDate := 20121231
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek ;201301
- An alternative system would label the week/partial week containing Jan 1 as week 1.
- A week where: Monday is the first day of the week, and Sunday is the last day of the week.
- Numbers of the form 'w' or 'ww', without a year.
- The week number ranges from 1-54. E.g. a leap year that started on a Sunday would have 54 'weeks' (1 + 52*7 + 1 days = 366 days).
- The 'week' lengths range from 1-7 days.

- Here is some example code for such a function.
- A bit of Mod logic, makes for much shorter code than otherwise.

Code: Select all

;if the first day of the week is defined as n (e.g. Mon),
;'week 1' is from 1 Jan to the first n-1 (e.g. Sun) of the year

;the current week number:
MsgBox, % A_YWeek " " DateGetWeek(A_Now)

vDate := 20060504030201
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek " " DateGetWeek(vDate) ;200618 19

vDate := 20060101
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek " " DateGetWeek(vDate) ;200552 1

vDate := 20061231
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek " " DateGetWeek(vDate) ;200652 53

vDate := 20121231
FormatTime, vYWeek, % vDate, YWeek
MsgBox, % vYWeek " " DateGetWeek(vDate) ;201301 54

DateGetWeek(vDate:="", vWeekStart:=2)
{
	;find day X, the week start day (e.g. Mon) in the period 26 Dec-1 Jan,
	;count the weeks between the requested date and day X:
	if (vDate = "")
		vDate := A_Now
	FormatTime, vWDay, % SubStr(vDate, 1, 4), WDay ;weekday on 1 Jan
	vDays := vDate
	EnvSub, vDays, % SubStr(vDate, 1, 4), Days ;days between 1 Jan and requested date
	vDays += Mod(7+vWDay-vWeekStart, 7) ;days between 1 Jan and day X
	return Format("{:02}", 1 + vDays//7)
}
- Link:
dates: date to week number - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=45466
[YWeek functionality recreated]
AutoHotkey via DllCall: AutoHotkey functions as custom functions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=37871

==================================================

YEARS: IS LEAP YEAR

- The logic of leap years:

Code: Select all

is it divisible by 4?
no: not leap
yes: ...

is it divisible by 100?
no: leap
yes: ...

is it divisible by 400?
no: not leap
yes: leap

==============================

in short:
leap year: 4Y AND (100N OR 400Y)
- Some example code to test if a year is a leap year.
- Either apply Mod to the year.
- Or check if 29 Feb is a valid date for that year.

Code: Select all

vYear := 2000

;is leap year via Mod:
;leap year: 4Y AND (100N OR 400Y)
vIsLeapYear := !Mod(vYear, 4) && (!!Mod(vYear, 100) || !Mod(vYear, 400))
MsgBox, % vIsLeapYear

;is leap year via 'is':
vDate := vYear "0229"
if vDate is date
	vIsLeapYear := 1
else
	vIsLeapYear := 0
MsgBox, % vIsLeapYear
==================================================

YEARS: CALCULATE HOLIDAYS

- A list of bank holidays in the UK, and other key days:

Code: Select all

bank holidays (England and Wales (8)):
New Year's Day [1-3 Jan][1 Jan or substitute day (first MTWTF in January)]
Good Friday [20 Mar-23 Apr][2 days before Easter Sunday (Gregorian)]
Easter Monday [23 Mar-26 Apr][day after Easter Sunday (Gregorian)]
Early May Bank Holiday [1-7 May][first Monday in May (UK)]
Spring Bank Holiday [25-31 May][last Monday in May (UK)]
Summer Bank Holiday [25-31 Aug][last Monday in August (England/Wales/Northern Ireland)][note: first Monday in August (Scotland)]
Christmas Day [25-27 Dec][25 Dec or substitute day (first MTWTF from 25 Dec)]
Boxing Day [26-28 Dec][26 Dec or substitute day (first MTWTF after 25 Dec holiday)]

note: Scotland (9): Easter Monday not a bank holiday, 2nd January and St Andrew's Day are
note: Northern Ireland (10): St Patrick's Day and Orangemen's Day are also bank holidays

other key days:
Mothering Sunday [1 Mar-4 Apr][3 weeks before Easter Sunday (Gregorian)][note: Mother's Day (US): 2nd Sunday in May]
Easter Sunday (Gregorian) [22 Mar-25 Apr][]
clocks forward [25-31 Mar][last Sunday in March (UK)][note US: second Sunday in March]
Father's Day [15-21 Jun][third Sunday in June (UK/US)]
clocks back [25-31 Oct][last Sunday in October (UK)][note US: first Sunday in November]

note: Christmas Day and Boxing Day bank holidays:
MT 25+26
TW 25+26
WT 25+26
TF 25+26
F(SS)M 26+28 [25 C, 28 B]
(SS)MT 27+28 [27 C, 28 B]
(S)MT 26+27 [26 C, 27 B][or alternatively: 26 *B*, 27 *C*]
- Code to calculate bank holidays and other key days (by using DateGetEaster/DateGetNthWeekdayInRange functions):

Code: Select all

q:: ;get key dates
vYear := 1999
vFormat := "ddd dd MMM yyyy"
vOutput2 := ""
VarSetCapacity(vOutput2, 1000000*2)
Loop 50
{
	vYear++
	vYearEnd := vYear "1231"
	vOutput := vYear "`r`n`r`n"
	vOutput .= "(NYD)`t" DateGetNthWeekdayInRange(vYear, vYearEnd, 1, 1234567, vFormat) "`r`n"
	vOutput .= "NYD BH`t" DateGetNthWeekdayInRange(vYear, vYearEnd, 1, 23456, vFormat) "`r`n"
	vOutput .= "GooF BH`t" DateGetEaster(vYear, -2, vFormat) "`r`n"
	vOutput .= "EasM BH`t" DateGetEaster(vYear, 1, vFormat) "`r`n"
	vOutput .= "EMay BH`t" DateGetNthWeekdayInRange(vYear "05", vYearEnd, 1, "Mon", vFormat) "`r`n"
	vOutput .= "Spr BH`t" DateGetNthWeekdayInRange(vYear "0525", vYearEnd, 1, "Mon", vFormat) "`r`n"
	vOutput .= "Sum BH`t" DateGetNthWeekdayInRange(vYear "0825", vYearEnd, 1, "Mon", vFormat) "`r`n"
	vOutput .= "(Xmas)`t" DateGetNthWeekdayInRange(vYear "1225", vYearEnd, 1, 1234567, vFormat) "`r`n"
	vOutput .= "(Box)`t" DateGetNthWeekdayInRange(vYear "1226", vYearEnd, 1, 1234567, vFormat) "`r`n"
	vOutput .= "Xmas BH`t" DateGetNthWeekdayInRange(vYear "1225", vYearEnd, 1, 23456, vFormat) "`r`n"
	vOutput .= "Box BH`t" DateGetNthWeekdayInRange(vYear "1225", vYearEnd, 2, 23456, vFormat) "`r`n"
	vOutput .= "`r`n"
	vOutput .= "MotSun`t" DateGetEaster(vYear, -21, vFormat) "`r`n"
	vOutput .= "EasSun`t" DateGetEaster(vYear, 0, vFormat) "`r`n"
	vOutput .= "BST`t" DateGetNthWeekdayInRange(vYear "0325", vYearEnd, 1, "Sun", vFormat) "`r`n"
	vOutput .= "FatDay`t" DateGetNthWeekdayInRange(vYear "0615", vYearEnd, 1, "Sun", vFormat) "`r`n"
	vOutput .= "GMT`t" DateGetNthWeekdayInRange(vYear "1025", vYearEnd, 1, "Sun", vFormat) "`r`n"
	vOutput2 .= vOutput "`r`n"
}

Clipboard := vOutput2
MsgBox, % "done"
return

;==================================================

;based on F_CatholicEaster() by art
;[Func] Calculating date of christian easter - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/41342-func-calculating-date-of-christian-easter/

;function gives same results for years 0 to 9999 as this link:
;Easter dates
;https://www.projectpluto.com/easter.htm

;year between 0 and 9999 if 1 parameter used
;year between 1601 and 9999 otherwise
;Easter (Western Christianity)
DateGetEaster(vDate, vDayOffset:=0, vFormat:="yyyyMMddHHmmss")
{
	local
	vYear := SubStr(vDate, 1, 4)

	c := Floor(vYear/100)
	n := vYear - 19*Floor(vYear/19)
	i := c - Floor(c/4) - Floor((c-Floor((c-17)/25))/3) + 19*n + 15
	i := i - 30*Floor(i/30)
	i := i - Floor(i/28)*(1-Floor(i/28)*Floor(29/(i+1))*Floor((21-n)/11))
	j := vYear + Floor(vYear/4) + i + 2 - c + Floor(c/4)
	j := j - 7*Floor(j/7)
	m := 3 + Floor((i-j+40)/44)
	d := i - j + 28 - 31*Floor(m/4)

	vDate2 := vYear Format("{:02}{:02}", m, d)
	if vDayOffset
		EnvAdd, vDate2, % vDayOffset, Days

	if !(vFormat == "yyyyMMddHHmmss")
		FormatTime, vDate2, % vDate2, % vFormat
	return vDate2
}

;==================================================

;vWeekdayList e.g. 23456 (working days), 17 (Saturday/Sunday), 7 (Sunday), Sun (Sunday), Sunday (Sunday)
DateGetNthWeekdayInRange(vDate1:="", vDate2:="", vNum:=0, vWDayList:="", vFormat:="yyyyMMddHHmmss")
{
	local
	static oArray := {Mon:2, Tue:3, Wed:4, Thu:5, Fri:6, Sat:7, Sun:1}
	if (vDate1 = "") || (vDate2 = "")
	{
		vNow := A_Now
		if (vDate1 = "")
			vDate1 := vNow
		if (vDate2 = "")
			vDate2 := vNow
	}
	if !vNum || !vWDayList
		return
	if oArray.HasKey(vWDayList)
		vWDayList := oArray[vWDayList]
	else if oArray.HasKey(SubStr(vWDayList, 1, 3))
		vWDayList := oArray[SubStr(vWDayList, 1, 3)]
	FormatTime, vDate1, % vDate1, yyyyMMdd000000
	FormatTime, vDate2, % vDate2, yyyyMMdd000000
	vSign := (vNum > 0) ? 1 : -1
	if ((vSign = 1) && (vDate1 > vDate2))
	|| ((vSign = -1) && (vDate1 < vDate2))
		vTemp := vDate1, vDate1 := vDate2, vDate2 := vTemp
	Loop 7
	{
		FormatTime, vWDay, % vDate1, WDay
		if InStr(vWDayList, vWDay)
		{
			vDate := vDate1
			break
		}
		EnvAdd, vDate1, % vSign, Days
	}
	if ((vSign = 1) && (vDate > vDate2))
	|| ((vSign = -1) && (vDate < vDate2))
		return
	vCount := 0
	vMax := vDate2
	EnvSub, vMax, vDate, Days

	;at this point, vDate is the first in the range, now to find the nth in the range
	vNum := Abs(vNum)
	Loop % Abs(vMax)+1
	{
		FormatTime, vWDay, % vDate, WDay
		if InStr(vWDayList, vWDay)
			vCount++
		if (vCount = vNum)
			break
		EnvAdd, vDate, % vSign, Days
	}

	if !(vCount = vNum)
		return
	if !(vFormat == "yyyyMMddHHmmss")
		FormatTime, vDate, % vDate, % vFormat
	return vDate
}
- A script to compare the DateGetEaster function against an external source:

Code: Select all

;test get easter dates
vYear := -1
oArray := {}
Loop 10000
{
	vYear++
	vDate := DateGetEaster(vYear)
	vDate := SubStr(vDate, StrLen(vYear)+1)
	FormatTime, vTemp, % 1999 vDate " L1033", _d MMMM ;note: L1033 to ensure English
	oArray[vDate vTemp] .= Format("{: 4},", vYear)
}

;list dates:
vOutput := ""
for vKey, vValue in oArray
{
	vKey := RegExReplace(vKey, ".*_")
	vValue := StrReplace(vValue, ",", " ")
	vValue := Trim(vValue)
	vValue := RegExReplace(vValue, " +", " ")
	vOutput .= vKey "=" vValue "`r`n"
}
vOutput := StrReplace(vOutput, "March", "Mar")
vOutput := StrReplace(vOutput, "April", "Apr")
vOutput .= "`r`n==================================================`r`n`r`n"

;list dates to match this link:
;Easter dates
;https://www.projectpluto.com/easter.htm
for vKey, vValue in oArray
{
	vKey := RegExReplace(vKey, ".*_")
	StrReplace(vValue, ",",, vCount)
	vChrSize := A_IsUnicode ? 2 : 1
	vType := A_IsUnicode ? "UShort" : "UChar"
	vPos := 1
	while (vPos := InStr(vValue, ",",, vPos, 15))
		NumPut(10, &vValue, (vPos-1)*vChrSize, vType) ;10 (linefeed)
	vValue := StrReplace(vValue, "`n", "`r`n ")
	vValue := StrReplace(vValue, ",", " ")
	vValue := RTrim(vValue)
	vOutput .= "Easter on " vKey " `r`n " vValue "`r`n" vCount " found over 10000 years`r`n`r`n"
}

vOutput := RTrim(vOutput, "`r`n") "`r`n"
MsgBox, % vOutput
Clipboard := vOutput
MsgBox, % "done"
return
- Links:
Bank holiday - Wikipedia
https://en.wikipedia.org/wiki/Bank_holiday#List_of_current_holidays_in_the_United_Kingdom,_Ireland_and_the_Crown_dependencies
UK bank holidays - GOV.UK
https://www.gov.uk/bank-holidays
Father's Day in the United Kingdom
https://www.timeanddate.com/holidays/uk/father-day
Daylight Saving Time Changes 2019 in London, England, United Kingdom
https://www.timeanddate.com/time/change/uk/london?year=2019

==================================================

YEARS: CALCULATE EASTER

- The DateGetEaster function, in the section above, uses a formula, to calculate the date of Easter Sunday (Gregorian) for a given year, for the years 0-9999.
- This code demonstrates how to calculate the date of Easter, from first principles, for the period 1900-2199 (or more specifically, 1894-2208):

Code: Select all

;based on:
;Gregorian Lunar Calendar
;http://home.telepath.com/~hrothgar/lunar_almanac.html

;note: rules applicable for 1900-2199:
;the Gregorian lunar calendar
;consists of 19-year cycles (e.g. 2 Jan 1995-1 Jan 2014)
;years 1,2,4,5,7,9,10,12,13,15,16,18 have 12 lunar months
;years 3,6,8,11,14,17,19 have 13 lunar months
;months per lunar year: 12,12,13,12,12,13,12,13,12,12,13,12,12,13,12,12,13,12,13
;days per lunar month: 30,29*,30,29,30,29,30,29,30,29,30,29**,30***
;*in some years, 30
;**in some years, 30 (e.g. 2199)
;***in the 19th year of a cycle, 29
;for most years, Easter occurs in the 4th lunar month
;for years 6 and 17 of the 19-year cycle, Easter occurs in the 5th lunar month
;(Easter occurs in the first lunar month of the year on or after 8 Mar)
;the ecclesiastical full moon occurs on the 14th day of the lunar month
;Easter occurs on the first Sunday after the ecclesiastical full moon of its lunar month

;Easter, iterative calculation
;correct for 1894-2208 inclusive
;e.g. Clipboard := DateGetEasterList("ddd dd MMM yyyy")
DateGetEasterList(vFormat:="yyyyMMddHHmmss")
{
	local
	oLunarYearMonthCount := [12,12,13,12,12,13,12,13,12,12,13,12,12,13,12,12,13,12,13] ;number of months in the nth lunar year
	oLunarYearEasterMonth := [4,4,4,4,4,5,4,4,4,4,4,4,4,4,4,4,5,4,4] ;the Easter month for the nth lunar year
	oLunarMonthDayCount := [30,29,30,29,30,29,30,29,30,29,30,29,30] ;days in the nth lunar month
	vDate := 18810102 ;note: a 19-year cycle starts on 2 Jan, on a year divisible by 19
	vList := ""
	Loop 40
	{
		Loop 19
		{
			vLunarYear := A_Index
			vLunarMonthCount := oLunarYearMonthCount[A_Index]
			vEasterMonth := oLunarYearEasterMonth[A_Index]
			Loop % vLunarMonthCount
			{
				vLunarMonth := A_Index
				vLunarMonthDayCount := oLunarMonthDayCount[A_Index]
				if (vLunarMonth = 12) && (SubStr(vDate, 1, 4) = 2199)
					vLunarMonthDayCount := 30
				else if (vLunarMonth = 13) && (vLunarYear = 19)
					vLunarMonthDayCount := 29
				if (vLunarMonth = vEasterMonth)
				{
					vDate2 := vDate
					FormatTime, vWDay, % vDate2, WDay ;(Sun:1, Sat:7)
					EnvAdd, vDate2, % 14+(vWDay=1?0:8-vWDay), D
					vYear := SubStr(vDate, 1, 4)
					FormatTime, vDate2, % vDate2, % vFormat
					if (vYear >= 1894)
						vList .= vDate2 "`r`n"
					if (vYear >= 2208)
						return vList
				}
				EnvAdd, vDate, % vLunarMonthDayCount, D
				if (vLunarMonth = 2)
				{
					vYear := SubStr(vDate, 1, 4)
					if (!Mod(vYear, 4) && (!!Mod(vYear, 100) || !Mod(vYear, 400))) ;is leap year
						EnvAdd, vDate, 1, D
				}
			}
		}
	}
	return vList
}
==================================================

DAYS: DATE TO NTH DAY OF YEAR

- AutoHotkey has an A_YDay variable, which returns the current day as the nth day of the year.
Current day of the year (1-366). The value is not zero-padded, e.g. 9 is retrieved, not 009. To retrieve a zero-padded value, use the following: FormatTime, OutputVar,, YDay0.
- Some code to convert a date to the nth day of the year:

Code: Select all

;now to nth day of year:

MsgBox, % A_YDay
FormatTime, vYDay,, YDay0
MsgBox, % vYDay ;0-padded equivalent of A_YDay

;==============================

;date to nth day of year:

vDate := 19990110
FormatTime, vYDay, % vDate, YDay
MsgBox, % vYDay
FormatTime, vYDay, % vDate, YDay0 ;0-padded
MsgBox, % vYDay

;==============================

;date to nth day of year from first principles:

oDays := [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
vDate := 20061231 ;not a leap year
;vDate := 20081231 ;a leap year
vYear := SubStr(vDate, 1, 4)
vMonth := SubStr(vDate, 5, 2)
vDay := SubStr(vDate, 7, 2)
vYDay := vDay + oDays[vMonth]

vIsLeapYear := !Mod(vYear, 4) && (!!Mod(vYear, 100) || !Mod(vYear, 400))
vYDay := vDay + oDays[vMonth]
if vIsLeapYear && (vMonth > 2)
	vYDay++

MsgBox, % vYDay
==================================================

TIME ZONES: NOW

- Some code for getting the current date (in your current local time zone, or UTC):

Code: Select all

;date now (local)
MsgBox, % A_Now

FormatTime, vNow,, ddd yyyy-MM-dd HH:mm:ss
MsgBox, % vNow

;==============================

;date now (UTC)
MsgBox, % A_NowUTC

FormatTime, vNowUTC, % A_NowUTC, ddd yyyy-MM-dd HH:mm:ss
MsgBox, % vNowUTC

;==============================

;date now (local/UTC) with milliseconds
;note: concatenating date A_ variables is unreliable
;this is why a function is used to retrieve the date/milliseconds

vDate := DateNow("", vMSec)
MsgBox, % vDate " " vMSec "`r`n" A_Now " " A_MSec

vDateUTC := DateNow("U", vMSec)
MsgBox, % vDateUTC " " vMSec "`r`n" A_NowUTC " " A_MSec

DateNow(vOpt:="", ByRef vMSec:="")
{
	local
	VarSetCapacity(SYSTEMTIME, 16, 0)
	if InStr(vOpt, "U")
		DllCall("kernel32\GetSystemTime", "Ptr",&SYSTEMTIME)
	else
		DllCall("kernel32\GetLocalTime", "Ptr",&SYSTEMTIME)
	vDate := ""
	Loop 7
		if !(A_Index = 3)
			vDate .= Format("{:02}", NumGet(&SYSTEMTIME, A_Index*2-2, "UShort"))
	vMSec := Format("{:03}", NumGet(&SYSTEMTIME, 14, "UShort"))
	return vDate
}
- Warning: don't concatenate date variables.
- It is unreliable to retrieve dates by combining variables such as A_YYYY A_MM A_DD.

Code: Select all

e.g. retrieving date bits separately:
on 2016-12-31 (New Year's Eve):

MsgBox, % A_YYYY "-" A_MM "-" A_DD ;2016-12-01
whoops the year changed as we were retrieving the info,
we've gone back in time by about 1 month

MsgBox, % A_DD "/" A_MM "/" A_YYYY ;31/12/2017
whoops the year changed as we were retrieving the info,
we've gone forward in time by about 1 year

thus use FormatTime:
FormatTime, vDate,, yyyy-MM-dd
MsgBox, % vDate
FormatTime, vDate,, dd/MM/yyyy
MsgBox, % vDate
- Links:
[an example to show that 'date := A_Now A_MSec' is unreliable]
jeeswg's documentation extension tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=33596
Get Current Micro/Nano seconds - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=26929

==================================================

TIME ZONES: DAYLIGHT SAVING TIME

INTRO

- There are generally 3 key time zones to consider:
- UTC / local (non-DST) / local (DST).
- And furthermore: local (non-DST) / local (DST) for other countries.

- UTC (Coordinated Universal Time), equivalent to the GMT (Greenwich Mean Time) time zone.
- And your local time zone when daylight saving time is/is not in force.

- Note: the UK is on GMT for only part of the year, from October to March. (It is on BST (British Summer Time) from March to October.)
- Note: DST from March to October is 7 months, the majority of the year.

THE TRANSITION BETWEEN NON-DST/DST

- Using the UK as an example.
- The mnemonic 'spring forward, fall back', indicates that the clocks go forward in Spring, and go back in Fall (Autumn).
- clocks forward [25-31 Mar][last Sunday in March (UK)][note US: second Sunday in March]
- clocks back [25-31 Oct][last Sunday in October (UK)][note US: first Sunday in November]

- Imagine 2 columns with the 2 time zones, and jumping from one to the other:

Code: Select all

clocks forward (DST begins, GMT to BST):
GMT		BST
[0:00-0:59]	 1:00-1:59
 1:00-1:59	[2:00-2:59]
 2:00-2:59	[3:00-3:59]
note: you skip 1 AM (lose an hour)

clocks back (DST ends, BST to GMT):
GMT		BST
 0:00-0:59	[1:00-1:59]
[1:00-1:59]	 2:00-2:59
[2:00-2:59]	 3:00-3:59
note: you get two lots of 1 AM (gain an hour)
NOW

- Some code to get the time/date now (local and UTC):

Code: Select all

;time now (local/UTC):
MsgBox, % A_Now "`r`n" A_NowUTC
- Some rough code to get the current offset from UTC:

Code: Select all

;current offset from UTC in minutes:
vIntervalsUTC := vIntervalsLocal := 0
DllCall("kernel32\GetSystemTimeAsFileTime", "Int64*",vIntervalsUTC)
DllCall("kernel32\FileTimeToLocalFileTime", "Int64*",vIntervalsUTC, "Int64*",vIntervalsLocal)
MsgBox, % (vIntervalsLocal - vIntervalsUTC) // 600000000 ;offset in minutes
- Some code to get the current offset from UTC:
- Some code to determine whether DST is in force or not for the current time zone:
- And some code to get the non-DST and DST offsets from UTC for the current time zone:

Code: Select all

;get DST information for the current time zone
VarSetCapacity(TIME_ZONE_INFORMATION, 172, 0)
vRet := DllCall("kernel32\GetTimeZoneInformation", "Ptr",&TIME_ZONE_INFORMATION, "UInt")
oArray := ["unknown", "no", "yes"]
vIsDST := oArray[vRet+1]
vBias := NumGet(&TIME_ZONE_INFORMATION, 0, "Int")
vStdOffset := vBias + NumGet(&TIME_ZONE_INFORMATION, 84, "Int")
vDltOffset := vBias + NumGet(&TIME_ZONE_INFORMATION, 168, "Int")
if (vRet = 1) ;TIME_ZONE_ID_STANDARD := 1
	vOffset := vStdOffset
else if (vRet = 2) ;TIME_ZONE_ID_DAYLIGHT := 2
	vOffset := vDltOffset
else ;TIME_ZONE_ID_UNKNOWN := 0
	vOffset := ""
vOutput := "is DST: " vIsDST "`r`n`r`n"
vOutput .= "current offset: " vOffset "`r`n"
vOutput .= "non-DST offset: " vStdOffset "`r`n"
vOutput .= "DST offset: " vDltOffset "`r`n`r`n"
vOutput .= "UTC to local: " (vOffset>0?"-":"+") Abs(vDltOffset) "`r`n" ;note: invert the sign
vOutput .= "local to UTC: " (vOffset<0?"-":"+") Abs(vDltOffset)
MsgBox, % vOutput
return
- Some notes on using a TIME_ZONE_INFORMATION struct to convert between UTC/local dates:

Code: Select all

_TIME_ZONE_INFORMATION | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/timezoneapi/ns-timezoneapi-_time_zone_information

typedef struct _TIME_ZONE_INFORMATION {
  LONG       Bias;
  WCHAR      StandardName[32];
  SYSTEMTIME StandardDate;
  LONG       StandardBias;
  WCHAR      DaylightName[32];
  SYSTEMTIME DaylightDate;
  LONG       DaylightBias;
} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION;

UTC time = local time (non-DST) + Bias + StandardBias
UTC time = local time (DST) + Bias + DaylightBias
local time (non-DST) = UTC time - Bias - StandardBias
local time (DST) = UTC time - Bias - DaylightBias

E.g. UTC+1 (BST): local time (DST) = UTC time - 0 min - (-60) min = UTC time + 60 min
MsgBox, % "UTC: " A_NowUTC "`r`n" "local: " A_Now
- Links:
[code to get the time for multiple time zones:]
time zones: get offset from UTC - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42769
Time Functions - Windows applications | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/SysInfo/time-functions
Daylight Saving Time Changes 2019 in London, England, United Kingdom
https://www.timeanddate.com/time/change/uk/london?year=2019

OTHER COUNTRIES / GET REGION INFO

- See this link, to get time zone info from the registry:
get DST (daylight saving time) start/end dates/times in UTC - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=28355&p=162191#p162191
- It displays clocks for multiple countries.
- The key idea is to obtain the StandardDate/DaylightDate rules from the registry, and then determine the DST start/end dates for a specific year, and then add/subtract minutes depending on whether the date is inside/outside the range.

- Important registry keys:
HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones

- Links:
_TIME_ZONE_INFORMATION | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/timezoneapi/ns-timezoneapi-_time_zone_information
DYNAMIC_TIME_ZONE_INFORMATION (timezoneapi.h) | Microsoft Docs
https://docs.microsoft.com/en-gb/windows/desktop/api/timezoneapi/ns-timezoneapi-dynamic_time_zone_information
Dynamic daylight saving time provides support for time zones whose boundaries for daylight saving time change from year to year.
Multilingual User Interface - Windows applications | Microsoft Docs
https://docs.microsoft.com/en-gb/windows/desktop/Intl/multilingual-user-interface

CONVERT HISTORIC DATES TO 'LOCAL' TIME

- To convert historic UTC dates to/from local non-DST/local DST dates, there are 2 classic options:
- FileTimeToLocalFileTime/LocalFileTimeToFileTime. Convert based on the current time zone. I.e. is the time zone now, non-DST or DST.
- SystemTimeToTzSpecificLocalTime/TzSpecificLocalTimeToSystemTime. Convert based on the time zone at the time. What would it say on a watch at the time, was it non-DST or DST at the time.
- A classic use is to convert a file modified date in UTC, e.g. obtained via the Winapi function GetFileTime, to the current/historic local time zone.
- FileTimeToLocalFileTime is simpler, it needs the current DST start/end dates.
- Ideally, SystemTimeToTzSpecificLocalTime needs DST start/end dates for all of history and all of the future.

Code: Select all

q:: ;UTC dates to local time (DST based on input date, not today's date)
vYear := A_Year
vOutput := ""
Loop 12
{
	vDate := A_Year Format("{:02}", A_Index) "1512"
	vDate1 := DateUTCToLocal(vDate)
	vDate2 := DateUTCToLocal(vDate, "X")
	FormatTime, vDate1, % vDate1, yyyyMMdd HHmmss
	FormatTime, vDate2, % vDate2, yyyyMMdd HHmmss
	vOutput .= vDate "`t" vDate1 "`t" vDate2 "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==================================================

;vOpt: X: DST/non-DST based on time zone at the time
;vOpt: (blank): DST/non-DST based on current time zone
DateUTCToLocal(vDate, vOpt:="")
{
	local
	if !InStr(vOpt, "X") ;DST adjusted based on current bias
	{
		EnvSub, vDate, 1601, Seconds
		vIntervals1 := vDate*10000000
		vIntervals2 := 0
		DllCall("kernel32\FileTimeToLocalFileTime", "Int64*",vIntervals1, "Int64*",vIntervals2)
		vDate := 1601
		EnvAdd, vDate, % vIntervals2//10000000, Seconds
	}
	else ;DST adjusted based on date's bias
	{
		FormatTime, vDate, % vDate, yyyy MM 0 dd HH mm ss
		VarSetCapacity(SYSTEMTIME1, 16, 0)
		Loop Parse, vDate, % " "
			NumPut(A_LoopField, &SYSTEMTIME1, A_Index*2-2, "UShort")
		VarSetCapacity(SYSTEMTIME2, 16, 0)
		DllCall("kernel32\SystemTimeToTzSpecificLocalTime", "Ptr",0, "Ptr",&SYSTEMTIME1, "Ptr",&SYSTEMTIME2)
		vDate := ""
		Loop 7
			if !(A_Index = 3)
				vDate .= Format("{:02}", NumGet(&SYSTEMTIME2, A_Index*2-2, "UShort"))
	}
	return vDate
}

;==================================================

;vOpt: X: DST/non-DST based on time zone at the time
;vOpt: (blank): DST/non-DST based on current time zone
DateLocalToUTC(vDate, vOpt:="")
{
	local
	if !InStr(vOpt, "X") ;DST adjusted based on current bias
	{
		EnvSub, vDate, 1601, Seconds
		vIntervals1 := vDate*10000000
		vIntervals2 := 0
		DllCall("kernel32\LocalFileTimeToFileTime", "Int64*",vIntervals1, "Int64*",vIntervals2)
		vDate := 1601
		EnvAdd, vDate, % vIntervals2//10000000, Seconds
	}
	else ;DST adjusted based on date's bias
	{
		FormatTime, vDate, % vDate, yyyy MM 0 dd HH mm ss
		VarSetCapacity(SYSTEMTIME1, 16, 0)
		Loop Parse, vDate, % " "
			NumPut(A_LoopField, &SYSTEMTIME1, A_Index*2-2, "UShort")
		VarSetCapacity(SYSTEMTIME2, 16, 0)
		DllCall("kernel32\TzSpecificLocalTimeToSystemTime", "Ptr",0, "Ptr",&SYSTEMTIME1, "Ptr",&SYSTEMTIME2)
		vDate := ""
		Loop 7
			if !(A_Index = 3)
				vDate .= Format("{:02}", NumGet(&SYSTEMTIME2, A_Index*2-2, "UShort"))
	}
	return vDate
}

;==================================================
EXPLORER FILE PROPERTIES DIALOGS

- Some links re. how the Explorer file properties dialog displays dates.
- In short, in the past, they used FileTimeToLocalFileTime (current DST), now, from Windows Vista onwards, they use SystemTimeToTzSpecificLocalTime (historic DST).
- One consequence is that file properties dialogs may now show you dates, on the same dialog, in different time zones (some DST, some non-DST).
- (Note: the MSDN documentation for SystemTimeToTzSpecificLocalTime is pretty unclear, I only understood what it does via external sources.)

- Links (Raymond Chen on time zones and datestamps):
Windows Confidential - Evolution of the File Property Timestamp | Microsoft Docs
https://docs.microsoft.com/en-us/previous-versions/technet-magazine/ff394358(v=msdn.10)
Why Daylight Savings Time is nonintuitive | The Old New Thing
https://devblogs.microsoft.com/oldnewthing/20031024-00/?p=42053
Why do Explorer and the command prompt interpret file times differently? | The Old New Thing
https://devblogs.microsoft.com/oldnewthing/20130308-00/?p=5023

- Links (Winapi functions):
[UTC to local (based on current DST)]
FileTimeToLocalFileTime function (fileapi.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-filetimetolocalfiletime
[UTC to local (based on historic DST)]
SystemTimeToTzSpecificLocalTime function (timezoneapi.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime
Time Functions - Windows applications | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/SysInfo/time-functions

- Links (further):
Problems and solutions with FileSetTime, FileGetTime and DST (Daylight Saving Time) enabled in Windows 7+. - AutoIt Example Scripts - AutoIt Forums
https://www.autoitscript.com/forum/topic/182494-problems-and-solutions-with-filesettime-filegettime-and-dst-daylight-saving-time-enabled-in-windows-7/
FileGetTime & DST problem - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=64093
Convert Timestamp to Local Time - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=65407

==================================================

DATE SYSTEMS: AUTOHOTKEY, EXCEL, FILETIME, SYSTEMTIME, UNIX

- Summaries of common date systems (units and start/end dates):

Code: Select all

AUTOHOTKEY (1601-9999, seconds):

FormatTime - Syntax & Usage | AutoHotkey
https://www.autohotkey.com/docs/commands/FormatTime.htm
Although only years between 1601 and 9999 are supported, a formatted time can still be produced for earlier years as long as the time portion is valid.

FileSetTime - Syntax & Usage | AutoHotkey
https://www.autohotkey.com/docs/commands/FileSetTime.htm#Remarks
The elements of the YYYYMMDDHH24MISS format

[solved] getting WEEKDAY on any DATE - Page 2 - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/85460-solved-getting-weekday-on-any-date/page-2
The calendar will repeat every 400 years.. So just add 400 years until it's past 1601.

==============================

EXCEL (1900-9999, days):

Differences between the 1900 and the 1904 date system in Excel
https://support.microsoft.com/en-us/help/214330/differences-between-the-1900-and-the-1904-date-system-in-excel
a serial number that represents the number of elapsed days since January 1, 1900

How Excel works with two-digit year numbers
https://support.microsoft.com/en-gb/help/214391/how-excel-works-with-two-digit-year-numbers
Dates in the inclusive range from January 1, 1900 (1/1/1900) to December 31, 9999 (12/31/9999) are valid.

DATEVALUE function - Office Support
https://support.office.com/en-us/article/datevalue-function-df8b07d4-7761-4a93-bc33-b7471bbff252
Using the default date system in Microsoft Excel for Windows, the date_text argument must represent a date between January 1, 1900 and December 31, 9999.

Excel incorrectly assumes that the year 1900 is a leap year
https://support.microsoft.com/en-us/help/214326/excel-incorrectly-assumes-that-the-year-1900-is-a-leap-year
Microsoft Excel incorrectly assumes that the year 1900 is a leap year. This article explains why the year 1900 is treated as a leap year, and outlines the behaviors that may occur if this specific issue is corrected.
...
When Lotus 1-2-3 was first released, the program assumed that the year 1900 was a leap year, even though it actually was not a leap year. This made it easier for the program to handle leap years and caused no harm to almost all date calculations in Lotus 1-2-3.
...
The WEEKDAY function returns incorrect values for dates before March 1, 1900. Because most users do not use dates before March 1, 1900, this problem is rare.

==================================================

EXCEL (fixing the date system):

1 Jan 1901 has value 1
28 Feb 1901 has value 59
29 Feb 1901 has value 60 (there was no such date)
1 Mar 1901 has value 61

This would make more sense:
1 Jan 1901 has value 2
28 Feb 1901 has value 60
1 Mar 1901 has value 61

This would also make sense (but would mess up Excel spreadsheets):
1 Jan 1901 has value 1
28 Feb 1901 has value 59
1 Mar 1901 has value 60

==============================

FILETIME (1601-30828, 100-nanosecond intervals = 0.1-microsecond intervals):

FILETIME (minwinbase.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime
Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).

System time - Wikipedia
https://en.wikipedia.org/wiki/System_time
1 January 1601 to AD 30,828

c++ - Latest possible FILETIME - Stack Overflow
https://stackoverflow.com/questions/9999393/latest-possible-filetime#
if you put 0x7fffffffffffffff into FileTimeToSystemTime() then you will end up in the year 30828, although this date is invalid for SYSTEMTIME
...
I would recommend not to go beyond 0x7fff35f4f06c58f0 in order to stay compatible with SYSTEMTIME

==============================

SYSTEMTIME (1601-30827, milliseconds):

SYSTEMTIME (minwinbase.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-systemtime
Specifies a date and time, using individual members for the month, day, year, weekday, hour, minute, second, and millisecond.
...
The year. The valid values for this member are 1601 through 30827.

==============================

UNIX 32-BIT (1970-2038, seconds):
UNIX 64-BIT (1970-292277026596, seconds):

Unix time - Wikipedia
https://en.wikipedia.org/wiki/Unix_time
the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970, minus the number of leap seconds that have taken place since then
...
signed 32-bit integer: 03:14:07 Tuesday, 19 January 2038 UTC
...
the Year 2038 problem
...
signed 64-bit integer: 15:30:08 Sunday, 4 December 292277026596 UTC
- Some calculations re. the end points for date systems:

Code: Select all

AUTOHOTKEY (1601-9999, seconds):

AutoHotkey uses datestamps of the form yyyyMMddHHmmss, where the year is 4-digits long.

==============================

EXCEL (1900-9999, days):

Excel uses the date range 1900-1999.

==============================

FILETIME (1601-30828, 100-nanosecond intervals = 0.1-microsecond intervals):

Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).

UInt64 (8 bytes = 64 bits) (0 to 18446744073709551615 = 0x0 to 0xFFFFFFFFFFFFFFFF)
where 18446744073709551615 = 256**8 - 1

in a 400-year cycle there are 97 leap years,
so, average year length = 365 + 97/400 days = 365.2425 days

18446744073709551616 100-nanosecond intervals
= 1844674407370955161.6 1000-nanosecond intervals
= 1844674407370955161.6 microseconds
= 1844674407370955.1616 milliseconds
= 1844674407370.9551616 seconds
= 21350398.23346 days
= approx. 21350398.23346/365.2425 years = 58455.404925 years

So possible end years:
1601 + Floor(58455.404925) = 60056
1601 + Floor(58455.404925/2) = 1601 + 29227 = 30828

[approx. 58455.404925 years]
18446744073709551615/(10*1000*1000*86400*365.2425) - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=18446744073709551615%2F(10*1000*1000*86400*365.2425)

[approx. 29227.702463 years]
18446744073709551615/(10*1000*1000*86400*365.2425*2) - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=18446744073709551615%2F(10*1000*1000*86400*365.2425*2)

==============================

SYSTEMTIME (1601-30827, milliseconds):

wYear is a WORD (2 bytes)

So possible end years:
256*256=65536
256*128=32768

==============================

UNIX 32-BIT (1970-2038, seconds):

signed 32-bit integer, maximum is: 2147483647 (0x7FFFFFFF)

vDate := 1970
EnvAdd, vDate, 2147483647, Seconds
MsgBox, % vDate ;20380119031407

1970 AD + 2147483647 seconds - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=1970+AD+%2B+2147483647+seconds
1970 AD + (2**31-1) seconds - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=1970+AD+%2B+(2**31-1)+seconds

2147483647/(365.2425*86400) years = 68.051048 years

1970 + 68 = 2038

==============================

UNIX 64-BIT (1970-292277026596, seconds):

signed 64-bit integer, maximum is: 9223372036854775807 (0x7FFFFFFFFFFFFFFF)

these calculations didn't work on Wolfram Alpha:
1970 AD + 9223372036854775807 seconds
1970 AD + (2**31-1) seconds

9223372036854775807/(365.2425*86400) years = 292277024626.92773 years

1970 + 292277024626 = 292277026596
- Equivalents of various years in different date systems:

Code: Select all

Excel:
1970	25569
1980	29221
1990	32874
2000	36526
2010	40179
2020	43831
2030	47484
2040	51136
2050	54789

FILETIME:
1970	116444736000000000
1980	119600064000000000
1990	122756256000000000
2000	125911584000000000
2010	129067776000000000
2020	132223104000000000
2030	135379296000000000
2040	138534624000000000
2050	141690816000000000

Unix:
1970	0
1980	315532800
1990	631152000
2000	946684800
2010	1262304000
2020	1577836800
2030	1893456000
2040	2208988800
2050	2524608000
- Some code to demonstrate storing 1 Jan 1970 as a SYSTEMTIME/FILETIME/AHK date:

Code: Select all

;1 Jan 1970 as a SYSTEMTIME/FILETIME/AHK date

;as a SYSTEMTIME:
VarSetCapacity(SYSTEMTIME, 16, 0)
NumPut(1970, &SYSTEMTIME, 0, "UShort") ;wYear
NumPut(1, &SYSTEMTIME, 2, "UShort") ;wMonth
NumPut(1, &SYSTEMTIME, 6, "UShort") ;wDay

;as a FILETIME:
vIntervals := 0
DllCall("kernel32\SystemTimeToFileTime", "Ptr",&SYSTEMTIME, "Int64*",vIntervals)
MsgBox, % vIntervals ;116444736000000000

;as an AHK date:
vDate := 1601
EnvAdd, vDate, % vIntervals//10000000, Seconds
MsgBox, % vDate ;19700101000000
return
- Some useful Winapi functions for handling FILETIME and SYSTEMTIME structs:

Code: Select all

GET/ADJUST SYSTEMTIME:
now (UTC): GetLocalTime
now (local): GetSystemTime
historic (UTC to current local): SystemTimeToFileTime, FileTimeToLocalFileTime, FileTimeToSystemTime
historic (UTC to historic local): SystemTimeToTzSpecificLocalTime
historic (current local to UTC): SystemTimeToFileTime, LocalFileTimeToFileTime, FileTimeToSystemTime
historic (historic local to UTC): TzSpecificLocalTimeToSystemTime

GET/ADJUST FILETIME:
now (UTC): GetSystemTimeAsFileTime
now (local): GetSystemTimeAsFileTime, FileTimeToLocalFileTime
historic (UTC to current local): FileTimeToLocalFileTime
historic (UTC to historic local): FileTimeToSystemTime, SystemTimeToTzSpecificLocalTime, SystemTimeToFileTime
historic (current local to UTC): LocalFileTimeToFileTime
historic (historic local to UTC): FileTimeToSystemTime, TzSpecificLocalTimeToSystemTime, SystemTimeToFileTime

FILETIME/SYSTEMTIME CONVERT:
F to S: FileTimeToSystemTime
S to F: SystemTimeToFileTime
- Links:
convert common date formats (Excel / FileTime / Unix) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=44727
Convert Timestamp to Local Time - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=65407
Time Functions - Windows applications | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/sysinfo/time-functions

- Links (further):
Year 2000 problem - Wikipedia
https://en.wikipedia.org/wiki/Year_2000_problem#See_also
Template:Year-related problems - Wikipedia
https://en.wikipedia.org/wiki/Template:Year-related_problems
Time formatting and storage bugs - Wikipedia
https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs
filesystems - How can a file have been 'created' in 1641? - Super User
https://superuser.com/questions/1308412/how-can-a-file-have-been-created-in-1641
Why is the Win32 epoch January 1, 1601? | The Old New Thing
https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913

==================================================

TIMESTAMPS/DURATIONS: HH:MM:SS TO/FROM SECONDS, DAYS

- Three common considerations covered in this section:
- 'd HH:mm:ss', '(HH>=24):mm:ss', '(HH<24):mm:ss (wraparound)'
- 22:00:00 + 3:00:00 -> 01:00:00 [time after duration (HH:mm:ss with wraparound)]
- 22:00:00 + 3:00:00 -> 25:00:00 [add durations (HH:mm:ss)]
- 22:00:00 + 3:00:00 -> 1 day + 01:00:00 [add durations (d HH:mm:ss)]

- Some HH:mm:ss to seconds examples:

Code: Select all

;HH:mm:ss to seconds
vHMS := "03:02:01"
oTemp := StrSplit(vHMS, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3 ;10800 + 120 + 1
oTemp := ""
MsgBox, % vSec ;10921

;==============================

;HH:mm:ss to seconds (via EnvSub) (warning: where HH < 24)
vHMS := "03:02:01"
vSec := "19990101" StrReplace(vHMS, ":")
EnvSub, vSec, % 199901, Seconds
MsgBox, % vSec

;==============================

;HH:mm:ss to seconds (via DateDiff in AHK v2) (warning: where HH < 24)
vHMS := "03:02:01"
vSec := "19990101" StrReplace(vHMS, ":")
vSec := DateDiff(vSec, 199901, "Seconds")
MsgBox(vSec)
- Some seconds to HH:mm:ss examples:

Code: Select all

;seconds to HH:mm:ss
vSec := 10921
MsgBox, % Format("{:02}:{:02}:{:02}", Floor(vSec/3600), Floor(Mod(vSec, 3600)/60), Mod(vSec, 60)) ;03:02:01

vSec := 10921 + 86400*4 ;03:02:01 + 96:00:00
MsgBox, % Format("{:02}:{:02}:{:02}", Floor(vSec/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01

;==============================

;seconds to days + HH:mm:ss
vSec := 10921 + 86400*4 ;03:02:01 + 96:00:00
MsgBox, % Format("{} days + {:02}:{:02}:{:02}", Floor(vSec/86400), Floor(Mod(vSec,86400)/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01

;==============================

;seconds to HH:mm:ss (via EnvAdd) (note: wraps around if HH >= 24)
vSec := 10921
vHMS := 1999
EnvAdd, vHMS, vSec, Seconds
FormatTime, vHMS, % vHMS, HH:mm:ss ;03:02:01
MsgBox, % vHMS

;==============================

;seconds to HH:mm:ss (via DateDiff in AHK v2) (note: wraps around if HH >= 24)
vSec := 10921
vHMS := DateAdd(1999, vSec, "Seconds")
vHMS := FormatTime(vHMS, "HH:mm:ss") ;03:02:01
MsgBox(vHMS)

;==============================
- Some classic duration/timestamp modification examples:

Code: Select all

;time after duration (now)

;e.g. what time will it be in 36 hours time
vDate := A_Now
vDuration := "36:00:00"
oTemp := StrSplit(vDuration, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3
EnvAdd, vDate, % vSec, Seconds
FormatTime, vTime2, % vDate, HH:mm:ss
MsgBox, % vTime2

;==============================

;time after duration

;e.g. if it's 11pm, and I watch a 1h40 movie, at what time will it finish
vTime := "23:00:00"
vDuration := "01:40:00"
oTemp := StrSplit(vDuration, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3
vDate := "19990101" StrReplace(vTime, ":")
EnvAdd, vDate, % vSec, Seconds
FormatTime, vTime2, % vDate, HH:mm:ss
MsgBox, % vTime2

;e.g. if it's 10pm, and I watch a 3h00 movie, at what time will it finish
vTime := "22:00:00"
vDuration := "03:00:00"
oTemp := StrSplit(vDuration, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3
vDate := "19990101" StrReplace(vTime, ":")
EnvAdd, vDate, % vSec, Seconds
FormatTime, vTime2, % vDate, HH:mm:ss
MsgBox, % vTime2

;==============================

;add durations (HH:mm:ss)
vHMS1 := "22:00:00"
vHMS2 := "03:00:00"
oTemp1 := StrSplit(vHMS1, ":")
oTemp2 := StrSplit(vHMS2, ":")
vSec := oTemp1.1*3600 + oTemp1.2*60 + oTemp1.3
vSec += oTemp2.1*3600 + oTemp2.2*60 + oTemp2.3
MsgBox, % Format("{:02}:{:02}:{:02}", Floor(vSec/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01

;==============================

;add durations (d HH:mm:ss)
vHMS1 := "22:00:00"
vHMS2 := "03:00:00"
oTemp1 := StrSplit(vHMS1, ":")
oTemp2 := StrSplit(vHMS2, ":")
vSec := oTemp1.1*3600 + oTemp1.2*60 + oTemp1.3
vSec += oTemp2.1*3600 + oTemp2.2*60 + oTemp2.3
MsgBox, % Format("{} days + {:02}:{:02}:{:02}", Floor(vSec/86400), Floor(Mod(vSec,86400)/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01

;==============================

;multiply durations (HH:mm:ss)
vHMS := "10:00:00"
vNum := 4
oTemp := StrSplit(vHMS, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3
vSec *= vNum
MsgBox, % Format("{:02}:{:02}:{:02}", Floor(vSec/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01

;==============================

;multiply durations (d HH:mm:ss)
vHMS := "10:00:00"
vNum := 4
oTemp := StrSplit(vHMS, ":")
vSec := oTemp.1*3600 + oTemp.2*60 + oTemp.3
vSec *= vNum
MsgBox, % Format("{} days + {:02}:{:02}:{:02}", Floor(vSec/86400), Floor(Mod(vSec,86400)/3600), Floor(Mod(vSec,3600)/60), Mod(vSec,60)) ;99:02:01
==================================================

DATE FORMAT: TWO-DIGIT YEARS TO FOUR-DIGIT YEARS

- An example of converting two-digit years to four-digit years:

Code: Select all

;00-29 -> 2000-2029
;30-99 -> 1900-1929
vOutput := ""
Loop 100
{
	vNum := Format("{:02}", A_Index-1)
	vYear := vNum
	if (vYear >= 0) && (vYear <= 29)
		vYear += 2000
	else
		vYear += 1900
	vOutput .= vNum "`t" vYear "`r`n"
}
Clipboard := vOutput
MsgBox, % "done"
- This value can be changed via:
intl.cpl, [e.g. press Win+R, and type this into the Run dialog]
Formats tab,
Additional settings...,
Date tab,
Calendar
- Search re. TwoDigitYearMax, to change this via the registry.

- Links:
How Excel works with two-digit year numbers
https://support.microsoft.com/en-us/help/214391/how-excel-works-with-two-digit-year-numbers
Date windowing - Wikipedia
https://en.wikipedia.org/wiki/Date_windowing

==================================================

DATE FORMAT: IS DATE VALID

- Check if a date is valid by using 'if var is (not) date':

Code: Select all

;a note re. syntax:
;AHK v1:
;if var is date
;if var is not date

;AHK v2:
;if var is "date"
;if !(var is "date")

;==============================

;if var is date
vDate := 19990101
if vDate is date
	vIsDate := 1
else
	vIsDate := 0
MsgBox, % vIsDate ? "y" : "n"

vDate := 19990101
if vDate is not date
	vIsDate := 0
else
	vIsDate := 1
MsgBox, % vIsDate ? "y" : "n"

vDate := "20060504030201"
;vDate := "20111111111111"
vOutput := ""
vTemp := ""
Loop Parse, vDate
{
	vTemp .= A_LoopField
	if vTemp is date
		vIsDate := 1
	else
		vIsDate := 0
	FormatTime, vTemp2, % vTemp, yyyy-MM-dd HH-mm-ss
	vOutput .= (vIsDate ? "y" : "n") "`t" vTemp "`t" vTemp2 "`r`n"
}
MsgBox, % vOutput

vDate := 19993232
if vDate is date
	MsgBox, % "y"
else
	MsgBox, % "n" ;n

vDate := 19991313
if vDate is date
	MsgBox, % "y"
else
	MsgBox, % "n" ;n
- Examples of FormatTime applied to valid/invalid dates:

Code: Select all

;using yyyyMMddHHmmss:

vDate := 19990101
FormatTime, vDate, % vDate, yyyyMMddHHmmss
MsgBox, % vDate ;19990101000000

vDate := 19993232
FormatTime, vDate, % vDate, yyyyMMddHHmmss
MsgBox, % vDate ;000000

;==============================

;using d:

vDate := 19990101
FormatTime, vDate, % vDate, d
MsgBox, % vDate ;1

vDate := 19993232
FormatTime, vDate, % vDate, d
MsgBox, % vDate ;(blank)

vDate := 20040229
FormatTime, vDate, % vDate, d
MsgBox, % vDate ;29

vDate := 20050229
FormatTime, vDate, % vDate, d
MsgBox, % vDate ;(blank)

;==============================

;using WDay (Sun:1, Mon:2, ..., Sat:7):

vDate := 19990101
FormatTime, vDate, % vDate, WDay
MsgBox, % vDate ;6

;warning: all invalid dates appear to return 2
;but some valid dates also return 2 (Monday)
vDate := 19993232
FormatTime, vDate, % vDate, WDay
MsgBox, % vDate ;2 (invalid date)

vDate := 19991313
FormatTime, vDate, % vDate, WDay
MsgBox, % vDate ;2 (invalid date)

vDate := 19000101
FormatTime, vDate, % vDate, WDay
MsgBox, % vDate ;2 (date is valid)
==================================================

DATE FORMAT: SPLIT/COMBINE

- Warning: combining a date variable like so is unreliable, because each time you retrieve a date variable, the current date is checked again:
vDate := % A_DD " " A_MMM " " A_YYYY
- This is reliable:
FormatTime, vDate,, dd MMM yyyy
- E.g. you could get '31 Dec 2017', when you meant '31 Dec 2016' or '01 Jan 2017'.
- This is explained here:
combining date variables is unreliable - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=36338

- Here are some examples to split dates (and recombine them):

Code: Select all

;split dates using RegExMatch:

vDate := 20060504030201

RegExMatch(vDate, "O)(....)(..)(..)(..)(..)(..)", oMatch)
MsgBox, % Format("{} {} {} {} {} {}", oMatch.1, oMatch.2, oMatch.3, oMatch.4, oMatch.5, oMatch.6)

;==============================

;split dates using RegExReplace:

vDate := 20060504030201

MsgBox, % RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")

MsgBox, % RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1-$2-$3 $4:$5:$6")

vDate2 := RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")
oTemp := StrSplit(vDate2, " ")
MsgBox, % Format("{} {} {} {} {} {}", oTemp*)

vDate2 := RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")
MsgBox, % Format("{} {} {} {} {} {}", StrSplit(vDate2, " ")*)

vDate2 := RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")
MsgBox, % Format("{} {:02} {:02} {:02} {:02} {:02}", StrSplit(vDate2, " ")*)

MsgBox, % RegExReplace(vDate, "(?<=..)..(?=.)", "$0 ") ;e.g. 20060504030201 -> 2006 05 04 03 02 01

;==============================

;split dates using SubStr:

vDate := 20060504030201
vYear := SubStr(vDate, 1, 4)
vMonth := SubStr(vDate, 5, 2)
vDay := SubStr(vDate, 7, 2)
vHour := SubStr(vDate, 9, 2)
vMin := SubStr(vDate, 11, 2)
vSec := SubStr(vDate, 13, 2)
MsgBox, % Format("{} {} {} {} {} {}", vYear, vMonth, vDay, vHour, vMin, vSec)
- Some RegEx alternatives to AutoHotkey's FormatTime function:

Code: Select all

;where A_Now is of the form 'yyyyMMddHHmmss'
MsgBox, % RegExReplace(A_Now, "(....)(..)(..).{6}", "$1-$2-$3")
MsgBox, % RegExReplace(A_Now, ".{8}(..)(..)(..)", "$1:$2:$3")
MsgBox, % RegExReplace(A_Now, "(....)(..)(..)(..)(..)(..)", "$1-$2-$3 $4:$5:$6")
MsgBox, % RegExReplace(A_Now, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")
MsgBox, % RegExReplace(A_Now, "(?<=..)..(?=.)", "$0-") ;e.g. 20060504030201 -> 2006-05-04-03-02-01
MsgBox, % RegExReplace(A_Now, "(?<=..)..(?=.)", "$0 ") ;e.g. 20060504030201 -> 2006 05 04 03 02 01
- Links:
[various date examples]
jeeswg's RegEx tutorial (RegExMatch, RegExReplace) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=28031

==================================================

DATE FORMAT: FORMATTIME (YYYYMMDDHH24MISS TO OTHER FORMATS)

- The FormatTime function can be used to present a datestamp of the form 'yyyyMMddHHmmss' in a friendly format.
- Years between 1601 and 9999 are supported.

Code: Select all

;some FormatTime examples:

;vDate := A_Now
vDate := 20060504030201

FormatTime, vDate2, % vDate, yyyy-MM-dd HH:mm:ss
MsgBox, % vDate2 ;2006-05-04 03:02:01

FormatTime, vDate2, % vDate, yyyyMMdd HHmmss
MsgBox, % vDate2 ;20060504 030201

FormatTime, vDate2, % vDate, ddd dd MMM yyyy
MsgBox, % vDate2 ;Thu 04 May 2006

FormatTime, vDate2, % vDate, yyyy-MM-dd
MsgBox, % vDate2 ;2006-05-04

FormatTime, vDate2, % vDate, HH:mm:ss
MsgBox, % vDate2 ;03:02:01
- Parts of the datestamp can be missed out:
- If the month/day is missing, it is assumed to be 1.
- If the hour/minute/second is missing, it is assumed to be 0
- E.g. if all 5 are omitted, Jan 1st 00:00:00 is assumed.

Code: Select all

;e.g. valid:
;2006 -> 20060101000000
;200605 -> 20060501000000
;20060504 -> 20060504000000
;2006050403 -> 2006050403000000
;200605040302 -> 200605040302000000

;e.g. invalid:
;2 -> 000000
;20 -> 000000
;200 -> 000000
;20060 -> 000000
;2006050 -> 000000

vDate := 2006
FormatTime, vDate, % vDate, yyyyMMddHHmmss
MsgBox, % vDate ;20060101000000

vDate := 20060 ;invalid
FormatTime, vDate, % vDate, yyyyMMddHHmmss
MsgBox, % vDate ;000000

vDate := 200601
FormatTime, vDate, % vDate, yyyyMMddHHmmss
MsgBox, % vDate ;20060101000000
- Link:
[ordinal indicators, 1st/2nd/3rd/4th etc]
jeeswg's mathematics tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=64545

==================================================

DATE FORMAT: LANGUAGES

- You can use arrays to present dates in the English language:

Code: Select all

oWDay := StrSplit("Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", ",")
oWDay3 := StrSplit("Sun,Mon,Tue,Wed,Thu,Fri,Sat", ",")
oMonth := StrSplit("January,February,March,April,May,June,July,August,September,October,November,December", ",")
oMonth3 := StrSplit("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",")

vDate := 20060504030201
FormatTime, vWDay, % vDate, WDay
MsgBox, % vWDay ;5
vWDay := oWDay3[0+vWDay]
vYear := SubStr(vDate, 1, 4)
vMonth := oMonth[0+SubStr(vDate, 5, 2)]
vDay := SubStr(vDate, 7, 2)
MsgBox, % Format("{} {} {} {}", vWDay, vDay, vMonth, vYear)
- Code using the FormatTime 'L' option to consistently get day/month names in a specific language:

Code: Select all

;FormatTime with L option: date now (locale and English):

;English (US): 0x0409 (1033)
;English (UK): 0x0809 (2057)

FormatTime, vDate1,, dddd dd MMMM yyyy
FormatTime, vDate2, L1033, dddd dd MMMM yyyy
vLang := Format("{:i}", "0x" A_Language)
FormatTime, vDate3, % "L" vLang, dddd dd MMMM yyyy
MsgBox, % vDate1 "`r`n" vDate2 "`r`n" vDate3

;FormatTime with L option: any date (locale and English):

vDate := 20060504030201
FormatTime, vDate1, % vDate, dddd dd MMMM yyyy
FormatTime, vDate2, % vDate " L1033", dddd dd MMMM yyyy
vLang := Format("{:i}", "0x" A_Language)
FormatTime, vDate3, % vDate " L" vLang, dddd dd MMMM yyyy
MsgBox, % vDate1 "`r`n" vDate2 "`r`n" vDate3
- Some examples of the FormatTime 'L' option to show a date in different languages:

Code: Select all

;show a date in different languages
;vDate := 20060504030201
vDate := A_Now

;Language Codes | AutoHotkey
;https://www.autohotkey.com/docs/misc/Languages.htm
vList := "
(
0436=Afrikaans
041c=Albanian
0401=Arabic_Saudi_Arabia
0801=Arabic_Iraq
0c01=Arabic_Egypt
0401=Arabic_Saudi_Arabia
0801=Arabic_Iraq
0c01=Arabic_Egypt
1001=Arabic_Libya
1401=Arabic_Algeria
1801=Arabic_Morocco
1c01=Arabic_Tunisia
2001=Arabic_Oman
2401=Arabic_Yemen
2801=Arabic_Syria
2c01=Arabic_Jordan
3001=Arabic_Lebanon
3401=Arabic_Kuwait
3801=Arabic_UAE
3c01=Arabic_Bahrain
4001=Arabic_Qatar
042b=Armenian
042c=Azeri_Latin
082c=Azeri_Cyrillic
042d=Basque
0423=Belarusian
0402=Bulgarian
0403=Catalan
0404=Chinese_Taiwan
0804=Chinese_PRC
0c04=Chinese_Hong_Kong
1004=Chinese_Singapore
1404=Chinese_Macau
041a=Croatian
0405=Czech
0406=Danish
0413=Dutch_Standard
0813=Dutch_Belgian
0409=English_United_States
0809=English_United_Kingdom
0c09=English_Australian
1009=English_Canadian
1409=English_New_Zealand
1809=English_Irish
1c09=English_South_Africa
2009=English_Jamaica
2409=English_Caribbean
2809=English_Belize
2c09=English_Trinidad
3009=English_Zimbabwe
3409=English_Philippines
0425=Estonian
0438=Faeroese
0429=Farsi
040b=Finnish
040c=French_Standard
080c=French_Belgian
0c0c=French_Canadian
100c=French_Swiss
140c=French_Luxembourg
180c=French_Monaco
0437=Georgian
0407=German_Standard
0807=German_Swiss
0c07=German_Austrian
1007=German_Luxembourg
1407=German_Liechtenstein
0408=Greek
040d=Hebrew
0439=Hindi
040e=Hungarian
040f=Icelandic
0421=Indonesian
0410=Italian_Standard
0810=Italian_Swiss
0411=Japanese
043f=Kazakh
0457=Konkani
0412=Korean
0426=Latvian
0427=Lithuanian
042f=Macedonian
043e=Malay_Malaysia
083e=Malay_Brunei_Darussalam
044e=Marathi
0414=Norwegian_Bokmal
0814=Norwegian_Nynorsk
0415=Polish
0416=Portuguese_Brazilian
0816=Portuguese_Standard
0418=Romanian
0419=Russian
044f=Sanskrit
081a=Serbian_Latin
0c1a=Serbian_Cyrillic
041b=Slovak
0424=Slovenian
040a=Spanish_Traditional_Sort
080a=Spanish_Mexican
0c0a=Spanish_Modern_Sort
100a=Spanish_Guatemala
140a=Spanish_Costa_Rica
180a=Spanish_Panama
1c0a=Spanish_Dominican_Republic
200a=Spanish_Venezuela
240a=Spanish_Colombia
280a=Spanish_Peru
2c0a=Spanish_Argentina
300a=Spanish_Ecuador
340a=Spanish_Chile
380a=Spanish_Uruguay
3c0a=Spanish_Paraguay
400a=Spanish_Bolivia
440a=Spanish_El_Salvador
480a=Spanish_Honduras
4c0a=Spanish_Nicaragua
500a=Spanish_Puerto_Rico
0441=Swahili
041d=Swedish
081d=Swedish_Finland
0449=Tamil
0444=Tatar
041e=Thai
041f=Turkish
0422=Ukrainian
0420=Urdu
0443=Uzbek_Latin
0843=Uzbek_Cyrillic
042a=Vietnamese
)"

vOutput := ""
Loop Parse, vList, % "`n", % "`r"
{
	oTemp := StrSplit(A_LoopField, "=")
	vLang := Format("{:i}", "0x" oTemp.1)
	FormatTime, vDate2, % vDate " L" vLang, dddd dd MMMM yyyy
	vOutput .= vDate2 "`t" oTemp.2 "`r`n"
}
Clipboard := vOutput
MsgBox, % "done"
return
- Some information on language IDs:

Code: Select all

;source: ntdef.h
;source: winnt.h
;#define MAKELANGID(p, s)       ((((USHORT)(s)) << 10) | (USHORT)(p))
;LANG_ENGLISH := 0x9
;SUBLANG_ENGLISH_US := 0x1
;SUBLANG_ENGLISH_UK := 0x2
;English (US): 0x0409 (1033)
;English (UK): 0x0809 (2057)
- Links:
MAKELANGID macro (winnt.h) | Microsoft Docs
https://docs.microsoft.com/en-gb/windows/desktop/api/winnt/nf-winnt-makelangid
Language Identifiers - Windows applications | Microsoft Docs
https://docs.microsoft.com/en-gb/windows/desktop/Intl/language-identifiers
Language Codes | AutoHotkey
https://www.autohotkey.com/docs/misc/Languages.htm
MAKELANGID()
https://hotkeyit.github.io/v2/docs/commands/MAKELANGID.htm
MAKELCID()
https://hotkeyit.github.io/v2/docs/commands/MAKELCID.htm

==================================================

DATE FORMAT: CONVERT DATES (OTHER FORMATS TO YYYYMMDDHH24MISS)

- To convert between date formats, you typically:
- Check a date is of a known format.
- Split it. (Into year/month/day/hour/minute/second.)
- Combine it in a new way.

- Converting between date formats is usually pretty easy, however handling AM/PM can be fiddly:
- Some notes on converting to/from 24-hour/12-hour:

Code: Select all

;24-hour to 12-hour

vOutput := ""
Loop 24
{
	vHour24 := Format("{:02}", A_Index-1)
	vDate := Format("19990101{:02}", vHour24)
	FormatTime, vHour12, % vDate, hh tt
	vOutput .= vHour24 " " vHour12 "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==============================

;the logic of 12-hour to 24-hour
;12 AM -> 0
;1-11 AM -> 1-11 (unchanged)
;12 PM -> 12 (unchanged)
;1-11 PM -> 13-23 (add 12)

;note: 11 *AM* is followed by 12 *PM*
;note: 11 *PM* is followed by 12 *AM*

vList := "12 AM,1 AM,2 AM,3 AM,4 AM,5 AM,6 AM,7 AM,8 AM,9 AM,10 AM,11 AM"
vList .= ",12 PM,1 PM,2 PM,3 PM,4 PM,5 PM,6 PM,7 PM,8 PM,9 PM,10 PM,11 PM"

Loop Parse, vList, % ","
{
	oTemp := StrSplit(A_LoopField, " ")
	if (oTemp.2 = "AM") && (oTemp.1 = 12) ;12 AM -> 0
		oTemp.1 := 0
	else if (oTemp.2 = "PM") && !(oTemp.1 = 12) ;1-11 PM -> 13-23 (add 12)
		oTemp.1 += 12
	;1-11 AM -> 1-11 (unchanged)
	;12 PM -> 12 (unchanged)
	oTemp.1 := Format("{:02}", oTemp.1)
	vOutput .= oTemp.1 " " A_LoopField "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==============================

;convert AM/PM dates

FormatTime, vDateEg1,, d/M/yyyy HH:mm:ss tt
vDateEg2 := "4/5/2006 12:00:00 AM" ;24-hour: 00
vDateEg3 := "4/5/2006 06:00:00 AM" ;24-hour: 06
vDateEg4 := "4/5/2006 12:00:00 PM" ;24-hour: 12
vDateEg5 := "4/5/2006 06:00:00 PM" ;24-hour: 18

Loop 5
{
	vDate := vDateEg%A_Index%

	if !RegExMatch(vDate, "^\d+/\d+/\d+ \d+:\d+:\d+ [AP]M$")
	{
		MsgBox, % "error: nonstandard date:`r`n" vDate
		return
	}
	vDate2 := RegExReplace(vDate, "^(\d+)/(\d+)/(\d+) (\d+:\d+:\d+).*$", "$3:$2:$1:$4")
	oDate := StrSplit(vDate2, ":")
	if RegExMatch(vDate, "AM$") && (oDate.4 = 12)
		oDate.4 := 0
	else if RegExMatch(vDate, "PM$") && !(oDate.4 = 12)
		oDate.4 += 12
	vDate2 := Format("{:04}{:02}{:02}{:02}{:02}{:02}", oDate*)
	MsgBox, % vDate "`r`n" vDate2
}
return
- Some code re. converting datestamps:

Code: Select all

;combine individual date numbers into a 'yyyyMMddHHmmss' datestamp
;2006 5 4 3 2 1 -> 20060504030201
vDate := "2006 5 4 3 2 1"
oTemp := StrSplit(vDate, " ")
vDate := Format("{}{:02}{:02}{:02}{:02}{:02}", oTemp*)
MsgBox, % vDate

==============================

;4 different ways to convert 'HH:mm dd/MM/yyyy' to 'yyyyMMddHHmm00'
vDate := "03:02 04/05/2006"

;is date of form 'HH:mm dd/MM/yyyy'
MsgBox, % RegExMatch(vDate, "^\d\d:\d\d \d\d/\d\d/\d\d\d\d$")
MsgBox, % RegExMatch(vDate, "^\d{2}:\d{2} \d{2}/\d{2}/\d{4}$") ;equivalent to line above

vDate1 := RegExReplace(vDate, "(..):(..)() (..)/(..)/(....)", "$6$5$4$1$200")

vDate2 := SubStr(vDate, 13, 4) SubStr(vDate, 10, 2) SubStr(vDate, 7, 2) SubStr(vDate, 1, 2) SubStr(vDate, 4, 2) "00"

vDate3 := RegExReplace(vDate, "[/:]", " ")
oTemp := StrSplit(vDate3, " ")
vDate3 := Format("{5}{4}{3}{1}{2}00", oTemp*)

vDate4 := RegExReplace(vDate, "[/:]", " ")
oTemp := StrSplit(vDate4, " ")
vDate4 := oTemp.5 oTemp.4 oTemp.3 oTemp.1 oTemp.2 "00"

MsgBox, % vDate1 "`r`n" vDate2 "`r`n" vDate3 "`r`n" vDate4

;==============================

;4 different ways to convert 'HH:mm:ss dd/MM/yyyy' to 'yyyyMMddHHmm00'

vDate := "03:02:01 04/05/2006"

;is date of form 'HH:mm dd/MM/yyyy'
MsgBox, % RegExMatch(vDate, "^\d\d:\d\d:\d\d \d\d/\d\d/\d\d\d\d$")
MsgBox, % RegExMatch(vDate, "^\d{2}:\d{2}:\d{2} \d{2}/\d{2}/\d{4}$") ;equivalent to line above

vDate1 := RegExReplace(vDate, "(..):(..):(..) (..)/(..)/(....)", "$6$5$4$1$2$3")

vDate2 := SubStr(vDate, 16, 4) SubStr(vDate, 13, 2) SubStr(vDate, 10, 2) SubStr(vDate, 1, 2) SubStr(vDate, 4, 2) SubStr(vDate, 7, 2)

vDate3 := RegExReplace(vDate, "[/:]", " ")
oTemp := StrSplit(vDate3, " ")
vDate3 := Format("{6}{5}{4}{1}{2}{3}", oTemp*)

vDate4 := RegExReplace(vDate, "[/:]", " ")
oTemp := StrSplit(vDate4, " ")
vDate4 := oTemp.6 oTemp.5 oTemp.4 oTemp.1 oTemp.2 oTemp.3

MsgBox, % vDate1 "`r`n" vDate2 "`r`n" vDate3 "`r`n" vDate4

;==============================

;one way to handle month words is to replace them

oMonth := StrSplit("January,February,March,April,May,June,July,August,September,October,November,December", ",")
oMonth3 := StrSplit("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",")

vDate := "04 May 2006"
for vIndex, vMonth in oMonth
	vDate := StrReplace(vDate, vMonth, vIndex)
for vIndex, vMonth in oMonth3
	vDate := StrReplace(vDate, vMonth, vIndex)
MsgBox, % vDate

vDate := "04 May 2006"
for vIndex, vMonth in oMonth
	vDate := StrReplace(vDate, vMonth, Format("{:02}", vIndex))
for vIndex, vMonth in oMonth3
	vDate := StrReplace(vDate, vMonth, Format("{:02}", vIndex))
MsgBox, % vDate

;==============================

;one way to handle month words is to look them up

vDate := "04 May 2006"

oMonth := StrSplit("January,February,March,April,May,June,July,August,September,October,November,December", ",")
oMonth3 := StrSplit("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",")

oTemp := StrSplit(vDate, " ")
Loop 12
{
	if (oTemp.2 = oMonth[vIndex := A_Index])
	|| (oTemp.2 = oMonth3[vIndex := A_Index])
	{
		oTemp.2 := vIndex
		break
	}
}
MsgBox, % Format("{} {} {}", oTemp*)
MsgBox, % Format("{:02} {:02} {:02}", oTemp*)
- Links:
[rearrange/convert between datestamps]
Format typed date - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=65066
FormatTime Issues - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=63317&p=270955#p270955

==================================================

DATE FORMAT: DECIMAL TIME ('METRIC TIME')

- Link:
decimal time ('metric time') - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=65474

==================================================

GET DATE: PC SWITCHED ON / PROCESS OPENED / FILE MODIFIED

PC SWITCHED ON / TICK COUNT

- AutoHotkey has a built-in variable A_TickCount which returns the milliseconds elapsed since the system was started.
- Note: the value resets to 0 after 49.7 days. This is because A_TickCount uses the Winapi function GetTickCount, which uses a 4-byte DWORD.
256**4-1 = 4294967295
4294967295 / (24*60*60*1000) = 49.71027
- Some examples of retrieving the tick count:

Code: Select all

;note: Format is used for UInt64 numbers,
;because AHK does not natively handle UInt64 numbers,
;for DllCall, 'UInt64' returns an Int64,
;Format can display the Int64 as a UInt64 string

vTickCount1 := A_TickCount
vTickCount2 := DllCall("kernel32\GetTickCount", "UInt")
vTickCount3 := Format("{:u}", DllCall("kernel32\GetTickCount64", "UInt64"))
MsgBox, % vTickCount1 "`r`n" vTickCount2 "`r`n" vTickCount3
- An example of converting the tick count to a friendly format:

Code: Select all

;get time/date computer switched on, and time since computer switched on
vSec := A_TickCount//1000
vDate := A_Now
EnvAdd, vDate, % -vSec, Seconds
FormatTime, vWDay, % vDate, ddd
FormatTime, vDate, % vDate, HH:mm dd/MM/yyyy
vDate := vWDay " " vDate
vDHMS := Format("{}d {}h {}m {}s", vSec//86400, Mod(vSec,86400)//3600, Mod(vSec,3600)//60, Mod(vSec,60))
MsgBox, % "on at: " vDate "`r`n" "on for: " vDHMS
- An example of using tick counts to measure the duration of an event:

Code: Select all

;get the duration that a MsgBox is displayed in milliseconds
vTickCount := A_TickCount
MsgBox
vMSec := A_TickCount - vTickCount
MsgBox, % vMSec " ms"
- An example of using QueryPerformanceCounter to measure the duration of an event:

Code: Select all

;based on:
;DllCall() - Syntax & Usage | AutoHotkey
;https://www.autohotkey.com/docs/commands/DllCall.htm#QPC

DllCall("kernel32\QueryPerformanceFrequency", "Int64*",vFreq)
DllCall("kernel32\QueryPerformanceCounter", "Int64*",vCount1)
Sleep, 1000
DllCall("kernel32\QueryPerformanceCounter", "Int64*",vCount2)
MsgBox, % (vCount2 - vCount1) / vFreq * 1000 " ms"
PROCESS OPENED

- Some example code to get the date/time that a process was opened.

Code: Select all

q:: ;get date process opened
WinGet, vPID, PID, A
MsgBox, % ProcessGetCreationTime(vPID, "yyyy-MM-dd HH:mm:ss")
return

ProcessGetCreationTime(vPIDOrName, vFormat:="yyyyMMddHHmmss", vOpt:="")
{
	local
	if !(vPID := ProcessExist(vPIDOrName))
		return 0
	vIntervalsUTC := vIntervalsLocal := 0
	;PROCESS_QUERY_INFORMATION := 0x400
	hProc := DllCall("kernel32\OpenProcess", "UInt",0x400, "Int",0, "UInt",vPID, "Ptr")
	DllCall("kernel32\GetProcessTimes", "Ptr",hProc, "Int64*",vIntervalsUTC, "Ptr",0, "Ptr",0, "Ptr",0)
	DllCall("kernel32\CloseHandle", "Ptr",hProc)
	if InStr(vOpt, "U")
		vIntervalsLocal := vIntervalsUTC
	else
		DllCall("kernel32\FileTimeToLocalFileTime", "Int64*",vIntervalsUTC, "Int64*",vIntervalsLocal)
	vDate := 1601
	vDate := DateAdd(vDate, vIntervalsLocal//10000000, "S")
	if !(vFormat == "yyyyMMddHHmmss")
		vDate := FormatTime(vDate, vFormat)
	return vDate
}

;==================================================

;commands as functions (AHK v2 functions for AHK v1) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=37&t=29689

DateAdd(DateTime, Time, TimeUnits)
{
	EnvAdd, DateTime, % Time, % TimeUnits
	return DateTime
}
DateDiff(DateTime1, DateTime2, TimeUnits)
{
	EnvSub, DateTime1, % DateTime2, % TimeUnits
	return DateTime1
}
FormatTime(YYYYMMDDHH24MISS:="", Format:="")
{
	local OutputVar
	FormatTime, OutputVar, % YYYYMMDDHH24MISS, % Format
	return OutputVar
}
ProcessExist(PIDOrName:="")
{
	Process, Exist, % PIDOrName
	return ErrorLevel
}

;==================================================
PROCESS OPENED (NOTIFIER)

- Some code to detect when processes start/end:
- (It is copied from the forum, but with the colour tags removed.)

Code: Select all

;New Process Notifier - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/56984-new-process-notifier/#entry358038

; Get WMI service object.
winmgmts := ComObjGet("winmgmts:")

; Create sink objects for receiving event noficiations.
ComObjConnect(createSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessCreate_")
ComObjConnect(deleteSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessDelete_")

; Set event polling interval, in seconds.
interval := 2

; Register for process creation notifications:
winmgmts.ExecNotificationQueryAsync(createSink
    , "Select * from __InstanceCreationEvent"
    . " within " interval
    . " where TargetInstance isa 'Win32_Process'")

; Register for process deletion notifications:
winmgmts.ExecNotificationQueryAsync(deleteSink
    , "Select * from __InstanceDeletionEvent"
    . " within " interval
    . " where TargetInstance isa 'Win32_Process'")

; Don't exit automatically.
#Persistent

; Called when a new process is detected:
ProcessCreate_OnObjectReady(obj) {
    proc := obj.TargetInstance
    TrayTip New Process Detected, % "
    (LTrim
        ID:`t" proc.ProcessID "
        Parent:`t" proc.ParentProcessID "
        Name:`t" proc.Name "
        Path:`t" proc.ExecutablePath "

        Command line (requires XP or later):

        " proc.CommandLine
    )
}

; Called when a process terminates:
ProcessDelete_OnObjectReady(obj) {
    proc := obj.TargetInstance
    TrayTip Process Terminated, % "
    (LTrim
        ID:`t" proc.Handle "
        Name:`t" proc.Name
    )
}
WINDOW CREATED (NOTIFIER)

- Example code that beeps when a window is created:

Code: Select all

;beep when a window is created

#Persistent
DetectHiddenWindows, On
DllCall("user32\RegisterShellHookWindow", "Ptr",A_ScriptHwnd)
OnMessage(DllCall("user32\RegisterWindowMessage", "Str","SHELLHOOK", "UInt"), "OnWinNew")
return

OnWinNew(wParam, lParam)
{
	if (wParam = 1) ;HSHELL_WINDOWCREATED := 1
	{
		SoundBeep
		;WinGetTitle, vWinTitle, % "ahk_id " lParam
		;MsgBox, % vWinTitle
	}
}
- Links:
[How to] Hook on to Shell to receive its messages? - Tutorials - AutoHotkey Community
https://autohotkey.com/board/topic/80644-how-to-hook-on-to-shell-to-receive-its-messages/
Detect when a new window is opened. - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/19672-detect-when-a-new-window-is-opened/

FILE MODIFIED DATE

- In AHK, the FileGetTime command, and the file loop (A_LoopFileTimeModified/A_LoopFileTimeCreated/A_LoopFileTimeAccessed), can be used to get the date modified/created/accessed for a file/folder.
- FileGetTime and the A_ variables always return the local date (adjusted for the current time zone).
- Here is an example to get the file modified date as UTC.
- Note: you could use the custom DateUTCToLocal function, introduced above, to convert a UTC date to a local date (based on the current/date's time zone).

Code: Select all

vPath := A_AhkPath
;vPath := A_ScriptFullPath
FileGetTime, vDateM1, % vPath, M
vDateM2 := FileGetTimeUTC(vPath, "M")
MsgBox, % vDateM1 "`r`n" vDateM2

FileGetTimeUTC(vPath, vOpt)
{
	if InStr(vOpt, "M")
		vOffset := 20
	else if InStr(vOpt, "C")
		vOffset := 4
	else if InStr(vOpt, "A")
		vOffset := 12
	else
		return
	if !FileExist(vPath)
		return
	VarSetCapacity(WIN32_FIND_DATA, 592, 0)
	hFile := DllCall("kernel32\FindFirstFile", "Str",vPath, "Ptr",&WIN32_FIND_DATA, "Ptr")
	if (hFile = -1) || (hFile = "")
		return
	DllCall("kernel32\FindClose", "Ptr",hFile)
	vIntervals := NumGet(&WIN32_FIND_DATA, vOffset, "UInt64")
	vDate := 1601
	EnvAdd, vDate, % vIntervals//10000000, S
	return vDate
}
FILE MODIFIED DATE (ODDITIES)

- When file date issues occur, it is worth noting the file system, e.g. FAT or NTFS.
- E.g. when files are copied from NTFS to FAT, any modified dates are rounded up the nearest 2 seconds. I.e. different systems store dates to different precisions.

- One oddity I have not had a full explanation for, although which is probably DST-related, is this:
- I have had copies of files on a PC and on a backup drive, but where the modified dates differ by exactly 1 or 2 hours.

- Links:
c# - Why does File Modified Time automatically increase by 2 seconds when copied to USB? - Stack Overflow
https://stackoverflow.com/questions/11546839/why-does-file-modified-time-automatically-increase-by-2-seconds-when-copied-to-u
Strange: Incorrect modification time (seconds) of a copyed file - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=40803&p=186247#p186247

==================================================

DATE LIST: DATE RANGE (EXPAND/CONTRACT LIST)

- Example code to expand/contract a list of dates:
- E.g. convert '19990109-19990112' to/from '19990109,19990110,19990111,19990112'.

Code: Select all

;expand a list of dates
vList := "19990101,19990104-19990105,19990109-19990112,19990120"
vList2 := DateListExpand(vList) ;19990101,19990104,19990105,19990109,19990110,19990111,19990112,19990120
MsgBox, % vList "`r`n`r`n" vList2

;contract list of dates
vList := "19990101,19990104,19990105,19990109,19990110,19990111,19990112,19990120"
vList2 := DateListContract(vList) ;19990101,19990104-19990105,19990109-19990112,19990120
MsgBox, % vList "`r`n`r`n" vList2
return

;==================================================

DateListExpand(vList)
{
	vOutput := ""
	Loop Parse, vList, % ","
	{
		oTemp := StrSplit(A_LoopField, "-")
		if (oTemp.Length() = 1)
			vOutput .= oTemp.1 ","
		else if (oTemp.Length() = 2)
		{
			FormatTime, vDate1, % oTemp.1, yyyyMMddHHmmss
			FormatTime, vDate2, % oTemp.2, yyyyMMddHHmmss
			if (vDate2 < vDate1)
				return ""
			Loop
			{
				vOutput .= SubStr(vDate1, 1, 8) ","
				EnvAdd, vDate1, 1, Days
				if (vDate1 > vDate2)
					break
			}
		}
		else
			return ""
	}
	return SubStr(vOutput, 1, -1)
}

;==================================================

DateListContract(vList)
{
	vOutput := "", vList .= ","
	Loop Parse, vList, % ","
	{
		if !(A_Index = 1)
		{
			vTemp := vLast
			EnvAdd, vTemp, 1, Days
			vTemp := SubStr(vTemp, 1, 8)
		}
		if (A_Index = 1)
			vMem := A_LoopField, vCount := 1
		else if !(A_LoopField = vTemp)
		{
			if (vCount = 1)
				vOutput .= vMem ","
			else
				vOutput .= vMem "-" vLast ","
			vMem := A_LoopField, vCount := 1
		}
		else
			vCount++
		vLast := A_LoopField
	}
	return SubStr(vOutput, 1, -1)
}
- Links:
[similar functions for integers: NumListExpand/NumListContract]
jeeswg's mathematics tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=64545

==================================================

DATE LIST: SORT BY DATE

- Example code to sort dates using custom comparison functions:

Code: Select all

;sort dates using custom comparison functions
vList := "03/01/1999,04/01/1999,05/09/1999,02/06/1999"

Sort, vList, D, F SortDateDMY
MsgBox, % "dd/MM/yyyy:`r`n" StrReplace(vList, ",", "`r`n")

Sort, vList, D, F SortDateMDY
MsgBox, % "MM/dd/yyyy:`r`n" StrReplace(vList, ",", "`r`n")
return

;==================================================

SortDateDMY(vDateA, vDateB, vOffset)
{
	local
	;'dd/MM/yyyy' -> 'yyyyMMdd':
	vDateA := RegExReplace(vDateA, "^(\d{2})/(\d{2})/(\d{4})$", "$3$2$1", vCountA)
	vDateB := RegExReplace(vDateB, "^(\d{2})/(\d{2})/(\d{4})$", "$3$2$1", vCountB)
	(!vCountA) && (vDateA := 0)
	(!vCountB) && (vDateB := 0)
	return (vDateA > vDateB) ? 1 : (vDateA < vDateB) ? -1 : -vOffset
}

;==================================================

SortDateMDY(vDateA, vDateB, vOffset)
{
	local
	;'MM/dd/yyyy' -> 'yyyyMMdd':
	vDateA := RegExReplace(vDateA, "^(\d{2})/(\d{2})/(\d{4})$", "$3$1$2", vCountA)
	vDateB := RegExReplace(vDateB, "^(\d{2})/(\d{2})/(\d{4})$", "$3$1$2", vCountB)
	(!vCountA) && (vDateA := 0)
	(!vCountB) && (vDateB := 0)
	return (vDateA > vDateB) ? 1 : (vDateA < vDateB) ? -1 : -vOffset
}
==================================================

LINKS (JULIAN/GREGORIAN CALENDARS)

- Links:
[Julian to/from Gregorian]
Julian date converter for google daterange search - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/19644-julian-date-converter-for-google-daterange-search/#entry129274
Julian dates - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/23673-julian-dates/
Conversion between Julian and Gregorian calendars - Wikipedia
https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars

- Links (further):
[Func] Calculating date of christian easter - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/41342-func-calculating-date-of-christian-easter/
Calendar - Rosetta Code
https://rosettacode.org/wiki/Calendar#AutoHotkey
1/1/1500 gregorian to julian - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=1%2F1%2F1500+gregorian+to+julian
1/1/1500 julian to gregorian - Wolfram|Alpha
https://www.wolframalpha.com/input/?i=1%2F1%2F1500+julian+to+gregorian

==================================================

LINKS (OTHER)

[leap seconds]
How to convert current time to unix timestamp? - Page 2 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=61194&start=20
Unix time - Wikipedia
https://en.wikipedia.org/wiki/Unix_time
Leap second - Wikipedia
https://en.wikipedia.org/wiki/Leap_second#Insertion_of_leap_seconds

[some examples of retrieving the date via the Internet]
Script checks date and not open on two computers at the same time - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=46192

[round down to the nearest 5 days]
Show date as previous 5/10/15/20th day of month - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=32514

[AutoHotkey's date functionality recreated]
AutoHotkey via DllCall: AutoHotkey functions as custom functions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=37871

[Windows 95/98: unfortunately-designed Date/Time control panel]
The Date/Time control panel is not a calendar | The Old New Thing
https://devblogs.microsoft.com/oldnewthing/20050621-04/?p=35253

==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
lmstearn
Posts: 688
Joined: 11 Aug 2016, 02:32
Contact:

Re: jeeswg's dates tutorial

Post by lmstearn » 20 Jan 2020, 08:09

MAKELANGID wants locale names from GetLocaleinfoEx now. Excerpt from winnt.h:
Spoiler
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH
DanRim
Posts: 153
Joined: 20 Jul 2018, 15:16

Re: jeeswg's dates tutorial

Post by DanRim » 18 Sep 2020, 10:45

@jeeswg Thank you for sharing this content. It took some time to got thought all examples and check them all but it was worth it. I learned something for sure. I really sincerely thankful for this tutorial.
User avatar
SirSocks
Posts: 360
Joined: 26 Oct 2018, 08:14

Re: jeeswg's dates tutorial

Post by SirSocks » 10 Jul 2021, 22:06

This is very informative and helpful. Thank you for all of your tutorials. :bravo:
Post Reply

Return to “Tutorials (v1)”