This is the software kind of date, not the kind that requires you to break your social-distancing protocols…

Calendar

As I mentioned on Monday, I decided that I needed to pre-generate skeletal posts for each of the upcoming Star Trek episodes, plus a few compilation posts. On the one hand, I’m interested in the overall result—the revelation that I now feel committed to doing this through March of 2022, which will be a retrospective of what we learned from the movies, followed by a retrospective of everything we’ve discovered across the entire run for the original cast.

But on the other, the process of generating a list of dates in a shell script might be as interesting to some readers as it was to me.

Unsurprisingly, this centers on the GNU date command.

Another Day, Another Time

The date documentation makes it clear enough that there is a --date (or -d) option that allows specifying a date, but what’s not made clear is how flexible this specifier can be. Any of the following (deep breath!) work.

  • A text-based date string, such as “18 Mar 2020” or “18 March 2020”
  • A numerical date string, such “01/02/03” (Day/Month/Year) or “01-02-03” (Year-Month-Day) or even “20200314 1440” (YYYYMMDD HHMM); years from 00–68 are in the 21st century (2000–2068) and 69–99 are in the 19th century, but 1–9 (no leading zero) are literal years CE 1–9
  • A time, whether on a twelve-hour (“10:35 p.m.”) or twenty-four-hour (“22:35”) clock, optionally including a time-zone specifier by name (“GMT” or “PDT”)
  • Complete timestamps (“2020-03-14T17:23:53.000000+0700”)
  • Upcoming days of the week (“Thursday”)
  • Space-separated relative specifiers (“last Thursday” or “2 years ago” or “last Monday +3 years +7 hours -21 minutes” or “now”)
  • Seconds since the UNIX Epoch
  • Any of the above prefixed with a long-form timezone like “TZ=’Europe/Geneva’”
  • A lot of these can be mixed, such as a numerical date string followed by relative specifiers.

Most of those are useful but obvious and uninteresting, but those relative specifiers? That’s the stuff of programming! With relative specifiers, we can do something like this contrived example.

count=0
for filename in *
do
  date --date "thursday +${count} days"
  count=$(($count + 7))
done

This takes each file in the current directory (not for any useful reason, just because I wanted a finite set) and prints the date of every Thursday going forward.

Harvesting Information

As far as it goes, the loop will serve us well…except that, if you’re familiar with Jekyll, you know that I need a couple of different kinds of dates. The post filenames are in a “YYYY-MM-DD-title.md” format (the file for this post is named 2020-03-18-date.md, for example, and the URL is derived from that), and the frontmatter of the post includes a date and time, where the date can be a wide variety of formats.

This brings us to formatting. The format needed for the filename’s date is easy. In fact, there’s an in-built formatter for dash-delimited values in descending order of significance.

date +%F
# outputs "2020-03-14"

Another interesting (if not relevant to where this conversation is going) format gives us UNIX time in seconds.

date +%s
# outputs "1584534083"

You can prepend an @ to that output and use it to set the time using --date, as above.

What I decided to do with the pre-generated posts was to give them random release times. I actually publish the Star Trek posts whenever I have a few free minutes on Thursday evenings, so the actual time doesn’t really matter and I didn’t want to convince myself that there was some “official” time. So, I generate random numbers for the minutes and seconds after 5 P.M.; I then assemble the time in the post’s frontmatter.

…Except that doing this isn’t aware of daylight saving time. So, if I generate 2020-03-19 17:06:15-0400 for The Corbomite Maneuver tomorrow, that’s fine, because I’m currently on Eastern Daylight Time. But 2020-12-19 17:06:15-0400 is supposed to be on Eastern Standard Time, so the displayed time is “04:06:15 PM EST.” And if I leave that specifier off, Jekyll assumes that I must mean UTC.

I know, I know, daylight saving is stupid. Time zones are stupid. We should all probably just use UNIX time or something…

Anyway, one way that we can solve this new problem is by requesting the time zone’s offset on the relevant day, instead of filling it in manually.

date +%z --date "thursday +343 days"
# outputs "-0500" for me
date +%z
# outputs "-0400"
date +%F,%z
# outputs "2020-03-18,-0400"

I can then append that time offset to my generated time and I have most of what I want.

…Except that I didn’t actually use Thursday +343 days in my script, since I only learned that was an option later. Rather than specifying “Thursday,” I stupidly specified the date of the first Thursday that I wanted using the faketime utility, which gave me an explicit time of midnight. So as I crossed daylight saving thresholds, the output times would bump back and forth by an hour, which meant being one day off (standard time shows as an hour behind, so midnight becomes 11 P.M. the previous night) for part of the year.

I could have solved this by doing better research on the aforementioned --date option, but I didn’t, and so the solution I came up with was to add another eight hours and request that date (%F) and time offset (%z).

Have a Good Time…

There’s obviously a little bit more to the script, like using a here document as a template for each file and creating the abbreviated titles for the filenames, but that’s all superfluous for this discussion, which is probably more than anybody could realistically want to know about generating a list of times.


Credits: The header image is untitled calendar by an unknown photographer from PxHere, made available under the CC0 1.0 Universal Public Domain Dedication.