Java 8 Nashorn CLI use case: adding ISO 8601 instant to CSV with UNIX timestamp

By William Narmontas,

This page can be edited on GitHub.

Original article at Medium.

Nashorn is a JavaScript engine that comes with a Java 8 installation. It allows you to write scripts that have access to all of Java very easily, without needing any compilation steps. It also comes with its own Shell Scripting facilities and JSON handling (as you’d expect from a JavaScript engine).

There’s a command line utility called jjs which will execute a given script or provide you with a REPL where you can type in custom commands.

Here for demonstration purposes we’ll utilise the powerful java.time library:

jjs
java.time.Instant.ofEpochSecond(1552134142)
2019-03-09T12:22:22Z

We can get the time in a different time zone, such as in New York:

var instant = java.time.Instant.ofEpochSecond(1552134142)
var newYorkZone = java.time.ZoneId.of("America/New_York")
instant.atZone(newYorkZone)
2019-03-09T07:22:22-05:00[America/New_York]

And if we wanted to go back a few years we can:

instant.atZone(newYorkZone).minusYears(5)
2014-03-09T07:22:22-04:00[America/New_York]

Notice the same local time and timezone but different offset due to New York’s DST.

Making a time-series CSV human-readable

Let’s suppose we had this CSV file come in from a vendor or a client:

1552134142,a
1552134143,b
1562134142,c

Are you able to discern what date and time the timestamp actually refers to? Not unless you check by hand. But would you be able to visually scan through such a file if it had 40 million lines? That’s only doable with human readable timestamps.

With jjs On UNIX, you can make a script to effortlessly enrich some CSV input:

#!/usr/bin/jjs
function isoTime(unixTime) {
  var instant = java.time.Instant.ofEpochSecond(unixTime);
  return java.time.format.DateTimeFormatter.ISO_INSTANT.format(instant);
}

java.nio.file.Files.lines(
  java.nio.file.Paths.get("/dev/stdin")
).map(function (line) line.split(",", -1))
 .map(function(columns) { columns.unshift(isoTime(columns[0])); return columns })
 .map(function(columns) columns.join(","))
 .forEach(print);

/**
Usage:
./prepend-iso-time.js <<EOF
1552134142,a
1552134143,b
1562134142,c
EOF
**/

When you run it against sample data, you’ll see a new column in the front:

./prepend-iso-time.js <<EOF
> 1552134142,a
> 1552134143,b
> 1562134142,c
> EOF
2019-03-09T12:22:22Z,1552134142,a
2019-03-09T12:22:23Z,1552134143,b
2019-07-03T06:09:02Z,1562134142,c

And if this were a 40 million line file, you can use less to scan through all the data very easily.

What about JSON?

You may also come across NDJSON, such as:

{"unix_timestamp": 1552134142}
{"unix_timestamp": 1552134143}
{"unix_timestamp": 1562134142}

Now let’s say you are in New York and want to know attach the local time. You could do:

#!/usr/bin/jjs
function cstTime(unixTime) {
  var instant = java.time.Instant.ofEpochSecond(unixTime);
  var zoned = instant.atZone(java.time.ZoneId.of("America/New_York"));
  return java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zoned);
}

java.nio.file.Files.lines(
  java.nio.file.Paths.get("/dev/stdin")
).map(JSON.parse)
 .map(function(obj) { obj.iso_timestamp = cstTime(obj.unix_timestamp); return obj; })
 .map(JSON.stringify)
 .forEach(print);

/*
Usage:
./add-ny-time-json.js << EOF
{"unix_timestamp": 1552134142}
{"unix_timestamp": 1552134143}
{"unix_timestamp": 1562134142}
EOF
*/
./add-ny-time-json.js << EOF
> {"unix_timestamp": 1552134142}
> {"unix_timestamp": 1552134143}
> {"unix_timestamp": 1562134142}
> EOF
{"unix_timestamp":1552134142,"iso_timestamp":"2019-03-09T07:22:22-05:00[America/New_York]"}
{"unix_timestamp":1552134143,"iso_timestamp":"2019-03-09T07:22:23-05:00[America/New_York]"}
{"unix_timestamp":1562134142,"iso_timestamp":"2019-07-03T02:09:02-04:00[America/New_York]"}

Many other use cases

Especially where you want to do some Java calls but don’t wish to set up an IDE and a Maven/SBT project…

And where you’d like to turn your scripts into proper libraries later on.

One nice use case of mine was to reconfigure a Scala/Play app at runtime:

Conclusion

I hope you found this useful and will investigate what else Nashorn has to offer. It’s a very solid tool that’s available where Java 8 is and can be utilised very easily. Explore :-)