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

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