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

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