]> git.madduck.net Git - etc/awesome.git/blob - src/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:

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