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

doc/timetable: Document unpack method
[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 -- Modify parameters so they all fit within the "normal" range
72 local function normalise ( year , month , day , hour , min , sec )
73         min  , sec  = increment ( min  , sec  , 60 ) -- TODO: consider leap seconds?
74         hour , min  = increment ( hour , min  , 60 )
75         day  , hour = increment ( day  , hour , 24 )
76
77         while day <= 0 do
78                 year = year - 1
79                 day  = day + year_length ( year )
80         end
81
82         -- Lua months start from 1, need -1 and +1 around this increment
83         month = month - 1
84         year , month = increment ( year , month , 12 )
85         month = month + 1
86
87         -- This could potentially be slow if `day` is very large
88         while true do
89                 local i = month_length ( month , year )
90                 if day <= i then break end
91                 day = day - i
92                 month = month + 1
93                 if month > 12 then
94                         month = 1
95                         year = year + 1
96                 end
97         end
98
99         return year , month , day , hour , min , sec
100 end
101
102 local leap_years_since_1970 = leap_years_since ( 1970 )
103 local function timestamp ( year , month , day , hour , min , sec )
104         year , month , day , hour , min , sec = normalise ( year , month , day , hour , min , sec )
105
106         local days_since_epoch = day_of_year ( day , month , year )
107                 + 365 * ( year - 1970 )
108                 -- Each leap year adds one day
109                 + ( leap_years_since ( year - 1 ) - leap_years_since_1970 ) - 1
110
111         return days_since_epoch * (60*60*24)
112                 + hour  * (60*60)
113                 + min   * 60
114                 + sec
115 end
116
117
118 local timetable_methods = { }
119
120 function timetable_methods:unpack ( )
121         return assert ( self.year  , "year required" ) ,
122                 assert ( self.month , "month required" ) ,
123                 assert ( self.day   , "day required" ) ,
124                 self.hour or 12 ,
125                 self.min  or 0 ,
126                 self.sec  or 0 ,
127                 self.yday ,
128                 self.wday
129 end
130
131 function timetable_methods:normalise ( )
132         local year , month , day
133         year , month , day , self.hour , self.min , self.sec = normalise ( self:unpack ( ) )
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 ( self:unpack ( ) )
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" , self:unpack ( ) )
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 ( self:unpack ( ) )
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 }