1 package time
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7
8 "github.com/pkg/errors"
9
10 arrow "github.com/bmuller/arrow/lib"
11
12 "github.com/noirbizarre/gonja/exec"
13 "github.com/noirbizarre/gonja/nodes"
14 "github.com/noirbizarre/gonja/parser"
15 "github.com/noirbizarre/gonja/tokens"
16 )
17
18 type TimeOffset struct {
19 Years int
20 Months int
21 Days int
22 Hours int
23 Minutes int
24 Seconds int
25 }
26
27 type NowStmt struct {
28 Location *tokens.Token
29 TZ string
30 Format string
31 Offset *TimeOffset
32 }
33
34 func (stmt *NowStmt) Position() *tokens.Token { return stmt.Location }
35 func (stmt *NowStmt) String() string {
36 t := stmt.Position()
37 return fmt.Sprintf("NowStmt(Line=%d Col=%d)", t.Line, t.Col)
38 }
39
40 func (stmt *NowStmt) Execute(r *exec.Renderer, tag *nodes.StatementBlock) error {
41 var now arrow.Arrow
42
43 cfg := r.Config.Ext["time"].(*Config)
44 format := cfg.DatetimeFormat
45
46 if cfg.Now != nil {
47 now = *cfg.Now
48 } else {
49 now = arrow.Now()
50 }
51
52 if stmt.Format != "" {
53 format = stmt.Format
54 }
55
56 now = now.InTimezone(stmt.TZ)
57
58 if stmt.Offset != nil {
59 offset := stmt.Offset
60 if offset.Years != 0 || offset.Months != 0 || offset.Days != 0 {
61 now = arrow.New(now.AddDate(offset.Years, offset.Months, offset.Days))
62 }
63 if offset.Hours != 0 {
64 now = now.AddHours(offset.Hours)
65 }
66 if offset.Minutes != 0 {
67 now = now.AddMinutes(offset.Minutes)
68 }
69 if offset.Seconds != 0 {
70 now = now.AddSeconds(offset.Seconds)
71 }
72 }
73
74 r.WriteString(now.CFormat(format))
75
76 return nil
77 }
78
79 func nowParser(p *parser.Parser, args *parser.Parser) (nodes.Statement, error) {
80 stmt := &NowStmt{
81 Location: p.Current(),
82 }
83
84
85 tz := args.Match(tokens.String)
86 if tz == nil {
87 return nil, args.Error(`now expect a timezone as first argument`, args.Current())
88 }
89 stmt.TZ = tz.Val
90
91
92 if sign := args.Match(tokens.Add, tokens.Sub); sign != nil {
93 offset := args.Match(tokens.String)
94 if offset == nil {
95 return nil, args.Error("Expected an time offset.", args.Current())
96 }
97 timeOffset, err := parseTimeOffset(offset.Val, sign.Val == "+")
98 if err != nil {
99 return nil, errors.Wrapf(err, `Unable to parse time offset '%s'`, offset.Val)
100 }
101 stmt.Offset = timeOffset
102 }
103
104
105 if args.Match(tokens.Comma) != nil {
106 format := args.Match(tokens.String)
107 if format == nil {
108 return nil, args.Error("Expected a format string.", args.Current())
109 }
110 stmt.Format = format.Val
111 }
112
113 if !args.End() {
114 return nil, args.Error("Malformed now-tag args.", nil)
115 }
116
117 return stmt, nil
118 }
119
120 func parseTimeOffset(offset string, add bool) (*TimeOffset, error) {
121 pairs := strings.Split(offset, ",")
122 specs := map[string]int{}
123 for _, pair := range pairs {
124 splitted := strings.Split(pair, "=")
125 if len(splitted) != 2 {
126 return nil, errors.Errorf(`Expected a key=value pair, got '%s'`, pair)
127 }
128 unit := strings.TrimSpace(splitted[0])
129 value, err := strconv.Atoi(strings.TrimSpace(splitted[1]))
130 if err != nil {
131 return nil, errors.Wrap(err, `Unable to parse int`)
132 }
133 specs[unit] = value
134 }
135 to := &TimeOffset{}
136 for unit, value := range specs {
137 if !add {
138 value = -value
139 }
140 switch strings.ToLower(unit) {
141 case "year", "years":
142 to.Years = value
143 case "month", "months":
144 to.Months = value
145 case "day", "days":
146 to.Days = value
147 case "hour", "hours":
148 to.Hours = value
149 case "minute", "minutes":
150 to.Minutes = value
151 case "second", "seconds":
152 to.Seconds = value
153 default:
154 return nil, errors.Errorf(`Unknown unit '%s`, unit)
155 }
156 }
157 return to, nil
158 }
159
160 func init() {
161 Statements.Register("now", nowParser)
162 }
163
View as plain text