latexnixbinary-reproducibility

Latex \today macro expanding to December 31st, 1979


With latex installed through the nix package manager (on nixos), \today always expands to December 31st, 1979. How do I get this to return the correct date?

MWE Create a directory and add the mwe.tex and flake.nix (based off https://flyx.org/nix-flakes-latex/).

-- mwe.tex --

\documentclass[11pt]{article}

\title{}
\date{\today}

\begin{document}
\maketitle
\end{document}

-- mwe.tex ends here --

-- flake.nix --

{
  description = "MWE for reproducing \\today macro problem";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        tex = pkgs.texlive.combine {
          inherit (pkgs.texlive) scheme-minimal latex-bin latexmk;
        };
      in rec {
        packages = {
          document = pkgs.stdenvNoCC.mkDerivation rec {
            name = "mwe";
            src = self;
            buildInputs = [ pkgs.coreutils tex ];
            phases = [ "unpackPhase" "buildPhase" "installPhase" ];
            buildPhase = ''
              export PATH="${pkgs.lib.makeBinPath buildInputs}";
              mkdir -p .cache/texmf-var
              env TEXMFHOME=.cache TEXMFVAR=.cache/texmf-var \
                latexmk -pdf -lualatex mwe.tex
            '';
            installPhase = ''
              mkdir -p $out
              cp mwe.pdf $out/
            '';
          };
        };
        defaultPackage = packages.document;
      });
}

-- flake.nix ends here --

Then run nix build "." in the new directory. The results should be a pdf containing December 31, 1979 (or when I just ran this I actually got January 1, 1980).


Solution

  • WARNING: Use a hardcoded date if you release your document close to a deadline.

    The date will most likely be based on UTC, so if your document is meant for a local context, this could lead to a small surprise when committing during the start or end of the day, depending on time zone.


    UPDATE (thanks @voidee123)

    latex uses the environment variable SOURCE_DATE_EPOCH to determine the date.

    This leads to a simpler solution

    {
      description = "MWE for reproducing \\today macro problem";
    
      inputs = {
        nixpkgs.url = "github:NixOS/nixpkgs";
        flake-utils.url = "github:numtide/flake-utils";
      };
    
      outputs = { self, nixpkgs, flake-utils }:
        flake-utils.lib.eachDefaultSystem (system:
          let
            pkgs = nixpkgs.legacyPackages.${system};
            tex = pkgs.texlive.combine {
              inherit (pkgs.texlive) scheme-minimal latex-bin latexmk;
            };
          in rec {
            packages = {
              inherit tex;
              document = pkgs.stdenvNoCC.mkDerivation rec {
                name = "mwe";
                src = self;
                nativeBuildInputs = [ pkgs.coreutils pkgs.libfaketime tex ];
                phases = [ "unpackPhase" "buildPhase" "installPhase" ];
                SOURCE_DATE_EPOCH = self.sourceInfo.lastModified;
                buildPhase = ''
                  mkdir -p .cache/texmf-var
                  env TEXMFHOME=.cache TEXMFVAR=.cache/texmf-var \
                    latexmk -pdf -lualatex mwe.tex
                '';
                installPhase = ''
                  mkdir -p $out
                  cp mwe.pdf $out/
                '';
              };
            };
            defaultPackage = packages.document;
          });
    }
    

    Original answer for context

    Nix tries its best to make builds reproducible. This includes setting the date, because too many tools leave a date in their outputs. In other words, Nix is a tool for making builds functional. In a mathematical function, time would be an input or parameter. You can do the same here, or simply hardcode the date you want to release the document.

    If it is a living document and it is committed to git, you can use of the flake metadata (making the metadata an input to your "function"). You can write it to a file in the build during buildPhase:

    # format: YYYYMMDDHHMMSS
    echo ${self.sourceInfo.lastModifiedDate} >date.txt
    
    # format: YYYY-MM-DD
    echo ${
      nixpkgs.lib.substring 0 4 self.sourceInfo.lastModifiedDate
      + "-" +
      nixpkgs.lib.substring 4 2 self.sourceInfo.lastModifiedDate
      + "-" +
      nixpkgs.lib.substring 6 2 self.sourceInfo.lastModifiedDate
    } >date.txt
    

    or

    # format: unix timestamp
    echo ${self.sourceInfo.lastModified} >date.txt
    

    You can then use \input{date.txt} where you need the date.