Skip to content

Commit

Permalink
Merge pull request #3 from sonirico/feature/quarter
Browse files Browse the repository at this point in the history
feat: quarters
  • Loading branch information
sonirico authored Oct 3, 2021
2 parents 6223a20 + df085a3 commit 377893e
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 3 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- **Time zone support**. Time zones tend to be a major pending subject to many devs.
- **Configure when week start**. Cause not every country cries on Mondays.
- **Business weeks**. The previous point allows this.
- **Quarters**

## Motivation

Expand All @@ -37,6 +38,11 @@ Some common examples of relative tokens:
| Custom range | `now+w-2d/h` | `now+2M-10h` |
| Last month first business week | `now-M/M+w/bw` | `now-M/+w@bw` |
| This year | `now/Y` | `now@Y` |
| This quarter | `now/Q` | `now@Q` |
| This first quarter (Q1) | `now/Q1` | `now@Q1` |
| This second quarter (Q2) | `now/Q2` | `now@Q2` |
| This third quarter (Q3) | `now/Q3` | `now@Q3` |
| This fourth quarter (Q4) | `now/Q4` | `now@Q4` |


As you may have noticed, token follow a pattern:
Expand All @@ -56,6 +62,7 @@ As you may have noticed, token follow a pattern:
- `w` weeks
- `M` months
- `Y` years
- `Q` quarters
- Optionally, there exist two extra modifiers to snap dates to the start or the
end of any given snapshot unit. Those are:
- `/` Snap the date to the start of the snapshot unit.
Expand Down
5 changes: 5 additions & 0 deletions evaluator/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const (
businessWeek = "bw"
month = "M"
year = "Y"
quarter = "Q"
quarter1 = "Q1"
quarter2 = "Q2"
quarter3 = "Q3"
quarter4 = "Q4"

monday = "mon"
tuesday = "tue"
Expand Down
36 changes: 36 additions & 0 deletions evaluator/dates.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ func BeginningOfYear(date time.Time) time.Time {
return time.Date(y, time.January, 1, 0, 0, 0, 0, date.Location())
}

// BeginningOfCurrentQuarter returns the given date to the start of then current quarter
func BeginningOfCurrentQuarter(date time.Time) time.Time {
_, m, _ := date.Date()
q := int((m - 1) / 3)

return BeginningOfQuarter(date, q)
}

// BeginningOfQuarter returns the given date to the start of the given quarter
func BeginningOfQuarter(date time.Time, q int) time.Time {
y, _, _ := date.Date()
m := time.Month(q*3 + 1)

return time.Date(y, m, 1, 0, 0, 0, 0, date.Location())
}

// EndOfMinute returns the given day to the end of the minute, with seconds truncated to 59
func EndOfMinute(date time.Time) time.Time {
return BeginningOfMinute(date).Add(time.Minute - time.Nanosecond)
Expand Down Expand Up @@ -82,6 +98,26 @@ func EndOfMonth(date time.Time) time.Time {
return BeginningOfMonth(date).AddDate(0, 1, 0).Add(-time.Nanosecond)
}

// EndOfCurrentQuarter returns the given date to the end of then current quarter
func EndOfCurrentQuarter(date time.Time) time.Time {
_, m, _ := date.Date()
q := int((m - 1) / 3)

return EndOfQuarter(date, q)
}

// EndOfQuarter returns the given date to the end of the given quarter
func EndOfQuarter(date time.Time, q int) time.Time {
y, _, _ := date.Date()
m := time.Month(q*3 + 3)
d := 30
if m == 12 || m == 3 {
d = 31
}

return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), date.Location())
}

// EndOfYear returns the given date snapped to the end of the year, being the day 31th of December. Hours are
// truncated to 23, whereas minutes and seconds will be truncated to 59
func EndOfYear(date time.Time) time.Time {
Expand Down
23 changes: 22 additions & 1 deletion evaluator/evaluator.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package evaluator

import (
"github.com/sonirico/datetoken.go/models"
"time"

"github.com/sonirico/datetoken.go/models"

"github.com/sonirico/datetoken.go/ast"
"github.com/sonirico/datetoken.go/lexer"
"github.com/sonirico/datetoken.go/parser"
Expand Down Expand Up @@ -102,6 +103,16 @@ func (e *Evaluator) evalStartSnap(node *ast.SnapNode) {
e.snapStartOfMonth()
case year:
e.snapStartOfYear()
case quarter:
e.snapStartOfCurrentQuarter()
case quarter1:
e.snapStartOfQuarter(0)
case quarter2:
e.snapStartOfQuarter(1)
case quarter3:
e.snapStartOfQuarter(2)
case quarter4:
e.snapStartOfQuarter(3)
// weekdays
case monday:
e.previousMonday()
Expand Down Expand Up @@ -137,6 +148,16 @@ func (e *Evaluator) evalEndSnap(node *ast.SnapNode) {
e.snapEndOfMonth()
case year:
e.snapEndOfYear()
case quarter:
e.snapEndOfCurrentQuarter()
case quarter1:
e.snapEndOfQuarter(0)
case quarter2:
e.snapEndOfQuarter(1)
case quarter3:
e.snapEndOfQuarter(2)
case quarter4:
e.snapEndOfQuarter(3)
// weekdays
case monday:
e.nextMonday()
Expand Down
16 changes: 16 additions & 0 deletions evaluator/evaluator_dates.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ func (e *Evaluator) snapStartOfYear() {
e.current = BeginningOfYear(e.current)
}

func (e *Evaluator) snapStartOfCurrentQuarter() {
e.current = BeginningOfCurrentQuarter(e.current)
}

func (e *Evaluator) snapStartOfQuarter(q int) {
e.current = BeginningOfQuarter(e.current, q)
}

func (e *Evaluator) snapEndOfCurrentQuarter() {
e.current = EndOfCurrentQuarter(e.current)
}

func (e *Evaluator) snapEndOfQuarter(q int) {
e.current = EndOfQuarter(e.current, q)
}

func (e *Evaluator) snapEndOfMinute() {
e.current = EndOfMinute(e.current)
}
Expand Down
34 changes: 34 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,40 @@ func TestEvaluator_SnapEndYear(t *testing.T) {
}
}

func TestEvaluator_SnapQuarter(t *testing.T) {
tests := []struct {
Token string
Initial string
Expected string
}{
// Start
{"now/Q", "2020-02-11 10:13:23", "2020-01-01 00:00:00"},
{"now/Q", "2020-04-11 10:13:23", "2020-04-01 00:00:00"},
{"now/Q", "2020-07-11 10:13:23", "2020-07-01 00:00:00"},
{"now/Q", "2020-10-11 10:13:23", "2020-10-01 00:00:00"},
{"now/Q1", "2020-02-11 10:13:23", "2020-01-01 00:00:00"},
{"now/Q2", "2020-02-11 10:59:59", "2020-04-01 00:00:00"},
{"now/Q3", "2020-02-11 23:59:50", "2020-07-01 00:00:00"},
{"now/Q4", "2020-02-11 23:59:50", "2020-10-01 00:00:00"},
// End
{"now@Q", "2020-02-11 10:13:23", "2020-03-31 23:59:59"},
{"now@Q", "2020-04-11 10:13:23", "2020-06-30 23:59:59"},
{"now@Q", "2020-07-11 10:13:23", "2020-09-30 23:59:59"},
{"now@Q", "2020-10-11 10:13:23", "2020-12-31 23:59:59"},
{"now@Q1", "2020-02-11 10:13:23", "2020-03-31 23:59:59"},
{"now@Q2", "2020-02-11 10:59:59", "2020-06-30 23:59:59"},
{"now@Q3", "2020-02-11 23:59:50", "2020-09-30 23:59:59"},
{"now@Q4", "2020-01-31 23:59:50", "2020-12-31 23:59:59"},
}

for _, test := range tests {
actual := testEval(t, test.Token, test.Initial)
if !assertTime(t, actual, test.Expected) {
t.FailNow()
}
}
}

// Arithmetic

func TestEvaluator_AddSeconds(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions examples/naive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"

"github.com/sonirico/datetoken.go"
)

Expand All @@ -15,6 +16,7 @@ func main() {
"now/bw",
"now/M",
"now/Y",
"now/Q",
}
fmt.Println("Snap to start of units")
for _, token := range tokens {
Expand All @@ -32,6 +34,7 @@ func main() {
"now@bw",
"now@M",
"now@Y",
"now@Q",
}
for _, token := range tokens {
date, _ := datetoken.EvalNaive(token)
Expand Down
2 changes: 1 addition & 1 deletion lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (l *Lexer) peekChar() byte {

func (l *Lexer) readWord() string {
pos := l.currentPointer
for isLetter(l.currentChar) {
for isLetter(l.currentChar) || isDigit(l.currentChar) {
l.readChar()
}
return l.payload[pos:l.currentPointer]
Expand Down
12 changes: 11 additions & 1 deletion lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func testToken(t *testing.T, payload string, expected []expectedResult) {
}
func TestLexer_WithNow(t *testing.T) {
input := `
now-s+2m_-3h+234d-w+M-Y/M@w@bw
now-s+2m_-3h+234d-w+M-Y/M@w@bw/Q/Q1/Q2/Q3/Q4
`
expected := []expectedResult{
{token.Start, "now"},
Expand Down Expand Up @@ -59,6 +59,16 @@ func TestLexer_WithNow(t *testing.T) {
{token.Unit, "w"},
{token.SnapEnd, "@"},
{token.Unit, "bw"},
{token.SnapStart, "/"},
{token.Unit, "Q"},
{token.SnapStart, "/"},
{token.Unit, "Q1"},
{token.SnapStart, "/"},
{token.Unit, "Q2"},
{token.SnapStart, "/"},
{token.Unit, "Q3"},
{token.SnapStart, "/"},
{token.Unit, "Q4"},
{token.End, ""},
}

Expand Down
5 changes: 5 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ var keywords = map[string]Type{
"fri": Wd,
"sat": Wd,
"sun": Wd,
"Q": Unit,
"Q1": Unit,
"Q2": Unit,
"Q3": Unit,
"Q4": Unit,
}

// LookupKeyword will return the associated token type for a given token literal. If no one is found, the literal
Expand Down

0 comments on commit 377893e

Please sign in to comment.