# biblatex: automatically abbreviate year ranges

For consistency's sake I need to abbreviate all number-ranges in my document.

While biblatex can easily be made to abbreviate page ranges the equivalent command for the year ranges breaks compilation with Argument of \@secondoftwo has an extra }

\documentclass{article}
\usepackage{filecontents}

\begin{filecontents*}{mylibrary.bib}
@book{somebook1769,
Author = {Name, Given},
Title = {Some title of this publication},
Date = {1769/1783}}
}
\end{filecontents*}

\usepackage[style=authoryear,backend=biber]{biblatex}
\usepackage{csquotes}

\DeclareFieldFormat{postnote}{\mkcomprange[{\mkpageprefix[pagination]}]{#1}}
\DeclareFieldFormat{pages}{\mkcomprange{#1}}

%%doesn't work:
%\DeclareFieldFormat{date}{\mkcomprange{#1}}

%%works (just for comparison)
%\DeclareFieldFormat{date}{\textbf{#1}}

\begin{document}
“some quote” from \cite[1348--1368]{somebook1769}

\printbibliography
\end{document}


(I tried to use

    Year = {1769--83}}


which looks right but biblatex warns

   WARN - year field '1769--83' in entry 'somebook1769' is not an integer - this will probably not sort properly.


and indeed the order of publications in the bibliography for several works by the same author is not correct.)

Most of the functions are already there, so we only need to piece them together. Unfortunately, the date handling code in biblatex is a bit complex due to the many EDTF/ISO 8601 features that are supported.

The American date format used by the default language english needs special handling. So the number of lines needed to get this done essentially double.

\documentclass[american]{article}
\usepackage{babel}
\usepackage{csquotes}
\usepackage[style=authoryear, backend=biber]{biblatex}

\DeclareFieldFormat{postnote}{\mkcomprange[{\mkpageprefix[pagination]}]{#1}}
\DeclareFieldFormat{pages}{\mkcomprange{#1}}

\makeatletter
\newcommand*{\ifcompyear}[1]{%
\ifboolexpr{        test {\iffieldundef{#1month}}
and     test {\iffieldundef{#1endmonth}}
and not test {\iffieldundef{#1endyear}}
and not test {\iffieldequalstr{#1endyear}{}}
and     test {\iffieldsequal{#1dateera}{#1enddateera}}
and test     {\iffieldint{#1year}}
and test     {\iffieldint{#1endyear}}}}

\newrobustcmd*{\compyear}[1]{%
\begingroup
\edef\blx@compyear@tempa{\thefield{#1year}-\thefield{#1endyear}}%
\expandafter\mkcomprange\expandafter{\blx@compyear@tempa}%
\endgroup}

\renewrobustcmd*{\mkdaterangetrunc}[2]{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
% Such a season component can only come from an ISO8601 season which replaces
% a normal month so if it exists, we know that a normal date print is ruled out
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\iffieldsequal{#2month}{#2endmonth}
{\csuse{mkbibdate#1}{}{}{#2day}}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\iffieldsequal{#2dateera}{#2enddateera}{}
{\dateeraprint{#2year}}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\iffieldsequal{#2dateera}{#2enddateera}{}
{\dateeraprint{#2year}}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{}
{\iffieldequalstr{#2endyear}{}
{\mbox{\bibdaterangesep}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endgroup}

\renewrobustcmd*{\mkdaterangetruncextra}[2]{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
% Such a season component can only come from an ISO8601 season which replaces
% a normal month so if it exists, we know that a normal date print is ruled out
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\iffieldsequal{#2month}{#2endmonth}
{\csuse{mkbibdate#1}{}{}{#2day}}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\iffieldsequal{#2dateera}{#2enddateera}{}
{\dateeraprint{#2year}}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\iffieldsequal{#2dateera}{#2enddateera}{}
{\dateeraprint{#2year}}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{\iffieldequalstr{#2endyear}{}
\printtext{\mbox{\bibdaterangesep}}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\printtext{%
\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endgroup}

\newrobustcmd*{\mkdaterangecompyear}[1]{%
\blx@imc@clearfield{#1season}%
\blx@imc@clearfield{#1month}%
\blx@imc@clearfield{#1day}%
\blx@imc@clearfield{#1hour}%
\blx@imc@clearfield{#1minute}%
\blx@imc@clearfield{#1second}%
\blx@imc@clearfield{#1timezone}%
\blx@imc@clearfield{#1endseason}%
\blx@imc@clearfield{#1endmonth}%
\blx@imc@clearfield{#1endday}%
\blx@imc@clearfield{#1endhour}%
\blx@imc@clearfield{#1endminute}%
\blx@imc@clearfield{#1endsecond}%
\blx@imc@clearfield{#1endtimezone}%
\ifdateyearsequal{#1}{#1end}
{\blx@imc@clearfield{#1endyear}}
{}%
\mkdaterangetrunc{short}{#1}}

\newrobustcmd*{\mkdaterangecompyearextra}[1]{%
\blx@imc@clearfield{#1season}%
\blx@imc@clearfield{#1month}%
\blx@imc@clearfield{#1day}%
\blx@imc@clearfield{#1hour}%
\blx@imc@clearfield{#1minute}%
\blx@imc@clearfield{#1second}%
\blx@imc@clearfield{#1timezone}%
\blx@imc@clearfield{#1endseason}%
\blx@imc@clearfield{#1endmonth}%
\blx@imc@clearfield{#1endday}%
\blx@imc@clearfield{#1endhour}%
\blx@imc@clearfield{#1endminute}%
\blx@imc@clearfield{#1endsecond}%
\blx@imc@clearfield{#1endtimezone}%
\ifdateyearsequal{#1}{#1end}
{\blx@imc@clearfield{#1endyear}}
{}%
\mkdaterangetruncextra{short}{#1}}

\DefineBibliographyExtras{english}{%
\protected\gdef\lbx@us@mkdaterangetrunc@long#1#2{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\dateeraprint{#2year}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\dateeraprint{#2year}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{}
{\iffieldequalstr{#2endyear}{}
{\mbox{\bibdaterangesep}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\iffieldsequal{#2month}{#2endmonth}
{\csuse{mkbibdate#1}{#2endyear}{}{#2endday}}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endgroup}%
\protected\gdef\lbx@us@mkdaterangetrunc@short#1#2{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\dateeraprint{#2year}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\dateeraprint{#2year}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{}
{\iffieldequalstr{#2endyear}{}
{\mbox{\bibdaterangesep}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endngroup}%
\protected\gdef\lbx@us@mkdaterangetruncextra@long#1#2{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\dateeraprint{#2year}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\dateeraprint{#2year}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{\iffieldequalstr{#2endyear}{}
\mbox{\bibdaterangesep}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\iffieldsequal{#2month}{#2endmonth}
{\csuse{mkbibdate#1}{#2endyear}{}{#2endday}}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endgroup}%
\protected\gdef\lbx@us@mkdaterangetruncextra@short#1#2{%
\begingroup
\iffieldundef{#2year}
{\blx@nounit}
{\printtext[#2date]{%
\datecircaprint
\iffieldundef{#2season}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibdate#1}{}{#2month}{#2day}}
{\ifcompyear{#2}
{}
{\csuse{mkbibdate#1}{#2year}{#2month}{#2day}%
\dateeraprint{#2year}}}}
{\ifdateyearsequal{#2}{#2end}
{\csuse{mkbibseasondate#1}{}{#2season}}
{\csuse{mkbibseasondate#1}{#2year}{#2season}%
\dateeraprint{#2year}}}%
\dateuncertainprint
\iffieldundef{#2endyear}
{\iffieldequalstr{#2endyear}{}
\mbox{\bibdaterangesep}}
{\ifcompyear{#2}
{\dateeraprintpre{#2endyear}%
\compyear{#2}%
\dateeraprint{#2endyear}}
{\bibdaterangesep
\enddatecircaprint
\iffieldundef{#2season}
{\csuse{mkbibdate#1}{#2endyear}{#2endmonth}{#2endday}}
{\csuse{mkbibseasondate#1}{#2endyear}{#2endseason}}%
\enddateuncertainprint
\dateeraprint{#2endyear}}}}}}%
\endgroup}%
}
\makeatother

\ExecuteBibliographyOptions{labeldate=compyear}

\usepackage{filecontents}
\begin{filecontents*}{\jobname.bib}
@book{somebook1769,
author  = {Name, Given},
title   = {Some title of this publication},
date    = {1769/1783},
}
\end{filecontents*}

• This is just stellar! Your huge effort is greatly appreciated! I hope this can be included into biblatex at some point. May 26 '19 at 20:04
• My MWE being minimal I didn't mention that I am using polyglossia and xelatex. There seems to be a direct conflict between polyglossia's english and your solution: It compiles all right but adds a space (quad?) before "Name". (There doesn't seem to be a problem with other languages.) If it is difficult to fix or you don't have the time right now, I should be able to switch to babel, though. May 26 '19 at 20:14
• @Florian Oh, sorry I forgot a few %. Should be fixed in the edit just now. The issue can occur with both polyglossia and babel, but due to some deficiencies with biblatex's polyglossia module (see tex.stackexchange.com/q/464281/35864) you may only have noticed it with polyglossia. Generally I would advise to use babel if at all possible and to only use polyglossia if it gives better results. polyglossia development has effectively stalled and many posts suggesting polyglossia are outdated. See also tex.stackexchange.com/q/482396/35864. May 26 '19 at 20:25