How to create a timestamp in a Nix expression?

I have a Nix expression for a project that also names log files based on timestamps, which are currently passed as so:

# Nix expression
{ timestamp }:


# call
$ nix-shell --argstr "timestamp" "$(date '+%Y-%m-%d_%H-%M-%S')" my_shell.nix

It’s a bit cumbersome this way, so I’m wondering if this would be possible purely in Nix? These are what I found thus far:

  • builtins.currentTime
    Unfortunately, it returns Unix Epoch time in seconds only (same as date +%s), and couldn’t find a way to convert it within Nix.

  • [Discourse] Adding date/time to a config option
    Don’t understand the examples there yet, but based on what little I know I don’t think runCommand would be useful for me. (Or is it but I can’t see it yet?)

  • [Discourse] Run shell commands in nix expressions
    My first thought was to call date from within the Nix expression, but apparently this is not really an option. (I couldn’t even find the documentation for builtins.exec, probably for a good reason.)

Here’s one way to run shell commands from within Nix expressions:

lib.readFile "${runCommand "timestamp" { env.when = builtins.currentTime; } "echo -n `date -d @$when +%Y-%m-%d_%H-%M-%S` > $out"}"
3 Likes

This is beautiful, thank you! I’m just going to break it down to future self like to a 5-year-old:

  1. pkgs.runCommand is a shortcut to pkgs.mkDerivation

    1. The value of builtins.currentTime will be put into an attribute set (env) whose attributes will be exported as environment variables when the derivation is built in its own Bash shell.

    2. The build command for the derivation in this case simply converts builtins.currentTime from Unix time to a date, and saves it into the output file.

  2. lib.readFile triggers the build of the derivation (the result of pkgs.runCommand), and reads the output file’s contents.

Notes

  • env.whenwhen

    As soon as I used this Nix expression in a Nix shell expression, error: cannot coerce a set to a string was thrown with the cursor pointing to env.when, and removing env. did the trick. Can’t say why the env.when version works in nix repl but not in a Nix shell expression though.

    The form I’m using for now is in the next item below.

  • builtins.currentTime is a constant

    No matter how many times this solution is called in a Nix expression, its value won’t change.

    nix-repl> t = with (import <nixpkgs> {});
                  builtins.readFile (         
                    runCommand "timestamp"
                               { when = builtins.currentTime; }
                               "echo -n `date -d @$when +%Y-%m-%d_%H-%M-%S` > $out"
                  )
    
    nix-repl> t
    "2023-07-12_11-58-34"
    
    nix-repl> t
    "2023-07-12_11-58-34"
    
  • lib.readFilebuiltins.readFile

    Couldn’t find the documentation for lib.readFile in Nixpkgs lib section of the Nixpkgs manual so tried replacing it with builtins.readFile and it worked.

    Also couldn’t find it in the manual, but apparently if a Nix derivation (i.e., a drv file) is read, it will be built first.

    nix-repl> t_drv = (import <nixpkgs> {}).runCommand "timestamp" { when = builtins.currentTime; } "echo -n `date -d @$when +%Y-%m-%d_%H-%M-%S` > $out"
    
    nix-repl> t_drv
    «derivation /nix/store/51fd42zyc5zwqam5fdqw2w9gxzqrba7n-timestamp.drv»
    
    nix-repl> t_drv.outPath
    "/nix/store/8gm3wz94d6w3k5pj5yiwyjvygabxf61j-timestamp"
    
    nix-repl> builtins.readFile t_drv.outPath
    "2023-07-12_12-19-18"
    

    and, because I’m getting rusty:

    $ nix-build /nix/store/s39casall3q89wk2zhdbwyyvs8gw66gf-timestamp.drv
    
    this derivation will be built:
    /nix/store/s39casall3q89wk2zhdbwyyvs8gw66gf-timestamp.drv
    building '/nix/store/s39casall3q89wk2zhdbwyyvs8gw66gf-timestamp.drv'...
    /nix/store/jv5xlbslbsxpvd86hj215hgq3n77ihrp-timestamp
    
    $ ll
    total 8
    drwxr-xr-x 2 toraritte toraritte 4096 Jul 12 12:07 ./
    drwxrwxr-x 6 toraritte toraritte 4096 Jul 12 12:07 ../
    lrwxrwxrwx 1 toraritte toraritte 53 Jul 12 12:07 result -> /nix/store/jv5xlbslbsxpvd86hj215hgq3n77ihrp-timestamp
    
    $ cat result
    2023-07-12_11-58-34
    
  • builtins.readFile "${ /* eval to path */}"
    =
    builtins.readFile ( /* eval to path */ )

4 Likes