DateTimeMate
July 18, 2026 ยท View on GitHub
Golang package and CLI to compute the difference between date, time or duration
The command-line program, dtmate (along with the golang package) allows you to answer these inquiries:
1. What is the duration between two different dates and/or times?
dtmate diff "2024-06-01 11:22:33" "2024-07-19 21:07:19"
- answer:
6 weeks 6 days 9 hours 44 minutes 46 seconds - answer with the
-boption:6W6D9h44m46s - start and end can be in various formats, such as:
-
11:22:33,2024-06-01,"2024-06-01 11:22:33",2024-06-01T11:22:33.456Z
2. What is the datetime when adding or subtracting a duration?
dtmate dur "2024-06-01 11:22:33" 6W6D9h44m46s -a
- answer:
2024-04-14 01:37:47 -0400 EDT - answer with the
-f "%Y-%m-%d %H:%M:%S"option:2024-04-14 01:37:47 - Duration examples include:
-
5 minutes 5 seconds or 5m5s
-
3 weeks 4 days 5 hours or 3W4D5h
-
1 year 3 days 4 hours 5 minutes 6 seconds 7 milliseconds 8 microseconds 9 nanoseconds or 1Y3D4h5m6s7ms8us9ns
3. Similar to previous question, but repeats a period multiple times or until a certain date/time is encountered.
- adding dates, repeat twice:
dtmate dur "2024-06-01 12:00:00" 1h5m10s -r 2 -a - subtracting until a date is exceeded:
dtmate dur "12:00:00" 1h5m10s -u "09:48" -s
4. Convert from one group of date/time units to another
- convert from seconds to weeks, days, hours, minutes, seconds:
dtmate conv 25771401s WDhms -
- 42 weeks 4 days 6 hours 43 minutes 21 seconds
- convert weeks, days, hours, minutes, seconds to just seconds, with brief output format:
dtmate conv "42 weeks 4 days 6 hours 43 minutes 21 seconds" seconds -b -
- 25771401s
5. Add or subtract two durations, even when expressed in different units?
- add:
dtmate durmath "1 hour 30 minutes" "45 minutes" -a -
2 hours 15 minutes
- subtract, with a signed result when the second duration is larger:
dtmate durmath "45 minutes" "1 hour" -s -
-15 minutes
- same input, always absolute with the
-Aoption:dtmate durmath "45 minutes" "1 hour" -s -A -
15 minutes
6. Reformat a date/time
- convert the output of the
dateutility:dtmate fmt "$(date)" "%F %T" -
- where
($date)equalsMon Jul 22 22:49:18 EDT 2024
- where
-
- output: 2024-07-22 22:49:18
7. Convert a date/time from one time zone to another?
dtmate tz "2024-01-15 12:00:00 UTC" America/New_York
- answer:
2024-01-15 07:00:00 -0500 EST - zones can be given in multiple styles:
-
- IANA names such as
America/New_York,Asia/Kolkata,Australia/Eucla(preferred; these are DST aware and case-insensitive)
- IANA names such as
-
- abbreviations such as
EST,JST,pst(case-insensitive, fixed offsets)
- abbreviations such as
-
- UTC offsets in seconds, such as
19800for UTC+5:30
- UTC offsets in seconds, such as
- the source may also be a unix timestamp in seconds or milliseconds, such as
1700265600 - pin ambiguous abbreviations with an environment variable:
DTMATE_TZ_ALIASES="IST=Asia/Jerusalem|CST=Asia/Shanghai" - reformat the result with strftime specifiers:
dtmate tz "2024-01-15 12:00:00 UTC" America/New_York --format "%Y-%m-%d %I:%M %p %Z" -
- output:
2024-01-15 07:00 AM EST
- output:
- list all supported abbreviations:
dtmate tz --list-zones - list all IANA zone names with their current offsets:
dtmate tz --list-iana
Installation
- Library:
go get -u github.com/jftuga/DateTimeMate - Command line tool:
go install -ldflags="-s -w" github.com/jftuga/DateTimeMate/cmd/dtmate@latest -
- Binaries for all platforms are provided in the releases section.
- Homebrew (MacOS / Linux):
-
brew tap jftuga/homebrew-tap; brew update; brew install jftuga/tap/dtmate
Library Usage
Example 1 - duration between two dates
Supported date time formats are listed in: https://go.dev/src/time/format.go
import "github.com/jftuga/DateTimeMate"
// example 1 - duration between two dates
start := "2024-06-01"
end := "2024-08-05 00:01:02"
brief := true
diff := DateTimeMate.NewDiff(DateTimeMate.DiffWithStart(start), DateTimeMate.DiffWithEnd(end),
DateTimeMate.DiffWithBrief(brief))
result, duration, err := diff.CalculateDiff()
if err != nil { ... }
fmt.Println(result, duration) // 9W2D1m2s 1560h1m2s
Example 2 - add a duration
// example 2 - add a duration and repeat it until the "until" date is exceeded
from := "2024-06-01"
d := "1 year 7 days 6 hours 5 minutes"
until := "2027-06-22 18:15:11"
ofmt := "%Y%m%d.%H%M%S"
dur := DateTimeMate.NewDur(DateTimeMate.DurWithFrom(from), DateTimeMate.DurWithDur(d),
DateTimeMate.DurWithRepeat(0), DateTimeMate.DurWithUntil(until),
DateTimeMate.DurWithOutputFormat(ofmt))
add, err := dur.Add()
if err != nil { ... }
fmt.Println(add) // [20250608.060500 20260615.121000 20270622.181500]
Example 3 - convert date/time units
source := "1367h29m13s"
target := "Dhms" // days, hours, minutes, seconds
conv := DateTimeMate.NewConv(
DateTimeMate.ConvWithSource(source),
DateTimeMate.ConvWithTarget(target))
newDuration, err := conv.ConvertDuration()
if err != nil { ... }
fmt.Println("new duration:", newDuration) // 56 days 23 hours 29 minutes 13 seconds
Example 4 - reformat a date/time
source := "Mon Jul 22 08:40:33 EDT 2024"
outputFormat := "%F %T"
newFormat, err := DateTimeMate.Reformat(source, outputFormat)
if err != nil { ... }
fmt.Println("new format:", newFormat) // 2024-07-22 08:40:33
Example 5 - duration arithmetic
first := "1 hour 30 minutes"
second := "45 minutes"
dm := DateTimeMate.NewDurMath(
DateTimeMate.DurMathWithFirst(first),
DateTimeMate.DurMathWithSecond(second))
sum, err := dm.Add()
if err != nil { ... }
fmt.Println(sum) // 2 hours 15 minutes
difference, err := dm.Sub()
if err != nil { ... }
fmt.Println(difference) // 45 minutes
Example 6 - convert between time zones
conv := DateTimeMate.NewTimeZoneConverter(
DateTimeMate.TimeZoneConverterWithZoneAbbrevs(DateTimeMate.LoadZoneDefinitions()))
result, err := conv.ConvertTimeZone("2024-01-15 12:00:00 UTC", "America/New_York")
if err != nil { ... }
fmt.Println(result.Format("2006-01-02 15:04:05 MST")) // 2024-01-15 07:00:00 EST
// pin ambiguous abbreviations to a specific IANA zone
aliases, err := DateTimeMate.ParseZoneAliases("IST=Asia/Jerusalem")
if err != nil { ... }
conv = DateTimeMate.NewTimeZoneConverter(
DateTimeMate.TimeZoneConverterWithZoneAbbrevs(DateTimeMate.LoadZoneDefinitions()),
DateTimeMate.TimeZoneConverterWithAliases(aliases))
result, err = conv.ConvertTimeZone("2024-07-15 12:00:00 UTC", "IST")
if err != nil { ... }
fmt.Println(result.Format("2006-01-02 15:04:05 MST")) // 2024-07-15 15:00:00 IDT
See also the example program.
Command Line Usage
Show
Compute date/time differences, durations, conversions, and reformatting
Usage:
dtmate [flags]
dtmate [command]
Available Commands:
conv Convert a duration from group of units to another
diff Output the difference between two date/times
dur Output a date/time when given a starting date/time and duration
durmath Add or subtract two durations
fmt Reformat a date/time
help Help about any command
tz Convert a date/time from one time zone to another
Flags:
-e, --examples show command-line examples
-h, --help help for dtmate
--help-all show help plus duration syntax, brief units, and conversion notes
-n, --nonewline do not output a newline character
-v, --version version for dtmate
Use "dtmate [command] --help" for more information about a command.
Use "dtmate --help-all" for duration syntax, brief units, and conversion notes.
Note: The -i switch can accept two different types of input:
- one line with start and end separated by a comma
- two lines with start on the first line and end on the second line
Note: The -n switch along with -r will emit a comma-delimited output
-
- Example:
dtmate dur now 1h -a -n -r 3
- Example:
Date and Duration Parsing Notes
- Supported input formats are a fixed, documented list rather than
fuzzy matching: ISO-style dates and date/times (padded or unpadded,
-,., or/separated,Tor space before the time, optional fractional seconds, optional zone or offset), year and year-month forms (2024,2024-01), month-name dates (Jan 2, 2024,January 2, 2024 08:30:00,2-Jan-2024 08:21:44, ANSIC forms such asJan 2 15:04:05 2024with optional weekday and zone), RFC822/850/1036/1123, Unix and Ruby date formats, slash dates, bare times of day (08:30,3:04pm,11:00 AM,12:34:56.1234, interpreted as today), Unix timestamps, and the relative wordsnow,today,yesterday, andtomorrow. Inputs outside this list are rejected with an error instead of being guessed at. -
- The time of day after any date may be 24-hour or am/pm; am/pm may be
joined to the time (
3:04PM) or separated by a space (3:04 pm), in either spelling.
- The time of day after any date may be 24-hour or am/pm; am/pm may be
joined to the time (
-
- A bare time with a zone suffix in
dtmate tz(such as08:30 CET) means that time on the zone's own current day, even when the local calendar day differs.
- A bare time with a zone suffix in
- Zone abbreviations inside date/times (such as
EDTinJan 15 12:00:00 EDT 2026) are honored when the local time zone defines them; an unrecognized abbreviation is rejected rather than silently read as UTC. For arbitrary zone conversions, usedtmate tz, which resolves abbreviations through its own zone table. - Slash dates default to US order, month first:
01/02/2024is January 2. -
- Set
DTMATE_DATE_ORDER=DMYfor day/month/year, orMDYto silence the ambiguity warning; a field greater than 12 (such as25/12/2024) disambiguates on its own.
- Set
-
- Two-digit years such as
1/2/24follow the same order rules; years 69-99 are 19xx and 00-68 are 20xx.
- Two-digit years such as
- Out-of-range date/times such as
2024-02-30or08:61:00are rejected instead of being silently normalized, and empty input is rejected instead of being read as the current time. - Pure integers parse by digit count: 10 digits are Unix seconds, 13 are
Unix milliseconds, while 4, 8, and 14 digits are a year (
2024), a compact date (20240101), and a compact date/time (20240101080102); 11, 12, and other digit counts are ambiguous and rejected. - Negative timestamps are rejected everywhere; pre-1970 date/times are
fully supported through normal date strings such as
1950-01-01. - Relative dates:
yesterdayandtomorroware exactly 24 hours from now, even across daylight saving transitions. - Duration amounts must be plain decimals (
90,1.5, and mid-string negatives such as1 year -30 daysinconv);NaN,Inf, exponent (1e2), and hex (0x1p4) forms are rejected. - Long-form unit names are case-insensitive (
1 Hourequals1 hour); brief units stay case-sensitive becauseDmeans days whilemmeans minutes. - Zone abbreviations such as
CETorESTalways mean their fixed UTC offsets, on any date; use an IANA name such asEurope/Paris(or aDTMATE_TZ_ALIASESalias) for DST-aware conversion. - Duration range and precision: durations are computed in integer nanoseconds, so integral amounts are exact; fractional amounts carry float64 precision (about 15-16 significant digits); totals are limited to about +/-292 years.
- Brief sub-second targets: a lone
usornstarget means that sub-second unit; a lonemskeeps its historical minutes+seconds meaning and warns on stderr (use.msormillisecondsfor milliseconds); combine larger and sub-second units with a dot, such asms.msusns. - Repeat and until:
-ris capped at 1,000,000 results, and-umust lie in the direction of travel (after the start when adding, before it when subtracting).
Command Line Examples
Show
########################### "dtmate diff" examples ###########################
# difference between two times on the same day
$ dtmate diff 12:00:00 15:30:45
3 hours 30 minutes 45 seconds
# same input, using brief output
$ dtmate diff 12:00:00 15:30:45 -b
3h30m45s
# using AM/PM and not 24-hour times
$ dtmate diff "11:00AM" "11:00PM"
12 hours
# using ISO-8601 dates
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-08T09:02:03Z
1 day 1 hour 2 minutes 3 seconds
# same input, also convert to seconds only, brief format
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-08T09:02:03Z --conv s -b
90123s
# using timezone offset
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-07T08:05:05-05:00
5 hours 5 minutes 5 seconds
# same input, also convert duration to minutes and seconds
# a bare "ms" target warns on stderr because ms means milliseconds elsewhere
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-07T08:05:05-05:00 -c ms
warning: target "ms" is ambiguous: interpreting as minutes+seconds; use ".ms" or "milliseconds" for milliseconds
305 minutes 5 seconds
# a dot selects sub-second units: .ms is milliseconds, no warning
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-07T08:05:05-05:00 -c .ms
18305000 milliseconds
# convert to a single unit, showing 2 decimal places
# without -d, this would truncate to just: 2 years
$ dtmate diff 2023-10-17 2026-07-04 -c Y -d 2
2.71 years
# differentiate sub-second durations with a dot
# note the "ms" on both sides of the dot: minutes & seconds vs milliseconds
$ dtmate diff now "2020-01-01 11:12:13.123456789" -c ms.msusns
-2566445 minutes 40 seconds 876 milliseconds 542 microseconds 985 nanoseconds
# using a format which includes spaces
$ dtmate diff "2024-06-07 08:01:02" "2024-06-07 08:02"
58 seconds
# using the built-in MacOS date program and do not include a newline character
$ dtmate diff "$(date -R)" "$(date -v+1M -v+30S)" -n
1 minute 30 seconds%
# using the cross-platform date program, ending time starting first
$ dtmate diff "$(date)" 2020
-4 years 24 weeks 1 day 7 hours 21 minutes 53 seconds
# same input, using brief output
$ dtmate diff "$(date)" 2020 -b
-4Y24W1D7h21m53s
# ending time first yields a signed result
$ dtmate diff 15:30:45 12:00:00
-3 hours 30 minutes 45 seconds
# same input, always output an absolute (positive) duration
$ dtmate diff 15:30:45 12:00:00 -A
3 hours 30 minutes 45 seconds
# using microsecond formatting
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-07T08:00:00.000123Z
123 microseconds
# using millisecond formatting, adding -b returns: 1m2s345ms
$ dtmate diff 2024-06-07T08:00:00Z 2024-06-07T08:01:02.345Z
1 minute 2 seconds 345 milliseconds
# read from STDIN in CSV format and do not include a newline character
$ dtmate diff -i -n
15:16:15,15:17
45 seconds%
# same as above, include newline character
$ echo 15:16:15,15:17 | dtmate diff -i
45 seconds
# read from STDIN with start on first line and end on second line
$ printf "15:16:15\n15:17:20" | dtmate diff -i
1 minute 5 seconds
# use relative start date with brief output
$ dtmate diff today 2024-07-07 -b
3D16h38m47s
########################### "dtmate dur" examples ###########################
# add time
# can also use "years", "weeks", "days"
$ dtmate dur 2024-01-01 "1 hour 30 minutes 45 seconds" -a
2024-01-01 01:30:45 -0500 EST
# subtract time
# can also use "milliseconds", "microseconds"
$ dtmate dur "2024-01-02 01:02:03" "1 day 1 hour 2 minutes 3 seconds" -s
2024-01-01 00:00:00 -0500 EST
# output multiple occurrences: add 5 weeks, for 3 intervals
$ dtmate dur "2024-01-02" "5W" -r 3 -a
2024-02-06 00:00:00 -0500 EST
2024-03-12 00:00:00 -0400 EDT
2024-04-16 00:00:00 -0400 EDT
# repeat until a certain datetime is encountered: subtract 5 minutes until 15:00
$ dtmate dur 15:20 5m -u 15:00 -s
2024-06-30 15:15:00 -0400 EDT
2024-06-30 15:10:00 -0400 EDT
2024-06-30 15:05:00 -0400 EDT
2024-06-30 15:00:00 -0400 EDT
# use relative date until tomorrow
$ dtmate dur today 7h10m -u tomorrow -a
2024-07-03 14:29:28 -0400 EDT
2024-07-03 21:39:28 -0400 EDT
2024-07-04 04:49:28 -0400 EDT
# set the output format
$ dtmate dur "2024-07-01 12:00:00" 1W2D3h4m5s -a -f "%Y%m%d.%H%M%S"
20240710.150405
# unix (epoch) timestamps are accepted: 10 digits for seconds, 13 for milliseconds
$ dtmate dur 1700265600 "1 day" -a
2023-11-18 19:00:00 -0500 EST
# combine with -f "%s" to also output unix time
$ dtmate dur 1700265600 "1 day" -a -f "%s"
1700352000
########################### "dtmate durmath" examples ###########################
# add two durations expressed in different units
$ dtmate durmath "1 hour 30 minutes" "45 minutes" -a
2 hours 15 minutes
# subtract the second duration from the first
$ dtmate durmath "1 hour 30 minutes" "45 minutes" -s
45 minutes
# brief input and output
$ dtmate durmath 1h30m 45m -a -b
2h15m
# results are signed when the second duration is larger
$ dtmate durmath "45 minutes" "1 hour" -s
-15 minutes
# same input, always output an absolute (positive) duration
$ dtmate durmath "45 minutes" "1 hour" -s -A
15 minutes
# mixed units between the two durations
$ dtmate durmath "1 week" "3 days 12 hours" -s
3 days 12 hours
# convert the result to specific target units
$ dtmate durmath "1 day" "90 minutes" -s -c minutes
1350 minutes
# show the smallest unit with decimal places, rounded
$ dtmate durmath "1 hour" "30 minutes" -s -c hours -d 1
0.5 hours
# sub-second units appear only when the result needs them
$ dtmate durmath "1.5 seconds" "250 milliseconds" -s
1 second 250 milliseconds
########################### "dtmate conv" examples ###########################
# convert from one group of date/time units to another
$ dtmate conv 25771401s WDhms
42 weeks 4 days 6 hours 43 minutes 21 seconds
# another conversion, in the opposite direction, brief output
$ dtmate conv 42W4D6h43m21s seconds -b
25771401s
# show the smallest unit with decimal places, rounded
$ dtmate conv "1 hour 30 minutes" hours -d 1
1.5 hours
########################### "dtmate fmt" examples ###########################
# reformat date/times
$ dtmate fmt "2024-07-22 08:21:44" "%T %D"
08:21:44 07/22/24
$ dtmate fmt "2024-07-22 08:21:44" "%v %r"
22-Jul-2024 08:21:44 AM
$ dtmate fmt "2024-07-22 08:21:44" "%Y%m%d.%H%M%S"
20240722.082144
$ dtmate fmt "2024-02-29T23:59:59Z" "%Y%m%d.%H%M%S"
20240229.235959
$ dtmate fmt "2024-02-29T23:59:59Z" "%Z"
UTC
$ dtmate fmt "Mon Jul 22 08:40:33 EDT 2024" "%Z %z"
EDT -0400
# convert to unix (epoch) time seconds
$ dtmate fmt "2024-11-16 14:01:02" "%s"
1731783662
# from unix (epoch) time seconds
$ dtmate fmt 1704085262 "%F %T"
2024-01-01 00:01:02
# also from milliseconds
$ dtmate fmt 1704085262999 "%F %T"
2024-01-01 00:01:02
# compact integer date/times: 4, 8, or 14 digits
$ dtmate fmt 20240101080102 "%F %T"
2024-01-01 08:01:02
# ambiguous slash dates default to month/day/year and warn on stderr
$ dtmate fmt 01/02/2024 "%F"
warning: "01/02/2024" is ambiguous: interpreting as month/day/year; set DTMATE_DATE_ORDER=DMY to override
2024-01-02
# pin the order with an environment variable
$ DTMATE_DATE_ORDER=DMY dtmate fmt 01/02/2024 "%F"
2024-02-01
########################### "dtmate tz" examples ###########################
# convert using IANA zone names (preferred; these are DST aware)
$ dtmate tz "2024-01-15 12:00:00 UTC" America/New_York
2024-01-15 07:00:00 -0500 EST
# the same source in July automatically yields daylight time
$ dtmate tz "2024-07-04 08:00:00 EDT" Europe/Paris
2024-07-04 14:00:00 +0200 CEST
# abbreviations work for both the source and the target
$ dtmate tz "2024-01-15 09:00:00 PST" JST
2024-01-16 02:00:00 +0900 JST
# abbreviations and IANA names are case-insensitive
$ dtmate tz "2024-01-15 12:00:00 UTC" jst
2024-01-15 21:00:00 +0900 JST
# a zone-less source is interpreted as local time
$ dtmate tz "2024-01-15 12:00:00" UTC
2024-01-15 17:00:00 +0000 UTC
# a unix timestamp in seconds or milliseconds also works as the source
$ dtmate tz "1700265600" UTC
2023-11-18 00:00:00 +0000 UTC
# a UTC offset in seconds is also accepted (19800 = UTC+5:30)
$ dtmate tz "2024-01-15 12:00:00 UTC" 19800
2024-01-15 17:30:00 +0530 UTC+05:30
# reformat the converted result with strftime specifiers
$ dtmate tz "2024-01-15 12:00:00 UTC" America/New_York --format "%Y-%m-%d %I:%M %p %Z"
2024-01-15 07:00 AM EST
# ambiguous abbreviations warn on stderr and use their primary meaning
$ dtmate tz "2024-01-15 12:00:00 UTC" IST
warning: IST is ambiguous: using India Standard Time (UTC+05:30), not Israel Standard Time (UTC+2), Irish Standard Time (UTC+1); set DTMATE_TZ_ALIASES="IST=<IANA zone>" to override
2024-01-15 17:30:00 +0530 IST
# pin an ambiguous abbreviation to an IANA zone; aliases stay DST aware
$ DTMATE_TZ_ALIASES="IST=Asia/Jerusalem" dtmate tz "2024-07-15 12:00:00 UTC" IST
2024-07-15 15:00:00 +0300 IDT
# multiple aliases are pipe-delimited
$ DTMATE_TZ_ALIASES="IST=Asia/Jerusalem|CST=Asia/Shanghai" dtmate tz "2024-01-15 12:00:00 UTC" CST
2024-01-15 20:00:00 +0800 CST
# list the supported abbreviations
$ dtmate tz --list-zones
ACDT UTC+10:30 Australian Central Daylight Time
ACST UTC+09:30 Australian Central Standard Time
ACWST UTC+08:45 Australian Central Western Standard Time
...
# list the IANA zone names with the offset currently in effect there
$ dtmate tz --list-iana
offsets and abbreviations are those currently in effect (2026-07-07)
Africa/Abidjan UTC+00:00 (GMT)
Africa/Accra UTC+00:00 (GMT)
...
America/New_York UTC-04:00 (EDT)
...
Europe/London UTC+01:00 (BST)
Europe/Paris UTC+02:00 (CEST)
...
# combine with grep to find a zone
$ dtmate tz --list-iana | grep -i sydney
Australia/Sydney UTC+10:00 (AEST)
# date/times before 1970 are rejected by default because time zone
# data is unreliable before then; use --force to convert anyway
$ dtmate tz --force "1900-02-28 23:59:59 UTC" Europe/London
1900-02-28 23:59:59 +0000 GMT
LICENSE
Acknowledgements
Imported Modules
- cobra - https://github.com/spf13/cobra
- strftime - https://github.com/lestrrat-go/strftime
The fallback parser's layout table (internal/dtparse) is partly derived
from the layout list in carbon - https://github.com/golang-module/carbon
(MIT License).
Disclosure Notification
This program is my own original idea and was completely developed on my own personal time, for my own personal benefit, and on my personally owned equipment.