From: martin f. krafft Date: Mon, 12 Feb 2018 21:38:41 +0000 (+1300) Subject: Add '.config/awesome/modules/luatz/' from commit 'bdbbf89c38126a71b17049469e8f976c571... X-Git-Url: https://git.madduck.net/etc/awesome.git/commitdiff_plain/3f9a35944dbf63de85654d45120ab4291837ef9c?ds=sidebyside;hp=-c;pf=etc Add '.config/awesome/modules/luatz/' from commit 'bdbbf89c38126a71b17049469e8f976c571b9392' git-subtree-dir: .config/awesome/modules/luatz git-subtree-mainline: de5931c65f3f8a7138a412fcdfa8fce830807679 git-subtree-split: bdbbf89c38126a71b17049469e8f976c571b9392 --- 3f9a35944dbf63de85654d45120ab4291837ef9c diff --combined .config/awesome/modules/luatz/.busted index 0000000,d1a4ad9..d1a4ad9 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/.busted +++ b/.config/awesome/modules/luatz/.busted @@@ -1,0 -1,5 +1,5 @@@ + return { + default = { + lpath = "./?.lua"; + }; + } diff --combined .config/awesome/modules/luatz/.gitignore index 0000000,a299fa5..a299fa5 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/.gitignore +++ b/.config/awesome/modules/luatz/.gitignore @@@ -1,0 -1,4 +1,4 @@@ + luatz-*.rock + doc/luatz.3 + doc/luatz.html + doc/luatz.pdf diff --combined .config/awesome/modules/luatz/.luacheckrc index 0000000,b1f4e2e..b1f4e2e mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/.luacheckrc +++ b/.config/awesome/modules/luatz/.luacheckrc @@@ -1,0 -1,2 +1,2 @@@ + std = "min" + files["spec"] = {std = "+busted"} diff --combined .config/awesome/modules/luatz/.luacov index 0000000,9e2d541..9e2d541 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/.luacov +++ b/.config/awesome/modules/luatz/.luacov @@@ -1,0 -1,10 +1,10 @@@ + return { + statsfile = "luacov.stats.out"; + reportfile = "luacov.report.out"; + deletestats = true; + include = { + "^./luatz/"; + }; + exclude = { + }; + } diff --combined .config/awesome/modules/luatz/.travis.yml index 0000000,7015a29..7015a29 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/.travis.yml +++ b/.config/awesome/modules/luatz/.travis.yml @@@ -1,0 -1,43 +1,43 @@@ + language: python + + sudo: false + + env: + matrix: + - LUA="lua 5.1" SOCKET=true + - LUA="lua 5.1" + - LUA="lua 5.2" SOCKET=true + - LUA="lua 5.2" + - LUA="lua 5.3" SOCKET=true + - LUA="lua 5.3" + - LUA="luajit 2.0" + - LUA="luajit 2.1" SOCKET=true SYSCALL=true + - LUA="luajit 2.1" SYSCALL=true + - LUA="luajit 2.1" + - LUA="luajit @" + + before_install: + - pip install hererocks + - hererocks here -r^ --$LUA # Install latest LuaRocks version + # plus the Lua version for this build job + # into 'here' subdirectory + - export PATH=$PATH:$PWD/here/bin # Add directory with all installed binaries to PATH + - eval `luarocks path --bin` + - luarocks install luacov-coveralls + - luarocks install busted + + install: + - luarocks make + - if [ "$SOCKET" = "true" ]; then luarocks install luasocket; fi + - if [ "$SYSCALL" = "true" ]; then luarocks install ljsyscall; fi + + script: + - busted -c + + after_success: + - luacov-coveralls -v + + notifications: + email: + on_success: change + on_failure: always diff --combined .config/awesome/modules/luatz/COPYING index 0000000,b4435bb..b4435bb mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/COPYING +++ b/.config/awesome/modules/luatz/COPYING @@@ -1,0 -1,20 +1,20 @@@ + The MIT License (MIT) + + Copyright (c) 2013-2017 Daurnimator + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --combined .config/awesome/modules/luatz/NEWS index 0000000,851fc16..851fc16 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/NEWS +++ b/.config/awesome/modules/luatz/NEWS @@@ -1,0 -1,31 +1,31 @@@ + UNRELEASED + + + 0.4 - 2017-12-09 + + - Fix timetable normalisation carry bugs (#10, #13) + - Clean up of docs + - No longer throw errors in parse module on error (now return nil, err) + - Support version 3 tzfiles + + + 0.3 - 2015-01-02 + + - Lua 5.3 support + - Fix bug in rfc-3339 serialisation (#4) + + + 0.2 - 2014-08-29 + + - Support for fractional timetable component normalisation + e.g. .month=6.5, .day=1 (which could be read as "the first day after the middle of June") normalises to .month=2, .day=16 + - Top level aliases for common operations + - Own implementation of stftime formatting (locales are not yet complete) + - Uses ljsyscall for more accurate time when available + + + 0.1 - 2013-11-23 + + - provides a `os.date` compatible class "timetable" + - timezone conversion + - rfc3339 parsing diff --combined .config/awesome/modules/luatz/README.md index 0000000,0c4575c..0c4575c mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/README.md +++ b/.config/awesome/modules/luatz/README.md @@@ -1,0 -1,29 +1,29 @@@ + # luatz + + A lua library for time and date manipulation. + + Features include: + + - Normalisation of broken down date objects + - allows for complex time/date manipulation logic e.g. "What day is it in 2 days, 5 hours from now?" + - Conversion between locations (time zones) using your local [zoneinfo](https://www.iana.org/time-zones) database. + - `strftime` style formatting + + + [![Build Status](https://travis-ci.org/daurnimator/luatz.png)](https://travis-ci.org/daurnimator/luatz) [![Coverage Status](https://coveralls.io/repos/github/daurnimator/luatz/badge.svg?branch=master)](https://coveralls.io/github/daurnimator/luatz?branch=master) + + Supported under Lua 5.1, 5.2, 5.3 and LuaJIT. + + + ## Documentation + + Documentation can be found in the `doc` sub-directory. + + An online version can be found at https://daurnimator.github.io/luatz/ + + + ## Installation + + ### via [luarocks](https://luarocks.org/modules/daurnimator/luatz) + + luarocks install luatz diff --combined .config/awesome/modules/luatz/doc/Makefile index 0000000,7d30015..7d30015 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/Makefile +++ b/.config/awesome/modules/luatz/doc/Makefile @@@ -1,0 -1,26 +1,26 @@@ + FILES = \ + index.md \ + gettime.md \ + parse.md \ + timetable.md \ + tzinfo.md \ + links.md + + all: luatz.html luatz.pdf luatz.3 + + luatz.html: template.html site.css metadata.yaml $(FILES) + pandoc -o $@ -t html5 -s --toc --template=template.html --section-divs --self-contained -c site.css metadata.yaml $(FILES) + + luatz.pdf: metadata.yaml $(FILES) + pandoc -o $@ -t latex -s --toc --toc-depth=2 -V documentclass=article -V classoption=oneside -V links-as-notes -V geometry=a4paper,includeheadfoot,margin=2.54cm metadata.yaml $(FILES) + + luatz.3: metadata.yaml $(FILES) + pandoc -o $@ -t man -s metadata.yaml $(FILES) + + man: luatz.3 + man -l $^ + + clean: + rm -f luatz.html luatz.pdf luatz.3 + + .PHONY: all man install clean diff --combined .config/awesome/modules/luatz/doc/README.md index 0000000,ed82567..ed82567 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/README.md +++ b/.config/awesome/modules/luatz/doc/README.md @@@ -1,0 -1,5 +1,5 @@@ + Documentation in this directory is intended to be converted to other formats using [pandoc](http://pandoc.org/). + + An online HTML version can be found at [https://daurnimator.github.io/luatz/](https://daurnimator.github.io/luatz/) + + The *Makefile* in this directory should be used to compile the documentation. diff --combined .config/awesome/modules/luatz/doc/gettime.md index 0000000,3550577..3550577 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/gettime.md +++ b/.config/awesome/modules/luatz/doc/gettime.md @@@ -1,0 -1,27 +1,27 @@@ + ## `luatz.gettime` {#gettime} + + A module to get the current time. + + Uses the most precise method available (in order:) + + - Use [ljsyscall](http://www.myriabit.com/ljsyscall/) to access `clock_gettime(2)` called with `CLOCK_REALTIME` + - [lunix](http://25thandclement.com/~william/projects/lunix.html)'s `unix.clock_gettime()` (Only on non-Apple systems) + - Use [ljsyscall](http://www.myriabit.com/ljsyscall/) to access `gettimeofday(2)` + - [lunix](http://25thandclement.com/~william/projects/lunix.html)'s `unix.gettimeofday()` + - [luasocket](http://w3.impa.br/~diego/software/luasocket/)'s `socket.gettime` + - [Openresty](http://openresty.org/)'s [`ngx.now`](http://wiki.nginx.org/HttpLuaModule#ngx.now) + - [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) + + ### `source` {#gettime.source} + + The library/function currently in use by [`gettime()`](#gettime.gettime). + + + ### `resolution` {#gettime.resolution} + + The smallest time resolution (in seconds) available from [`gettime()`](#gettime.gettime). + + + ### `gettime()` {#gettime.gettime} + + Returns the number of seconds since unix epoch (1970-01-01T00:00:00Z) as a lua number diff --combined .config/awesome/modules/luatz/doc/index.md index 0000000,b69b2d8..b69b2d8 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/index.md +++ b/.config/awesome/modules/luatz/doc/index.md @@@ -1,0 -1,51 +1,51 @@@ + ## `luatz` + + Requiring the base luatz module will give you a table of commonly used functions and submodules. + + The table includes the following sub modules, which have their own documentation: + + - [`parse`](#parse): Parses common date/time formats + - [`timetable`](#timetable): Class for date/time objects supporting normalisation + + ### `time()` {#luatz.time} + + Returns the current unix timestamp using the most precise source available. + See [`gettime`](#gettime) for more information. + + + ### `now()` {#luatz.now} + + Returns the current time as a timetable object + See `timetable` for more information + + + ### `get_tz([timezone_name])` {#luatz.get_tz} + + Returns a timezone object (see `tzinfo` documentation) for the given `timezone_name`. + If `timezone_name` is `nil` then the local timezone is used. + If `timezone_name` is an absolute path, then that `tzinfo` file is used + + This uses the local [zoneinfo database](https://www.iana.org/time-zones); + names are usually of the form `Country/Largest_City` e.g. "America/New_York". + Check [wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for an example list. + + + ### `time_in(timezone_name[, utc_ts])` {#luatz.time_in} + + Returns the current time in seconds since 1970-01-01 0:00:00 in the given timezone as a string, + (same semantics as [`get_tz`](#luatz.get_tz)) at the given UTC time (defaults to now). + + + ### `gmtime(ts)` {#luatz.gmtime} + + As in the C standard library + + + ### `localtime(ts)` {#luatz.localtime} + + As in the C standard library + + + ### `ctime(ts)` {#luatz.ctime} + + As in the C standard library diff --combined .config/awesome/modules/luatz/doc/links.md index 0000000,3e8905f..3e8905f mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/links.md +++ b/.config/awesome/modules/luatz/doc/links.md @@@ -1,0 -1,5 +1,5 @@@ + # Links + + - [Github](https://github.com/daurnimator/luatz) + - [Issue tracker](https://github.com/daurnimator/luatz/issues) + - [luarocks](https://luarocks.org/modules/daurnimator/luatz) diff --combined .config/awesome/modules/luatz/doc/metadata.yaml index 0000000,4f144f0..4f144f0 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/metadata.yaml +++ b/.config/awesome/modules/luatz/doc/metadata.yaml @@@ -1,0 -1,6 +1,6 @@@ + --- + title: luatz + subtitle: A lua library for time and date manipulation + author: Daurnimator + section: 3 + ... diff --combined .config/awesome/modules/luatz/doc/parse.md index 0000000,c41eae5..c41eae5 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/parse.md +++ b/.config/awesome/modules/luatz/doc/parse.md @@@ -1,0 -1,12 +1,12 @@@ + ## `luatz.parse` {#parse} + + Provides parsers for common time and date formats. + + Functions take the source string and an optional initial postition. + + ### `rfc_3339(string[, init])` {#parse.rfc_3339} + + If the string is a valid RFC-3339 timestamp, + returns a luatz timetable and the (optional) time zone offset in seconds. + + Otherwise returns `nil` and an error message diff --combined .config/awesome/modules/luatz/doc/site.css index 0000000,8858002..8858002 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/site.css +++ b/.config/awesome/modules/luatz/doc/site.css @@@ -1,0 -1,156 +1,156 @@@ + * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box + } + html, + body { + height: 100% + } + article, + aside, + figure, + footer, + header, + hgroup, + menu, + nav, + section { + display: block + } + body { + margin: 0 + } + h1, + h2, + h3 { + margin: 1rem 0 + } + h4, + h5, + h6, + ul, + ol, + dl, + blockquote, + address, + p, + figure { + margin: 0 0 1rem 0 + } + img { + max-width: 100% + } + h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: 700 + } + h1 { + font-size: 2.5rem; + line-height: 3rem + } + h2 { + font-size: 1.5rem; + line-height: 2rem + } + h3 { + font-size: 1.25rem; + line-height: 1.5rem + } + h4, + h5, + h6 { + font-size: 1rem; + line-height: 1.25rem + } + hr { + border: 0; + border-bottom: 1px solid; + margin-top: -1px; + margin-bottom: 1rem + } + a:hover { + color: inherit + } + small { + font-size: .875rem + } + ul, + ol { + padding-left: 1rem + } + ul ul, + ul ol, + ol ol, + ol ul { + margin: 0 + } + dt { + font-weight: 700 + } + dd { + margin: 0 + } + blockquote { + border-left: 1px solid; + padding-left: 1rem + } + address { + font-style: normal + } + html { + color: #333; + font: 100%/1.5 Avenir, 'Helvetica Neue', Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + background: #FFF; + } + a { + color: #999; + text-decoration: none; + transition: color 0.3s; + } + a > h1, + a > h2, + a > h3 { + color: #333; + } + + body > * { + padding: 0 1rem; + } + .subtitle { + font-size: 1rem; + line-height: 1.5rem + } + .author { + display: none + } + @media screen and (min-width: 55rem) { + .meta { + position: fixed; + width: 20rem; + height: 100%; + overflow: auto; + background: #FFF; + z-index: 1; + } + main { + display: block; /* required for e.g. konqueror */ + margin-left: 20rem; + overflow: auto; + } + } + @media print { + section.level1 { + page-break-inside: avoid + } + nav a::after { + content: leader('.') target-counter(attr(href url), page, decimal) + } + } diff --combined .config/awesome/modules/luatz/doc/template.html index 0000000,a74a7b6..a74a7b6 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/template.html +++ b/.config/awesome/modules/luatz/doc/template.html @@@ -1,0 -1,71 +1,71 @@@ + + + + + + + $for(author-meta)$ + + $endfor$ + $if(date-meta)$ + + $endif$ + $if(keywords)$ + + $endif$ + $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ + + $if(quotes)$ + + $endif$ + $if(highlighting-css)$ + + $endif$ + $for(css)$ + + $endfor$ + $if(math)$ + $math$ + $endif$ + + $for(header-includes)$ + $header-includes$ + $endfor$ + + + $for(include-before)$ + $include-before$ + $endfor$ +
+ $if(title)$ +
+

$title$

+ $if(subtitle)$ +

$subtitle$

+ $endif$ + $for(author)$ +

$author$

+ $endfor$ + $if(date)$ +

$date$

+ $endif$ +
+ $endif$ + $if(toc)$ + + $endif$ +
+
+ $body$ +
+ $for(include-after)$ + $include-after$ + $endfor$ + + diff --combined .config/awesome/modules/luatz/doc/timetable.md index 0000000,812da43..812da43 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/timetable.md +++ b/.config/awesome/modules/luatz/doc/timetable.md @@@ -1,0 -1,71 +1,71 @@@ + ## `luatz.timetable` {#timetable} + + Provides an class to represent a time and date. + Objects have no concept of timezone or utc offset. + + The fields are intentionally compatible with the lua standard library's `os.date` and `os.time`. Objects have fields: + + - `year` + - `month` + - `day` + - `hour` + - `min` + - `sec` + - `yday` (optional) + - `wday` (optional) + + timetable components may be outside of their standard range (e.g. a month component of + 14) to facilitate arithmetic operations on date components. `:normalise()` can be + called to modify components to return to their standard range. + + Equality and comparisons should work between timetable objects. + + + ### `new(year, month, day, hour, min, sec[, yday[, [wday]])` {#timetable.new} + + Returns a new timetable with the given contents. + + + ### `new_from_timestamp(timestamp)` {#timetable.new_from_timestamp} + + Returns a new (normalised) timetable, given a timestamp in seconds since the unix epoch of + 1970-01-01. + + + ### `timetable:clone()` {#timetable:clone} + + Returns a new independent instance of an existing timetable object. + + + ### `timetable:normalise()` {#timetable:normalise} + + Mutates the current object's time and date components so that are integers within 'normal' + ranges e.g. `month` is `1`-`12`; `min` is `0`-`59` + + First, fractional parts are propagated down. + e.g. `.month=6.5` `.day=1` (which could be read as "the first day after the middle of June") + normalises to `.month=2` `.day=16` + + Second, any fields outside of their normal ranges are propagated up + e.g. `.hour=10` `.min=100` (100 minutes past 10am) + normalises to `.hour=11` `.min=40` + + + ### `timetable:rfc_3339()` {#timetable:rfc_3339} + + Returns the timetable formatted as an rfc-3339 style string. + The timezone offset (or Z) is not appended. + The ranges of components are not checked, if you want a valid timestamp, + [`:normalise()`](#timetable:normalise) should be called first. + + This function is also the `__tostring` metamethod for timetable objects + + + ### `timetable:timestamp()` {#timetable:timestamp} + + Returns the timetable as the number of seconds since unix epoch (1970-01-01) as a lua number. + + + ### `timetable:unpack()` {#timetable:unpack} + + Unpacks the timetable object; returns `year`, `month`, `day`, `hour`, `min`, `sec`, `yday`, `wday` diff --combined .config/awesome/modules/luatz/doc/tzinfo.md index 0000000,d4791e2..d4791e2 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/doc/tzinfo.md +++ b/.config/awesome/modules/luatz/doc/tzinfo.md @@@ -1,0 -1,45 +1,45 @@@ + ## `luatz.tzinfo` {#tzinfo} + + Provides a metatable for the timezone class. + + Created in `luatz.tzfile` and managed by `luatz.tzcache`; + a timezone object contains information about a timezone. + These objects are based on the information available in a "zoneinfo" file. + + Timezone objects should be considered opaque and immutable; + so the following details can be skipped over. + + ------------------------------------------------------------------------------ + + The table contains a sequence of tables that describe the timezone at a given point + using a `transition_time`: the unix timestamp (in UTC) that this definition starts, and + a `tt_info` object. + + A `tt_info` object contains information about a time offset; + and contains the following fields: + + - `gmtoff` (number) The offset from GMT (UTC) in seconds + - `isdst` (boolean): If this change was declared as daylight savings + - `abbrind` (number, abbreviation id) + - `abbr` (string): short name for this gmt offset + - `isstd` (boolean) + - `isgmt` (boolean) + + + ### `tzinfo:find_current(utc_ts)` {#tzinfo:find_current} + + Returns the relevant `tt_info` object for the given UTC timestamp in the timezone. + + + ### `tzinfo:localise(utc_ts)` and `tzinfo:localize(utc_ts)` {#tzinfo:localise} + + Convert the given UTC timestamp to the timezone. + Returns the number of seconds since unix epoch in the given timezone. + + + ### `tzinfo:utctime(local_ts)` {#tzinfo:utctime} + + Convert the given local timestamp (seconds since unix epoch in the time zone) to a UTC timestamp. + This may result in ambigous results, in which case multiple values are returned. + + e.g. consider that when daylight savings rewinds your local clock from 3am to 2am there will be two 2:30ams. diff --combined .config/awesome/modules/luatz/examples/date_arithmetic.lua index 0000000,4065122..4065122 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/examples/date_arithmetic.lua +++ b/.config/awesome/modules/luatz/examples/date_arithmetic.lua @@@ -1,0 -1,33 +1,33 @@@ + local luatz = require "luatz" + + -- We do this a few times ==> Convert a timestamp to timetable and normalise + local function ts2tt(ts) + return luatz.timetable.new_from_timestamp(ts) + end + + -- Get the current time in UTC + local utcnow = luatz.time() + local now = ts2tt(utcnow) + print(now, "now (UTC)") + + -- Get a new time object 6 months from now + local x = now:clone() + x.month = x.month + 6 + x:normalise() + print(x, "6 months from now") + + -- Find out what time it is in Melbourne at the moment + local melbourne = luatz.get_tz("Australia/Melbourne") + local now_in_melbourne = ts2tt(melbourne:localise(utcnow)) + print(now_in_melbourne, "Melbourne") + + -- Six months from now in melbourne (so month is incremented; but still the same time) + local m = now_in_melbourne:clone() + m.month = m.month + 6 + m:normalise() + print(m, "6 months from now in melbourne") + + -- Convert time back to utc; a daylight savings transition may have taken place! + -- There may be 2 results, but for we'll ignore the second possibility + local c, _ = melbourne:utctime(m:timestamp()) + print(ts2tt(c), "6 months from now in melbourne converted to utc") diff --combined .config/awesome/modules/luatz/examples/os_date.lua index 0000000,ad254ce..ad254ce mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/examples/os_date.lua +++ b/.config/awesome/modules/luatz/examples/os_date.lua @@@ -1,0 -1,25 +1,25 @@@ + --[[ + Re-implementation of `os.date` from the standard lua library + ]] + + local gettime = require "luatz.gettime".gettime + local new_from_timestamp = require "luatz.timetable".new_from_timestamp + local get_tz = require "luatz.tzcache".get_tz + + local function os_date(format_string, timestamp) + format_string = format_string or "%c" + timestamp = timestamp or gettime() + if format_string:sub(1, 1) == "!" then -- UTC + format_string = format_string:sub(2) + else -- Localtime + timestamp = get_tz():localise(timestamp) + end + local tt = new_from_timestamp(timestamp) + if format_string == "*t" then + return tt + else + return tt:strftime(format_string) + end + end + + return os_date diff --combined .config/awesome/modules/luatz/luatz-scm-0.rockspec index 0000000,2ceae4c..2ceae4c mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz-scm-0.rockspec +++ b/.config/awesome/modules/luatz/luatz-scm-0.rockspec @@@ -1,0 -1,40 +1,40 @@@ + package = "luatz" + version = "scm-0" + + description = { + summary = "library for time and date manipulation."; + detailed = [[ + A lua library for time and date manipulation. + + Features include: + - Normalisation of broken down date objects + - allows for complex time/date manipulation logic e.g. "what day is it in 2 days, 5 hours from now?" + - Conversion between locations (time zones) using your local zoneinfo database. + - strftime style formatting + + All operations are possible without C extensions, though if available they may be used to increase accuracy. + ]]; + license = "MIT"; + } + + dependencies = { + "lua >= 5.1"; + } + + source = { + url = "git://github.com/daurnimator/luatz.git"; + } + + build = { + type = "builtin"; + modules = { + ["luatz.init"] = "luatz/init.lua"; + ["luatz.gettime"] = "luatz/gettime.lua"; + ["luatz.parse"] = "luatz/parse.lua"; + ["luatz.timetable"] = "luatz/timetable.lua"; + ["luatz.strftime"] = "luatz/strftime.lua"; + ["luatz.tzcache"] = "luatz/tzcache.lua"; + ["luatz.tzfile"] = "luatz/tzfile.lua"; + ["luatz.tzinfo"] = "luatz/tzinfo.lua"; + }; + } diff --combined .config/awesome/modules/luatz/luatz/gettime.lua index 0000000,4d6f45a..4d6f45a mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/gettime.lua +++ b/.config/awesome/modules/luatz/luatz/gettime.lua @@@ -1,0 -1,53 +1,53 @@@ + local _M = {} + + _M.source, _M.resolution, _M.gettime = (function() + local has_syscall, syscall = pcall(require, "syscall") + if has_syscall and syscall.clock_gettime and syscall.c.CLOCK then + local clock_id = syscall.c.CLOCK.REALTIME + local function timespec_to_number(timespec) + return tonumber(timespec.tv_sec) + tonumber(timespec.tv_nsec) * 1e-9 + end + return "syscall.clock_gettime(CLOCK_REALTIME)", + syscall.clock_getres and timespec_to_number(syscall.clock_getres(clock_id)) or 1e-9, + function() + return timespec_to_number(syscall.clock_gettime(clock_id)) + end + end + + local has_unix, unix = pcall(require, "unix") + -- On Apple devices lunix only uses gettimeofday() + if has_unix and unix.clock_gettime and unix.uname and unix.uname().sysname ~= "Darwin" then + return "unix.clock_gettime(CLOCK_REALTIME)", 1e-9, function() + return unix.clock_gettime() + end + end + + if has_syscall and syscall.gettimeofday then + local function timeval_to_number(timeval) + return tonumber(timeval.tv_sec) + tonumber(timeval.tv_nsec) * 1e-6 + end + return "syscall.gettimeofday()", 1e-6, + function() + return timeval_to_number(syscall.gettimeofday()) + end + end + + if has_unix and unix.gettimeofday then + return "unix.gettimeofday()", 1e-6, unix.gettimeofday + end + + local has_socket, socket = pcall(require, "socket") + if has_socket and socket.gettime then + -- on windows, this uses GetSystemTimeAsFileTime, which has resolution of 1e-7 + -- on linux, this uses gettimeofday, which has resolution of 1e-6 + return "socket.gettime()", 1e-6, socket.gettime + end + + if ngx and ngx.now then -- luacheck: ignore 113 + return "ngx.now()", 1e-3, ngx.now -- luacheck: ignore 113 + end + + return "os.time()", 1, os.time + end)() + + return _M diff --combined .config/awesome/modules/luatz/luatz/init.lua index 0000000,4423189..4423189 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/init.lua +++ b/.config/awesome/modules/luatz/luatz/init.lua @@@ -1,0 -1,40 +1,40 @@@ + local _M = { + gettime = require "luatz.gettime"; + parse = require "luatz.parse"; + strftime = require "luatz.strftime"; + timetable = require "luatz.timetable"; + tzcache = require "luatz.tzcache"; + } + + --- Top-level aliases for common functions + + _M.time = _M.gettime.gettime + _M.get_tz = _M.tzcache.get_tz + + --- Handy functions + + _M.time_in = function(tz, now) + return _M.get_tz(tz):localize(now) + end + + _M.now = function() + local ts = _M.gettime.gettime() + return _M.timetable.new_from_timestamp(ts) + end + + --- C-like functions + + _M.gmtime = function(ts) + return _M.timetable.new_from_timestamp(ts) + end + + _M.localtime = function(ts) + ts = _M.time_in(nil, ts) + return _M.gmtime(ts) + end + + _M.ctime = function(ts) + return _M.strftime.asctime(_M.localtime(ts)) + end + + return _M diff --combined .config/awesome/modules/luatz/luatz/parse.lua index 0000000,6feeb29..6feeb29 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/parse.lua +++ b/.config/awesome/modules/luatz/luatz/parse.lua @@@ -1,0 -1,40 +1,40 @@@ + local new_timetable = require "luatz.timetable".new + + --- Parse an RFC 3339 datetime at the given position + -- Returns a time table and the `tz_offset` + -- Return value is not normalised (this preserves a leap second) + -- If the timestamp is only partial (i.e. missing "Z" or time offset) then `tz_offset` will be nil + -- TODO: Validate components are within their boundarys (e.g. 1 <= month <= 12) + local function rfc_3339(str, init) + local year, month, day, hour, min, sec, patt_end = str:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)()", init) -- luacheck: ignore 631 + if not year then + return nil, "Invalid RFC 3339 timestamp" + end + year = tonumber(year, 10) + month = tonumber(month, 10) + day = tonumber(day, 10) + hour = tonumber(hour, 10) + min = tonumber(min, 10) + sec = tonumber(sec, 10) + + local tt = new_timetable(year, month, day, hour, min, sec) + + local tz_offset + if str:match("^[Zz]", patt_end) then + tz_offset = 0 + else + local hour_offset, min_offset = str:match("^([+-]%d%d):(%d%d)", patt_end) + if hour_offset then + tz_offset = tonumber(hour_offset, 10) * 3600 + tonumber(min_offset, 10) * 60 + else -- luacheck: ignore 542 + -- Invalid RFC 3339 timestamp offset (should be Z or (+/-)hour:min) + -- tz_offset will be nil + end + end + + return tt, tz_offset + end + + return { + rfc_3339 = rfc_3339; + } diff --combined .config/awesome/modules/luatz/luatz/strftime.lua index 0000000,223da0f..223da0f mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/strftime.lua +++ b/.config/awesome/modules/luatz/luatz/strftime.lua @@@ -1,0 -1,210 +1,210 @@@ + local strformat = string.format + local floor = math.floor + local function idiv(n, d) + return floor(n / d) + end + + local c_locale = { + abday = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + day = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + abmon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + mon = {"January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + am_pm = {"AM", "PM"}; + } + + --- ISO-8601 week logic + -- ISO 8601 weekday as number with Monday as 1 (1-7) + local function iso_8601_weekday(wday) + if wday == 1 then + return 7 + else + return wday - 1 + end + end + local iso_8601_week do + -- Years that have 53 weeks according to ISO-8601 + local long_years = {} + for _, v in ipairs { + 4, 9, 15, 20, 26, 32, 37, 43, 48, 54, 60, 65, 71, 76, 82, + 88, 93, 99, 105, 111, 116, 122, 128, 133, 139, 144, 150, 156, 161, 167, + 172, 178, 184, 189, 195, 201, 207, 212, 218, 224, 229, 235, 240, 246, 252, + 257, 263, 268, 274, 280, 285, 291, 296, 303, 308, 314, 320, 325, 331, 336, + 342, 348, 353, 359, 364, 370, 376, 381, 387, 392, 398 + } do + long_years[v] = true + end + local function is_long_year(year) + return long_years[year % 400] + end + function iso_8601_week(self) + local wday = iso_8601_weekday(self.wday) + local n = self.yday - wday + local year = self.year + if n < -3 then + year = year - 1 + if is_long_year(year) then + return year, 53, wday + else + return year, 52, wday + end + elseif n >= 361 and not is_long_year(year) then + return year + 1, 1, wday + else + return year, idiv(n + 10, 7), wday + end + end + end + + --- Specifiers + local t = {} + function t:a(locale) + return "%s", locale.abday[self.wday] + end + function t:A(locale) + return "%s", locale.day[self.wday] + end + function t:b(locale) + return "%s", locale.abmon[self.month] + end + function t:B(locale) + return "%s", locale.mon[self.month] + end + function t:c(locale) + return "%.3s %.3s%3d %.2d:%.2d:%.2d %d", + locale.abday[self.wday], locale.abmon[self.month], + self.day, self.hour, self.min, self.sec, self.year + end + -- Century + function t:C() + return "%02d", idiv(self.year, 100) + end + function t:d() + return "%02d", self.day + end + -- Short MM/DD/YY date, equivalent to %m/%d/%y + function t:D() + return "%02d/%02d/%02d", self.month, self.day, self.year % 100 + end + function t:e() + return "%2d", self.day + end + -- Short YYYY-MM-DD date, equivalent to %Y-%m-%d + function t:F() + return "%d-%02d-%02d", self.year, self.month, self.day + end + -- Week-based year, last two digits (00-99) + function t:g() + return "%02d", iso_8601_week(self) % 100 + end + -- Week-based year + function t:G() + return "%d", iso_8601_week(self) + end + t.h = t.b + function t:H() + return "%02d", self.hour + end + function t:I() + return "%02d", (self.hour-1) % 12 + 1 + end + function t:j() + return "%03d", self.yday + end + function t:m() + return "%02d", self.month + end + function t:M() + return "%02d", self.min + end + -- New-line character ('\n') + function t:n() -- luacheck: ignore 212 + return "\n" + end + function t:p(locale) + return self.hour < 12 and locale.am_pm[1] or locale.am_pm[2] + end + -- TODO: should respect locale + function t:r(locale) + return "%02d:%02d:%02d %s", + (self.hour-1) % 12 + 1, self.min, self.sec, + self.hour < 12 and locale.am_pm[1] or locale.am_pm[2] + end + -- 24-hour HH:MM time, equivalent to %H:%M + function t:R() + return "%02d:%02d", self.hour, self.min + end + function t:s() + return "%d", self:timestamp() + end + function t:S() + return "%02d", self.sec + end + -- Horizontal-tab character ('\t') + function t:t() -- luacheck: ignore 212 + return "\t" + end + -- ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S + function t:T() + return "%02d:%02d:%02d", self.hour, self.min, self.sec + end + function t:u() + return "%d", iso_8601_weekday(self.wday) + end + -- Week number with the first Sunday as the first day of week one (00-53) + function t:U() + return "%02d", idiv(self.yday - self.wday + 7, 7) + end + -- ISO 8601 week number (00-53) + function t:V() + return "%02d", select(2, iso_8601_week(self)) + end + -- Weekday as a decimal number with Sunday as 0 (0-6) + function t:w() + return "%d", self.wday - 1 + end + -- Week number with the first Monday as the first day of week one (00-53) + function t:W() + return "%02d", idiv(self.yday - iso_8601_weekday(self.wday) + 7, 7) + end + -- TODO make t.x and t.X respect locale + t.x = t.D + t.X = t.T + function t:y() + return "%02d", self.year % 100 + end + function t:Y() + return "%d", self.year + end + -- TODO timezones + function t:z() -- luacheck: ignore 212 + return "+0000" + end + function t:Z() -- luacheck: ignore 212 + return "GMT" + end + -- A literal '%' character. + t["%"] = function(self) -- luacheck: ignore 212 + return "%%" + end + + local function strftime(format_string, timetable) + return (string.gsub(format_string, "%%([EO]?)(.)", function(locale_modifier, specifier) + local func = t[specifier] + if func then + return strformat(func(timetable, c_locale)) + else + error("invalid conversation specifier '%"..locale_modifier..specifier.."'", 3) + end + end)) + end + + local function asctime(timetable) + -- Equivalent to the format string "%c\n" + return strformat(t.c(timetable, c_locale)) .. "\n" + end + + return { + strftime = strftime; + asctime = asctime; + } diff --combined .config/awesome/modules/luatz/luatz/timetable.lua index 0000000,1a304e2..1a304e2 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/timetable.lua +++ b/.config/awesome/modules/luatz/luatz/timetable.lua @@@ -1,0 -1,254 +1,254 @@@ + local strftime = require "luatz.strftime".strftime + local strformat = string.format + local floor = math.floor + local idiv do + -- Try and use actual integer division when available (Lua 5.3+) + local idiv_loader = (loadstring or load)([[return function(n,d) return n//d end]], "idiv") -- luacheck: ignore 113 + if idiv_loader then + idiv = idiv_loader() + else + idiv = function(n, d) + return floor(n/d) + end + end + end + + + local mon_lengths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + -- Number of days in year until start of month; not corrected for leap years + local months_to_days_cumulative = {0} + for i = 2, 12 do + months_to_days_cumulative[i] = months_to_days_cumulative[i-1] + mon_lengths[i-1] + end + -- For Sakamoto's Algorithm (day of week) + local sakamoto = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + + local function is_leap(y) + if (y % 4) ~= 0 then + return false + elseif (y % 100) ~= 0 then + return true + else + return (y % 400) == 0 + end + end + + local function month_length(m, y) + if m == 2 then + return is_leap(y) and 29 or 28 + else + return mon_lengths[m] + end + end + + local function leap_years_since(year) + return idiv(year, 4) - idiv(year, 100) + idiv(year, 400) + end + + local function day_of_year(day, month, year) + local yday = months_to_days_cumulative[month] + if month > 2 and is_leap(year) then + yday = yday + 1 + end + return yday + day + end + + local function day_of_week(day, month, year) + if month < 3 then + year = year - 1 + end + return(year + leap_years_since(year) + sakamoto[month] + day) % 7 + 1 + end + + local function borrow(tens, units, base) + local frac = tens % 1 + units = units + frac * base + tens = tens - frac + return tens, units + end + + local function carry(tens, units, base) + if units >= base then + tens = tens + idiv(units, base) + units = units % base + elseif units < 0 then + tens = tens + idiv(units, base) + units = (base + units) % base + end + return tens, units + end + + -- Modify parameters so they all fit within the "normal" range + local function normalise(year, month, day, hour, min, sec) + -- `month` and `day` start from 1, need -1 and +1 so it works modulo + month, day = month - 1, day - 1 + + -- Convert everything (except seconds) to an integer + -- by propagating fractional components down. + year , month = borrow(year , month, 12) + -- Carry from month to year first, so we get month length correct in next line around leap years + year , month = carry(year, month, 12) + month, day = borrow(month, day , month_length(floor(month + 1), year)) + day , hour = borrow(day , hour , 24) + hour , min = borrow(hour , min , 60) + min , sec = borrow(min , sec , 60) + + -- Propagate out of range values up + -- e.g. if `min` is 70, `hour` increments by 1 and `min` becomes 10 + -- This has to happen for all columns after borrowing, as lower radixes may be pushed out of range + min , sec = carry(min , sec , 60) -- TODO: consider leap seconds? + hour , min = carry(hour, min , 60) + day , hour = carry(day , hour, 24) + -- Ensure `day` is not underflowed + -- Add a whole year of days at a time, this is later resolved by adding months + -- TODO[OPTIMIZE]: This could be slow if `day` is far out of range + while day < 0 do + month = month - 1 + if month < 0 then + year = year - 1 + month = 11 + end + day = day + month_length(month + 1, year) + end + year, month = carry(year, month, 12) + + -- TODO[OPTIMIZE]: This could potentially be slow if `day` is very large + while true do + local i = month_length(month + 1, year) + if day < i then break end + day = day - i + month = month + 1 + if month >= 12 then + month = 0 + year = year + 1 + end + end + + -- Now we can place `day` and `month` back in their normal ranges + -- e.g. month as 1-12 instead of 0-11 + month, day = month + 1, day + 1 + + return year, month, day, hour, min, sec + end + + local leap_years_since_1970 = leap_years_since(1970) + local function timestamp(year, month, day, hour, min, sec) + year, month, day, hour, min, sec = normalise(year, month, day, hour, min, sec) + + local days_since_epoch = day_of_year(day, month, year) + + 365 * (year - 1970) + -- Each leap year adds one day + + (leap_years_since(year - 1) - leap_years_since_1970) - 1 + + return days_since_epoch * (60*60*24) + + hour * (60*60) + + min * 60 + + sec + end + + + local timetable_methods = {} + + function timetable_methods:unpack() + return assert(self.year , "year required"), + assert(self.month, "month required"), + assert(self.day , "day required"), + self.hour or 12, + self.min or 0, + self.sec or 0, + self.yday, + self.wday + end + + function timetable_methods:normalise() + local year, month, day + year, month, day, self.hour, self.min, self.sec = normalise(self:unpack()) + + self.day = day + self.month = month + self.year = year + self.yday = day_of_year(day, month, year) + self.wday = day_of_week(day, month, year) + + return self + end + timetable_methods.normalize = timetable_methods.normalise -- American English + + function timetable_methods:timestamp() + return timestamp(self:unpack()) + end + + function timetable_methods:rfc_3339() + local year, month, day, hour, min, fsec = self:unpack() + local sec, msec = borrow(fsec, 0, 1000) + msec = math.floor(msec) + return strformat("%04u-%02u-%02uT%02u:%02u:%02d.%03d", year, month, day, hour, min, sec, msec) + end + + function timetable_methods:strftime(format_string) + return strftime(format_string, self) + end + + local timetable_mt + + local function coerce_arg(t) + if getmetatable(t) == timetable_mt then + return t:timestamp() + end + return t + end + + timetable_mt = { + __index = timetable_methods; + __tostring = timetable_methods.rfc_3339; + __eq = function(a, b) + return a:timestamp() == b:timestamp() + end; + __lt = function(a, b) + return a:timestamp() < b:timestamp() + end; + __sub = function(a, b) + return coerce_arg(a) - coerce_arg(b) + end; + } + + local function cast_timetable(tm) + return setmetatable(tm, timetable_mt) + end + + local function new_timetable(year, month, day, hour, min, sec, yday, wday) + return cast_timetable { + year = year; + month = month; + day = day; + hour = hour; + min = min; + sec = sec; + yday = yday; + wday = wday; + } + end + + function timetable_methods:clone() + return new_timetable(self:unpack()) + end + + local function new_from_timestamp(ts) + if type(ts) ~= "number" then + error("bad argument #1 to 'new_from_timestamp' (number expected, got " .. type(ts) .. ")", 2) + end + return new_timetable(1970, 1, 1, 0, 0, ts):normalise() + end + + return { + is_leap = is_leap; + day_of_year = day_of_year; + day_of_week = day_of_week; + normalise = normalise; + timestamp = timestamp; + + new = new_timetable; + new_from_timestamp = new_from_timestamp; + cast = cast_timetable; + timetable_mt = timetable_mt; + } diff --combined .config/awesome/modules/luatz/luatz/tzcache.lua index 0000000,ae32dce..ae32dce mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/tzcache.lua +++ b/.config/awesome/modules/luatz/luatz/tzcache.lua @@@ -1,0 -1,35 +1,35 @@@ + local read_tzfile = require "luatz.tzfile".read_tzfile + + local base_zoneinfo_path = "/usr/share/zoneinfo/" + local local_zoneinfo_path = "/etc/localtime" + local tz_cache = {} + + local function name_to_zoneinfo_path(name) + if name == nil then + return local_zoneinfo_path + elseif name:sub(1, 1) == "/" then + return name + else + return base_zoneinfo_path .. name + end + end + + local function clear_tz_cache(name) + tz_cache[name_to_zoneinfo_path(name)] = nil + end + + local function get_tz(name) + local path = name_to_zoneinfo_path(name) + -- TODO: stat path + local tzinfo = tz_cache[path] + if tzinfo == nil then + tzinfo = read_tzfile(path) + tz_cache[path] = tzinfo + end + return tzinfo + end + + return { + get_tz = get_tz; + clear_tz_cache = clear_tz_cache; + } diff --combined .config/awesome/modules/luatz/luatz/tzfile.lua index 0000000,e57db6d..e57db6d mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/tzfile.lua +++ b/.config/awesome/modules/luatz/luatz/tzfile.lua @@@ -1,0 -1,251 +1,251 @@@ + local tz_info_mt = require "luatz.tzinfo".tz_info_mt + local tt_info_mt = require "luatz.tzinfo".tt_info_mt + + local read_int32be, read_int64be + + -- luacheck: push std max + if string.unpack then + -- Only available in Lua 5.3+ + function read_int32be(fd) + local data, err = fd:read(4) + if data == nil then return nil, err end + return string.unpack(">i4", data) + end + + function read_int64be(fd) + local data, err = fd:read(8) + if data == nil then return nil, err end + return string.unpack(">i8", data) + end + else -- luacheck: pop + function read_int32be(fd) + local data, err = fd:read(4) + if data == nil then return nil, err end + local o1, o2, o3, o4 = data:byte(1, 4) + + local unsigned = o4 + o3*2^8 + o2*2^16 + o1*2^24 + if unsigned >= 2^31 then + return unsigned - 2^32 + else + return unsigned + end + end + + function read_int64be(fd) + local data, err = fd:read(8) + if data == nil then return nil, err end + local o1, o2, o3, o4, o5, o6, o7, o8 = data:byte(1, 8) + + local unsigned = o8 + o7*2^8 + o6*2^16 + o5*2^24 + o4*2^32 + o3*2^40 + o2*2^48 + o1*2^56 + if unsigned >= 2^63 then + return unsigned - 2^64 + else + return unsigned + end + end + end + + local function read_flags(fd, n) + local data, err = fd:read(n) + if data == nil then return nil, err end + + local res = {} + for i=1, n do + res[i] = data:byte(i,i) ~= 0 + end + return res + end + + local fifteen_nulls = ("\0"):rep(15) + local function read_tz(fd) + assert(fd:read(4) == "TZif", "Invalid TZ file") + local version = assert(fd:read(1)) + if version == "\0" or version == "2" or version == "3" then + local MIN_TIME = -2^32+1 + + assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls") + + -- The number of UTC/local indicators stored in the file. + local tzh_ttisgmtcnt = assert(read_int32be(fd)) + + -- The number of standard/wall indicators stored in the file. + local tzh_ttisstdcnt = assert(read_int32be(fd)) + + -- The number of leap seconds for which data is stored in the file. + local tzh_leapcnt = assert(read_int32be(fd)) + + -- The number of "transition times" for which data is stored in the file. + local tzh_timecnt = assert(read_int32be(fd)) + + -- The number of "local time types" for which data is stored in the file (must not be zero). + local tzh_typecnt = assert(read_int32be(fd)) + + -- The number of characters of "timezone abbreviation strings" stored in the file. + local tzh_charcnt = assert(read_int32be(fd)) + + local transition_times = {} + for i=1, tzh_timecnt do + transition_times[i] = assert(read_int32be(fd)) + end + local transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)} + + local ttinfos = {} + for i=1, tzh_typecnt do + ttinfos[i] = { + gmtoff = assert(read_int32be(fd)); + isdst = assert(fd:read(1)) ~= "\0"; + abbrind = assert(fd:read(1)):byte(); + } + end + + local abbreviations = assert(fd:read(tzh_charcnt)) + + local leap_seconds = {} -- luacheck: ignore 241 + for i=1, tzh_leapcnt do + leap_seconds[i] = { + offset = assert(read_int32be(fd)); + n = assert(read_int32be(fd)); + } + end + + local isstd = assert(read_flags(fd, tzh_ttisstdcnt)) + + local isgmt = assert(read_flags(fd, tzh_ttisgmtcnt)) + + local TZ + + if version == "2" or version == "3" then + --[[ + For version-2-format timezone files, the above header and data is followed by a second header and data, + identical in format except that eight bytes are used for each transition time or leap-second time. + ]] + assert(fd:read(4) == "TZif") + assert(fd:read(1) == version) + assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls") + + MIN_TIME = -2^64+1 + + -- The number of UTC/local indicators stored in the file. + tzh_ttisgmtcnt = assert(read_int32be(fd)) + + -- The number of standard/wall indicators stored in the file. + tzh_ttisstdcnt = assert(read_int32be(fd)) + + -- The number of leap seconds for which data is stored in the file. + tzh_leapcnt = assert(read_int32be(fd)) + + -- The number of "transition times" for which data is stored in the file. + tzh_timecnt = assert(read_int32be(fd)) + + -- The number of "local time types" for which data is stored in the file (must not be zero). + tzh_typecnt = assert(read_int32be(fd)) + + -- The number of characters of "timezone abbreviation strings" stored in the file. + tzh_charcnt = assert(read_int32be(fd)) + + transition_times = {} + for i=1, tzh_timecnt do + transition_times[i] = assert(read_int64be(fd)) + end + transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)} + + ttinfos = {} + for i=1, tzh_typecnt do + ttinfos[i] = { + gmtoff = assert(read_int32be(fd)); + isdst = assert(fd:read(1)) ~= "\0"; + abbrind = assert(fd:read(1)):byte(); + } + end + + abbreviations = assert(fd:read(tzh_charcnt)) + + leap_seconds = {} + for i=1, tzh_leapcnt do + leap_seconds[i] = { + offset = assert(read_int64be(fd)); + n = assert(read_int32be(fd)); + } + end + + isstd = assert(read_flags(fd, tzh_ttisstdcnt)) + + isgmt = assert(read_flags(fd, tzh_ttisgmtcnt)) + + --[[ + After the second header and data comes a newline-enclosed, POSIX-TZ-environment-variable-style string + for use in handling instants after the last transition time stored in the file + (with nothing between the newlines if there is no POSIX representation for such instants). + ]] + + --[[ + For version-3-format time zone files, the POSIX-TZ-style string may + use two minor extensions to the POSIX TZ format, as described in newtzset (3). + First, the hours part of its transition times may be signed and range from + -167 through 167 instead of the POSIX-required unsigned values + from 0 through 24. Second, DST is in effect all year if it starts + January 1 at 00:00 and ends December 31 at 24:00 plus the difference + between daylight saving and standard time. + ]] + + assert(assert(fd:read(1)) == "\n", "Expected newline at end of version 2 header") + + TZ = assert(fd:read("*l")) + if #TZ == 0 then + TZ = nil + end + end + + for i=1, tzh_typecnt do + local v = ttinfos[i] + v.abbr = abbreviations:sub(v.abbrind+1, v.abbrind+3) + v.isstd = isstd[i] or false + v.isgmt = isgmt[i] or false + setmetatable(v, tt_info_mt) + end + + --[[ + Use the first standard-time ttinfo structure in the file + (or simply the first ttinfo structure in the absence of a standard-time structure) + if either tzh_timecnt is zero or the time argument is less than the first transition time recorded in the file. + ]] + local first = 1 + do + for i=1, tzh_ttisstdcnt do + if isstd[i] then + first = i + break + end + end + end + + local res = { + future = TZ; + [0] = { + transition_time = MIN_TIME; + info = ttinfos[first]; + } + } + for i=1, tzh_timecnt do + res[i] = { + transition_time = transition_times[i]; + info = ttinfos[transition_time_ind[i]+1]; + } + end + return setmetatable(res, tz_info_mt) + else + error("Unsupported version") + end + end + + local function read_tzfile(path) + local fd = assert(io.open(path, "rb")) + local tzinfo = read_tz(fd) + fd:close() + return tzinfo + end + + return { + read_tz = read_tz; + read_tzfile = read_tzfile; + } diff --combined .config/awesome/modules/luatz/luatz/tzinfo.lua index 0000000,e37483f..e37483f mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/luatz/tzinfo.lua +++ b/.config/awesome/modules/luatz/luatz/tzinfo.lua @@@ -1,0 -1,97 +1,97 @@@ + local gettime = require "luatz.gettime".gettime + local timetable_mt = require "luatz.timetable".timetable_mt + + local function to_timestamp(o) + if type(o) == "number" then + return o + elseif getmetatable(o) == timetable_mt then + return o:timestamp() + end + end + + local tz_info_methods = { } + local tz_info_mt = { + __name = "luatz.tz_info"; + __index = tz_info_methods; + } + local tt_info_mt = { + __name = "luatz.tt_info"; + __tostring = function(self) + return string.format("tt_info:%s=%d", self.abbr, self.gmtoff) + end; + } + + -- Binary search + local function find_current(tzinfo, target, i, j) + if i >= j then return j end + + local half = math.ceil((j+i) / 2) + + if target >= tzinfo[half].transition_time then + return find_current(tzinfo, target, half, j) + else + return find_current(tzinfo, target, i, half-1) + end + end + + local function find_current_local(tzinfo, ts_local) + -- Find two best possibilities by searching back and forward a day (assumes transition is never by more than 24 hours) + local tz_first = find_current(tzinfo, ts_local-86400, 0, #tzinfo) + local tz_last = find_current(tzinfo, ts_local+86400, 0, #tzinfo) + + local n_candidates = tz_last - tz_first + 1 + + if n_candidates == 1 then + return tz_first + elseif n_candidates == 2 then + local tz_first_ob = tzinfo[tz_first] + local tz_last_ob = tzinfo[tz_last] + + local first_gmtoffset = tz_first_ob.info.gmtoff + local last_gmtoffset = tz_last_ob .info.gmtoff + + local t_start = tz_last_ob.transition_time + first_gmtoffset + local t_end = tz_last_ob.transition_time + last_gmtoffset + + -- If timestamp is before start or after end + if ts_local < t_start then + return tz_first + elseif ts_local > t_end then + return tz_last + end + + -- If we get this far, the local time is ambiguous + return tz_first, tz_last + else + error("Too many transitions in a 2 day period") + end + end + + function tz_info_methods:find_current(current) + current = assert(to_timestamp(current), "invalid timestamp to :find_current") + return self[find_current(self, current, 0, #self)].info + end + + function tz_info_methods:localise(utc_ts) + utc_ts = utc_ts or gettime() + return utc_ts + self:find_current(utc_ts).gmtoff + end + tz_info_methods.localize = tz_info_methods.localise + + function tz_info_methods:utctime(ts_local) + ts_local = assert(to_timestamp(ts_local), "invalid timestamp to :utctime") + local tz1, tz2 = find_current_local(self, ts_local) + tz1 = self[tz1].info + if tz2 == nil then + return ts_local - tz1.gmtoff + else -- Local time is ambiguous + tz2 = self[tz2].info + + return ts_local - tz2.gmtoff, ts_local - tz2.gmtoff + end + end + + return { + tz_info_mt = tz_info_mt; + tt_info_mt = tt_info_mt; + } diff --combined .config/awesome/modules/luatz/spec/Godthab.tz index 0000000,111d9a8..111d9a8 mode 000000,100644..100644 Binary files differ diff --combined .config/awesome/modules/luatz/spec/parse_spec.lua index 0000000,e38ad38..e38ad38 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/spec/parse_spec.lua +++ b/.config/awesome/modules/luatz/spec/parse_spec.lua @@@ -1,0 -1,17 +1,17 @@@ + describe("Time parsing library", function() + local timetable = require "luatz.timetable" + local parse = require "luatz.parse" + + it("#RFC3339 parsing", function() + assert.same(timetable.new(2013,10,22,14,17,02), (parse.rfc_3339 "2013-10-22T14:17:02Z")) + + -- Numeric offsets accepted + assert.same({timetable.new(2013,10,22,14,17,02), 10*3600 }, {parse.rfc_3339 "2013-10-22T14:17:02+10:00" }) + + -- Missing offsets parse + assert.same(timetable.new(2013,10,22,14,17,02), (parse.rfc_3339 "2013-10-22T14:17:02")) + + -- Invalid + assert.same(nil, (parse.rfc_3339 "an invalid timestamp")) + end) + end) diff --combined .config/awesome/modules/luatz/spec/strftime_spec.lua index 0000000,39f92c7..39f92c7 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/spec/strftime_spec.lua +++ b/.config/awesome/modules/luatz/spec/strftime_spec.lua @@@ -1,0 -1,30 +1,30 @@@ + local luatz = require "luatz.init" + local time = 1234567890 + local base_tt = luatz.gmtime(time) + describe("#strftime works the same as os.date", function() + local strftime = luatz.strftime.strftime + for _, spec in ipairs { + "a", "A", "b", "B", "c", "C", "d", "D", "e", "F", + "g", "G", "H", "I", "j", "m", "M", "n", "p", "r", + "R", --[["s",]] "S", "t", "T", "u", "U", "V", "w", "W", + "y", "Y", "z", "Z" , "%" + } do + local tt = base_tt:clone() + local f = "%"..spec + local osdf = "!%"..spec + it("format specifier '"..f.."' is equivalent to os.date('"..osdf.."')", function() + for i=1, 365*12 do + local t = time + 60*60*24*i + tt.day = tt.day + 1 + tt:normalise() + assert.are.same(os.date(osdf,t), strftime(f,tt)) + end + end) + end + end) + describe("#asctime", function() + local asctime = luatz.strftime.asctime + it("should format correctly", function() + assert.are.same("Fri Feb 13 23:31:30 2009\n", asctime(base_tt)) + end) + end) diff --combined .config/awesome/modules/luatz/spec/timetable_spec.lua index 0000000,b7bc3e3..b7bc3e3 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/spec/timetable_spec.lua +++ b/.config/awesome/modules/luatz/spec/timetable_spec.lua @@@ -1,0 -1,138 +1,138 @@@ + describe("Timetable library", function() + local timetable = require "luatz.timetable" + + local function native_normalise(year, month, day) + return os.date("*t",os.time { + year = year; + month = month; + day = day; + }) + end + + it("#is_leap is correct", function() + assert.same(false, timetable.is_leap(1)) + assert.same(false, timetable.is_leap(3)) + assert.same(true , timetable.is_leap(4)) + assert.same(true , timetable.is_leap(2000)) + assert.same(true , timetable.is_leap(2004)) + assert.same(true , timetable.is_leap(2012)) + assert.same(false, timetable.is_leap(2013)) + assert.same(false, timetable.is_leap(2014)) + assert.same(false, timetable.is_leap(2100)) + assert.same(true , timetable.is_leap(2400)) + end) + + it("#normalise gets #wday (day of week) correct", function() + + local function assert_same_wday(year, month, day) + return assert.are.same( + native_normalise(year, month, day).wday, + timetable.new(year, month, day):normalise().wday + ) + end + + assert_same_wday(2013, 7, 23) + assert_same_wday(2013, 7, 24) + assert_same_wday(2013, 7, 25) + assert_same_wday(2013, 7, 26) + assert_same_wday(2013, 7, 27) + assert_same_wday(2013, 7, 28) + assert_same_wday(2013, 7, 29) + assert_same_wday(2014, 1, 1) + assert_same_wday(2014, 1, 6) + assert_same_wday(2016, 2, 28) + assert_same_wday(2016, 2, 29) + assert_same_wday(2016, 3, 1) + end) + + local function native_timestamp(year, month, day) + return assert(tonumber(assert(io.popen( + string.format('date -u -d "%d-%d-%d" +%%s', year, month, day) + )):read "*l")) + end + + it("#timestamp creation is valid", function() + for y=1950,2013 do + for m=1,12 do + assert.same(native_timestamp(y,m,1), timetable.timestamp(y,m,1,0,0,0)) + end + end + end) + + it("#normalise handles out of range days in a year", function() + assert.same({2014,1,1,0,0,0}, {timetable.normalise(2013,1,366,0,0,0)}) + assert.same({2014,2,4,0,0,0}, {timetable.normalise(2013,1,400,0,0,0)}) + assert.same({2017,2,3,0,0,0}, {timetable.normalise(2016,1,400,0,0,0)}) + assert.same({2016,3,5,0,0,0}, {timetable.normalise(2015,1,430,0,0,0)}) + assert.same({2017,3,5,0,0,0}, {timetable.normalise(2016,1,430,0,0,0)}) + assert.same({2027,5,18,0,0,0}, {timetable.normalise(2000,1,10000,0,0,0)}) + assert.same({29379,1,25,0,0,0}, {timetable.normalise(2000,1,10000000,0,0,0)}) + end) + + it("#normalise handles out of range days in a #month", function() + assert.same({2012,12,1,0,0,0}, {timetable.normalise(2013,0,1,0,0,0)}) + assert.same({2016,6,1,0,0,0}, {timetable.normalise(2013,42,1,0,0,0)}) + + -- Correct behaviour around leap days + assert.same({2012,3,23,0,0,0}, {timetable.normalise(2012,2,52,0,0,0)}) + assert.same({2013,3,24,0,0,0}, {timetable.normalise(2013,2,52,0,0,0)}) + + assert.same({2012,2,27,0,0,0}, {timetable.normalise(2012,3,-2,0,0,0)}) + assert.same({2013,2,26,0,0,0}, {timetable.normalise(2013,3,-2,0,0,0)}) + + -- Also when more fields are out of range + assert.same({2016,7,22,0,0,0}, {timetable.normalise(2013,42,52,0,0,0)}) + assert.same({2016,7,24,2,0,0}, {timetable.normalise(2013,42,52,50,0,0)}) + end) + + it("#normalise handles fractional #month", function() + assert.same({2015,2,15,0,0,0}, {timetable.normalise(2014,14.5,1,0,0,0)}) + assert.same({2016,2,15,12,0,0}, {timetable.normalise(2015,14.5,1,0,0,0)}) -- leap year, so hours is 12 + assert.same({2017,2,15,0,0,0}, {timetable.normalise(2016,14.5,1,0,0,0)}) + end) + + it("#normalise handles negative carry (issue #10)", function() + assert.same({1970,01,01,00,59,00}, {timetable.normalise(1970,01,01,01,00,-60)}) + assert.same({1970,01,01,00,58,58}, {timetable.normalise(1970,01,01,01,00,-62)}) + assert.same({1969,12,31,23,55,58}, {timetable.normalise(1970,01,01,01,-63,-62)}) + assert.same({2017,02,3,0,0,0}, {timetable.normalise(2017,02,13,0,-14400,0)}) + end) + + it("#normalise handles negative day carry (issue #13)", function() + assert.same({2016,11,30,00,00,00}, {timetable.normalise(2016,12,0,0,0,0)}) + assert.same({2017,11,30,00,00,00}, {timetable.normalise(2017,12,0,0,0,0)}) + assert.same({2018,11,30,00,00,00}, {timetable.normalise(2018,12,0,0,0,0)}) + + assert.same({2017,2,13,0,0,0}, {timetable.normalise(2017,3,-15,0,0,0)}) + assert.same({2016,10,1,0,0,0}, {timetable.normalise(2017,3,-150,0,0,0)}) + assert.same({2013,1,20,0,0,0}, {timetable.normalise(2017,3,-1500,0,0,0)}) + assert.same({1976,2,4,0,0,0}, {timetable.normalise(2017,3,-15000,0,0,0)}) + assert.same({1606,6,23,0,0,0}, {timetable.normalise(2017,3,-150000,0,0,0)}) + end) + + local function round_trip_add(t, field, x) + local before = t:clone() + t[field]=t[field]+x; + t:normalise(); + t[field]=t[field]-x; + t:normalise(); + assert.same(0, t-before) + end + it("#normalise round trips", function() + round_trip_add(timetable.new(2000,2,28,0,0,0), "month", 0.5) + round_trip_add(timetable.new(2014,8,28,19,23,0), "month", 0.4) + round_trip_add(timetable.new(2014,14.5,28,0,0,0), "month", 0.4) + end) + + it("#rfc_3339 works with fractional milliseconds", function() + -- on lua 5.3 this used to throw an error due to milliseconds not being an integer + timetable.new_from_timestamp(1415141759.999911111):rfc_3339() + end) + + it("#rfc_3339 doesn't round seconds up to 60 (issue #4)", function() + assert.same("2014-11-04T22:55:59.999", timetable.new_from_timestamp(1415141759.999911111):rfc_3339()) + assert.same("1970-01-01T00:00:59.999", timetable.new_from_timestamp(59.9999999):rfc_3339()) + assert.same("1969-12-31T23:59:59.999", timetable.new_from_timestamp(-0.001):rfc_3339()) + assert.same("1969-12-31T23:59:00.000", timetable.new_from_timestamp(-59.9999999):rfc_3339()) + end) + end) diff --combined .config/awesome/modules/luatz/spec/tzcache_spec.lua index 0000000,ea0c211..ea0c211 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/spec/tzcache_spec.lua +++ b/.config/awesome/modules/luatz/spec/tzcache_spec.lua @@@ -1,0 -1,17 +1,17 @@@ + describe("Opening/reading system files", function() + local tzcache = require "luatz.tzcache" + it("should have a localtime", function() + tzcache.get_tz() + end) + it("should be able to open UTC", function() + tzcache.get_tz("UTC") + end) + it("should re-use results from cache", function() + -- If cached it should return the same table + local localtime = tzcache.get_tz() + assert.are.equal(localtime, tzcache.get_tz()) + -- Once cache is cleared it should return a new table + tzcache.clear_tz_cache() + assert._not.equal(localtime, tzcache.get_tz()) + end) + end) diff --combined .config/awesome/modules/luatz/spec/tzfile_spec.lua index 0000000,4f10b71..4f10b71 mode 000000,100644..100644 --- a/.config/awesome/modules/luatz/spec/tzfile_spec.lua +++ b/.config/awesome/modules/luatz/spec/tzfile_spec.lua @@@ -1,0 -1,8 +1,8 @@@ + describe("Opening/reading tz files", function() + local tzfile = require "luatz.tzfile" + it("should be able to open a version 3 file", function() + -- The tz file for America/Godthab from 2015g + -- One of the smallest tzif3 files I have + tzfile.read_tzfile("spec/Godthab.tz") + end) + end)