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

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