package ch.elexis.agenda.series; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import ch.elexis.actions.Activator; import ch.elexis.agenda.data.IPlannable; import ch.elexis.agenda.data.Termin; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.events.ElexisEventDispatcher; import ch.elexis.data.Kontakt; import ch.elexis.data.Query; import ch.rgw.tools.TimeSpan; import ch.rgw.tools.TimeTool; public class SerienTermin { //@formatter:off /** * configuration string syntax * * BEGINTIME,ENDTIME;SERIES_TYPE;[SERIES_PATTERN];BEGINDATE;[ENDING_TYPE];[ENDING_PATTERN] * * [SERIES_TYPE] * D aily * W eekly * M onthly * Y early * * [SERIES_PATTERN] * daily "" * weekly Number_of_weeks_between, day { day } . * monthly day_of_month * yearly ddMM * * [ENDING_TYPE] * O ends after n occurences -> requires number of occurences * D ends on date -> requires date * * [ENDING_PATTERN] * if EA: number * if EO: date */ //@formatter:on public static DateFormat dateFormat = new SimpleDateFormat("ddMMyyyy"); public static DateFormat timeFormat = new SimpleDateFormat("HHmm"); public static DecimalFormat decimalFormat = new DecimalFormat("00"); private Date beginTime; private Date endTime; private Date seriesStartDate; private Date endsOnDate; private String endsAfterNDates; private SeriesType seriesType; private EndingType endingType; private String seriesPatternString; private String endingPatternString; private Kontakt contact; private String freeText; // if contact == null may contain freetext private String reason; // persistence information private String groupId; private Termin rootTermin; // ------------------ public final static long SECOND_MILLIS = 1000; public final static long MINUTE_MILLIS = SECOND_MILLIS * 60; public final static long HOUR_MILLIS = MINUTE_MILLIS * 60; public final static long DAY_MILLIS = HOUR_MILLIS * 24; public final static long YEAR_MILLIS = DAY_MILLIS * 365; public SerienTermin(){ beginTime = new Date(); Calendar endTimeCalendar = Calendar.getInstance(); endTimeCalendar.add(Calendar.MINUTE, 30); endTime = endTimeCalendar.getTime(); Calendar startDateMidnight = new GregorianCalendar(); // reset hour, minutes, seconds and millis startDateMidnight.set(Calendar.HOUR_OF_DAY, 0); startDateMidnight.set(Calendar.MINUTE, 0); startDateMidnight.set(Calendar.SECOND, 0); startDateMidnight.set(Calendar.MILLISECOND, 0); seriesStartDate = startDateMidnight.getTime(); seriesType = SeriesType.WEEKLY; seriesPatternString = "1," + Calendar.MONDAY; endingType = EndingType.ON_SPECIFIC_DATE; Calendar nextWeek = Calendar.getInstance(); nextWeek.add(Calendar.DAY_OF_YEAR, 7); endsOnDate = nextWeek.getTime(); contact = ElexisEventDispatcher.getSelectedPatient(); if (contact == null) freeText = ""; } public SerienTermin(IPlannable pl){ Termin t = (Termin) pl; groupId = t.get(Termin.FLD_LINKGROUP); rootTermin = Termin.load(groupId); contact = rootTermin.getKontakt(); if (contact == null) setFreeText(rootTermin.get(Termin.FLD_PATIENT)); reason = rootTermin.getGrund(); parseSerienTerminConfigurationString(rootTermin.get(Termin.FLD_EXTENSION)); } /** * Initialize a {@link SerienTermin} according to a <i>serientermin configuration string</i> * such as for example <code>1200,1230;W,1,3|4;04042008,EA,10</code> for the syntax see the * documentation in. the {@link SerienTermin} class <br> * <br> * Use with care, malformed strings will not be treated defensively! * * * @param serienTerminConfigurationString */ private void parseSerienTerminConfigurationString(String serienTerminConfigurationString){ String[] terms = serienTerminConfigurationString.split(";"); String[] termin = terms[0].split(","); try { beginTime = timeFormat.parse(termin[0]); endTime = timeFormat.parse(termin[1]); seriesStartDate = dateFormat.parse(terms[3]); } catch (ParseException e) { // TODO log e.printStackTrace(); } char seriesTypeCharacter = terms[1].toUpperCase().charAt(0); setSeriesType(SeriesType.getForCharacter(seriesTypeCharacter)); seriesPatternString = terms[2]; char endingTypeCharacter = terms[4].toUpperCase().charAt(0); endingType = EndingType.getForCharacter(endingTypeCharacter); endingPatternString = terms[5]; switch (endingType) { case ON_SPECIFIC_DATE: try { endsOnDate = dateFormat.parse(endingPatternString); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case AFTER_N_OCCURENCES: endsAfterNDates = endingPatternString; break; default: break; } } /** * persist the recurring date into the database; this creates a series of {@link Termin} entries * according to the pattern */ public void persist(){ if (groupId != null) delete(false); createRootDate(); createSubSequentDates(); } /** * Deletes the entire {@link SerienTermin} * * @param askForConfirmation */ public void delete(boolean askForConfirmation){ rootTermin.delete(askForConfirmation); } private void createSubSequentDates(){ TimeTool dateIncrementer = rootTermin.getStartTime(); int occurences = 0; TimeTool endingDate = null; if (endingType.equals(EndingType.AFTER_N_OCCURENCES)) { occurences = (Integer.parseInt(endsAfterNDates) - 1); } else { endingDate = new TimeTool(endsOnDate); } switch (seriesType) { case DAILY: if (endingType.equals(EndingType.ON_SPECIFIC_DATE)) { occurences = dateIncrementer.daysTo(endingDate) + 1; } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.DAY_OF_YEAR, 1); writeSubsequentDateEntry(dateIncrementer); } break; case WEEKLY: String[] separatedSeriesPattern = getSeriesPatternString().split(","); int weekStepSize = Integer.parseInt(separatedSeriesPattern[0]); System.out.println("week step size =" + weekStepSize); // handle week 1 for (int i = 1; i < separatedSeriesPattern[1].length(); i++) { Calendar cal = Calendar.getInstance(); cal.setTime(dateIncrementer.getTime()); int dayValue = Integer.parseInt(separatedSeriesPattern[1].charAt(i) + ""); cal.set(Calendar.DAY_OF_WEEK, dayValue); writeSubsequentDateEntry(new TimeTool(cal.getTime())); } if (endingType.equals(EndingType.ON_SPECIFIC_DATE)) { long milisecondsDiff = 0; if (endingDate != null) { milisecondsDiff = endingDate.getTime().getTime() - dateIncrementer.getTime().getTime(); } int days = (int) (milisecondsDiff / (1000 * 60 * 60 * 24)); int weeks = days / 7; occurences = weeks / weekStepSize; } // handle subsequent weeks for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.WEEK_OF_YEAR, weekStepSize); for (int j = 0; j < separatedSeriesPattern[1].length(); j++) { Calendar cal = Calendar.getInstance(); cal.setTime(dateIncrementer.getTime()); int dayValue = Integer.parseInt(separatedSeriesPattern[1].charAt(j) + ""); cal.set(Calendar.DAY_OF_WEEK, dayValue); writeSubsequentDateEntry(new TimeTool(cal.getTime())); } } break; case MONTHLY: if (endingType.equals(EndingType.ON_SPECIFIC_DATE) && endingDate != null ) { occurences = (endingDate.get(Calendar.YEAR) - dateIncrementer.get(Calendar.YEAR)) * 12 + (endingDate.get(Calendar.MONTH) - dateIncrementer.get(Calendar.MONTH)) + (endingDate.get(Calendar.DAY_OF_MONTH) >= dateIncrementer .get(Calendar.DAY_OF_MONTH) ? 0 : -1); } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.MONTH, 1); writeSubsequentDateEntry(dateIncrementer); } break; case YEARLY: if (endingType.equals(EndingType.ON_SPECIFIC_DATE) && endingDate != null ) { int monthOccurences = (endingDate.get(Calendar.YEAR) - dateIncrementer.get(Calendar.YEAR)) * 12 + (endingDate.get(Calendar.MONTH) - dateIncrementer.get(Calendar.MONTH)) + (endingDate.get(Calendar.DAY_OF_MONTH) >= dateIncrementer .get(Calendar.DAY_OF_MONTH) ? 0 : -1); occurences = (monthOccurences / 12); } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.YEAR, 1); writeSubsequentDateEntry(dateIncrementer); } break; default: break; } } private void writeSubsequentDateEntry(TimeTool dateIncrementer){ TimeTool endTime = new TimeTool(dateIncrementer); endTime.addMinutes(getAppointmentDuration()); TimeSpan ts = new TimeSpan(dateIncrementer, endTime); Termin t = new Termin(Activator.getDefault().getActResource(), ts, "series"); t.set(Termin.FLD_LINKGROUP, groupId); System.out.println("writing subsequent date entry " + endTime.dump()); } private void createRootDate(){ Calendar cal = Calendar.getInstance(); cal.clear(); cal.setTime(seriesStartDate); cal.add(Calendar.HOUR, beginTime.getHours()); cal.add(Calendar.MINUTE, beginTime.getMinutes()); TimeTool startTime = getRootTerminStartTime(cal); TimeTool endTime = new TimeTool(startTime); endTime.addMinutes(getAppointmentDuration()); TimeSpan ts = new TimeSpan(startTime, endTime); rootTermin = new Termin(Activator.getDefault().getActResource(), ts, "series"); groupId = rootTermin.getId(); rootTermin.set(Termin.FLD_LINKGROUP, groupId); if (contact != null) { rootTermin.setKontakt(contact); } else { rootTermin.set(Termin.FLD_PATIENT, getFreeText()); } rootTermin.setGrund(reason); rootTermin.set("ErstelltVon", CoreHub.actUser.getLabel()); rootTermin.set(Termin.FLD_EXTENSION, this.toString()); } private TimeTool getRootTerminStartTime(Calendar cal){ TimeTool tt = new TimeTool(cal.getTime()); switch (seriesType) { case DAILY: return tt; case WEEKLY: Calendar cal2 = Calendar.getInstance(); cal2.setTime(cal.getTime()); int firstDay = Integer.parseInt(getSeriesPatternString().split(",")[1].charAt(0) + ""); cal2.set(Calendar.DAY_OF_WEEK, firstDay); TimeTool ret = new TimeTool(cal2.getTime()); return ret; case MONTHLY: int monthDay = Integer.parseInt(seriesPatternString); Calendar calendarMonth = Calendar.getInstance(); calendarMonth.clear(); calendarMonth.set(Calendar.YEAR, tt.get(TimeTool.YEAR)); if (tt.get(Calendar.DAY_OF_MONTH) <= monthDay) { calendarMonth.set(Calendar.MONTH, tt.get(Calendar.MONTH)); } else { calendarMonth.set(Calendar.MONTH, tt.get(Calendar.MONTH)); calendarMonth.add(Calendar.MONTH, 1); } calendarMonth.set(Calendar.DAY_OF_MONTH, monthDay); return new TimeTool(calendarMonth.getTime()); case YEARLY: Calendar targetCal = Calendar.getInstance(); targetCal.clear(); targetCal.set(Calendar.YEAR, tt.get(TimeTool.YEAR)); int day = Integer.parseInt(seriesPatternString.substring(0, 2)); int month = Integer.parseInt(seriesPatternString.substring(2, 4)); targetCal.set(Calendar.DAY_OF_MONTH, day); targetCal.set(Calendar.MONTH, month - 1); TimeTool target = new TimeTool(targetCal.getTime()); if (tt.isBefore(target)) return target; target.add(TimeTool.YEAR, 1); return target; } return tt; } public boolean collidesWithLockTimes(){ Calendar cal = Calendar.getInstance(); cal.clear(); cal.setTime(seriesStartDate); cal.add(Calendar.HOUR, beginTime.getHours()); cal.add(Calendar.MINUTE, beginTime.getMinutes()); TimeTool startTime = getRootTerminStartTime(cal); TimeTool endTime = new TimeTool(startTime); endTime.addMinutes(getAppointmentDuration()); TimeSpan ts = new TimeSpan(startTime, endTime); rootTermin = new Termin(Activator.getDefault().getActResource(), ts, "series"); TimeTool dateIncrementer = rootTermin.getStartTime(); List<TimeTool> seriesTimesList = getAllTimesOfSeries(dateIncrementer); String bereich = Activator.getDefault().getActResource(); for (TimeTool sTime : seriesTimesList) { TimeTool eTime = new TimeTool(sTime); eTime.addMinutes(getAppointmentDuration()); TimeSpan span = new TimeSpan(sTime, eTime); // get all appointments where type=locked and day=X and bereich=y Query<Termin> qbe = new Query<Termin>(Termin.class); qbe.add(Termin.FLD_TERMINTYP, Query.EQUALS, ch.elexis.agenda.Messages.Termin_range_locked); qbe.add(Termin.FLD_TAG, Query.EQUALS, sTime.toString(TimeTool.DATE_COMPACT)); qbe.add(Termin.FLD_BEREICH, Query.EQUALS, bereich); qbe.add(Termin.FLD_DELETED, Query.EQUALS, "0"); List<Termin> locks = qbe.execute(); for (Termin lockTermin : locks) { TimeSpan lockSpan = lockTermin.getTimeSpan(); if (lockSpan.overlap(span) != null) { rootTermin.delete(false); return true; } } } //clean up rootTermin.delete(false); return false; } private List<TimeTool> getAllTimesOfSeries(TimeTool dateIncrementer){ List<TimeTool> seriesTimesList = new ArrayList<TimeTool>(); //calculate occurrences int occurences = 0; TimeTool endingDate = null; if (endingType.equals(EndingType.AFTER_N_OCCURENCES)) { occurences = (Integer.parseInt(endsAfterNDates) - 1); } else { endingDate = new TimeTool(endsOnDate); } switch (seriesType) { case DAILY: if (endingType.equals(EndingType.ON_SPECIFIC_DATE)) { occurences = dateIncrementer.daysTo(endingDate) + 1; } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.DAY_OF_YEAR, 1); seriesTimesList.add(dateIncrementer); } break; case WEEKLY: String[] seriesPattern = getSeriesPatternString().split(","); int weekStepSize = Integer.parseInt(seriesPattern[0]); // handle start week for (int i = 1; i < seriesPattern[1].length(); i++) { Calendar calWeekOne = Calendar.getInstance(); calWeekOne.setTime(dateIncrementer.getTime()); int dayValue = Integer.parseInt(seriesPattern[1].charAt(i) + ""); calWeekOne.set(Calendar.DAY_OF_WEEK, dayValue); seriesTimesList.add(new TimeTool(calWeekOne.getTime())); } // calculate occurrences per week if (endingType.equals(EndingType.ON_SPECIFIC_DATE)) { long milisecondsDiff = 0; if (endingDate != null) { milisecondsDiff = endingDate.getTime().getTime() - dateIncrementer.getTime().getTime(); } int days = (int) (milisecondsDiff / (1000 * 60 * 60 * 24)); int weeks = days / 7; occurences = weeks / weekStepSize; } // handle subsequent weeks for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.WEEK_OF_YEAR, weekStepSize); for (int j = 0; j < seriesPattern[1].length(); j++) { Calendar calSub = Calendar.getInstance(); calSub.setTime(dateIncrementer.getTime()); int dayValue = Integer.parseInt(seriesPattern[1].charAt(j) + ""); calSub.set(Calendar.DAY_OF_WEEK, dayValue); seriesTimesList.add(new TimeTool(calSub.getTime())); } } break; case MONTHLY: //calculate occurrences per month if (endingType.equals(EndingType.ON_SPECIFIC_DATE) && endingDate != null) { occurences = (endingDate.get(Calendar.YEAR) - dateIncrementer.get(Calendar.YEAR)) * 12 + (endingDate.get(Calendar.MONTH) - dateIncrementer.get(Calendar.MONTH)) + (endingDate.get(Calendar.DAY_OF_MONTH) >= dateIncrementer .get(Calendar.DAY_OF_MONTH) ? 0 : -1); } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.MONTH, 1); seriesTimesList.add(dateIncrementer); } break; case YEARLY: //calculate occurrences per year if (endingType.equals(EndingType.ON_SPECIFIC_DATE) && endingDate != null ) { int monthOccurences = (endingDate.get(Calendar.YEAR) - dateIncrementer.get(Calendar.YEAR)) * 12 + (endingDate.get(Calendar.MONTH) - dateIncrementer.get(Calendar.MONTH)) + (endingDate.get(Calendar.DAY_OF_MONTH) >= dateIncrementer .get(Calendar.DAY_OF_MONTH) ? 0 : -1); occurences = (monthOccurences / 12); } for (int i = 0; i < occurences; i++) { dateIncrementer.add(Calendar.YEAR, 1); seriesTimesList.add(dateIncrementer); } break; default: break; } return seriesTimesList; } @Override public String toString(){ // BEGINTIME,ENDTIME;SERIES_TYPE;[SERIES_PATTERN];BEGINDATE;[ENDING_TYPE];[ENDING_PATTERN] StringBuilder sb = new StringBuilder(); try { sb.append(timeFormat.format(beginTime)); sb.append(","); sb.append(timeFormat.format(endTime)); sb.append(";"); sb.append(getSeriesType().getSeriesTypeCharacter()); sb.append(";"); sb.append(seriesPatternString); sb.append(";"); sb.append(dateFormat.format(seriesStartDate)); sb.append(";"); sb.append(endingType.getEndingTypeChar()); sb.append(";"); switch (getEndingType()) { case AFTER_N_OCCURENCES: sb.append(endsAfterNDates); break; case ON_SPECIFIC_DATE: sb.append(dateFormat.format(endsOnDate)); break; default: break; } } catch (NullPointerException npe) { sb.append("incomplete configuration string: " + npe.getMessage()); } return sb.toString(); } /** * @return the duration of the appointment (endTime - beginTime); if < 0 returns 0 */ public int getAppointmentDuration(){ int result = (int) ((endTime.getTime() / MINUTE_MILLIS) - (beginTime.getTime() / MINUTE_MILLIS)); if (result < 0) return 0; return result; } public Date getBeginTime(){ return beginTime; } public void setBeginTime(Date beginTime){ this.beginTime = beginTime; } public Date getEndTime(){ return endTime; } public void setEndTime(Date endTime){ this.endTime = endTime; } public Date getSeriesStartDate(){ return seriesStartDate; } public void setSeriesStartDate(Date seriesStartDate){ this.seriesStartDate = seriesStartDate; } public EndingType getEndingType(){ return endingType; } public void setEndingType(EndingType endingType){ this.endingType = endingType; } public String getSeriesPatternString(){ return seriesPatternString; } public void setSeriesPatternString(String seriesPatternString){ this.seriesPatternString = seriesPatternString; } public String getEndingPatternString(){ return endingPatternString; } public void setEndingPatternString(String endingPatternString){ this.endingPatternString = endingPatternString; } public Kontakt getContact(){ return contact; } public void setContact(Kontakt contact){ this.contact = contact; } public String getReason(){ return reason; } public void setReason(String reason){ this.reason = reason; } public SeriesType getSeriesType(){ return seriesType; } public void setSeriesType(SeriesType seriesType){ this.seriesType = seriesType; } public Date getEndsOnDate(){ return endsOnDate; } public void setEndsOnDate(Date endsOnDate){ this.endsOnDate = endsOnDate; } public String getEndsAfterNDates(){ return endsAfterNDates; } public void setEndsAfterNDates(String endsAfterNDates){ this.endsAfterNDates = endsAfterNDates; } public Termin getRootTermin(){ return rootTermin; } public String getFreeText(){ return freeText; } public void setFreeText(String freeText){ this.freeText = freeText; } }