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

c71c59bc25d737ebc27c60b0586b719dd1d25c8f
[etc/awesome.git] / src / timetable.lua
1 local floor = math.floor
2 local function idiv ( n , d )
3         return floor ( n / d )
4 end
5
6
7 local mon_lengths = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
8 -- Number of days in year until start of month; not corrected for leap years
9 local months_to_days_cumulative = { 0 }
10 for i = 2, 12 do
11         months_to_days_cumulative [ i ] = months_to_days_cumulative [ i-1 ] + mon_lengths [ i-1 ]
12 end
13
14 local function is_leap ( y )
15         return (y % 4) == 0 and (y % 100) ~= 0 or (y % 400) == 0
16 end
17
18 local function year_length ( y )
19         return is_leap ( y ) and 366 or 365
20 end
21
22 local function month_length ( m , y )
23         if m == 2 then
24                 return is_leap ( y ) and 29 or 28
25         else
26                 return mon_lengths [ m ]
27         end
28 end
29
30 local function doomsday ( year )
31         return ( 3 -- Tuesday
32                 - 1 + year + idiv ( year , 4 ) - idiv ( year , 100 ) + idiv ( year , 400 ) )
33                 % 7 + 1
34 end
35 local doomsday_cache = setmetatable ( { } , {
36         __index = function ( cache , year )
37                 local d = doomsday ( year )
38                 cache [ year ] = d
39                 return d
40         end ;
41 } )
42
43 local function day_of_year ( day , month , year )
44         local yday = months_to_days_cumulative [ month ]
45         if month > 2 and is_leap ( year ) then
46                 yday = yday + 1
47         end
48         return yday + day
49 end
50
51 local function day_of_week ( yday , year )
52         return ( yday - doomsday_cache [ year ] - 1 ) % 7 + 1
53 end
54
55 local function increment ( tens , units , base )
56         if units >= base then
57                 tens  = tens + idiv ( units , base )
58                 units = units % base
59         elseif units < 0 then
60                 tens  = tens - 1 + idiv ( -units , base )
61                 units = base - ( -units % base )
62         end
63         return tens , units
64 end
65
66 local function unpack_tm ( tm )
67         return assert ( tm.year  , "year required" ) ,
68                 assert ( tm.month , "month required" ) ,
69                 assert ( tm.day   , "day required" ) ,
70                 tm.hour or 12 ,
71                 tm.min  or 0 ,
72                 tm.sec  or 0
73 end
74
75 -- Modify parameters so they all fit within the "normal" range
76 local function normalise ( year , month , day , hour , min , sec )
77         min  , sec  = increment ( min  , sec  , 60 ) -- TODO: consider leap seconds?
78         hour , min  = increment ( hour , min  , 60 )
79         day  , hour = increment ( day  , hour , 24 )
80
81         while day <= 0 do
82                 year = year - 1
83                 day  = day + year_length ( year )
84         end
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         end
93
94         -- Lua months start from 1, need -1 and +1 around this increment
95         year , month = increment ( year , month - 1 , 12 )
96         month = month + 1
97
98         return year , month , day , hour , min , sec
99 end
100
101 local function timestamp ( year , month , day , hour , min , sec )
102         return 60*60*24*(
103                         year * year_length ( year )
104                         + month * month_length ( month , year )
105                         + day
106                 )
107                 + hour  * (60*60)
108                 + min   * 60
109                 + sec
110 end
111
112
113 local timetable_methods = { }
114
115 function timetable_methods:normalise ( )
116         local year , month , day
117         year , month , day , self.hour , self.min , self.sec = normalise ( unpack_tm ( self ) )
118
119         self.day   = day
120         self.month = month
121         self.year  = year
122
123         local yday = day_of_year ( day , month , year )
124         local wday = day_of_week ( yday , year )
125
126         self.yday = yday
127         self.wday = wday
128
129         return self
130 end
131 timetable_methods.normalize = timetable_methods.normalise -- American English
132
133 function timetable_methods:timestamp ( )
134         return timestamp ( unpack_tm ( self ) )
135 end
136
137 function timetable_methods:rfc_3339 ( )
138         -- %06.4g gives 3 (=6-4+1) digits after decimal
139         return strformat ( "%04u-%02u-%02uT%02u:%02u:%06.4g" , unpack_tm ( self ) )
140 end
141
142 local timetable_mt = {
143         __index    = timetable_methods ;
144         __tostring = timetable_methods.rfc_3339 ;
145         __eq = function ( a , b )
146                 return a:timestamp() < b:timestamp()
147         end ;
148 }
149
150 local function cast_timetable ( tm )
151         return setmetatable ( tm , timetable_mt )
152 end
153
154 local function new_timetable ( year , month , day , hour , min , sec )
155         return cast_timetable {
156                 year  = year ;
157                 month = month ;
158                 day   = day ;
159                 hour  = hour ;
160                 min   = min ;
161                 sec   = sec ;
162         }
163 end
164
165 return {
166         doomsday  = doomsday ;
167         normalise = normalise ;
168         timestamp = timestamp ;
169
170         new = new_timetable ;
171         cast = cast_timetable ;
172         timetable_mt = timetable_mt ;
173 }