Date-Based Expiring Code Blocks in Nix

Published on

Oftentimes, in a NixOS configuration, you want to introduce temporary changes that aren’t meant for long term: for example, you want to run a new version of a software product before it’s in nixpkgs, or apply your own temporary patches, maybe even, use an API token that will expire eventually1.

I’ve recently read the article Nix Overrides That Expire Themselves by Jezen Thomas, in which the author comes up with a handy trick: make NixOS overlays automatically expire themselves, once they aren’t needed.

It’s something I would have needed many times before, and is much better than the alternative: scattering TODO comments around the code, in the hopes that some day, when the code isn’t needed anymore, it’ll be manually removed.

Their method for expiring an override is based on version numbers, but I think date-based expiration fits my needs better. So I set out to create code blocks with an expiration date in Nix.

Getting the date

Nix has builtins.currentTime, but of course, you can’t use it in pure evaluation mode; that would defeat the point of pure eval. I use flakes and pure eval, so I was looking for a solution compatible with these.

The good news is: we have a proxy! Any flake input has a lastModified attribute that stores the last modification date of the input, and this is available in pure mode.

Because nixpkgs-unstable updates frequently, nixpkgs.lastModified is a good proxy for the current date.

Converting dates to timestamps

One problem: lastModified is a Unix timestamp, so to compare it with an expiration date, we first need to convert the date to a timestamp. Thankfully, I’ve found a paper which presents many methods for converting dates to timestamps, and back2: Euclidean affine functions and their application to calendar algorithms by Cassio Neri and Lorenz Schneider.

Incidentally, an algorithm from that paper is used by the linux kernel itself.

The code

So, based on these, here’s the full code:

let
  # algorithm 13 from https://onlinelibrary.wiley.com/doi/10.1002/spe.3172
  date-to-nr-days-since-epoch =
    year: month: day:
    let
      s = 82;
      K = 719468 + 146097 * s;
      L = 400 * s;
      J = if month <= 2 then 1 else 0;
      uint32_cast = x: if x < 0 then x + 4294967296 else x;
      Y = uint32_cast year + L - J;
      M = if J > 0 then month + 12 else month;
      D = day - 1;
      C = Y / 100;

      y_star = 1461 * Y / 4 - C + C / 4;
      m_star = (979 * M - 2919) / 32;
      N = y_star + m_star + D;
    in
    N - K;
  date-to-unix-timestamp =
    year: month: day:
    (date-to-nr-days-since-epoch year month day) * 86400;

  /**
    usage: expiringCodeBlock lastModified year month day code
    where
      lastModified: unix timestamp (eg. nixpkgs.lastModified)
      year, month, day: integers
  */
  expiringCodeBlock =
    lastModified: year: month: day: code:
    if lastModified <= (date-to-unix-timestamp year month day) then
      code
    else
      throw "EXPIRED CODE BLOCK!\nThis code block expired on ${toString year}-${toString month}-${toString day}";

in
expiringCodeBlock

You can use this to expire any code block, after a given date. Applying it to the original problem, overlays:

nixpkgs.overlays = [
  (expiringCodeBlock nixpkgs.lastModified 2026 09 01 (
    final: prev: {
      hello = throw "Hello world is disabled temporarily for maintenance!";
    }
  ))
];

Footnotes

  1. This is, of course, not recommended, since the API token will get written to the nix store, which is world-readable. Don’t do this!

  2. Actually, converting a date to days since the unix epoch; but of course, there’s a bijection between days and seconds elapsed.