1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.promotego.logic.storehours;
20
21 import java.util.ArrayList;
22 import java.util.Calendar;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32 import java.util.TimeZone;
33
34 import org.apache.commons.lang.StringUtils;
35 import org.apache.commons.lang.math.IntRange;
36
37 /***
38 * A representation of times a store is open and closed.
39 *
40 * The StoreHours object is mapped to and from a simple language for
41 * purposes of mapping the object to a persistent store. The format
42 * is as follows:
43 *
44 * MON 8-19;TUE-FRI 7-21;SAT-SUN 10-19
45 *
46 * @author alf
47 */
48 public class StoreHours
49 {
50 private static Map<String,Integer> s_dayMapping;
51 private static int SECONDS_IN_DAY = 24*3600;
52 static int SECONDS_IN_WEEK = 7*SECONDS_IN_DAY;
53
54 private WeeklyHours m_hours;
55 private Set<HourSpecification> m_hourSpecs;
56
57 static {
58 s_dayMapping = new HashMap<String,Integer>();
59 s_dayMapping.put("SUN", 0);
60 s_dayMapping.put("MON", 1);
61 s_dayMapping.put("TUE", 2);
62 s_dayMapping.put("WED", 3);
63 s_dayMapping.put("THU", 4);
64 s_dayMapping.put("FRI", 5);
65 s_dayMapping.put("SAT", 6);
66 }
67
68 public StoreHours()
69 {
70 m_hours = new WeeklyHours();
71 }
72
73 public StoreHours(String hourString)
74 {
75 this();
76 setHours(hourString);
77 }
78
79 public StoreHours(Collection<HourSpecification> hourSpecs)
80 {
81 this();
82 m_hourSpecs = new HashSet<HourSpecification>(hourSpecs);
83 }
84
85 public boolean isOpen(Date startTime, Date endTime)
86 {
87 if (endTime.before(startTime))
88 {
89 throw new IllegalArgumentException("endTime (" + endTime + " ) may not occur before startTime (" + startTime + ").");
90 }
91
92 if (endTime.getTime() - startTime.getTime() > SECONDS_IN_DAY*1000)
93 {
94 throw new IllegalArgumentException("Date ranges greater than one day not supported (startTime: " + startTime
95 + " endTime: " + endTime + ")");
96 }
97
98
99 IntRange theRange = getSecondsRange(startTime, endTime);
100
101
102 return m_hours.isOpen(theRange);
103 }
104
105 /***
106 * @param startTime
107 * @param endTime
108 * @return
109 */
110 private IntRange getSecondsRange(Date startTime, Date endTime)
111 {
112 if (startTime.after(endTime))
113 {
114 throw new IllegalArgumentException("startTime (" + startTime + ") may not occur after endTime (" + endTime + ")");
115 }
116
117 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
118 int startSeconds = getSecondsInWeek(startTime, calendar);
119 int endSeconds = getSecondsInWeek(endTime, calendar);
120
121 if (endSeconds < startSeconds)
122 {
123 endSeconds += SECONDS_IN_WEEK;
124 }
125
126 return new IntRange(startSeconds, endSeconds);
127 }
128
129 /***
130 * @param time
131 * @param calendar
132 */
133 private int getSecondsInWeek(Date time, Calendar calendar)
134 {
135 calendar.setTime(time);
136
137
138
139
140 int day = (calendar.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY + 7)%7;
141
142
143 int hours = calendar.get(Calendar.HOUR_OF_DAY);
144 int minutes = calendar.get(Calendar.MINUTE);
145
146 return day*SECONDS_IN_DAY + 3600*hours + 60*minutes;
147 }
148
149 @Override
150 public String toString()
151 {
152 StringBuilder retval = new StringBuilder();
153 List<HourSpecification> theList = new ArrayList<HourSpecification>(m_hourSpecs);
154 Collections.sort(theList, new HourSpecificationComparator());
155 for (HourSpecification thisSpec : theList)
156 {
157 if (retval.length() != 0)
158 {
159 retval.append(";");
160 }
161
162 retval.append(thisSpec.toString());
163 }
164
165 return retval.toString();
166 }
167
168 public void setHours(String hourString)
169 {
170 m_hours = new WeeklyHours();
171 m_hourSpecs = new HashSet<HourSpecification>();
172
173 StringTokenizer strtok = new StringTokenizer(hourString, ";");
174
175 while (strtok.hasMoreTokens())
176 {
177
178 String thisToken = StringUtils.strip(strtok.nextToken());
179
180 HourSpecification theSpec = new HourSpecification(thisToken);
181
182 m_hourSpecs.add(theSpec);
183
184 for (Day day : theSpec.getDays())
185 {
186 for (IntRange range : theSpec.getSecondRanges())
187 {
188 m_hours.addRange(new IntRange(range.getMinimumInteger()+day.ordinal()*SECONDS_IN_DAY,
189 range.getMaximumInteger()+day.ordinal()*SECONDS_IN_DAY));
190 }
191 }
192 }
193
194 coalesceHourSpecs();
195 }
196
197 private void coalesceHourSpecs()
198 {
199 List<HourSpecification> theList = new ArrayList<HourSpecification>(m_hourSpecs);
200
201
202 for (int i=0; i<theList.size(); i++)
203 {
204 for (int j=i+1; j<theList.size(); j++)
205 {
206 HourSpecification spec1 = theList.get(i);
207 HourSpecification spec2 = theList.get(j);
208
209 if (spec1.sameDays(spec2))
210 {
211 assert j > i : "j must be greater than i";
212
213
214
215 theList.remove(j);
216 theList.remove(i);
217 theList.add(i, spec1.mergeWith(spec2));
218
219
220
221 j--;
222 }
223 }
224 }
225
226
227 for (int i=0; i<theList.size(); i++)
228 {
229 for (int j=i+1; j<theList.size(); j++)
230 {
231 HourSpecification spec1 = theList.get(i);
232 HourSpecification spec2 = theList.get(j);
233
234 if (spec1.sameHours(spec2))
235 {
236 assert j > i : "j must be greater than i";
237
238
239
240 theList.remove(j);
241 theList.remove(i);
242 theList.add(i, spec1.mergeWith(spec2));
243
244
245
246 j--;
247 }
248 }
249 }
250
251 Set<HourSpecification> newSet = new HashSet<HourSpecification>();
252 newSet.addAll(theList);
253 m_hourSpecs = newSet;
254 }
255
256 public Set<HourSpecification> getHourSpecifications()
257 {
258 return new HashSet<HourSpecification>(m_hourSpecs);
259 }
260
261 @Override
262 public boolean equals(Object anotherObject)
263 {
264 if (anotherObject == null || !(anotherObject instanceof StoreHours))
265 {
266 return false;
267 }
268
269 StoreHours otherHours = (StoreHours)anotherObject;
270
271 return m_hours.equals(otherHours.m_hours);
272 }
273
274 @Override
275 public int hashCode()
276 {
277 return m_hours.hashCode();
278 }
279 }