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

luatz/timetable: Rename `increment` to `carry`
[etc/awesome.git] / luatz / timetable.lua
1 local strftime = require "luatz.strftime".strftime
2 local strformat = string.format
3 local floor = math.floor
4 local function idiv ( n , d )
5         return floor ( n / d )
6 end
7
8
9 local mon_lengths = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
10 -- Number of days in year until start of month; not corrected for leap years
11 local months_to_days_cumulative = { 0 }
12 for i = 2, 12 do
13         months_to_days_cumulative [ i ] = months_to_days_cumulative [ i-1 ] + mon_lengths [ i-1 ]
14 end
15 -- For Sakamoto's Algorithm (day of week)
16 local sakamoto = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
17
18 local function is_leap ( y )
19         if (y % 4) ~= 0 then
20                 return false
21         elseif (y % 100) ~= 0 then
22                 return true
23         else
24                 return (y % 400) == 0
25         end
26 end
27
28 local function year_length ( y )
29         return is_leap ( y ) and 366 or 365
30 end
31
32 local function month_length ( m , y )
33         if m == 2 then
34                 return is_leap ( y ) and 29 or 28
35         else
36                 return mon_lengths [ m ]
37         end
38 end
39
40 local function leap_years_since ( year )
41         return idiv ( year , 4 ) - idiv ( year , 100 ) + idiv ( year , 400 )
42 end
43
44 local function day_of_year ( day , month , year )
45         local yday = months_to_days_cumulative [ month ]
46         if month > 2 and is_leap ( year ) then
47                 yday = yday + 1
48         end
49         return yday + day
50 end
51
52 local function day_of_week ( day , month , year )
53         if month < 3 then
54                 year = year - 1
55         end
56         return ( year + leap_years_since ( year ) + sakamoto[month] + day ) % 7 + 1
57 end
58
59 local function carry ( 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 -- Modify parameters so they all fit within the "normal" range
71 local function normalise ( year , month , day , hour , min , sec )
72         -- Propagate out of range values up
73         -- e.g. if `min` is 70, `hour` increments by 1 and `min` becomes 10
74         min   , sec   = carry ( min   , sec   , 60 ) -- TODO: consider leap seconds?
75         hour  , min   = carry ( hour  , min   , 60 )
76         day   , hour  = carry ( day   , hour  , 24 )
77
78         while day <= 0 do
79                 year = year - 1
80                 day  = day + year_length ( year )
81         end
82
83         -- Lua months start from 1, need -1 and +1 around this increment
84         month = month - 1
85         year , month = carry ( year , month , 12 )
86         month = month + 1
87
88         -- This could potentially be slow if `day` is very large
89         while true do
90                 local i = month_length ( month , year )
91                 if day <= i then break end
92                 day = day - i
93                 month = month + 1
94                 if month > 12 then
95                         month = 1
96                         year = year + 1
97                 end
98         end
99
100         return year , month , day , hour , min , sec
101 end
102
103 local leap_years_since_1970 = leap_years_since ( 1970 )
104 local function timestamp ( year , month , day , hour , min , sec )
105         year , month , day , hour , min , sec = normalise ( year , month , day , hour , min , sec )
106
107         local days_since_epoch = day_of_year ( day , month , year )
108                 + 365 * ( year - 1970 )
109                 -- Each leap year adds one day
110                 + ( leap_years_since ( year - 1 ) - leap_years_since_1970 ) - 1
111
112         return days_since_epoch * (60*60*24)
113                 + hour  * (60*60)
114                 + min   * 60
115                 + sec
116 end
117
118
119 local timetable_methods = { }
120
121 function timetable_methods:unpack ( )
122         return assert ( self.year  , "year required" ) ,
123                 assert ( self.month , "month required" ) ,
124                 assert ( self.day   , "day required" ) ,
125                 self.hour or 12 ,
126                 self.min  or 0 ,
127                 self.sec  or 0 ,
128                 self.yday ,
129                 self.wday
130 end
131
132 function timetable_methods:normalise ( )
133         local year , month , day
134         year , month , day , self.hour , self.min , self.sec = normalise ( self:unpack ( ) )
135
136         self.day   = day
137         self.month = month
138         self.year  = year
139         self.yday  = day_of_year ( day , month , year )
140         self.wday  = day_of_week ( day , month , year )
141
142         return self
143 end
144 timetable_methods.normalize = timetable_methods.normalise -- American English
145
146 function timetable_methods:timestamp ( )
147         return timestamp ( self:unpack ( ) )
148 end
149
150 function timetable_methods:rfc_3339 ( )
151         -- %06.3f gives 3 (=6-3) digits after decimal
152         return strformat ( "%04u-%02u-%02uT%02u:%02u:%06.3f" , self:unpack ( ) )
153 end
154
155 function timetable_methods:strftime ( format_string )
156         return strftime ( format_string , self )
157 end
158
159 local timetable_mt
160
161 local function coerce_arg ( t )
162         if getmetatable ( t ) == timetable_mt then
163                 return t:timestamp ( )
164         end
165         return t
166 end
167
168 timetable_mt = {
169         __index    = timetable_methods ;
170         __tostring = timetable_methods.rfc_3339 ;
171         __eq = function ( a , b )
172                 return a:timestamp ( ) == b:timestamp ( )
173         end ;
174         __lt = function ( a , b )
175                 return a:timestamp ( ) < b:timestamp ( )
176         end ;
177         __sub = function ( a , b )
178                 return coerce_arg ( a ) - coerce_arg ( b )
179         end ;
180 }
181
182 local function cast_timetable ( tm )
183         return setmetatable ( tm , timetable_mt )
184 end
185
186 local function new_timetable ( year , month , day , hour , min , sec , yday , wday )
187         return cast_timetable {
188                 year  = year ;
189                 month = month ;
190                 day   = day ;
191                 hour  = hour ;
192                 min   = min ;
193                 sec   = sec ;
194                 yday  = yday ;
195                 wday  = wday ;
196         }
197 end
198
199 function timetable_methods:clone ( )
200         return new_timetable ( self:unpack ( ) )
201 end
202
203 local function new_from_timestamp ( ts )
204         if type ( ts ) ~= "number" then
205                 error ( "bad argument #1 to 'new_from_timestamp' (number expected, got " .. type ( ts ) .. ")" , 2 )
206         end
207         return new_timetable ( 1970 , 1 , 1 , 0 , 0 , ts )
208 end
209
210 return {
211         is_leap = is_leap ;
212         day_of_year = day_of_year ;
213         day_of_week = day_of_week ;
214         normalise = normalise ;
215         timestamp = timestamp ;
216
217         new = new_timetable ;
218         new_from_timestamp = new_from_timestamp ;
219         cast = cast_timetable ;
220         timetable_mt = timetable_mt ;
221 }