Tempus is a DateTime library for AutoHotkey. It is, essentially, a wrapper to expose the API of the Rust jiff crate in AHK.
So, to know what tempus.ahk is about, is to know what jiff is about:
Jiff is a datetime library for Rust that encourages you to jump into the pit of success. The focus of this library is providing high level datetime primitives that are difficult to misuse and have reasonable performance. Jiff supports automatic and seamless integration with the Time Zone Database, DST aware arithmetic and rounding, formatting and parsing zone aware datetimes losslessly, [...] and a whole lot more.
Jiff takes enormous inspiration from Temporal, which is a TC39 proposal to improve datetime handling in JavaScript.
Right now, only a portion of the API is implemented, but development towards completion is rapidly underway.
This project has two components: the compiled tempus_ahk.dll
and the tempus.ahk
script, which is intended to be used
via #Include
. This script is for AHK v2 only. In principle,
the DLL can also be used with AHK v1, but no such script is provided.
From the releases page you can download the compiled
tempus_ahk.dll
file and tempus.ahk
file (or the tempus_ahk.zip
containing these). To ensure Dll loading works correctly, you should ensure that
tempus_ahk.dll
is somewhere on the Dll Library load search path, such as in the working directory, or
a directory on PATH
. Alternatively, you may provide your own DllLoad
directive before #Include tempus.ahk
to load the DLL. See
DllLoad for more information.
See also: Binary security.
The exposed AHK API aims to mirror, as much as is reasonable, the API of jiff
. Most of the usage is a straightforward
translation from the rust API for jiff.
For example, in Rust with jiff
:
extern crate jiff;
use jiff::{Timestamp, ToSpan};
let time: Timestamp = "2024-07-11T01:14:00Z".parse().unwrap();
assert_eq!(time.as_second(), 1720660440);
Looks like this with tempus.ahk
:
#Include "tempus.ahk"
time := Timestamp.parse("2024-07-11T01:14:00Z")
MsgBox(time.as_second())
Jiff Timestamp
Timestamp.strptime
/ Timestamp.as_second
ts := Timestamp.strptime("%F %H:%M %:z", "2024-07-14 21:14 -04:00")
MsgBox(ts.as_second()) ; 1721006040
Timestamp.parse
/ Timestamp.to_string
ts := Timestamp.parse("2024-01-01T00:00:00Z")
MsgBox(ts.to_string()) ; 2024-01-01T00:00:00Z
Timestamp.strftime
/ Timestamp.from_second
ts := Timestamp.from_second(86400)
MsgBox(ts.strftime("%a %b %e %I:%M:%S %p UTC %Y")) ; Fri Jan 2 12:00:00 AM UTC 1970
Timestamp.round
round
takes three arguments: the rounding unit, the increment, and the rounding mode.
ts := Timestamp.parse("2024-06-20 03:25:01Z")
rounded := ts.round(Unit.Minute, 1, RoundMode.Ceil)
MsgBox(rounded.to_string()) ; 2024-06-20T03:26:00Z
Convenience objects are available for specifying the unit and mode:
Unit := {
Nanosecond: 0,
Microsecond: 1,
Millisecond: 2,
Second: 3,
Minute: 4,
Hour: 5,
Day: 6,
Week: 7,
Month: 8,
Year: 9
}
RoundMode := {
Ceil: 1,
Floor: 2,
Expand: 3,
Trunc: 4,
HalfCeil: 5,
HalfFloor: 6,
HalfExpand: 7,
HalfTrunc: 8,
HalfEven: 9,
}
Jiff Span
span1 := Span.new().hours(2).minutes(59)
span2 := Span.new().minutes(2)
span3 := span1.checked_add(span2)
MsgBox(span3.to_string()) ; PT3H1M
span.parse
/ span.round
span1 := Span.parse("PT23h50m3.123s")
expected := Span.new().hours(24)
rounded := span1.round(Unit.Minute, 30)
expected.eq(rounded) ; true
span.total
span1 := Span.new().hours(3).minutes(10)
MsgBox(span1.total(Unit.Second)) ; 11400.0
Comparisons
The methods eq
, gt
, lt
, gte
, and lte
can be used to compare two span objects
span1 := Span.new().hours(3)
span2 := Span.new().minutes(180)
if (span1.eq(span2)) { ; true
MsgBox("They are equal in length")
}
By default, jiff
takes into account various factors when comparing spans and does not assume all days are 24 hours.
Therefore, when a span's smallest component is days or greater (that is, it includes a calendar component),
you either need to associate a relative datetime (Because, for example, 1 month from March 1 is 31 days, but 1 month from April 1 is 30 days.)
or (to compare weeks) opt into an assumption/invariant of days being calculated as 24 hours.
for example:
span1 := Span.new().weeks(4)
span2 := Span.new().days(30)
span1.eq(span2) ; error!
But opting into the 24-hour-days invariant (by passing true
as the second argument to the compare method) allows this:
span1 := Span.new().weeks(4)
span2 := Span.new().days(30)
; opt into 24-hour-days invariant to allow comparison of days/weeks
span1.gt(span2, true) ; OK!
Support for specifying a relative timeframe is not yet available.
Jiff SignedDuration
duration := SignedDuration.parse("2h 30m")
This project is distributed, in part, as a DLL file. DLL files are software compiled in binary form. Because these files are not human-readable, it is important that you can trust the authors that produce them and that you can verify the authenticity and integrity of the file you downloaded.
For this project, the DLL binaries in the releases page are
digitally signed as part of the GitHub Action where they are built. This digital signature can be used to verify that
you are receiving an authentic copy of tempus_ahk.dll
that has not been tampered with.
You can view the digital signature by right-clicking the tempus_ahk.dll
file, selecting "properties", clicking the "Digital Signatures" tab
and locating the digital signature of "Young Enterprise Solutions LLC" whose signing certificate is issued by Microsoft.
If you do not see the "Digital Signatures" tab or the signature shows as invalid or is signed by any other entity,
that means you do not have an authentic signed copy of the tempus_ahk.dll
binary.
Moreover, the releases page also contains the hashes of all release files for each release. These can be used to verify their integrity. We also proactively submit our DLLs to VirusTotal to ensure our files are free of unexpected detections. You can find the links in the releases page.
Alternatively, you may build this binary yourself from source using Rust. See the Building notes below.
Building this project is fairly simple. But I will try to explain the steps for those who may not have prior experience building Rust projects.
It's expected you already have Rust installed (e.g., you can run rustup
, cargo
, etc.).
Prerequisites:
This project uses the GNU toolchain by default, so you will need this installed and ensure you add the target with rustup
:
- Add the
x86_64-pc-windows-gnu
target:rustup target add x86_64-pc-windows-gnu
- Ensure you have the compatible linker on PATH (e.g. you can run
x86_64-w64-mingw32-gcc --version
to verify this). For example, you can installMSYS2
and have the toolchain bin directory (C:\msys64\mingw64\bin
) onPATH
. If you don't see files in this directory, you must install the toolchain by opening the mingw bash shell (C:\msys64\Mingw64.exe
) and running the commandpacman -S --needed base-devel mingw-w64-x86_64-toolchain
Build:
- run
cargo build --release
which should produce the DLL located attarget/x86_64-pc-windows-gnu/release/tempus_ahk.dll
If you struggle with building on the GNU toolchain with MSYS2, you can build against the default Windows target
by running cargo build --target x86_64-pc-windows-msvc
. Though note that the produced DLL will have a dependency
on vcruntime140.dll
, so target machines you run this on will need the VC redistributable package
installed (which, in all likelihood, many users already have due to this being a fairly ubiquitous dependency).
This is mostly for loose reference. Not all methods will be implemented. Not all methods are listed here (especially things like trait impls, arithmetic, comparisons and more). But may give you an idea of what will be available.
- now
-
parse
(equivalent oflet ts: Timestamp = "2024-07-11T01:14:00Z".parse()
) --Timestamp.parse(mystring)
in AHK - new
- UNIX_EPOCH
- from_second
- from_millisecond
- from_microsecond
-
from_nanosecondNot supported - from_duration
- as_second
- as_millisecond
- as_microsecond
-
as_nanosecondNot supported - subsec_millisecond
- subsec_microsecond
- subsec_nanosecond
- as_duration
- signum
- is_zero
- in_tz
- to_zoned
- checked_add
- checked_sub
- saturating_add
- saturating_sub
- until
- since
- duration_until
- duration_since
- round
- series
- strptime
- strftime
- display_with_offset
-
to_string
(Display
trait impl) - Others (trait impls, arithmetic, comparisons)
- now
-
parse
(equivalent oflet ts: Zoned = "2024-07-11T01:14:00Z".parse()
) --Zoned.parse(mystring)
in AHK - new
- with
- with_time_zone
- in_tz
- time_zone
- year
- era_year
- month
- day
- hour
- minute
- second
- millisecond
- microsecond
- nanosecond
- subsec_nanosecond
- weekday
- day_of_year
- day_of_year_no_leap
- start_of_day
- end_of_day
- first_of_month
- last_of_month
- days_in_month
- first_of_year
- last_of_year
- days_in_year
- in_leap_year
- tomorrow
- yesterday
- nth_weekday_of_month
- nth_weekday
- timestamp
- datetime
- date
- time
- iso_week_date
- offset
- checked_add
- checked_sub
- saturating_add
- saturating_sub
- until
- since
- duration_until
- duration_since
- round
- strptime
- strftime
- Others (trait impls (to_string, parse/from_str, etc.), arithmetic, comparisons)
- new
-
parse
- years
- months
- weeks
- days
- hours
- minutes
- seconds
- milliseconds
- microseconds
- nanoseconds
- get_years
- get_months
- get_weeks
- get_days
- get_hours
- get_minutes
- get_seconds
- get_milliseconds
- get_microseconds
- get_nanoseconds
- abs
- negate
- signum
- is_positive
- is_negative
- is_zero
-
fieldwise - checked_mul (for
Span
only so far) - checked_add (for
Span
only so far) - checked_sub
- compare
- compare (with relative timeframe)
- total
- total (with relative timeframe)
- round
- round (with relative timeframe)
- to_duration
- Others (trait impls (to_string, parse/from_str, etc.), arithmetic, comparisons)
(some variants will likely just be implemented once with 64bit precision)
-
parse
-
ZERO
-
MIN
-
MAX
- cmp (
compare
,lt
,gt
,gte
,lte
,eq
) - new
- from_secs
- from_millis
- from_micros
- from_nanos
- from_hours
- from_mins
- is_zero
- as_secs
- subsec_millis
- subsec_micros
- subsec_nanos
- as_millis
- as_micros
-
as_nanosnot supported - checked_add
- saturating_add
- checked_sub
- saturating_sub
- checked_mul
- saturating_mul
- checked_div
-
as_secs_f64useas_secs
-
as_secs_f32 -
as_millis_f64useas_millis
-
as_millis_f32 -
from_secs_f64usefrom_secs
-
from_secs_f32 -
try_from_secs_f64 -
try_from_secs_f32 - mul_f64
- mul_f32
- div_f64
- div_f32
- div_duration_f64
-
div_duration_f32 - as_hours
- as_mins
- abs
-
unsigned_abs(std::time::Duration
support not planned for now) - checked_neg
- signum
- is_positive
- is_negative
- system_until
- round
- system (uses try_system and fallsback to UTC on failure)
- get
- fixed
- posix
- tzif
- unknown
- iana_name
- is_unknown
- to_datetime
- to_offset
- to_offset_info
- to_fixed_offset
- to_zoned
- to_ambiguous_zoned
- into_ambiguous_zoned
- to_timestamp
- to_ambiguous_timestamp
- preceding
- following