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
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.