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
-
Canonical Bumps LTS Support to 12 years
If you're worried that your Ubuntu LTS release won't be supported long enough to last, Canonical has a surprise for you in the form of 12 years of security coverage.
-
Fedora 40 Beta Released Soon
With the official release of Fedora 40 coming in April, it's almost time to download the beta and see what's new.
-
New Pentesting Distribution to Compete with Kali Linux
SnoopGod is now available for your testing needs
-
Juno Computers Launches Another Linux Laptop
If you're looking for a powerhouse laptop that runs Ubuntu, the Juno Computers Neptune 17 v6 should be on your radar.
-
ZorinOS 17.1 Released, Includes Improved Windows App Support
If you need or desire to run Windows applications on Linux, there's one distribution intent on making that easier for you and its new release further improves that feature.
-
Linux Market Share Surpasses 4% for the First Time
Look out Windows and macOS, Linux is on the rise and has even topped ChromeOS to become the fourth most widely used OS around the globe.
-
KDE’s Plasma 6 Officially Available
KDE’s Plasma 6.0 "Megarelease" has happened, and it's brimming with new features, polish, and performance.
-
Latest Version of Tails Unleashed
Tails 6.0 is based on Debian 12 and includes GNOME 43.
-
KDE Announces New Slimbook V with Plenty of Power and KDE’s Plasma 6
If you're a fan of KDE Plasma, you'll be thrilled to hear they've announced a new Slimbook with an AMD CPU and the latest version of KDE Plasma desktop.
-
Monthly Sponsorship Includes Early Access to elementary OS 8
If you want to get a glimpse of what's in the pipeline for elementary OS 8, just set up a monthly sponsorship to help fund its continued existence.