001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright © 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.pm.calendar;
051:
052: import java.io.Serializable;
053: import java.util.ArrayList;
054: import java.util.Arrays;
055: import java.util.Calendar;
056: import java.util.Date;
057: import java.util.GregorianCalendar;
058: import java.util.List;
059:
060: import com.projity.configuration.Settings;
061: import com.projity.strings.Messages;
062: import com.projity.util.DateTime;
063:
064: /**
065: *
066: */
067: public class WorkingHours implements Cloneable, Serializable {
068: static final long serialVersionUID = 83888849333431L;
069: //private static Log log = LogFactory.getLog(WorkingHours.class);
070: private static long MS_PER_MINUTE = 60000L;
071: /**
072: * milliseconds of work time in the day
073: */
074: long duration = 0;
075:
076: WorkRange workRange[] = new WorkRange[Settings.CALENDAR_INTERVALS];
077: private static GregorianCalendar helper = DateTime
078: .calendarInstance();;
079:
080: public Object clone() {
081: WorkingHours newOne = new WorkingHours();
082: for (int i = 0; i < workRange.length; i++) {
083: if (workRange[i] == null)
084: newOne.workRange[i] = null;
085: else
086: newOne.workRange[i] = (WorkRange) workRange[i].clone();
087: }
088: newOne.duration = duration;
089: return newOne;
090: }
091:
092: /** Create a new working hours by intersecting this one's ranges with another's. Not that it's possible that the result has
093: * up to twice as many ranges as normal in a worst case
094: * @param other
095: * @return
096: */
097: WorkingHours intersectWith(WorkingHours other) {
098: int this Index = 0;
099: int otherIndex = 0;
100:
101: long start;
102: long end;
103: WorkRange this Range;
104: WorkRange otherRange;
105: ArrayList list = new ArrayList();
106: for (;;) {
107: // check boundary conditions. if one of the working hours is exhausted, then no more intersection
108: if (this Index == workRange.length)
109: break;
110: if (otherIndex == other.workRange.length)
111: break;
112:
113: this Range = workRange[this Index];
114: otherRange = other.workRange[otherIndex];
115: if (this Range == null || otherRange == null)
116: break;
117:
118: // the start is always the maximum of the current ranges
119: start = Math.max(this Range.getStart(), otherRange
120: .getStart());
121:
122: // the end is the minimum of the current ranges
123: if (this Range.getEnd() < otherRange.getEnd()) {
124: end = this Range.getEnd();
125: this Index++;
126: } else {
127: end = otherRange.getEnd();
128: otherIndex++;
129: }
130: if (end > start) //if the range is not degenerate, then there is an overlap
131: try {
132: list.add(new WorkRange(start, end));
133: } catch (WorkRangeException e) {
134: // this should never happen
135: e.printStackTrace();
136: }
137: }
138: // make a new working hours and use the work ranges that were generated
139: WorkingHours result = new WorkingHours();
140: result.workRange = new WorkRange[list.size()];
141: list.toArray(result.workRange);
142: result.initialize();
143: return result;
144:
145: }
146:
147: synchronized private static long getHoursAndMinutes(Date date) {
148: helper.setTime(date);
149: // the date needs to be normalized for GMT. because can wrap around, use modulus
150: long minutes = 60L
151: * (24 + helper.get(GregorianCalendar.HOUR_OF_DAY))
152: + helper.get(GregorianCalendar.MINUTE)
153: - date.getTimezoneOffset();
154: minutes = minutes % (24 * 60);
155: return 60000L * minutes;
156: }
157:
158: synchronized public static long hourTime(int hour) {
159: helper.setTimeInMillis(0);
160: helper.set(GregorianCalendar.HOUR_OF_DAY, hour);
161: return helper.getTimeInMillis();
162: }
163:
164: /**
165: * Used by importing, not the dialog box
166: * @param number
167: * @param start
168: * @param end
169: * @return
170: */
171: public boolean setInterval(int number, Date start, Date end) {
172: if (start == null || end == null)
173: return false;
174:
175: try {
176: setInterval(number, getHoursAndMinutes(start),
177: getHoursAndMinutes(end));
178: return true;
179: } catch (WorkRangeException e) {
180: // TODO Auto-generated catch block
181: e.printStackTrace();
182: return false;
183: }
184:
185: }
186:
187: /**
188: * Set an interval. Start and end must be on 1/1/70 and must have their hours set using a GregorianCalendar
189: * to avoid daylight savings issues.
190: * @param number - Range number 0 up to Settings.CALENDAR_INTERVALS -1. Currently range is 0-4
191: * @param start - start time. A value of -1 signifies that this range is null
192: * @param end - end time. If end time is 0, it is treated as midnight the next day
193: * @throws WorkRangeException
194: */
195: public void setInterval(int number, long start, long end)
196: throws WorkRangeException {
197: if (start == -1 || end == -1)
198: workRange[number] = null;
199: else {
200: GregorianCalendar cal = DateTime.calendarInstance();
201: cal.setTimeInMillis(end);
202: if (cal.get(GregorianCalendar.HOUR_OF_DAY) == 0
203: && cal.get(GregorianCalendar.MINUTE) == 0) { // test for midnight end next day
204: cal.add(GregorianCalendar.HOUR_OF_DAY, 24);
205: end = cal.getTimeInMillis();
206: }
207: workRange[number] = new WorkRange(start, end);
208: }
209:
210: initialize();
211: }
212:
213: public WorkRange getInterval(int number) {
214: return workRange[number];
215: }
216:
217: public List getIntervals() {
218: return Arrays.asList(workRange);
219: }
220:
221: /**
222: *
223: */
224: public WorkingHours() {
225: super ();
226: for (int i = 0; i < workRange.length; i++)
227: workRange[i] = null;
228: }
229:
230: /**
231: * Validate that the work ranges do not overlap
232: * @param range
233: * @return
234: */
235: void validate() throws WorkRangeException {
236: boolean foundNull = false;
237:
238: // check for gaps first
239: for (int i = 0; i < workRange.length - 1; i++) {
240: if (workRange[i] == null) {
241: foundNull = true;
242: } else {
243: if (foundNull)
244: throw new WorkRangeException(
245: Messages
246: .getString("WorkRangeException.RangeIncomplete"));
247: }
248:
249: }
250: for (int i = 0; i < workRange.length - 1; i++) {
251: for (int j = i + 1; j < workRange.length; j++) {
252: if (workRange[i] != null
253: && !workRange[i].isBefore(workRange[j]))
254: throw new WorkRangeException(
255: Messages
256: .getString("WorkRangeException.RangesMustBeOrdered"));
257: }
258: }
259: initialize();
260: }
261:
262: /**
263: * @return Returns the workTime.
264: */
265: public long getDuration() {
266: return duration;
267: }
268:
269: void initialize() {
270: duration = 0;
271: for (int i = 0; i < workRange.length; i++) {
272: if (workRange[i] != null)
273: duration += workRange[i].calcWorkingHours();
274: }
275: }
276:
277: public void setNonWorking() {
278: duration = 0;
279: }
280:
281: /**
282: * Calculates the time of day when there is still x time left to do
283: * @param duration The x time of work left after the return value
284: * @return Time of day
285: */
286: public long calcTimeAtRemainingWork(long duration) {
287: long work = 0;
288: for (int i = workRange.length - 1; i >= 0; i--) {
289: if (workRange[i] != null) {
290: work += workRange[i].calcWorkingHours();
291: if (work >= duration) {
292: return (workRange[i].getStart() + (work - duration));
293: }
294: }
295: }
296: // log.error("calcTimeAtRemainingWork didn't finish");
297: return -1; // error, return day start
298: }
299:
300: /**
301: * @param duration
302: * @return
303: */
304: public long calcTimeAtWork(long duration) {
305: long work = 0;
306: for (int i = 0; i < workRange.length; i++) {
307: if (workRange[i] != null) {
308: work += workRange[i].calcWorkingHours();
309: if (work >= duration) {
310: return (workRange[i].getEnd() - (work - duration));
311: }
312: }
313: }
314: // log.error("calcTimeAtWork didn't finish");
315: return -1;//24L*60*60*1000; // error return midnight next day
316: }
317:
318: /**
319: * Calculate how much work time is remaining after the given time.
320: * @param time
321: * @return
322: */
323: public long calcWorkTimeAfter(long time) {
324: long work = 0;
325: for (int i = 0; i < workRange.length; i++) {
326: if (workRange[i] != null) {
327: if (workRange[i].getEnd() > time)
328: work += (workRange[i].getEnd() - Math.max(time,
329: workRange[i].getStart()));
330: }
331: }
332: return work;
333: }
334:
335: /**
336: * @param date
337: * @return
338: */
339: public long calcWorkTimeBefore(long time) {
340: return duration - calcWorkTimeAfter(time);
341: }
342:
343: public long calcWorkTime(long time, boolean after) {
344: return after ? calcWorkTimeAfter(time)
345: : calcWorkTimeBefore(time);
346: }
347:
348: public String toString() {
349: String result = "WorkingHours\n";
350: for (int i = 0; i < workRange.length; i++) {
351: if (workRange[i] != null) {
352: if (result.length() != 0)
353: result += "\n";
354: result += "Range " + i + " - " + workRange[i];
355: }
356: }
357: return result;
358: }
359:
360: private static WorkingHours defaultWorkingHours = null;
361:
362: public static WorkingHours getDefault() {
363: if (defaultWorkingHours == null) {
364: defaultWorkingHours = new WorkingHours();
365: try {
366: Calendar cal = DateTime.calendarInstance();
367: cal.setTimeInMillis(0);
368: cal.set(Calendar.HOUR_OF_DAY, 8);
369: long start = cal.getTimeInMillis();
370: cal.set(Calendar.HOUR_OF_DAY, 12);
371: long end = cal.getTimeInMillis();
372:
373: defaultWorkingHours.setInterval(0, start, end);
374:
375: cal.set(Calendar.HOUR_OF_DAY, 13);
376: start = cal.getTimeInMillis();
377: cal.set(Calendar.HOUR_OF_DAY, 17);
378: end = cal.getTimeInMillis();
379: defaultWorkingHours.setInterval(1, start, end);
380:
381: } catch (WorkRangeException e) {
382: e.printStackTrace();
383: }
384: }
385: return defaultWorkingHours;
386: }
387:
388: private static WorkingHours nonStopWorkingHours = null;
389:
390: public static WorkingHours getNonStop() {
391: if (nonStopWorkingHours == null) {
392: nonStopWorkingHours = new WorkingHours();
393: try {
394: Calendar cal = DateTime.calendarInstance();
395: cal.setTimeInMillis(0);
396: cal.set(Calendar.HOUR_OF_DAY, 0);
397: long start = cal.getTimeInMillis();
398:
399: nonStopWorkingHours.setInterval(0, start, start);
400:
401: } catch (WorkRangeException e) {
402: e.printStackTrace();
403: }
404: }
405: return nonStopWorkingHours;
406: }
407:
408: public boolean equals(Object arg0) {
409: if (!(arg0 instanceof WorkingHours))
410: return false;
411: if (this == arg0)
412: return true;
413: WorkingHours to = (WorkingHours) arg0;
414: for (int i = 0; i < workRange.length; i++) {
415: if (workRange[i] != null) {
416: if (!workRange[i].equals(to.workRange[i]))
417: return false;
418: } else if (to.workRange[i] != null) {
419: return false;
420: }
421: }
422: return true;
423: }
424:
425: public boolean hasHours() {
426: duration = 0;
427: for (int i = 0; i < workRange.length; i++) {
428: if (workRange[i] != null)
429: return true;
430: }
431: return false;
432: }
433: }
|