]> git.madduck.net Git - etc/awesome.git/blob - luatz/timetable.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

99ac52ee8d238f85e350e5e4ed2a92b1fa6b32b6
[etc/awesome.git] / luatz / timetable.lua
1 local strftime = require "luatz.strftime".strftime
2 local strformat = string.format
3 local floor = math.floor
4 local function idiv ( n , d )
5         return floor ( n / d )
6 end
7
8
9 local mon_lengths = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
10 -- Number of days in year until start of month; not corrected for leap years
11 local months_to_days_cumulative = { 0 }
12 for i = 2, 12 do
13         months_to_days_cumulative [ i ] = months_to_days_cumulative [ i-1 ] + mon_lengths [ i-1 ]
14 end
15 -- For Sakamoto's Algorithm (day of week)
16 local sakamoto = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
17
18 local function is_leap ( y )
19         if (y % 4) ~= 0 then
20                 return false
21         elseif (y % 100) ~= 0 then
22                 return true
23         else
24                 return (y % 400) == 0
25         end
26 end
27
28 local function year_length ( y )
29         return is_leap ( y ) and 366 or 365
30 end
31
32 local function month_length ( m , y )
33         if m == 2 then
34                 return is_leap ( y ) and 29 or 28
35         else
36                 return mon_lengths [ m ]
37         end
38 end
39
40 local function leap_years_since ( year )
41         return idiv ( year , 4 ) - idiv ( year , 100 ) + idiv ( year , 400 )
42 end
43
44 local function day_of_year ( day , month , year )
45         local yday = months_to_days_cumulative [ month ]
46         if month > 2 and is_leap ( year ) then
47                 yday = yday + 1
48         end
49         return yday + day
50 end
51
52 local function day_of_week ( day , month , year )
53         if month < 3 then
54                 year = year - 1
55         end
56         return ( year + leap_years_since ( year ) + sakamoto[month] + day ) % 7 + 1
57 end
58
59 local function increment ( tens , units , base )
60         if units >= base then
61                 tens  = tens + idiv ( units , base )
62                 units = units % base
63         elseif units < 0 then
64                 tens  = tens - 1 + idiv ( -units , base )
65                 units = base - ( -units % base )
66         end
67         return tens , units
68 end
69
70 -- Modify parameters so they all fit within the "normal" range
71 local function normalise ( year , month , day , hour , min , sec )
72         min  , sec  = increment ( min  , sec  , 60 ) -- TODO: consider leap seconds?
73         hour , min  = increment ( hour , min  , 60 )
74         day  , hour = increment ( day  , hour , 24 )
75
76         while day <= 0 do
77                 year = year - 1
78                 day  = day + year_length ( year )
79         end
80
81         -- Lua months start from 1, need -1 and +1 around this increment
82         month = month - 1
83         year , month = increment ( year , month , 12 )
84         month = month + 1
85
86         -- This could potentially be slow if `day` is very large
87         while true do
88                 local i = month_length ( month , year )
89                 if day <= i then break end
90                 day = day - i
91                 month = month + 1
92                 if month > 12 then
93                         month = 1
94                         year = year + 1
95                 end
96         end
97
98         return year , month , day , hour , min , sec
99 end
100
101 local leap_years_since_1970 = leap_years_since ( 1970 )
102 local function timestamp ( year , month , day , hour , min , sec )
103         year , month , day , hour , min , sec = normalise ( year , month , day , hour , min , sec )
104
105         local days_since_epoch = day_of_year ( day , month , year )
106                 + 365 * ( year - 1970 )
107                 -- Each leap year adds one day
108                 + ( leap_years_since ( year - 1 ) - leap_years_since_1970 ) - 1
109
110         return days_since_epoch * (60*60*24)
111                 + hour  * (60*60)
112                 + min   * 60
113                 + sec
114 end
115
116
117 local timetable_methods = { }
118
119 function timetable_methods:unpack ( )
120         return assert ( self.year  , "year required" ) ,
121                 assert ( self.month , "month required" ) ,
122                 assert ( self.day   , "day required" ) ,
123                 self.hour or 12 ,
124                 self.min  or 0 ,
125                 self.sec  or 0 ,
126                 self.yday ,
127                 self.wday
128 end
129
130 function timetable_methods:normalise ( )
131         local year , month , day
132         year , month , day , self.hour , self.min , self.sec = normalise ( self:unpack ( ) )
133
134         self.day   = day
135         self.month = month
136         self.year  = year
137         self.yday  = day_of_year ( day , month , year )
138         self.wday  = day_of_week ( day , month , year )
139
140         return self
141 end
142 timetable_methods.normalize = timetable_methods.normalise -- American English
143
144 function timetable_methods:timestamp ( )
145         return timestamp ( self:unpack ( ) )
146 end
147
148 function timetable_methods:rfc_3339 ( )
149         -- %06.3f gives 3 (=6-3) digits after decimal
150         return strformat ( "%04u-%02u-%02uT%02u:%02u:%06.3f" , self:unpack ( ) )
151 end
152
153 function timetable_methods:strftime ( format_string )
154         return strftime ( format_string , self )
155 end
156
157 local timetable_mt
158
159 local function coerce_arg ( t )
160         if getmetatable ( t ) == timetable_mt then
161                 return t:timestamp ( )
162         end
163         return t
164 end
165
166 timetable_mt = {
167         __index    = timetable_methods ;
168         __tostring = timetable_methods.rfc_3339 ;
169         __eq = function ( a , b )
170                 return a:timestamp ( ) == b:timestamp ( )
171         end ;
172         __lt = function ( a , b )
173                 return a:timestamp ( ) < b:timestamp ( )
174         end ;
175         __sub = function ( a , b )
176                 return coerce_arg ( a ) - coerce_arg ( b )
177         end ;
178 }
179
180 local function cast_timetable ( tm )
181         return setmetatable ( tm , timetable_mt )
182 end
183
184 local function new_timetable ( year , month , day , hour , min , sec , yday , wday )
185         return cast_timetable {
186                 year  = year ;
187                 month = month ;
188                 day   = day ;
189                 hour  = hour ;
190                 min   = min ;
191                 sec   = sec ;
192                 yday  = yday ;
193                 wday  = wday ;
194         }
195 end
196
197 function timetable_methods:clone ( )
198         return new_timetable ( self:unpack ( ) )
199 end
200
201 local function new_from_timestamp ( ts )
202         if type ( ts ) ~= "number" then
203                 error ( "bad argument #1 to 'new_from_timestamp' (number expected, got " .. type ( ts ) .. ")" , 2 )
204         end
205         return new_timetable ( 1970 , 1 , 1 , 0 , 0 , ts )
206 end
207
208 return {
209         is_leap = is_leap ;
210         day_of_year = day_of_year ;
211         day_of_week = day_of_week ;
212         normalise = normalise ;
213         timestamp = timestamp ;
214
215         new = new_timetable ;
216         new_from_timestamp = new_from_timestamp ;
217         cast = cast_timetable ;
218         timetable_mt = timetable_mt ;
219 }