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