This is the software kind of date—the kind at a UNIX/Linux command line—not the kind that requires you to break your social-distancing protocols…
Recently, I decided that I needed to pre-generate skeletal posts for an ongoing project on my personal blog. On the one hand, I’m interested in the overall result of seeing how many weeks I've committed to staring me in the face. But on the other—more relevant to an audience of fellow developers—the process of generating a list of dates in a shell script turned out to be interesting and satisfying.
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 work. Deep breath!
- 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. However, those relative specifiers? That’s the stuff of programming! With relative specifiers, we can do something like this somewhat-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 by counting up in increments of seven days.
Harvesting Information
As far as it goes, the loop will serve us well…except that, if you’re familiar with Jekyll (which is what I use to run my blog), you probably 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 my original post on this is named 2020-03-18-date.md
, for example), and the front-matter 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 file name’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. I also have some scripts that subtract two times in this format and feed the difference into date
to get minutes and seconds.
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 front-matter.
…Except that doing this isn’t aware of daylight saving time. So, if I generate 2020-03-19 17:06:15-0400
for my post tomorrow, that’s fine, because I’m currently on Eastern Daylight Time and will be tomorrow, too. 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 Swatch Internet 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 parse that time offset and append to my generated time to get most of what I want.
…Except that I didn’t actually use Thursday +343 days
in my script, since I only learned that combining mixed kinds of specifiers was an option later in the process. Rather than specifying “Thursday,” I stupidly specified the date of the first Thursday that I wanted using the faketime
utility, which came with 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
). That's close enough for a quick hack.
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.
Top comments (0)