-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdate.go
173 lines (147 loc) · 4.71 KB
/
date.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Package brazil provides utilities for parsing and formatting dates specific to Brazilian formats.
package brazil
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
const (
// dateDelimiter is the delimiter used in date strings.
dateDelimiter = "/"
// dateDelimiterPattern is the regex pattern for date delimiters.
dateDelimiterPattern = `[ ]{0,1}%s[ ]{0,1}`
// digitsPattern is the regex pattern for digits.
digitsPattern = `\d+`
// DateFormatShort is the short date format (DD/MM/YY).
DateFormatShort = "02/01/06"
// DateFormatLong is the long date format (DD/MM/YYYY).
DateFormatLong = "02/01/2006"
)
var (
// delimiters is a list of possible date delimiters.
delimiters = []string{`\/`, `\.`, `\-`, `[ ]`, `,`, `de`}
// months is a map of month abbreviations to their corresponding month numbers.
months = monthsOfYear{
"JAN": 1, "FEV": 2, "MAR": 3, "ABR": 4, "MAI": 5, "JUN": 6, "JUL": 7, "AGO": 8, "SET": 9, "OUT": 10, "NOV": 11, "DEZ": 12,
}
// monthPattern is the regex pattern for matching month names or numbers.
monthPattern = `(?:` + digitsPattern + `|(` + strings.Join(months.getMonths(), "|") + `)[\D!ç]{0,7})`
// datePattern is the regex pattern for matching dates.
datePattern = `(?i)` + digitsPattern + dateDelimiterPattern + monthPattern + dateDelimiterPattern + digitsPattern
)
type monthsOfYear map[string]int8
// getMonths returns the months of the year as a slice of strings.
func (m monthsOfYear) getMonths() []string {
keys := make([]string, 0, len(m))
for name := range m {
keys = append(keys, name)
}
return keys
}
// date struct represents a date value.
type date struct {
value time.Time
}
// Time returns the date as a time.Time.
func (d date) Time() time.Time {
return d.value
}
// String returns the date as a string with a DateFormatLong format.
func (d date) String() string {
return d.value.Format(DateFormatLong)
}
// ParseDate parses a date from a string and returns a date struct.
// Here are some possibilities for usage:
// - "5 de Abril de 1999"
// - "05 Abril 1999"
// - "5-4-1999"
// - "05.ABR.1999"
// - "5,4,1999"
//
// The date can be separated by a space, a dot, a hyphen, a comma, or the word "de".
func ParseDate(value string) (date, error) {
var (
matches []string
err error
time *time.Time
)
// Create a pattern for each delimiter and find all matches.
for _, delimiter := range delimiters {
pattern := fmt.Sprintf(datePattern, delimiter, delimiter)
matches = append(matches, regexp.MustCompile(pattern).FindAllString(value, -1)...)
}
// Try to get the time from the matches.
for _, match := range matches {
time, err = getTime(match)
// If there has been an error, return it.
if err != nil {
return date{}, err
}
}
// If the time is nil, return an error.
if time == nil {
return date{}, ErrInvalidDate
}
// Return the date.
return date{value: *time}, nil
}
// getTime parses a date string and returns a time.Time pointer.
func getTime(value string) (*time.Time, error) {
// Replace all non-word characters with the date delimiter.
r := regexp.MustCompile(`(?:\W|(`+strings.Join(delimiters, "|")+`)\W)+`).ReplaceAllString(value, dateDelimiter)
// Split the string by the date delimiter.
arr := strings.Split(r, dateDelimiter)
// If the array has less than 3 elements, return an error.
if len(arr) < 3 {
return nil, ErrInvalidDate
}
// Set the day, month, and year.
day := arr[0]
month := normalizeMonth(arr[1])
year := arr[len(arr)-1:][0]
// Set the default date format and year digits.
dateFormat := DateFormatLong
yearDigits := "%04d"
// If the year has 2 digits, change the date format and the year digits.
if len(year) == 2 {
dateFormat = DateFormatShort
yearDigits = "%02d"
}
// Parse the date with the format obtained.
layout := fmt.Sprintf("%02d/%02d/"+yearDigits, parseToint(day), parseToint(month), parseToint(year))
date, err := time.Parse(dateFormat, layout)
if err != nil {
return nil, fmt.Errorf("parsing time with layout %q: %w", layout, ErrInvalidDate)
}
// Return the date.
return &date, nil
}
// normalizeMonth converts a month name to its corresponding month number.
func normalizeMonth(text string) string {
// If the text is empty or has less than 3 characters, return it.
if len(text) < 3 {
return text
}
// Get the first 3 characters of the text and convert them to uppercase.
month := strings.ToUpper(text)[0:3]
// Check if the month is in the map, if not return an empty string.
value, ok := months[month]
if !ok {
return ""
}
// Return the month number with 2 digits.
return fmt.Sprintf("%02d", value)
}
// parseToint converts a string to an integer.
func parseToint(value string) int {
if value == "" {
return 0
}
i, err := strconv.Atoi(value)
if err != nil {
return 0
}
return i
}