/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.f1x.v1.schedule;
import org.f1x.util.AsciiUtils;
import org.f1x.util.parse.TimeOfDayParser;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Simple implementation of SessionSchedule that can define daily or weekly schedule during with specific start/end time of day.
*
* This implementation is NOT thread-safe.
*/
public class SimpleSessionSchedule implements SessionSchedule {
protected final TimeEndPoint start;
protected final TimeEndPoint end;
private final boolean isDailySchedule;
private final boolean isEndTimeBeforeStartTimeOfDay;
/**
* @param startDayOfWeek Session Start day of week (can be <code>-1</code> for sessions that happen every day). Week starts with sunday and has code 1. For example, <code>Calendar.MONDAY</code>.
* @param endDayOfWeek Session End day of week (must be <code>-1</code> if startDayOfWeek is <code>-1</code>). Week starts with sunday and has code 1. For example, <code>Calendar.FRIDAY</code>.
* @param startTimeOfDay session start time of day (in HH:MM:SS format)
* @param endTimeOfDay session end time of day (in HH:MM:SS format)
* @param isDailySchedule <code>true</code> for daily FIX sessions, <code>false</code> for multi-day sessions. Makes sense only when startDayOfWeek and endDayOfWeek are set.
* @param tz optional time zone (otherwise local TZ is assumed)
*/
public SimpleSessionSchedule (int startDayOfWeek, int endDayOfWeek, String startTimeOfDay, String endTimeOfDay, boolean isDailySchedule, TimeZone tz) {
if (startDayOfWeek != -1 && endDayOfWeek == -1 || startDayOfWeek == -1 && endDayOfWeek != -1)
throw new IllegalArgumentException("Start and End day of weeks must be specified together");
if ( ! isDailySchedule && startDayOfWeek == -1)
throw new IllegalArgumentException("Weekly schedule must have Start and End day of week defined");
if (tz == null)
tz = TimeZone.getDefault();
this.start = new TimeEndPoint(startDayOfWeek, startTimeOfDay, tz);
this.end = new TimeEndPoint(endDayOfWeek, endTimeOfDay, tz);
this.isEndTimeBeforeStartTimeOfDay = start.calcTimeInSeconds() > end.calcTimeInSeconds();
this.isDailySchedule = isDailySchedule;
}
@Override
public SessionTimes getCurrentSessionTimes(long currentTime) {
setMostRecentSessionBefore(currentTime);
if (currentTime < end.getTimeInMillis()) {
return new SessionTimes(start.getTimeInMillis(), end.getTimeInMillis());
} else {
// Session is over
setNextSessionAfter(currentTime);
}
return new SessionTimes(start.getTimeInMillis(), end.getTimeInMillis());
}
/** Sets start/end times of current session if it includes given timestamp or the most recent session if given timestamp is outside of this schedule */
protected void setMostRecentSessionBefore(long timestamp) { //assert Thread.holdsLock(this); // using {start, end}
start.setAndAdjust(timestamp);
if (start.dayOfWeek != -1) {
start.calendar.set(Calendar.DAY_OF_WEEK, start.dayOfWeek);
if (start.after(timestamp))
start.calendar.add(Calendar.WEEK_OF_YEAR, -1);
if (isDailySchedule) {
int correctedDayOfWeek = (isEndTimeBeforeStartTimeOfDay) ? end.dayOfWeek - 1 : end.dayOfWeek;
while (start.notAfter(timestamp) && start.getDayOfWeek () < correctedDayOfWeek) {
start.calendar.add(Calendar.DAY_OF_YEAR, 1);
}
if (start.after(timestamp))
start.calendar.add(Calendar.DAY_OF_YEAR, -1);
}
} else if (start.after(timestamp)) {
start.calendar.add(Calendar.DAY_OF_YEAR, -1);
}
adjustIntervalEnd();
}
/** Sets start/end times to the next session (called ensures that given timestamp is outside of this schedule) */
protected void setNextSessionAfter(long timestamp) { //assert Thread.holdsLock(this); // using {start, end}
start.setAndAdjust(timestamp);
if (start.dayOfWeek != -1) {
if (isDailySchedule) {
if (start.before(timestamp))
start.calendar.add(Calendar.DAY_OF_YEAR, 1);
int correctedDayOfWeek = (isEndTimeBeforeStartTimeOfDay) ? end.dayOfWeek - 1 : end.dayOfWeek;
if (start.getDayOfWeek() > correctedDayOfWeek) {
start.calendar.set(Calendar.DAY_OF_WEEK, start.dayOfWeek);
start.calendar.add(Calendar.WEEK_OF_YEAR, 1);
}
} else {
start.calendar.set(Calendar.DAY_OF_WEEK, start.dayOfWeek);
if (start.before(timestamp))
start.calendar.add(Calendar.WEEK_OF_YEAR, 1);
}
} else if (start.before(timestamp)) {
start.calendar.add(Calendar.DAY_OF_YEAR, 1);
}
adjustIntervalEnd();
}
/** Adjust End of interval based on Start point */
private void adjustIntervalEnd() {
end.setAndAdjust(start.getTimeInMillis());
if (end.dayOfWeek != -1) {
if (isDailySchedule) {
if (end.notAfter(start))
end.calendar.add(Calendar.DAY_OF_WEEK, 1);
} else {
end.calendar.set(Calendar.DAY_OF_WEEK, end.dayOfWeek);
if (end.notAfter(start)) {
end.calendar.add(Calendar.WEEK_OF_MONTH, 1);
}
}
} else if (end.notAfter(start)) {
end.calendar.add(Calendar.DAY_OF_WEEK, 1);
}
}
// protected void setNextSessionAfterOld(long timestamp) { assert Thread.holdsLock(this); // using {start, end}
// end.setAndAdjust(timestamp);
// if (end.dayOfWeek != -1) {
// end.calendar.set(Calendar.DAY_OF_WEEK, end.dayOfWeek);
// if (end.before(timestamp))
// end.calendar.add(Calendar.WEEK_OF_YEAR, 1);
//
// if (isDailySchedule) {
// //int correctedDayOfWeek = (isEndTimeBeforeStartTimeOfDay) ? start.dayOfWeek + 1 : start.dayOfWeek; //TODO
// //while (end.getTimeInMillis() >= timestamp && end.getDayOfWeek () < correctedDayOfWeek) {
// while (end.notBefore(timestamp) && end.getDayOfWeek () < end.dayOfWeek) {
// end.calendar.add(Calendar.DAY_OF_YEAR, -1);
// }
// if (end.before(timestamp))
// end.calendar.add(Calendar.DAY_OF_YEAR, 1);
// }
// } else if (end.before(timestamp)) {
// end.calendar.add(Calendar.DAY_OF_YEAR, 1);
// }
//
// start.setAndAdjust(end.getTimeInMillis());
// if (end.dayOfWeek != -1) {
//
// if (isDailySchedule) {
// if (start.notBefore(end))
// start.calendar.add(Calendar.DAY_OF_WEEK, -1);
// } else {
// start.calendar.set(Calendar.DAY_OF_WEEK, start.dayOfWeek);
// if (start.notBefore(end)) {
// start.calendar.add(Calendar.WEEK_OF_MONTH, -1);
// }
// }
// } else if (start.notBefore(end)) {
// start.calendar.add(Calendar.DAY_OF_WEEK, -1);
// }
// }
@Override
public String toString() {
return start + " " + end + ((isDailySchedule)?" Daily":" Weekly") ;
}
protected static class TimeEndPoint {
private final Calendar calendar;
private final int dayOfWeek;
private final int hour;
private final int minute;
private final int second;
private TimeEndPoint(int dayOfWeek, String timeOfDay, TimeZone tz) {
this.calendar = Calendar.getInstance(tz);
this.dayOfWeek = dayOfWeek;
int [] parsed = TimeOfDayParser.parseTimeOfDay(AsciiUtils.getBytes(timeOfDay));
this.hour = parsed[0];
this.minute = parsed[1];
this.second = parsed[2];
}
private int calcTimeInSeconds () {
return (hour * 3600) + (minute * 60) + second;
}
private void setAndAdjust(long timestamp) {
calendar.setTimeInMillis(timestamp);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, 0);
}
protected long getTimeInMillis() {
return calendar.getTimeInMillis();
}
private boolean before(long timestamp) {
return getTimeInMillis() < timestamp;
}
private boolean after(long timestamp) {
return getTimeInMillis() > timestamp;
}
private boolean notAfter(long timestamp) {
return getTimeInMillis() <= timestamp;
}
private boolean notBefore(long timestamp) {
return getTimeInMillis() >= timestamp;
}
private boolean before(TimeEndPoint timestamp) {
return before(timestamp.getTimeInMillis());
}
private boolean after(TimeEndPoint timestamp) {
return after(timestamp.getTimeInMillis());
}
private boolean notAfter(TimeEndPoint timestamp) {
return notAfter(timestamp.getTimeInMillis());
}
private boolean notBefore(TimeEndPoint timestamp) {
return notBefore(timestamp.getTimeInMillis());
}
private int getDayOfWeek () {
return calendar.get(Calendar.DAY_OF_WEEK);
}
@Override
public String toString() {
return String.format("%02d:%02d:%02d (%d)", hour, minute, second, dayOfWeek);
}
}
}