]> 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:

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