Skip to content

Commit

Permalink
fix #26: normalize components to avoid panics
Browse files Browse the repository at this point in the history
use github actions
  • Loading branch information
yaa110 committed Jul 12, 2023
1 parent 383c792 commit 9969591
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 62 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
on: [push, pull_request]
name: Test and Build
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: "1.16"
- name: Checkout code
uses: actions/checkout@v2
- name: Build
run: go build
- name: Test and Coverage
run: go test ./... -v -covermode=count
13 changes: 0 additions & 13 deletions .travis.yml

This file was deleted.

33 changes: 3 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Go Persian Calendar

[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/yaa110/go-persian-calendar) [![Build Status](https://travis-ci.org/yaa110/go-persian-calendar.svg)](https://travis-ci.org/yaa110/go-persian-calendar) [![goreportcard](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](http://goreportcard.com/report/yaa110/go-persian-calendar) [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/yaa110/go-persian-calendar/blob/master/LICENSE)
[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/yaa110/go-persian-calendar) [![Test and Build](https://github.com/yaa110/go-persian-calendar/workflows/Test%20and%20Build/badge.svg)](https://github.com/yaa110/go-persian-calendar/actions?query=workflow%3A"Test+and+Build") [![goreportcard](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](http://goreportcard.com/report/yaa110/go-persian-calendar) [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/yaa110/go-persian-calendar/blob/master/LICENSE)

**Go Persian Calendar** provides functionality for conversion among Persian (Solar Hijri) and Gregorian calendars. A Julian calendar is used as an interface for all conversions. The package name is `ptime` and it is compatible with the package [time](https://golang.org/pkg/time). All months are available with both Iranian and Dari Persian names. This source code is licensed under MIT license that can be found in the LICENSE file.

Expand Down Expand Up @@ -177,36 +177,9 @@ fmt.Println(pt.TimeFormat("2 Jan 2006")) // output: 2 مهر 1394
// Z07:00 zone offset (e.g. +03:30)
```

## Changelog
## Limitation

### v1.0.0

- Remove `loc *time.Location` argument from `ptime.Unix()`, `ptime.SetUnix()` and `ptime.Now()`

### v0.6.0

- Support standard time format

### v0.5.0

- Add `BeginningOfWeek`, `BeginningOfMonth` and `BeginningOfYear` methods.
- Format UTC timezone by `Z` instead of `+00:00`

### v0.4.1

- Refactor code

### v0.4.0

- Change module import name

### v0.3.1

- Use Go modules

### v0.3

- `ptime.Iran` and `ptime.Afghanistan` changed to `ptime.Iran()` and `ptime.Afghanistan()`, respectively.
- The minimum value of Gregorian year is 1097, otherwise a zero instance of `ptime.Time` is returned.

## Documentation

Expand Down
144 changes: 125 additions & 19 deletions ptime.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,45 +215,98 @@ func (t Time) String() string {

// Dari returns the Dari name of the month.
func (m Month) Dari() string {
return dmonths[m-1]
switch {
case m < 1:
return dmonths[0]
case m > 11:
return dmonths[11]
default:
return dmonths[m-1]
}
}

// String returns the Persian name of the month.
func (m Month) String() string {
return months[m-1]
switch {
case m < 1:
return months[0]
case m > 11:
return months[11]
default:
return months[m-1]
}
}

// String returns the Persian name of the day in week.
func (d Weekday) String() string {
return days[d]
switch {
case d < 0:
return days[0]
case d > 6:
return days[6]
default:
return days[d]
}
}

// Short returns the Persian short name of the day in week.
func (d Weekday) Short() string {
return sdays[d]
switch {
case d < 0:
return sdays[0]
case d > 6:
return sdays[6]
default:
return sdays[d]
}
}

// String returns the Persian name of 12-Hour marker.
func (a AmPm) String() string {
return amPm[a]
switch {
case a < 0:
return amPm[0]
case a > 1:
return amPm[1]
default:
return amPm[a]
}
}

// Short returns the Persian short name of 12-Hour marker.
func (a AmPm) Short() string {
return sAmPm[a]
switch {
case a < 0:
return sAmPm[0]
case a > 1:
return sAmPm[1]
default:
return sAmPm[a]
}
}

// String returns the Persian name of day time.
func (d DayTime) String() string {
return daytimes[d]
switch {
case d < 0:
return daytimes[0]
case d > 7:
return daytimes[7]
default:
return daytimes[d]
}
}

// New converts Gregorian calendar to Persian calendar and
//
// returns a new instance of Time corresponding to the time of t.
// returns a new instance of Time corresponding to the time of t or a zero instance of time if Gregorian year is less than 1097.
//
// t is an instance of time.Time in Gregorian calendar.
func New(t time.Time) Time {
if t.Year() < 1097 {
return Time{}
}

pt := new(Time)
pt.SetTime(t)

Expand Down Expand Up @@ -290,7 +343,12 @@ func (t Time) Time() time.Time {
year = 4*k + n + i - 4716
}

return time.Date(year, time.Month(month), day, t.hour, t.min, t.sec, t.nsec, t.loc)
loc := t.loc
if loc == nil {
loc = time.Local
}

return time.Date(year, time.Month(month), day, t.hour, t.min, t.sec, t.nsec, loc)
}

// Date returns a new instance of Time.
Expand All @@ -299,10 +357,10 @@ func (t Time) Time() time.Time {
//
// hour, min minute, sec seconds, nsec nanoseconds offsets represent a moment in time.
//
// loc is a pointer to time.Location and must not be nil.
// loc is a pointer to time.Location, if loc is nil then the local time is used.
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *time.Location) Time {
if loc == nil {
panic("ptime: the Location must not be nil in call to Date")
loc = time.Local
}

t := new(Time)
Expand Down Expand Up @@ -439,6 +497,13 @@ func (t *Time) Set(year int, month Month, day, hour, min, sec, nsec int, loc *ti
// Normalize month, overflowing into year.
m := int(month) - 1
year, m = norm(year, m, 12)

if m < 0 {
m = 0
} else if m > 11 {
m = 11
}

if isLeap(year) {
m, day = normDay(m, day, pMonthCount[m][1])
} else {
Expand Down Expand Up @@ -525,6 +590,11 @@ func (t *Time) At(hour, min, sec, nsec int) {
t.SetNanosecond(nsec)
}

// IsZero returns true if t is zero time instance
func (t Time) IsZero() bool {
return t == Time{}
}

// Unix returns the number of seconds since January 1, 1970 UTC.
func (t Time) Unix() int64 {
return t.Time().Unix()
Expand Down Expand Up @@ -610,7 +680,14 @@ func (t Time) Location() *time.Location {

// YearDay returns the day of year of t.
func (t Time) YearDay() int {
return pMonthCount[t.month-1][2] + t.day
m := t.month - 1
if m < 0 {
m = 0
} else if m > 11 {
m = 11
}

return pMonthCount[m][2] + t.day
}

// RYearDay returns the number of remaining days of the year of t.
Expand All @@ -633,7 +710,15 @@ func (t Time) RMonthDay() int {
if t.IsLeap() {
i = 1
}
return pMonthCount[t.month-1][i] - t.day

m := t.month - 1
if m < 0 {
m = 0
} else if m > 11 {
m = 11
}

return pMonthCount[m][i] - t.day
}

// BeginningOfWeek returns a new instance of Time representing the first day of the week of t.
Expand Down Expand Up @@ -685,7 +770,15 @@ func (t Time) LastMonthDay() Time {
if t.IsLeap() {
i = 1
}
ld := pMonthCount[t.month-1][i]

m := t.month - 1
if m < 0 {
m = 0
} else if m > 11 {
m = 11
}

ld := pMonthCount[m][i]
if ld == t.day {
return t
}
Expand Down Expand Up @@ -861,12 +954,17 @@ func (t Time) ZoneOffset(f ...string) string {
// z the name of location
// Z zone offset (e.g. +03:30)
func (t Time) Format(format string) string {
year := strconv.Itoa(t.year)
if len(year) < 4 {
year = fmt.Sprintf("%04d", t.year)
}

r := strings.NewReplacer(
"yyyy", strconv.Itoa(t.year),
"yyy", strconv.Itoa(t.year),
"yyyy", year,
"yyy", year,
"MMM", t.month.String(),
"MMI", t.month.Dari(),
"yy", strconv.Itoa(t.year)[2:],
"yy", year[2:],
"MM", fmt.Sprintf("%02d", t.month),
"rw", strconv.Itoa(t.RYearWeek()),
"RD", strconv.Itoa(t.RYearDay()),
Expand All @@ -879,7 +977,7 @@ func (t Time) Format(format string) string {
"mm", fmt.Sprintf("%02d", t.min),
"ns", strconv.Itoa(t.nsec),
"ss", fmt.Sprintf("%02d", t.sec),
"y", strconv.Itoa(t.year),
"y", year,
"M", strconv.Itoa(int(t.month)),
"w", strconv.Itoa(t.YearWeek()),
"W", strconv.Itoa(t.MonthWeek()),
Expand Down Expand Up @@ -1069,7 +1167,15 @@ func (t *Time) normDay() {
if t.IsLeap() {
i = 1
}
between(&t.day, 1, pMonthCount[t.month-1][i])

m := t.month - 1
if m < 0 {
m = 0
} else if m > 11 {
m = 11
}

between(&t.day, 1, pMonthCount[m][i])
}

func modifyHour(value, max int) int {
Expand Down
28 changes: 28 additions & 0 deletions ptime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,34 @@ func TestWeeks(t *testing.T) {
}
}

func TestPanic(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("the test has paniced")
}
}()

for y := 0; y < 3000; y++ {
for m := 0; m < 13; m++ {
for d := 0; d < 32; d++ {
ti := time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local)
pt := New(ti)
_ = pt.Month().String()
}
}
}

pt := Time{}
_ = pt.String()
}

func TestZero(t *testing.T) {
pt := New(time.Time{})
if !pt.IsZero() {
t.Error("time must be zero")
}
}

func TestFormat(t *testing.T) {
ti := Date(1394, Mehr, 2, 12, 59, 59, 50260050, Iran())

Expand Down

0 comments on commit 9969591

Please sign in to comment.