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

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