Compare commits

...

6 commits

Author SHA1 Message Date
Petar Kapriš cc6e52d352 Add tests for julian package 2024-07-21 00:28:25 +02:00
Petar Kapriš bd5c9fe692 Add julian Date package with conversion to time.Time
The julian.Date type will be used within the project to support indexing
of holidays using the Julian calendar, Gregorian dates are implemented
by using time.Time (since it is already gregorian). Currently only
conversion Julian->Gregorian has been implemented, but the opposite will
be added soon.
2024-07-21 00:21:34 +02:00
Petar Kapriš 73fde0698c Add translation for UI message in source comments 2024-07-15 19:44:54 +02:00
Petar Kapriš 62c969b015 Add style for "red letter" days in drw/drw.go 2024-07-15 19:43:08 +02:00
Petar Kapriš 5574f20de8 add gitignore 2024-07-15 19:39:20 +02:00
Petar Kapriš 98dd500385 Bump golang to version 1.22 2024-07-15 19:34:04 +02:00
6 changed files with 286 additions and 2 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/ck

View file

@ -27,6 +27,10 @@ var (
Background(tcell.ColorReset).
Foreground(tcell.GetColor("green")).
Bold(true)
HolyStyle tcell.Style = tcell.StyleDefault.
Background(tcell.ColorReset).
Foreground(tcell.GetColor("red")).
Bold(true)
)
func centeredText(s string, x, y, width int, scr tcell.Screen, style tcell.Style) {

View file

@ -169,7 +169,7 @@ func DrawWall() {
Wall.Clear()
w, h := Wall.Size()
if w < maxMonthWidth+2*smallGap || h < maxMonthHeight+2*smallGap+titleHeight {
wrappedText("Екран је премали за календар.", Wall, DefStyle)
wrappedText("Екран је премали за календар.", Wall, DefStyle) // The screen is too small for the calender
return
}
monthsWide = (w - 2*smallGap + largeGap) / (maxMonthWidth + largeGap)

12
go.mod
View file

@ -1,5 +1,15 @@
module gitlab.com/tvrdosrz/ck
go 1.16
go 1.22
require github.com/gdamore/tcell/v2 v2.3.11
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.0 // indirect
)

122
julian/julian.go Normal file
View file

@ -0,0 +1,122 @@
package julian
import "time"
// TODO: describe how time doesn't go past BCE, and therefore we don't either
// Keep in mind, Go's time.Time gregorian dates are `proleptic`, ie. there is
// no need to give special care to dates before 15. October 1582.
type Date struct {
year int
month time.Month
day int
}
func greaterOrEqual(a, b Date) bool {
if a.year != b.year {
return a.year > b.year
} else if a.month != b.month {
return a.month > b.month
} else {
return a.day >= b.day
}
}
func monthLength(year int, month time.Month) int {
lengths := map[time.Month]int{
time.January: 31,
time.March: 31,
time.April: 30,
time.May: 31,
time.June: 30,
time.July: 31,
time.August: 31,
time.September: 30,
time.October: 31,
time.November: 30,
time.December: 31,
}
if _, ok := lengths[month]; ok {
return lengths[month]
}
// February
if year%4 == 0 {
return 29
}
return 28
}
func dateDiff(d Date, days int) Date {
// keep in mind throughout this code that the original "days" value can
// be negative
var fourYearPeriods int
var periodLen int = (3 * 365) + 366
// a difference which is an exact multiple of 4 years can be dealt with
// easily, so we only have to take greater care with the remainder
fourYearPeriods, days = days/periodLen, days%periodLen
// this `if' will happen if days was originally <0, AND is not an exact
// multiple of periodLen
if days < 0 {
fourYearPeriods -= 1
days += periodLen
}
// now days must be 0 <= days < periodLen, so our job is easier
// first we shift in the big periods
d.year += fourYearPeriods * 4
// We shift the date to the first of the month to simplify our
// calculation
days += d.day - 1
d.day = 1
// since days < periodLen+31-1, we will go through this loop at most
// 4*12+1 = 49 times
for days >= monthLength(d.year, d.month) {
days -= monthLength(d.year, d.month)
d.year, d.month = nextMonth(d.year, d.month)
}
d.day += days
return d
}
func nextMonth(year int, month time.Month) (int, time.Month) {
if month == time.December {
return year + 1, time.January
} else {
return year, month + 1
}
}
func New(year int, month time.Month, day int) Date {
// TODO: deal with users giving negative dates
for day > monthLength(year, month) {
day -= monthLength(year, month)
year, month = nextMonth(year, month)
}
return Date{year, month, day}
}
// The algorithm, in large part: follows this article:
// https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars
func ToTime(d Date) time.Time {
offset := -2
borderDate := Date{100, time.March, 2}
// We don't increase the offset for years divisible by 400, ie. every
// fourth loop, we keep track of that with skipYear, we can't simply
// check borderDate.year % 400, because when we reach 31. December
// 8299, the year will not be divisible by 100 anymore
skipYear := 1
for greaterOrEqual(d, borderDate) {
// We can simply add 100 to the year because in the julian
// calendar, if a date exists in year y, it always exists in
// year y+100
borderDate.year += 100
if skipYear%4 != 0 {
borderDate = dateDiff(borderDate, -1)
offset++
}
skipYear++
}
d = dateDiff(d, offset)
return time.Date(d.year, d.month, d.day, 0, 0, 0, 0, time.UTC)
}

147
julian/julian_test.go Normal file
View file

@ -0,0 +1,147 @@
package julian
import (
"fmt"
"testing"
"time"
)
func TestMonthLength(t *testing.T) {
var tests = []struct {
y int
m time.Month
want int
}{
{2001, time.January, 31},
{2001, time.February, 28},
{2000, time.February, 29},
{2001, time.March, 31},
{2001, time.April, 30},
{2001, time.May, 31},
{2001, time.June, 30},
{2001, time.July, 31},
{2001, time.August, 31},
{2001, time.September, 30},
{2001, time.October, 31},
{2001, time.November, 30},
{2001, time.December, 31},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %d", tt.m, tt.y)
t.Run(testname, func(t *testing.T) {
ans := monthLength(tt.y, tt.m)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
})
}
}
func TestDateDiff(t *testing.T) {
var tests = []struct {
d Date
days int
want Date
}{
{Date{2001, time.January, 20}, 5, Date{2001, time.January, 25}},
{Date{2001, time.February, 28}, 5, Date{2001, time.March, 5}},
{Date{2004, time.February, 28}, 5, Date{2004, time.March, 4}},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %d", tt.d, tt.days)
t.Run(testname, func(t *testing.T) {
ans := dateDiff(tt.d, tt.days)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
})
}
}
func TestNextMonth(t *testing.T) {
ans1, ans2 := nextMonth(2000, time.December)
if ans1 != 2001 || ans2 != time.January {
t.Errorf("nextMonth(2000, time.December) = %d, %v; want 2001, time.January", ans1, ans2)
}
}
func TestToTime(t *testing.T) {
gDate := func(year int, month time.Month, day int) time.Time {
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
}
var tests = []struct {
d Date
want time.Time
}{
{Date{100, time.February, 29}, gDate(100, time.February, 27)},
{Date{100, time.March, 1}, gDate(100, time.February, 28)},
{Date{100, time.March, 2}, gDate(100, time.March, 1)},
{Date{200, time.February, 28}, gDate(200, time.February, 27)},
{Date{200, time.February, 29}, gDate(200, time.February, 28)},
{Date{200, time.March, 1}, gDate(200, time.March, 1)},
{Date{300, time.February, 28}, gDate(300, time.February, 28)},
{Date{300, time.February, 29}, gDate(300, time.March, 1)},
{Date{300, time.March, 1}, gDate(300, time.March, 2)},
{Date{500, time.February, 28}, gDate(500, time.March, 1)},
{Date{500, time.February, 29}, gDate(500, time.March, 2)},
{Date{500, time.March, 1}, gDate(500, time.March, 3)},
{Date{600, time.February, 28}, gDate(600, time.March, 2)},
{Date{600, time.February, 29}, gDate(600, time.March, 3)},
{Date{600, time.March, 1}, gDate(600, time.March, 4)},
{Date{700, time.February, 28}, gDate(700, time.March, 3)},
{Date{700, time.February, 29}, gDate(700, time.March, 4)},
{Date{700, time.March, 1}, gDate(700, time.March, 5)},
{Date{900, time.February, 28}, gDate(900, time.March, 4)},
{Date{900, time.February, 29}, gDate(900, time.March, 5)},
{Date{900, time.March, 1}, gDate(900, time.March, 6)},
{Date{1000, time.February, 28}, gDate(1000, time.March, 5)},
{Date{1000, time.February, 29}, gDate(1000, time.March, 6)},
{Date{1000, time.March, 1}, gDate(1000, time.March, 7)},
{Date{1100, time.February, 28}, gDate(1100, time.March, 6)},
{Date{1100, time.February, 29}, gDate(1100, time.March, 7)},
{Date{1100, time.March, 1}, gDate(1100, time.March, 8)},
{Date{1300, time.February, 28}, gDate(1300, time.March, 7)},
{Date{1300, time.February, 29}, gDate(1300, time.March, 8)},
{Date{1300, time.March, 1}, gDate(1300, time.March, 9)},
{Date{1400, time.February, 28}, gDate(1400, time.March, 8)},
{Date{1400, time.February, 29}, gDate(1400, time.March, 9)},
{Date{1400, time.March, 1}, gDate(1400, time.March, 10)},
{Date{1500, time.February, 28}, gDate(1500, time.March, 9)},
{Date{1500, time.February, 29}, gDate(1500, time.March, 10)},
{Date{1500, time.March, 1}, gDate(1500, time.March, 11)},
{Date{1582, time.October, 4}, gDate(1582, time.October, 14)},
{Date{1582, time.October, 5}, gDate(1582, time.October, 15)},
{Date{1582, time.October, 6}, gDate(1582, time.October, 16)},
{Date{1700, time.February, 18}, gDate(1700, time.February, 28)},
{Date{1700, time.February, 19}, gDate(1700, time.March, 1)},
{Date{1700, time.February, 28}, gDate(1700, time.March, 10)},
{Date{1700, time.February, 29}, gDate(1700, time.March, 11)},
{Date{1700, time.March, 1}, gDate(1700, time.March, 12)},
{Date{1800, time.February, 17}, gDate(1800, time.February, 28)},
{Date{1800, time.February, 18}, gDate(1800, time.March, 1)},
{Date{1800, time.February, 28}, gDate(1800, time.March, 11)},
{Date{1800, time.February, 29}, gDate(1800, time.March, 12)},
{Date{1800, time.March, 1}, gDate(1800, time.March, 13)},
{Date{1900, time.February, 16}, gDate(1900, time.February, 28)},
{Date{1900, time.February, 17}, gDate(1900, time.March, 1)},
{Date{1900, time.February, 28}, gDate(1900, time.March, 12)},
{Date{1900, time.February, 29}, gDate(1900, time.March, 13)},
{Date{1900, time.March, 1}, gDate(1900, time.March, 14)},
{Date{2100, time.February, 15}, gDate(2100, time.February, 28)},
{Date{2100, time.February, 16}, gDate(2100, time.March, 1)},
{Date{2100, time.February, 28}, gDate(2100, time.March, 13)},
{Date{2100, time.February, 29}, gDate(2100, time.March, 14)},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %v", tt.d, tt.want)
t.Run(testname, func(t *testing.T) {
ans := ToTime(tt.d)
if ans != tt.want {
t.Errorf("got %v, want %v", ans, tt.want)
}
})
}
}