]> 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: Move `asctime` from timetable.lua to strftime.lua. It doesn't deserve to be...
[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 strftime = require "luatz.strftime".strftime
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 }