biblatex: extrayear only if full date is equal?

I have a bunch of bib entries of the same author which are labeled by the full date. The problem is that some of them have the same date and so need an extrayear label. But extrayear works by year not by full date. So it looks as if I can only either label all entries or none. Has someone an idea how in the following example the two middle entries could get the labels "a" and "b" while the other have no label?

\documentclass[fontsize=11pt,parskip=half]{scrartcl}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[backend=biber,
style=authoryear-ibid,
datelabel=short,
]{biblatex}

%\AtEveryBibitem{\iffieldundef{day}{}{\clearfield{extrayear}}{}}
%\AtEveryCitekey{\iffieldundef{day}{}{\clearfield{extrayear}}{}}

\begin{document}
abc\nocite{*}

\printbibliography

\end{document}


Bib (edited to contain two unrelated entries which should not be affected).

@article{authorA1,
author={authorA},
title={blaa},
date={2014-03-03}
}

@article{authorA2,
author={authorA},
title={blab},
date={2014-03-04}
}

@article{authorA3,
author={authorA},
title={blac},
date={2014-03-04}
}

@article{authorA4,
author={authorA},
date={2014-03-05}
}

@article{gambol,
author={Gambolputty, Johann},
title={Titel},
year={1970}}

@book{authorB,
author={authorB},
title={Titel},
date={2010-10}}

• But the point of the letters is to see what a citation refers to. Citations with that style looks like "authorA 2014c", and the letters are needed. – pst Apr 3 '14 at 12:49
• @pst: I redefine cite:labelyear+extrayear to take care of citations (and naturally I will need the correct extra label there too) but I didn't want to make the example to long so I left this part out in the question. – Ulrike Fischer Apr 3 '14 at 13:05
• Do you need solution at the biblatex level? I was thinking to parse the bib file and update that field. It is probably a bad idea of mine. – Malipivo Apr 3 '14 at 13:41
• @Malipivo I take any solution that works. But the extrayear field is created by biber so changing the bib imho doesn't help. – Ulrike Fischer Apr 3 '14 at 13:48
• Yes, that's correct, it even raises an error invalid format. For other solvers: The key file is bbl, the fields are stored there. We can use them in an unformatted way e.g. by \AtEveryBibitem{\strfield{year}\strfield{month}\strfield{day}}. – Malipivo Apr 3 '14 at 15:05

With the new \DeclareExtradate (see https://github.com/plk/biblatex/issues/540) introduced in biblatex 3.8/Biber 2.8 you only need

\DeclareExtradate{%
\scope{
\field{labelyear}
\field{year}
}
\scope{
\field{labelmonth}
}
\scope{
\field{labelday}
}
}


Since the date format you uses already puts the extradate field (used to be called extrayear) into the right place, we don't even need to change the date macros. One may need to modify the date printing macros in case a different output like 2017-02-27b is desired.

• I seem to have missed this answer. This is certainly the better one. – Ulrike Fischer Nov 7 '18 at 8:08

I tried to create a standalone Lua module to fix the bbl file. Sorting is not an issue here, it uses common year-month-day format. In this example we are trying to get a series from a,b,c,d to ,a,b,. We need look ahead searching otherwise we would get series of a,a,b,a. My sorting key to decide about a change in series is namehash+year+month+day, it is a combination of fields from the bbl file. The algorithm can be briefly described as follows:

• In case two consecutive dates are not equal, comment out that first line.
• In case they are, use a counter and modify the number (that's the second TeX parameter) in that field in both lines, e.g. to 1 and 2, 2 and 3...
• Skip an entry on purpose if there are no year, month and day data.

Edit: We are getting a separate series for year, year+month, and year+month+day due to the used sorting key. I've fixed the fixer by adding default values, so it is working if there is no month or day data.

In our example we will get changes in the first four occurrences of extrayear from (this is the previous version, please see a revision list):

\field{extrayear}{1}
\field{extrayear}{2}
\field{extrayear}{3}
\field{extrayear}{4}

to

%\field{extrayear}{1}
\field{extrayear}{1}%2
\field{extrayear}{2}%3
%\field{extrayear}{4}

Edit: A new field is added instead of changing extrayear in a new version for safety reasons, e.g. \field{userb}{1}.

We can compare the resuls before and after use of a fixer:

Note: I think that the citations in text should be fixed somehow (hopefully at the biblatex level), it doesn't look right as we can spot AuthorA 2014 twice but for two different books. If we are using \nocite{*}, it's all right. Ulrike Fischer recommends to redefine the \cite command.

I'm using the so-called fixer (a Lua standalone library) this way:

lualatex mal-biber
biber mal-biber
luatex --luaonly mal-fixer.lua mal-biber
lualatex mal-biber

You may spot two lines in the terminal if everything is working as should be:

Mal-fixer is processing the mal-biber.bbl file...
Mal-fixer has done its assignment with 12 items!

Although I run lualatex, any LaTeX format can be used. I enclose the main TeX code and the Lua library.

%! *latex mal-biber.tex
\documentclass[fontsize=11pt,parskip=half]{scrartcl}
\pagestyle{empty}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[backend=biber,
style=authoryear-ibid,
datelabel=short,
]{biblatex}
\usepackage{etoolbox}
\usepackage{filecontents}
\def\malfile{testextra1.bib}
\begin{filecontents*}{\malfile}
@article{authorA4,
author={AuthorA},
date={2014-03-05},
}
@article{authorA3,
author={AuthorA},
title={blac},
date={2014-03-04},
}
@article{authorA2,
author={AuthorA},
title={blab},
date={2014-03-04},
}
@article{authorA1,
author={AuthorA},
title={blaa},
date={2014-03-03},
}
@article{gambol,
author={Gambolputty, Johann},
title={Titel},
year={1970},
}
@article{gambolb,
author={Gambolputty, Johann},
title={Titel},
year={1970},
}
@book{authorBa,
author={AuthorB},
title={Titel},
date={2010-10},
}
@book{authorBb,
author={AuthorB},
title={Titel},
date={2010-10},
}
@book{authorBc,
author={AuthorB},
title={Titel},
date={2010-11},
}
@book{authorBd,
author={AuthorB},
title={Titel},
date={2010-11},
}
@book{authorC,
author={AuthorC},
title={Titel},
}
@book{authorD,
author={AuthorD},
title={Titel},
date={},
}
\end{filecontents*}
%\ifx\relax % An updated version...
\AtEveryBibitem{% A tip for using \savefield->\restorefield by Ulrike Fischer...
\iffieldundef{extrayear}{}{%
\clearfield{extrayear}%
\iffieldequalstr{userb}{}{}{\savefield{userb}{\myusera}%
\restorefield{extrayear}{\myusera}}%
}% End of testing extrayear...
}% End of \AtEveryBibitem...
%\AtEveryCitekey{}
%\fi
\begin{document}
\nocite{*}
\ifx\relax % The \cite command must be redefined to fit those three fields...
% A tip by Ulrike Fischer: \mkbibdateshort{labelyear}{labelmonth}{labelday}
The regular paragraph\ldots\par
\cite{authorA4}\par\cite{authorA3}\par\cite{authorA2}\par\cite{authorA1}\par
\cite{gambol}\par\cite{gambolb}\par
\cite{authorBa}\par\cite{authorBb}\par\cite{authorBc}\par\cite{authorBd}\par
\cite{authorC}\par\cite{authorD}\par
\fi
\printbibliography
\end{document}


And the library is this file:

-- I am a mal-fixer.lua file, version 3 from 2014-04-04...
-- I am fixing extrayear in the bbl file...

---- Common usage: ----
-- lualatex mal-biber
-- biber mal-biber
-- luatex --luaonly mal-fixer.lua mal-biber
-- lualatex mal-biber

local name=arg[1] -- Get me a name from the command line...
if string.sub(name,-4)~=".bbl" then name=name..".bbl" end -- Add me an extension if I don't have one
--print(name) -- Show me the name of the file...
--do return end -- Quick exit of Lua...
print("\nMal-fixer is processing the "..name.." file...") -- Information sent to the terminal...

local myfile=io.open(name,"r") -- Read the file...
local content=myfile:lines() --("*a")

local mybackup=io.open(name.."-backup","w") -- Open me as a backup file...

local saved={} -- Storage of all data...
local mal=0 -- A counter of \entry commands...

for oneline in content do -- The main cycle over lines...
mybackup:write(oneline.."\n") -- Backup of a single line...

-- If a line contains \entry, use counter mal +1...
malvalue=string.find(oneline,"\\entry")
if malvalue~=nil then
mal=mal+1
saved[mal]={}
-- initial setting...
saved[mal][1]="" -- In general, there is no need for namehash to be initialized, it is always there, but just to be sure...
saved[mal][2]=""
saved[mal][3]=""
saved[mal][4]=""
--print(oneline)
end

-- Search for these items in a line...
maltran={"namehash", "year", "month", "day"}
for k,v in pairs(maltran) do
searchme=".-{"..v.."}{(.-)}"
malvalue=string.find(oneline,searchme) --,"%1"
--print("MY LINE: "..oneline)
if malvalue~=nil then
--print(malvalue)
value=string.gsub(oneline,searchme,"%1")
--print(value)
saved[mal][k]=value -- Add their TeX value to my storage...
end --of if malvalue
end --of for k,v

end --of for oneline
--print(mal)

-- A creation of long hash...
for k=1,#saved do
saved[k][5]=saved[k][1]..saved[k][2]..saved[k][3]..saved[k][4]
saved[k][6]=0 -- the default value is zero, comment out that extrayear
--print(saved[k][5])
end

-- The comparison of two citations...
maltemp=1
for k=1,#saved-1 do
if saved[k][5]==saved[k+1][5] then
--print("they are equal") -- 1 2, 2 3, 3 4, ...
saved[k][6]=maltemp
maltemp=maltemp+1
saved[k+1][6]=maltemp
else
--print("they are not the same") -- Don't change anything...
maltemp=1 -- just set counter to one
end -- of if saved
end --of for k

-- For checking purposes only: list me the values...
-- for k=1,#saved do
-- print(saved[k][6])
-- end --of for k

-- Close those two files and open them again read and write mode in reverse...
myfile:close()
mybackup:close()

myfile=io.open(name,"w") -- From reading to writing...
mybackup=io.open(name.."-backup","r") -- From writing to reading...
content=mybackup:lines()

maltemp=0
for oneline in content do
malvalue=string.find(oneline,"\\entry") -- {extrayear} was here
malpercent=string.find(oneline,"%%")

if malvalue~=nil and malpercent==nil then -- Skip me if it is already updated (it occures only when there are multiple runs of mal-fixer.lua)...
maltemp=maltemp+1 -- n-th occurence in the file

if saved[maltemp][2]..saved[maltemp][3]..saved[maltemp][4]=="" then -- Neither year nor month nor day is stored in the bibliography entry, then do absolutly nothing...
else

if saved[maltemp][6]==0 then -- Does this date stand alone? Yes, it does.
-- This is an old version changing extrayear field...
-- oneline=string.gsub(oneline,"(.-)(\\.-)","%1%%%2") -- Comment out this line...
oneline=oneline.."\n      \\field{userb}{}"
else -- No, it doesn't.
-- This is an old version...
-- oneline=string.gsub(oneline,"(.-year}{)(.-)(}.-)","%1"..tostring(saved[maltemp][6]).."%3%%%2") -- Change the value inside the TeX parameter...
oneline=oneline.."\n      \\field{userb}{"..tostring(saved[maltemp][6]).."}"
end --of saved

end --of saved[maltemp] year+month+day

end --of if malvalue

myfile:write(oneline.."\n")
end --of for oneline (second cycle)
-- print(maltemp)

-- Close those two files again...
myfile:close()
mybackup:close()

-- Send a note to the terminal and exit the code...
local maltextbonus=""
if maltemp>1 then maltextbonus="s" end
print("Mal-fixer has done its assignment with "..tostring(mal).." item"..maltextbonus.."!")
--

• Well it looks as if you got hooked by the problem ;-). It works fine for my example and also if I add more double dates for authora I get the expected behaviour (the label restarts with a). But if fails if the bib contains unrelated entries without a full date. One gets the error mal-fixer.lua:54: attempt to concatenate field '?' (a nil value). So some test "if labelmonth or labelday don't exist do nothing" is needed. – Ulrike Fischer Apr 4 '14 at 7:19
• Yes, it is an interesting problem. I believe there is a one-liner solution in BibLaTeX, but it is rather my wild guess. Could you please extend your bib file in the post by those new entries in consideration? I think we are considering these situations: no date at all, only a year, only a year+month, of course, I will take a look at it... – Malipivo Apr 4 '14 at 7:36
• I don't think that an online solution exists. The information if the full dates are doublettes is not provided by biber (unless there is some biber option I haven't found yet) so one really has to parse and compare all the entries to get it. I have added some bib entries. And from my point it is fine to process only entries with full dates, but in general it would be logical to check entries with year+month too. – Ulrike Fischer Apr 4 '14 at 8:04
• Imho it would be enough if you build the new hash as "namehash + (if exists labelyear) + (if exist labelmonth) + (if exist labelday)" and then compare. – Ulrike Fischer Apr 4 '14 at 8:18
• This seems to work. It is easy to use the field. In my case I added \AtEveryBibitem{\iffieldundef{day}{}{\clearfield{extrayear}}\iffieldequalstr{userb}{}{}{\savefield*{userb}{\myusera}\restorefield{extrayear}{\myusera}}} (I'm not quite sure if one couldn't improve the logic, but currently it seems to work ok). For the citations I redefined the cite command so that it uses \mkbibdateshort{labelyear}{labelmonth}{labelday} – Ulrike Fischer Apr 5 '14 at 10:11

I hope someone will have a better answer, but meanwhile I just want to point out that you can mistreat the year field by putting more there, and biber won't complain:

@article{authorA1,
author={authorA},
title={blaa},
year={03.03.2014}
}

@article{authorA2,
author={authorA},
title={blab},
year={04.03.2014}
}

@article{authorA3,
author={authorA},
title={blac},
year={04.03.2014}
}

@article{authorA4,
author={authorA},