summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraalatchm <aalatchm@jamailca.com>2011-03-22 18:27:08 +0000
committerNatanael Copa <ncopa@alpinelinux.org>2011-03-22 20:56:07 +0000
commit0c244a4e12eb65cf72972bdf49b93366a174e9b8 (patch)
tree2873e7cfe24f999fc058a699e94a2c8d82b24a19
parente18d43f6d7c2cb9d239eb9de0aa7489233a45a1f (diff)
downloadposixtz-0c244a4e12eb65cf72972bdf49b93366a174e9b8.tar.bz2
posixtz-0c244a4e12eb65cf72972bdf49b93366a174e9b8.tar.xz
added posixtz.lua for parse functionality
The parse() function takes a POSIX TZ string and returns a table with TZ parameters.
-rw-r--r--posixtz.lua192
1 files changed, 192 insertions, 0 deletions
diff --git a/posixtz.lua b/posixtz.lua
new file mode 100644
index 0000000..7eb92dd
--- /dev/null
+++ b/posixtz.lua
@@ -0,0 +1,192 @@
+-------------------------------------------------------------------------------
+-- POSIX TZ Parser for Lua
+-- Copyright (C) 2011 H. Andrew Latchman <aldevel@jamailca.com>
+--
+-- Performs some validation of TZ string
+-- based on fields to determine offset and dst settings
+--
+-- See http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
+-- TZ variable, with no leading colon
+-- Syntax: stdoffset[dst[offset][,start[/time],end[/time]]]
+-- Examples: 'GMT0' 'EST5EDT,M3.2.0,M11.1.0' '<GMT+5>5'
+-------------------------------------------------------------------------------
+
+-- Import Section
+-- declare everything this module needs from outside
+local error = error
+local string = string
+local tonumber = tonumber
+local pairs = pairs
+local ipairs = ipairs
+
+-- Rest of the code is part of module "posixtz"
+module('posixtz')
+
+function parse ( str )
+
+ -- create tz table for data to be returned
+ local tz = {}
+ -- create temp table for storing what is left to be parsed
+ local temp = {}
+
+ if not str then
+ error('Nothing to parse')
+ end
+
+
+ -- Check that values.device.timezone has required fields
+ -- (technically, std must be at least 3 chars, but we don't care)
+
+ -- Parse out 'std' and validate.
+ -- check for quoted format of std
+ tz.std, temp.afterstd = string.match(str, '^(<[%w+-]+>)(.*)')
+ if not tz.std then
+ -- try unquoted format of std
+ tz.std, temp.afterstd = string.match(str, '^(%a+)(.*)')
+ end
+
+ -- tz.std must be defined by now
+ if not tz.std then
+ error('Not a valid POSIX TZ. "std" invalid')
+ end
+
+
+ -- After 'std' there must be [+|-]hh[:mm[:ss]] as the offset
+ -- the following code simplifies to [+|-]hh[[:][mm[[:][ss]]
+ tz.offset = {}
+ tz.offset.sign, tz.offset.hour, tz.offset.min, tz.offset.sec, temp.afteroffset = string.match(temp.afterstd, '^([+-]?)(%d+):?(%d?%d?):?(%d?%d?)(.*)')
+
+ -- tz.offset.hour must be non-empty, the other vars may be '' if not present
+ if tz.offset.hour and ( tz.offset.hour ~= '' ) then
+
+ tz.offset.total = 3600 * tonumber(tz.offset.sign .. tz.offset.hour)
+
+ -- use tonumber to check that tz.offset[min|sec] are not ''
+ if tonumber(tz.offset.min) then
+ tz.offset.total = tz.offset.total + 60 * tonumber(tz.offset.sign .. tz.offset.min)
+ end
+ if tonumber(tz.offset.sec) then
+ tz.offset.total = tz.offset.total + tonumber(tz.offset.sign .. tz.offset.sec)
+ end
+
+ else
+ error('Not a valid POSIX TZ. "offset" invalid')
+ end
+
+
+ -- The rest of the POSIX fields are optional
+ if ( temp.afteroffset == '' ) then
+ -- we are finished. no more data.
+ return tz
+ end
+
+
+ -- DST -- If we are here, then "dst" field is present
+
+ -- Parse out "dst" and validate
+ -- dst name comes after offset
+ tz.dst = {}
+ tz.dst.name, temp.afterdst = string.match(temp.afteroffset, '^(<[%w+-]+>)(.*)')
+ if not tz.dst.name then
+ -- try unquoted format of dst
+ tz.dst.name, temp.afterdst = string.match(temp.afteroffset, '^(%a+)(.*)')
+ end
+ if not tz.dst.name then
+ error('Not a valid POSIX TZ. "dst" present but not valid')
+ end
+
+ -- tz.dst.name is defined, so process "rule"
+ -- Parse out specifications from "rule"
+ temp.dststart, temp.dststop = string.match(temp.afterdst, '^,([^,]+),([^,]+)')
+ if not temp.dststart then
+ error('Not a valid POSIX TZ. "dst" present but "rule" is not')
+ end
+
+
+ local function dstparse ( pos, spec )
+ local t = {}
+ tz.dst[pos] = t
+
+
+ -- Handle explicit hour for DST change
+ t.hour = string.match(spec, '/(%d+)')
+ -- TODO: Implement explicit min and sec
+
+
+ local monkey = {}
+ monkey[1] = 31 -- January
+ monkey[2] = 28
+ monkey[3] = 31
+ monkey[4] = 30
+ monkey[5] = 31
+ monkey[6] = 30
+ monkey[7] = 31
+ monkey[8] = 31
+ monkey[9] = 30
+ monkey[10] = 31
+ monkey[11] = 30
+ monkey[12] = 31
+
+ local function whichmonth ( dayofyear )
+ -- assume day = 1 for '1 Jan'
+ -- uses monkey to determine month
+ local month, dayofmonth
+ local remaindays = tonumber(dayofyear)
+ for i, n in ipairs(monkey) do
+ if remaindays > n then
+ -- Jnday is not in this month
+ remaindays = remaindays - n
+ else
+ month = i
+ dayofmonth = remaindays
+ break
+ end
+ end
+ t.month = month
+ t.day = dayofmonth
+ end
+
+ -- Jn format (Julian day, with [1 <= n <= 365], no Feb 29)
+ t.Jnday = string.match(spec, '^J(%d+)')
+ if t.Jnday then
+
+ whichmonth(t.Jnday)
+
+ end
+
+ -- n format (zero-based Julian day, [0 <= n <= 364/5], counting Feb 29)
+ t.nday = string.match(spec, '^(%d+)')
+ if t.nday then
+
+ monkey[2] = 29
+ whichmonth(t.nday + 1)
+
+ end
+
+ -- Mm.w.d (day d of week w of month m)
+ -- m is between 1 and 12
+ -- w is between 1 and 5, where 1st,2nd..4th, and 5=last
+ -- d is between 0 (Sun) and 6 (Sat)
+ t.Mmonth, t.week, t.weekday = string.match(spec, 'M(%d+)%.(%d)%.(%d)')
+ if t.Mmonth then
+ t.month = t.Mmonth
+ end
+
+
+ -- Validation: t.month must be defined by now.
+ if not t.month then
+ error('Not a valid POSIX TZ. "rule" for DST "'..pos..'" is invalid')
+ end
+ end
+
+ if temp.dststart and temp.dststop then
+ -- Parse and validate dst rule specifications.
+ dstparse('start', temp.dststart)
+ dstparse('stop', temp.dststop)
+ end
+
+
+ return tz
+
+end
+