# Date(time) ranges using datetime2

I would like to use the datetime2 package to format date(time) ranges.

For example, \DTMdaterange{2017-08-05}{2017-09-10} would output 2017-08-05/09-10 in ISO format, or something like August 5 to September 10, 2017 in US English.

I looked into implementing it myself, but find it a bit hard. Is there any package that already includes this functionality aside from isodate? I thought biblatex might be a good candidate, but cannot find anything useful (I am mostly interested in formatting dates; times are not necessary).

• Welcome to TeX.SX! Please help us (and also you) and add a minimal working example (MWE), that illustrates your problem. Reproducing the problem and finding out what the issue is will be much easier when we see compilable code, starting with \documentclass and ending with \end{document}. – Bobyandbob Sep 10 '17 at 10:09
• @Bobyandbob I don't see how I could produce a MWE as I'm asking about implementing new functionality. If you could tell me which part of my question is not clear, I will try to explain my thoughts better. – d125q Sep 10 '17 at 10:29
• Compilable code with datetime2 package for example. So everyone could add the new functionality. – Bobyandbob Sep 10 '17 at 10:49
• It's something I've been thinking about but haven't had time to implement. – Nicola Talbot Sep 10 '17 at 13:32

I don't know what the datetime2 package can do but I was curious to see what you could do using pgfkeys to format dates in a uniform way.

This provides some code for inputting dates and a uniform way of adjusting the formatting of the displayed date. There is also a \DateRange that, I think, does what you want in a way that depends on the current "date format".

The code below produces:

The last two lines are "date ranges" with different formats.

Here's the code:

\documentclass{article}
\usepackage{pgfkeys}
\usepackage{pgfmath}% for calculating yy from yyyy
\usepackage{siunitx}% we use the \num command for dd and mm format in the

\newif\ifPrintYear\PrintYeartrue%   controls if year  is printed
\newif\ifPrintMonth\PrintMonthtrue% controls if month is printed

\pgfkeys{/mydate/.is family, /mydate,
year/.initial=0,
month/.initial=0,
day/.initial=0,
date format/.initial = dmyy,
date range separator/.initial = {-},% used to separate dates in \DateRange
format day d/.code = {\pgfkeysvalueof{/mydate/day}}, % format ways to the day
format day dd/.code = {\num[minimum-integer-digits=2]{\pgfkeysvalueof{/mydate/day}}},
format month/.code n args={3}{%  <format month><pre-text><post-text>
\ifPrintMonth#2\pgfkeys{/mydate/format month #1}#3\fi%
},
format month m/.code = {\pgfkeysvalueof{/mydate/month}},
format month mm/.code = {\num[minimum-integer-digits=2]{\pgfkeysvalueof{/mydate/month}}},
format month mmm/.code={%
\expandafter\ifcase\pgfkeysvalueof{/mydate/month}??%
\or Jan%
\or Feb%
\or Mar%
\or Apr%
\or May%
\or Jun%
\or Jul%
\or Aug%
\or Sep%
\or Oct%
\or Nov%
\or Dec%
\else ???%
\fi%
},
format month mmmm/.code={%
\expandafter\ifcase\pgfkeysvalueof{/mydate/month}??%
\or January%
\or February%
\or March%
\or April%
\or May%
\or June%
\or July%
\or August%
\or September%
\or October%
\or November%
\or December%
\else ???%
\fi%
},
format year/.code n args={3}{% <format year><pre-text><post-text>
\ifPrintYear#2\pgfkeys{/mydate/format year #1}#3\fi%
},
format year yy/.code = {%
\pgfmathparse{Mod(\pgfkeysvalueof{/mydate/year},100)}%
\num[minimum-integer-digits=2,zero-decimal-to-integer]{\pgfmathresult}%
},
format year yyyy/.code = {%
\pgfkeysvalueof{/mydate/year}%
},
output/dmyy/.code = {%
\pgfkeys{/mydate/format day d}%
\pgfkeys{/mydate/format month={m}{/}{\relax}}%
\pgfkeys{/mydate/format year={yy}{/}{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/dmyyyy/.code = {%
\pgfkeys{/mydate/format day d}%
\pgfkeys{/mydate/format month={m}{/}{\relax}}%
\pgfkeys{/mydate/format year={yyyy}{/}{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/ddmmyy/.code = {%
\pgfkeys{/mydate/format day dd}%
\pgfkeys{/mydate/format month={mm}{/}{\relax}}%
\pgfkeys{/mydate/format year={yy}{/}{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/ddmmyyyy/.code = {%
\pgfkeys{/mydate/format day dd}%
\pgfkeys{/mydate/format month={mm}{/}{\relax}}%
\pgfkeys{/mydate/format year={yyyy}{/}{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/mmmdyyyy/.code = {%
\pgfkeys{/mydate/format month={mmm}{\relax}{ }}%
\pgfkeys{/mydate/format day d}%
\pgfkeys{/mydate/format year={yyyy}{, }{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/mmmmdyyyy/.code = {%
\pgfkeys{/mydate/format month={mmmm}{\relax}{ }} %
\pgfkeys{/mydate/format day d}%
\pgfkeys{/mydate/format year={yyyy}{ }{\relax}}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/yyyymd/.code = {%
\pgfkeys{/mydate/format year={yyyy}{\relax}{/}}%
\pgfkeys{/mydate/format month={m}{\relax}{/}}%
\pgfkeys{/mydate/format day d}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/yyyymmdd/.code = {%
\pgfkeys{/mydate/format year={yyyy}{\relax}{/}}%
\pgfkeys{/mydate/format month={mm}{\relax}{/}}%
\pgfkeys{/mydate/format day dd}%
\pgfkeyssetvalue{/mydate/date range separator}{ to }%
},
output/iso/.code = {%
\pgfkeys{/mydate/format year={yyyy}{\relax}{-}}%
\pgfkeys{/mydate/format month={mm}{\relax}{-}}%
\pgfkeys{/mydate/format day dd}%
\pgfkeyssetvalue{/mydate/date range separator}{/}%
},
printdate/.code={\pgfkeys{/mydate/output/\pgfkeysvalueof{/mydate/date format}}}
}

% format date input yyyy-m-d ---- but allow yy-m-d
\pgfkeysdefargs{/mydate/date}{#1-#2-#3}{%
\ifnum#1<100\relax \pgfkeyssetvalue{/mydate/year}{20#1}% assume year is 20XX
\else\pgfkeyssetvalue{/mydate/year}{#1}%
\fi%
\pgfkeyssetvalue{/mydate/month}{#2}%
\pgfkeyssetvalue{/mydate/day}{#3}%
}

\newcommand\DateFormat[1]{\pgfkeys{/mydate/date format=#1}}
\newcommand\Date[1]{\pgfkeys{/mydate, date=#1, printdate}}

\newcommand\DateRange[2]{%
\bgroup% start a group so that PrintYear and PrintMonth changes are local
\Date{#1}%
\xdef\lastYear{\pgfkeysvalueof{/mydate/year}}%
\xdef\lastMonth{\pgfkeysvalueof{/mydate/month}}%
\pgfkeys{/mydate/date=#2}%
\xdef\thisYear{\pgfkeysvalueof{/mydate/year}}%
\xdef\thisMonth{\pgfkeysvalueof{/mydate/month}}%
\ifnum\thisYear=\lastYear\relax%
\PrintYearfalse%
\ifnum\thisMonth=\lastMonth\relax%
\PrintMonthfalse%
\fi
\fi%
\pgfkeysvalueof{/mydate/date range separator}%
\pgfkeys{/mydate/printdate}%
\egroup
}

\begin{document}

\Date{2017-08-28}

\DateFormat{ddmmyy}\Date{2017-08-09}

\DateFormat{iso}\Date{2017-08-09}

\DateFormat{mmmdyyyy}\Date{2017-08-09}

\DateFormat{mmmmdyyyy}\Date{2017-08-09}

\DateFormat{iso}
\DateRange{2017-08-05}{2017-08-10}

\DateRange{2017-08-05}{2017-09-10}

\DateRange{2017-08-05}{2018-09-10}

\DateFormat{mmmdyyyy}
\DateRange{2017-08-05}{2017-08-10}

\DateRange{2017-08-05}{2017-09-10}

\DateRange{2017-08-05}{2018-09-10}

\end{document}


The idea of the code is that the date is inputted in the format yyyy-m-d using \Date. At anytime the format of the date can be changed using \DateFormat. Behind the scenes there is some pgfkeys trickery that calls a particular date format when the printing is really done. The supported formats are all subkeys of the /mydate/output key and, in turn, these invoke various year, month and day format keys.

There is an additional complication to cater for the date range: the year formats, through /mydate/format year, all use \ifPrintYear to see if the year should be printed and to make this work the year formats all accept two arguments, which is the text before and after the year since they also need to be dropped when the year is not printed. Similarly, the months use \ifPrintMonth. The \DateRange checks the two years in the date range and if they are equal sets \PrintYearfalse, with the result that the year for the second date is not printed.

• Thank you Andrew. My ideal \DTMdaterange would "ignore" the year and the month of one of the dates if they're the same. For example, the range 2017-08-05/09-20 written in the ISO 8601 standard means in fact 2017-08-05 to 2017-09-20, but 2017 is omitted in the second date because it is the same. – d125q Sep 10 '17 at 18:15
• @d125q Thanks for explaining what \DateRange should do. I have edited the code so that it now provides such a macro -- it now will check for a repeated year or a repeated year and month. In the process I streamlined the code quite a bit. – Andrew Sep 12 '17 at 10:56

The sample document datetime2-sample-journal.tex included in the download of datetime2 has an example of how to make a date range command. The code (not my own) is pasted below for completeness.

\usepackage[en-GB]{datetime2}

\newcommand*{\numdash}{\texorpdfstring{\,--\,}{--}}

% A range of dates.
% Syntax: \daterange{yyyy1}{mm1}{dd1}{yyyy2}{mm2}{dd2}
\newcommand*{\daterange}[6]{%
\ifnum#1=#4
% Same year
\ifnum#2=#5
% Same month
\ifnum#3=#6
% Same day - not a range!
\DTMdisplaydate{#1}{#2}{#3}%
\else
% Range within the same month.
% This needs to be done explicitly but match the separators
% with the en-US style.
\DTMenglishordinal{#3}\numdash\DTMenglishordinal{#6}%
\DTMenGBdaymonthsep
\DTMenglishmonthname{#2}%
\DTMenGBmonthyearsep\number#1
\fi
\else
% Not the same month
\DTMdisplaydate{#1}{#2}{#3}{-1}\numdash\DTMdisplaydate{#4}{#5}{#6}{-1}%
\fi
\else
% Not same year
\DTMdisplaydate{#1}{#2}{#3}{-1}\numdash\DTMdisplaydate{#4}{#5}{#6}{-1}%
\fi
}


Which can then be used as:

\daterange{2016}{02}{04}{2016}{03}{01}\\
\daterange{2019}{01}{01}{2019}{01}{20}\\
\daterange{2017}{03}{04}{2018}{04}{01}\\


to display:

February 2016 – March 2016
1st – 20th January 2019
March 2017 – April 2018

• If you are using the en-US option to datetime2 instead of en-GB, then you should re-arrange some of the code. \DTMenglishordinal{#3}\numdash\DTMenglishordinal{#6} \DTMenGBdaymonthsep \DTMenglishmonthname{#2} \DTMenGBmonthyearsep\number#1 should be \DTMenglishmonthname{#2}\DTMenUSmonthdaysep\DTMenglishordinal{#3}\numdash\DTMenglishordinal{#6} \DTMenUSdayyearsep\number#1 Code for other locales can be inferred from rcweb.dartmouth.edu/doc/texmf-dist/doc/latex/datetime2-english/… – Clément Oct 16 '19 at 20:13