time_calculation.rb 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. module TimeCalculation
  2. =begin
  3. put working hours matrix and timezone in function, returns UTC working hours matrix
  4. working_hours_martix = TimeCalculation.working_hours('2013-10-27 20:00:15', working_hours_matrix, 'Europe/Berlin')
  5. working_hours_martix = {
  6. :Mon => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
  7. :Tue => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
  8. :Wed => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
  9. :Thu => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
  10. :Fri => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
  11. :Sat => [],
  12. :Sun => [],
  13. }
  14. =end
  15. def self.working_hours(start_time, config, timezone)
  16. time_diff = 0
  17. if timezone
  18. begin
  19. time_diff = Time.parse(start_time.to_s).in_time_zone(timezone).utc_offset
  20. rescue Exception => e
  21. puts "ERROR: Can't fine tomezone #{timezone}"
  22. puts e.inspect
  23. puts e.backtrace
  24. end
  25. end
  26. beginning_of_workday = Time.parse("1977-10-27 #{config['beginning_of_workday']}")
  27. end_of_workday = Time.parse("1977-10-27 #{config['end_of_workday']}") - 3600
  28. config_ok = false
  29. working_hours = {}
  30. [:Mon, :Tue, :Wed, :Thu, :Fri, :Sat, :Sun].each {|day|
  31. working_hours[day] = []
  32. if config[day.to_s] == true || config[day.to_s] == day.to_s
  33. config_ok = true
  34. (0..23).each {|hour|
  35. time = Time.parse("1977-10-27 #{hour}:00:00")
  36. if time >= beginning_of_workday && time <= end_of_workday
  37. working_hours[day].push true
  38. else
  39. working_hours[day].push nil
  40. end
  41. }
  42. end
  43. }
  44. if !config_ok
  45. raise "sla config is invalid! " + config.inspect
  46. end
  47. # shift working hours / if needed
  48. if time_diff && time_diff != 0
  49. hours_to_shift = (time_diff / 3600 ).round
  50. move_items = {
  51. :Mon => [],
  52. :Tue => [],
  53. :Wed => [],
  54. :Thu => [],
  55. :Fri => [],
  56. :Sat => [],
  57. :Sun => [],
  58. }
  59. (1..hours_to_shift).each {|count|
  60. working_hours.each {|day, value|
  61. if working_hours[day]
  62. to_move = working_hours[day].shift
  63. if day == :Mon
  64. move_items[:Tue].push to_move
  65. elsif day == :Tue
  66. move_items[:Wed].push to_move
  67. elsif day == :Wed
  68. move_items[:Thu].push to_move
  69. elsif day == :Thu
  70. move_items[:Fri].push to_move
  71. elsif day == :Fri
  72. move_items[:Sat].push to_move
  73. elsif day == :Sat
  74. move_items[:Sun].push to_move
  75. elsif day == :Sun
  76. move_items[:Mon].push to_move
  77. end
  78. end
  79. }
  80. }
  81. move_items.each {|day, value|
  82. value.each {|item|
  83. working_hours[day].push item
  84. }
  85. }
  86. end
  87. return working_hours
  88. end
  89. =begin
  90. returns business hours in minutes between to dates
  91. business_hours_in_min = Time.Calculation.business_time_diff(
  92. '2013-10-27 14:00:15',
  93. '2013-10-27 18:10:15',
  94. working_hours_martix,
  95. 'Europe/Berlin',
  96. )
  97. =end
  98. def self.business_time_diff(start_time, end_time, config = nil, timezone = '')
  99. if start_time.class == String
  100. start_time = Time.parse( start_time.to_s + 'UTC' )
  101. end
  102. if end_time.class == String
  103. end_time = Time.parse( end_time.to_s + 'UTC' )
  104. end
  105. # if no config is given, just return calculation directly
  106. if !config
  107. return ((end_time - start_time) / 60 ).round
  108. end
  109. working_hours = self.working_hours(start_time, config, timezone)
  110. week_day_map = {
  111. 1 => :Mon,
  112. 2 => :Tue,
  113. 3 => :Wed,
  114. 4 => :Thu,
  115. 5 => :Fri,
  116. 6 => :Sat,
  117. 0 => :Sun,
  118. }
  119. count = 0
  120. calculation = true
  121. first_loop = true
  122. while calculation do
  123. week_day = start_time.wday
  124. day = start_time.day
  125. month = start_time.month
  126. year = start_time.year
  127. hour = start_time.hour
  128. # check if it's vacation day
  129. if config
  130. if config['holidays']
  131. if config['holidays'].include?("#{year}-#{month}-#{day}")
  132. # jump to next day
  133. start_time = start_time.beginning_of_day + 86400
  134. next
  135. end
  136. end
  137. end
  138. # check if it's countable day
  139. if working_hours[ week_day_map[week_day] ].empty?
  140. # jump to next day
  141. start_time = start_time.beginning_of_day + 86400
  142. next
  143. end
  144. # fillup to first full hour
  145. if first_loop
  146. diff = end_time - start_time
  147. if diff > 59 * 60
  148. diff = start_time - start_time.beginning_of_hour
  149. end
  150. start_time += diff
  151. # check if it's countable hour
  152. if working_hours[ week_day_map[week_day] ][ hour ]
  153. count += diff
  154. end
  155. end
  156. first_loop = false
  157. # loop to next hour
  158. (hour..23).each { |next_hour|
  159. # check if end time is lower
  160. if start_time >= end_time
  161. calculation = false
  162. break
  163. end
  164. # check if end_time is within this hour
  165. diff = end_time - start_time
  166. if diff > 59 * 60
  167. diff = 3600
  168. end
  169. # keep it in current day
  170. if next_hour == 23
  171. start_time += diff-1
  172. else
  173. start_time += diff
  174. end
  175. # check if it's business hour and count
  176. if working_hours[ week_day_map[week_day] ][ next_hour ]
  177. count += diff
  178. end
  179. }
  180. # loop to next day
  181. start_time = start_time.beginning_of_day + 86400
  182. end
  183. diff = count / 60
  184. diff.round
  185. end
  186. =begin
  187. returns destination date of start time plus X minutes
  188. dest_time = Time.Calculation.dest_time(
  189. '2013-10-27 14:00:15',
  190. 120,
  191. working_hours_martix,
  192. 'Europe/Berlin',
  193. )
  194. =end
  195. def self.dest_time(start_time, diff_in_min, config = nil, timezone = '')
  196. if start_time.class == String
  197. start_time = Time.parse( start_time.to_s + ' UTC' )
  198. end
  199. return start_time if diff_in_min == 0
  200. # if no config is given, just return calculation directly
  201. if !config
  202. return start_time + (diff_in_min * 60)
  203. end
  204. # loop
  205. working_hours = self.working_hours(start_time, config, timezone)
  206. week_day_map = {
  207. 1 => :Mon,
  208. 2 => :Tue,
  209. 3 => :Wed,
  210. 4 => :Thu,
  211. 5 => :Fri,
  212. 6 => :Sat,
  213. 0 => :Sun,
  214. }
  215. count = diff_in_min * 60
  216. calculation = true
  217. first_loop = true
  218. while calculation do
  219. week_day = start_time.wday
  220. day = start_time.day
  221. month = start_time.month
  222. year = start_time.year
  223. hour = start_time.hour
  224. #puts "start outer loop #{start_time}-#{week_day}-#{year}-#{month}-#{day}-#{hour}|c#{count}"
  225. # check if it's vacation day
  226. if config
  227. if config['holidays']
  228. if config['holidays'].include?("#{year}-#{month}-#{day}")
  229. # jump to next day
  230. start_time = start_time.beginning_of_day + 86400
  231. next
  232. end
  233. end
  234. end
  235. # check if it's countable day
  236. if working_hours[ week_day_map[week_day] ].empty?
  237. # jump to next day
  238. start_time = start_time.beginning_of_day + 86400
  239. next
  240. end
  241. # fillup to first full hour
  242. if first_loop
  243. # get rest of this hour if diff_in_min in lower the one hour
  244. diff_to_count = 3600
  245. if diff_to_count > (diff_in_min * 60)
  246. diff_to_count = diff_in_min * 60
  247. end
  248. diff = diff_to_count - (start_time - start_time.beginning_of_hour)
  249. start_time += diff
  250. # check if it's countable hour
  251. if working_hours[ week_day_map[week_day] ][ hour ]
  252. count -= diff
  253. end
  254. # start on next hour of we moved to next
  255. if diff != 0
  256. hour += 1
  257. end
  258. end
  259. first_loop = false
  260. # loop to next hour
  261. (hour..23).each { |next_hour|
  262. diff = 3600
  263. # check if count positiv
  264. if count <= 0
  265. calculation = false
  266. break
  267. end
  268. # check if it's business hour and count
  269. if working_hours[ week_day_map[week_day] ][ next_hour ]
  270. # check if count is within this hour
  271. if count > 59 * 60
  272. diff = 3600
  273. else
  274. diff = count
  275. end
  276. count -= diff
  277. end
  278. # keep it in current day
  279. if next_hour == 23
  280. start_time += diff-1
  281. else
  282. start_time += diff
  283. end
  284. }
  285. # check if count positiv
  286. if count <= 0
  287. calculation = false
  288. break
  289. end
  290. # loop to next day
  291. start_time = start_time.beginning_of_day + 86400
  292. end
  293. return start_time
  294. end
  295. end