]> 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: Coercion doesn't work ==> lua relational operators only work on...
[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         __index    = timetable_methods ;
148         __tostring = timetable_methods.rfc_3339 ;
149         __eq = function ( a , b )
150                 return a:timestamp ( ) == b:timestamp ( )
151         end ;
152         __lt = function ( a , b )
153                 return a:timestamp ( ) < b:timestamp ( )
154         end ;
155 }
156
157 local function cast_timetable ( tm )
158         return setmetatable ( tm , timetable_mt )
159 end
160
161 local function new_timetable ( year , month , day , hour , min , sec , yday , wday )
162         return cast_timetable {
163                 year  = year ;
164                 month = month ;
165                 day   = day ;
166                 hour  = hour ;
167                 min   = min ;
168                 sec   = sec ;
169                 yday  = yday ;
170                 wday  = wday ;
171         }
172 end
173
174 function timetable_methods:clone ( )
175         return new_timetable ( self:unpack ( ) )
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         day_of_year = day_of_year ;
184         day_of_week = day_of_week ;
185         normalise = normalise ;
186         timestamp = timestamp ;
187
188         new = new_timetable ;
189         new_from_timestamp = new_from_timestamp ;
190         cast = cast_timetable ;
191         timetable_mt = timetable_mt ;
192 }