-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathdateparser.lua
192 lines (160 loc) · 6.07 KB
/
dateparser.lua
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
local difftime, time, date = os.difftime, os.time, os.date
local format = string.format
local tremove, tinsert = table.remove, table.insert
local pcall, pairs, ipairs, tostring, tonumber, type, setmetatable = pcall, pairs, ipairs, tostring, tonumber, type, setmetatable
local dateparser={}
--we shall use the host OS's time conversion facilities. Dealing with all those leap seconds by hand can be such a bore.
local unix_timestamp
do
local now = time()
local local_UTC_offset_sec = difftime(time(date("!*t", now)), time(date("*t", now)))
unix_timestamp = function(t, offset_sec)
local success, improper_time = pcall(time, t)
if not success or not improper_time then return nil, "invalid date. os.time says: " .. (improper_time or "nothing") end
return improper_time - local_UTC_offset_sec - offset_sec
end
end
local formats = {} -- format names
local format_func = setmetatable({}, {__mode='v'}) --format functions
---register a date format parsing function
function dateparser.register_format(format_name, format_function)
if type(format_name)~="string" or type(format_function)~='function' then return nil, "improper arguments, can't register format handler" end
local found
for i, f in ipairs(format_func) do --for ordering
if f==format_function then
found=true
break
end
end
if not found then
tinsert(format_func, format_function)
end
formats[format_name] = format_function
return true
end
---register a date format parsing function
function dateparser.unregister_format(format_name)
if type(format_name)~="string" then return nil, "format name must be a string" end
formats[format_name]=nil
end
---return the function responsible for handling format_name date strings
function dateparser.get_format_function(format_name)
return formats[format_name] or nil, ("format %s not registered"):format(format_name)
end
---try to parse date string
--@param str date string
--@param date_format optional date format name, if known
--@return unix timestamp if str can be parsed; nil, error otherwise.
function dateparser.parse(str, date_format)
local success, res, err
if date_format then
if not formats[date_format] then return 'unknown date format: ' .. tostring(date_format) end
success, res = pcall(formats[date_format], str)
else
for i, func in ipairs(format_func) do
success, res = pcall(func, str)
if success and res then return res end
end
end
return success and res
end
dateparser.register_format('W3CDTF', function(rest)
local year, day_of_year, month, day, week
local hour, minute, second, second_fraction, offset_hours
local alt_rest
year, rest = rest:match("^(%d%d%d%d)%-?(.*)$")
day_of_year, alt_rest = rest:match("^(%d%d%d)%-?(.*)$")
if day_of_year then rest=alt_rest end
month, rest = rest:match("^(%d%d)%-?(.*)$")
day, rest = rest:match("^(%d%d)(.*)$")
if #rest>0 then
rest = rest:match("^T(.*)$")
hour, rest = rest:match("^([0-2][0-9]):?(.*)$")
minute, rest = rest:match("^([0-6][0-9]):?(.*)$")
second, rest = rest:match("^([0-6][0-9])(.*)$")
second_fraction, alt_rest = rest:match("^%.(%d+)(.*)$")
if second_fraction then
rest=alt_rest
end
if rest=="Z" then
rest=""
offset_hours=0
else
local sign, offset_h, offset_m
sign, offset_h, rest = rest:match("^([+-])(%d%d)%:?(.*)$")
local offset_m, alt_rest = rest:match("^(%d%d)(.*)$")
if offset_m then rest=alt_rest end
offset_hours = tonumber(sign .. offset_h) + (tonumber(offset_m) or 0)/60
end
if #rest>0 then return nil end
end
year = tonumber(year)
local d = {
year = year and (year > 100 and year or (year < 50 and (year + 2000) or (year + 1900))),
month = tonumber(month) or 1,
day = tonumber(day) or 1,
hour = tonumber(hour) or 0,
min = tonumber(minute) or 0,
sec = tonumber(second) or 0,
isdst = false
}
local t = unix_timestamp(d, (offset_hours or 0) * 3600)
if second_fraction then
return t + tonumber("0."..second_fraction)
else
return t
end
end)
do
local tz_table = { --taken from http://www.timeanddate.com/library/abbreviations/timezones/
A = 1, B = 2, C = 3, D = 4, E=5, F = 6, G = 7, H = 8, I = 9,
K = 10, L = 11, M = 12, N = -1, O = -2, P = -3, Q = -4, R = -5,
S = -6, T = -7, U = -8, V = -9, W = -10, X = -11, Y = -12,
Z = 0,
EST = -5, EDT = -4, CST = -6, CDT = -5,
MST = -7, MDT = -6, PST = -8, PDT = -7,
GMT = 0, UT = 0, UTC = 0
}
local month_val = {Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12}
dateparser.register_format('RFC2822', function(rest)
local year, month, day, day_of_year, week_of_year, weekday
local hour, minute, second, second_fraction, offset_hours
local alt_rest
weekday, alt_rest = rest:match("^(%w%w%w),%s+(.*)$")
if weekday then rest=alt_rest end
day, rest=rest:match("^(%d%d?)%s+(.*)$")
month, rest=rest:match("^(%w%w%w)%s+(.*)$")
month = month_val[month]
year, rest = rest:match("^(%d%d%d?%d?)%s+(.*)$")
hour, rest = rest:match("^(%d%d?):(.*)$")
minute, rest = rest:match("^(%d%d?)(.*)$")
second, alt_rest = rest:match("^:(%d%d)(.*)$")
if second then rest = alt_rest end
local tz, offset_sign, offset_h, offset_m
tz, alt_rest = rest:match("^%s+(%u+)(.*)$")
if tz then
rest = alt_rest
offset_hours = tz_table[tz]
else
offset_sign, offset_h, offset_m, rest = rest:match("^%s+([+-])(%d%d)(%d%d)%s*(.*)$")
offset_hours = tonumber(offset_sign .. offset_h) + (tonumber(offset_m) or 0)/60
end
if #rest>0 or not (year and day and month and hour and minute) then
return nil
end
year = tonumber(year)
local d = {
year = year and ((year > 100) and year or (year < 50 and (year + 2000) or (year + 1900))),
month = month,
day = tonumber(day),
hour= tonumber(hour) or 0,
min = tonumber(minute) or 0,
sec = tonumber(second) or 0,
isdst = false
}
return unix_timestamp(d, offset_hours * 3600)
end)
end
dateparser.register_format('RFC822', formats.RFC2822) --2822 supercedes 822, but is not a strict superset. For our intents and purposes though, it's perfectly good enough
dateparser.register_format('RFC3339', formats.W3CDTF) --RFC3339 is a subset of W3CDTF
return dateparser