Calculating weekdays and dates with Go
Programming Snapshot – Go
Math wizards amaze audiences by naming the day of the week for any date called out by the audience. Mike Schilli writes a training program in Go to turn amateurs into headline performers.
Math geniuses can do this: Someone from the audience calls out "December 12, 2019," and the numbers wizard announces "Thursday!" after just a few seconds. How did he do that? Does the entertainer have a photographic memory, or some kind of calendar function built into his head? The solution is surprisingly simple: He only has to go through a few rules that are easy to remember and, with a little bit of practice, can come up with the day of the week for any given date.
Years ago in this column, I introduced a similar mental arithmetic method for calculating weekdays, but with more elaborate steps [1]. A reader then replied that the method was unnecessarily complex and referred me to the simpler Doomsday rule [2], which I will use here to create a Go training program for weekday prediction.
Last Day
According to the Doomsday rule, the following days of a year always fall on the same weekday: 5/9, 9/5, 11/7, and 7/11 (using the Month/Day format). You can easily memorize this with the formula "9-5 at 7/11" (i.e., the typical nine-to-five workday at 7-Eleven, the US convenience store chain).
Many other Doomsday days fall on day-month duplicates: 4/4, 6/6, 8/8, 10/10, and 12/12. January, February, and March are the only exceptions; in non-leap years, the day Doomsday falls on is January 3, February 7, and March 7 (Figure 1). In leap years, Doomsday changes to January 4 and February 8, while March 7 stays the same.
The Doomsday for 2019 is Thursday according to a separate procedure (2018 was a Wednesday; 2020 will be a Saturday; see Figure 2). So, if someone asks you for April 4, 2019, the answer is obvious: Thursday, because April 4 is the Doomsday.
What about April 25, 2019? Which weekday was this date? Again, it's a Thursday, of course, because the 25th is exactly 21 days after the 4th (i.e., exactly three weeks later to the day). How about November 12, 2019? Because of the "9-5 at 7/11" rule, November 7 is a Thursday, so November 12 is five days (or one week minus two days) later, and therefore a Tuesday.
Or, you can either count the weekdays in your head, on your fingers, or by numbering the weekdays Sunday to Saturday from zero to six and then knocking off the remainder after dividing by seven to reach a result. Thursday is day four in this scheme; five added to it gives you nine, and after dividing by seven, you are left with two: So, it's a Tuesday.
Easy Learning Method
What about January 1, 2020? Next year, the Doomsday is a Saturday, according to Figure 2, so January 4 (watch out – it's a leap year!) is a Saturday and New Year's Day thus a Wednesday. Slowly the mists clear, and the truth comes to light: There is no magic involved, just simple mnemonic rules that anyone can easily practice before going on stage.
To train would-be number wizards, the Go program presented here selects a random date in the current year and lets the user choose between seven weekdays. If you click on the right day after applying the formula in your head, you win a point, and the counter in the display's upper-right corner is incremented by one (Figure 3). The display changes to a new date, and the game resumes.
If, on the other hand, the player miscalculates and bets on the wrong day of the week, a penalty follows: All the points you scored so far expire, and the counter drops back to zero (Figure 4). Afterwards, you can try again and hopefully choose the right day of the week; again you score a point and can slowly climb to a new high score.
The game runs in a terminal user interface (UI) after you launch it at the command line. Even exhausted datacenter system administrators therefore can take a little break to relax. Go and the termui
library, introduced in a previous column [3], run on all conceivable platforms including Linux, but also on other Unix derivatives and macOS – it even runs on Windows.
To create an executable binary from the Go code for Listing 1 [4], first create a new Go module (using go-1.12
or later) and then start the compilation process with build
; this automatically retrieves all libraries identified as dependencies off the web and compiles them, too:
go mod init dateday go build dateday.go
Listing 1
dateday.go
001 package main 002 003 import ( 004 "errors" 005 "fmt" 006 ui "github.com/gizak/termui/v3" 007 "github.com/gizak/termui/v3/widgets" 008 "math/rand" 009 "strings" 010 "time" 011 ) 012 013 var wdays = []string{"Sunday", "Monday", 014 "Tuesday", "Wednesday", "Thursday", 015 "Friday", "Saturday"} 016 017 func main() { 018 year := time.Now().Year() 019 wins := 0 020 021 if err := ui.Init(); err != nil { 022 panic(err) 023 } 024 defer ui.Close() 025 026 task := randDate(year) 027 028 p := widgets.NewParagraph() 029 p.SetRect(0, 0, 25, 3) 030 displayTask(task, wins, p) 031 032 days := widgets.NewParagraph() 033 days.Text = fmt.Sprintf( 034 "[%s](fg:black)", 035 strings.Join(wdays, "\n")) 036 days.SetRect(0, 3, 25, 12) 037 ui.Render(p, days) 038 039 uiEvents := ui.PollEvents() 040 for { 041 e := <-uiEvents 042 switch e.ID { 043 case "q", "<C-c>": 044 return 045 case "<MouseLeft>": 046 wdayGuess, err := wdayPick( 047 e.Payload.(ui.Mouse).Y) 048 if err != nil { // invalid click? 049 continue 050 } 051 wdayName := wdays[task.Weekday()] 052 053 if wdayGuess == wdayName { 054 days.BorderStyle.Fg = 055 ui.ColorGreen 056 task = randDate(year) 057 wins++ 058 } else { 059 days.BorderStyle.Fg = ui.ColorRed 060 wins = 0 061 } 062 063 displayTask(task, wins, p) 064 ui.Render(p, days) 065 go func() { 066 <-time.After( 067 200 * time.Millisecond) 068 days.BorderStyle.Fg = 069 ui.ColorWhite 070 ui.Render(days) 071 }() 072 } 073 } 074 } 075 076 func displayTask(task time.Time, 077 wins int, widget *widgets.Paragraph) { 078 079 widget.Text = fmt.Sprintf( 080 "[%d-%02d-%02d](fg:black)" + 081 "%s[%3d](fg:green)", 082 task.Year(), task.Month(), task.Day(), 083 " ", wins) 084 } 085 086 func wdayPick(y int) (string, error) { 087 if y > 10 || y < 4 { 088 return "", errors.New("Invalid pick") 089 } 090 return wdays[y-4], nil 091 } 092 093 func randDate(year int) time.Time { 094 start := time.Date(year, time.Month(1), 095 1, 0, 0, 0, 0, time.Local) 096 end := start.AddDate(1, 0, 0) 097 098 s1 := rand.NewSource( // random seed 099 time.Now().UnixNano()) 100 r1 := rand.New(s1) 101 102 epoch := start.Unix() + int64(r1.Intn( 103 int(end.Unix()-start.Unix()))) 104 return time.Unix(epoch, 0) 105 }
As the installation process in Figure 5 shows, go build
takes a whole bunch of libraries as source code from their GitHub repositories and bundles them all in one binary, which is not overly large at 2.8MB.
Opening and Closing
Line 6 of Listing 1 adds the code for the terminal UI library under ui
. Its Init()
function switches the terminal window into graphics mode in line 21 and delays a clean teardown until the end of the main program with defer
in line 24. The UI in Figures 3 and 4 consists of two stacked Paragraph
widgets from the termui widgets library.
The upper widget shows the date to be guessed; on the right-hand side, you can see the number of successful guesses in the wins
variable. The lower section shows a static string that displays the days of the week from Sunday through Saturday separated by newline characters. The SetRect()
method sets the size of the widgets in rows and columns that each can hold precisely one character.
In order for the UI framework to render the widgets on the terminal interface, it notifies the rendering engine via ui.Render()
in line 37. That's all there is to drawing the GUI. Line 39 then opens a channel with a call to ui.PollEvents()
; it reports UI events like key presses, mouse clicks, or window resize actions. Line 41 blocks until an event occurs, while the subsequent switch statement checks whether the user has pressed Ctrl+C or Q (i.e., to end the program).
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Endless OS 6 has Arrived
After more than a year since the last update, the latest release of Endless OS is now available for general usage.
-
Fedora Asahi 40 Remix Available for Macs with Apple Silicon
If you've been anticipating KDE's Plasma 6 for your Apple Silicon-powered Mac, then you're in luck.
-
Red Hat Adds New Deployment Option for Enterprise Linux Platforms
Red Hat has re-imagined enterprise Linux for an AI future with Image Mode.
-
OSJH and LPI Release 2024 Open Source Pros Job Survey Results
See what open source professionals look for in a new role.
-
Proton 9.0-1 Released to Improve Gaming with Steam
The latest release of Proton 9 adds several improvements and fixes an issue that has been problematic for Linux users.
-
So Long Neofetch and Thanks for the Info
Today is a day that every Linux user who enjoys bragging about their system(s) will mourn, as Neofetch has come to an end.
-
Ubuntu 24.04 Comes with a “Flaw"
If you're thinking you might want to upgrade from your current Ubuntu release to the latest, there's something you might want to consider before doing so.
-
Canonical Releases Ubuntu 24.04
After a brief pause because of the XZ vulnerability, Ubuntu 24.04 is now available for install.
-
Linux Servers Targeted by Akira Ransomware
A group of bad actors who have already extorted $42 million have their sights set on the Linux platform.
-
TUXEDO Computers Unveils Linux Laptop Featuring AMD Ryzen CPU
This latest release is the first laptop to include the new CPU from Ryzen and Linux preinstalled.