package org.sharegov.cirm.rest; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import mjson.Json; import org.sharegov.cirm.AutoConfigurable; import org.sharegov.cirm.OWL; import org.sharegov.cirm.Refs; import org.sharegov.cirm.utils.GenUtils; /** * * Encapsulates business calendar functions( i.e., the calculating of business * days between two dates or adding of business days to a date, observed * holidays, fiscal start and end. ) * * @author SABBAS */ @Path("calendar") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class CalendarService extends RestService implements AutoConfigurable { public static final String DEFAULT_FISCAL_START = "1 January"; public static final int SECONDS_IN_DAY = 86400; private Json config = Json.object(); public CalendarService() { autoConfigure(Refs.owlJsonCache.resolve() .individual(OWL.fullIri("CalendarService")).resolve()); } /** * Returns the next business day taking into consideration * any holidays that have been ontology configured and * and weekends (SAT + SUN) are considered non work days. * * @param start * @param daysToAdd * @return A json structure containing the next business day. */ @GET @Path("/nextBusinessDay") @Produces(MediaType.APPLICATION_JSON) public Json nextBusinessDay( @QueryParam(value = "startDate") String startDate, @QueryParam(value = "daysToAdd") int daysToAdd) { Date result = null; int secondsToAdd = (int) (SECONDS_IN_DAY * daysToAdd); Calendar c = Calendar.getInstance(); //put the holidays as strings in a set for matching. Set<String> holidays = getHolidaysAsSet(); c.setTime(OWL.parseDate(startDate)); int diff = secondsToAdd % SECONDS_IN_DAY; for (int workSeconds = 0; workSeconds < secondsToAdd - diff;) { c.add(Calendar.SECOND, SECONDS_IN_DAY); int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); if (!(dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY)) { String dateAsString = OWL.dateLiteral(c.getTime()).getLiteral(); if (!holidays.contains(dateAsString)) workSeconds = workSeconds + SECONDS_IN_DAY; } } c.add(Calendar.SECOND, diff); holidays.clear(); result = c.getTime(); return GenUtils.ok().set("nextBusinessDay", OWL.toJSON(OWL.dateLiteral(result))); } @GET @Path("/nextBusinessDayExcludeHolidays") @Produces(MediaType.APPLICATION_JSON) public Json nextBusinessDayExcludeHolidays( @QueryParam(value = "startDate") String startDate, @QueryParam(value = "daysToAdd") int daysToAdd) { Date result = null; int secondsToAdd = (int) (SECONDS_IN_DAY * daysToAdd); Calendar c = Calendar.getInstance(); //put the holidays as strings in a set for matching. c.setTime(OWL.parseDate(startDate)); int diff = secondsToAdd % SECONDS_IN_DAY; for (int workSeconds = 0; workSeconds < secondsToAdd - diff;) { c.add(Calendar.SECOND, SECONDS_IN_DAY); int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); if (!(dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY)) { workSeconds = workSeconds + SECONDS_IN_DAY; } } c.add(Calendar.SECOND, diff); result = c.getTime(); return GenUtils.ok().set("nextBusinessDay", OWL.toJSON(OWL.dateLiteral(result))); } @GET @Path("/nextBusinessDayExcludeWeekends") @Produces(MediaType.APPLICATION_JSON) public Json nextBusinessDayExcludeWeekends( @QueryParam(value = "startDate") String startDate, @QueryParam(value = "daysToAdd") int daysToAdd) { Date result = null; int secondsToAdd = (int) (SECONDS_IN_DAY * daysToAdd); Calendar c = Calendar.getInstance(); //put the holidays as strings in a set for matching. Set<String> holidays = getHolidaysAsSet(); c.setTime(OWL.parseDate(startDate)); int diff = secondsToAdd % SECONDS_IN_DAY; for (int workSeconds = 0; workSeconds < secondsToAdd - diff;) { c.add(Calendar.SECOND, SECONDS_IN_DAY); String dateAsString = OWL.dateLiteral(c.getTime()).getLiteral(); if (!holidays.contains(dateAsString)) { workSeconds = workSeconds + SECONDS_IN_DAY; } } c.add(Calendar.SECOND, diff); holidays.clear(); result = c.getTime(); return GenUtils.ok().set("nextBusinessDay", OWL.toJSON(OWL.dateLiteral(result))); } @GET @Path("/nextBusinessDayExcludeHolidaysAndWeekends") @Produces(MediaType.APPLICATION_JSON) public Json nextBusinessDayExcludeHolidaysAndWeekends( @QueryParam(value = "startDate") String startDate, @QueryParam(value = "daysToAdd") int daysToAdd) { Date result = null; int secondsToAdd = (int) (SECONDS_IN_DAY * daysToAdd); Calendar c = Calendar.getInstance(); c.setTime(OWL.parseDate(startDate)); int diff = secondsToAdd % SECONDS_IN_DAY; for (int workSeconds = 0; workSeconds < secondsToAdd - diff;) { c.add(Calendar.SECOND, SECONDS_IN_DAY); workSeconds = workSeconds + SECONDS_IN_DAY; } c.add(Calendar.SECOND, diff); result = c.getTime(); return GenUtils.ok().set("nextBusinessDay", OWL.toJSON(OWL.dateLiteral(result))); } /** * Calculates the days between two dates considering holidays and * weekends as non-working days. * * @param fromDate iso8601 from date * @param toDate iso8601 to dates * @return a Json structure containing a daysBetween property as an integer count. */ @GET @Path("/daysBetween") @Produces(MediaType.APPLICATION_JSON) public Json daysBetween( @QueryParam(value = "fromDate") String fromDate, @QueryParam(value = "toDate") String toDate) { try { int result = 0; Date from = OWL.parseDate(fromDate); Date to = OWL.parseDate(toDate); if((to.getTime() - from.getTime()) < 0) { return GenUtils.ko("Parameter toDate must be greater than or equal to fromDate"); } Calendar c = Calendar.getInstance(); Set<String> holidays = getHolidaysAsSet(); c.setTime(from); while(!OWL.dateLiteral(to) .equals(OWL.dateLiteral(c.getTime()))) { int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); if (!(dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY)) { String dateAsString = OWL.dateLiteral(c.getTime()).getLiteral(); if (!holidays.contains(dateAsString)) { result = result + 1; } } c.add(Calendar.SECOND, SECONDS_IN_DAY); } return GenUtils.ok().set("daysBetween", result); }catch(Throwable t) { return GenUtils.ko(t); } } @GET @Path("/daysBetweenExcludeHolidays") @Produces(MediaType.APPLICATION_JSON) public Json daysBetweenExcludeHolidays( @QueryParam(value = "fromDate") String fromDate, @QueryParam(value = "toDate") String toDate) { try { int result = 0; Date from = OWL.parseDate(fromDate); Date to = OWL.parseDate(toDate); if((to.getTime() - from.getTime()) < 0) { return GenUtils.ko("Parameter toDate must be greater than or equal to fromDate"); } Calendar c = Calendar.getInstance(); c.setTime(from); while(!OWL.dateLiteral(to) .equals(OWL.dateLiteral(c.getTime()))) { int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); if (!(dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY)) { result = result + 1; } c.add(Calendar.SECOND, SECONDS_IN_DAY); } return GenUtils.ok().set("daysBetween", result); }catch(Throwable t) { return GenUtils.ko(t); } } @GET @Path("/daysBetweenExcludeHolidaysAndWeekends") @Produces(MediaType.APPLICATION_JSON) public Json daysBetweenExcludeHolidaysAndWeekends( @QueryParam(value = "fromDate") String fromDate, @QueryParam(value = "toDate") String toDate) { try { int result = 0; Date from = OWL.parseDate(fromDate); Date to = OWL.parseDate(toDate); if((to.getTime() - from.getTime()) < 0) { return GenUtils.ko("Parameter toDate must be greater than or equal to fromDate"); } Calendar c = Calendar.getInstance(); c.setTime(from); while(!OWL.dateLiteral(to) .equals(OWL.dateLiteral(c.getTime()))) { result = result + 1; c.add(Calendar.SECOND, SECONDS_IN_DAY); } return GenUtils.ok().set("daysBetween", result); }catch(Throwable t) { return GenUtils.ko(t); } } /** * Returns the ontology configuration of the start of a fiscal * year. Format is 'dd MMMMM' where dd is the day in month * and MMMMM is the full month name. * @return */ @GET @Path("/fiscalStart") @Produces(MediaType.APPLICATION_JSON) public Json fiscalStart() { return GenUtils.ok().set("hasFiscalYearStart", config.at("hasFiscalYearStart", DEFAULT_FISCAL_START)); } /** * Determines the start and end of the current fiscal year. * * @return A json structure containing the fiscalStart date and * currentFiscalEnd date based on the currentTime given by * operationsService. */ @GET @Path("/currentFiscalYear") @Produces(MediaType.APPLICATION_JSON) public Json currentFiscalYear() { Json result = null; try { long opTime = new OperationService().getTime().at("time").asLong(); result = getFiscalYear(opTime); } catch (Throwable t) { result = GenUtils.ko(t); } return result; } @Override public void autoConfigure(Json config) { if (config == null) { throw new IllegalArgumentException( "configuration of calendar service cannot be null"); } this.config = config; } public Date getFiscalYearStartDate(int year) { try { SimpleDateFormat myDateFormat = new SimpleDateFormat( "dd MMMMM yyyy"); String fiscalStartDate = config.at("hasFiscalYearStart", DEFAULT_FISCAL_START).asString() + " " + year; return myDateFormat.parse(fiscalStartDate); } catch (ParseException ex) { throw new RuntimeException(ex); } } /** * For a given date calculate the fiscalStart and * fiscalEnd dates based on the fiscalYearStart configuration * of the CalendarService. * * @param forDate * @return A json structure containing the fiscalStart and fiscalEnd date */ public Json getFiscalYear(long forDate) { Calendar opCalendar = Calendar.getInstance(); opCalendar.setTime(new Date(forDate)); int opYear = opCalendar.get(Calendar.YEAR); int dayInCurrentYear = opCalendar.get(Calendar.DAY_OF_YEAR); Date fiscalStartDate = getFiscalYearStartDate(opYear); opCalendar.setTime(fiscalStartDate); int fiscalDayInCurrentYear = opCalendar.get(Calendar.DAY_OF_YEAR); if (dayInCurrentYear < fiscalDayInCurrentYear) { opCalendar.add(Calendar.YEAR, -1); fiscalStartDate = opCalendar.getTime(); } opCalendar.add(Calendar.YEAR, 1); opCalendar.add(Calendar.DAY_OF_MONTH, -1); Date fiscalEndDate = opCalendar.getTime(); return GenUtils .ok() .set("fiscalStart", OWL.toJSON(OWL.dateLiteral(fiscalStartDate))) .set("fiscalEnd", OWL.toJSON(OWL.dateLiteral(fiscalEndDate))); } private Set<String> getHolidaysAsSet() { Set<String> result = new HashSet<String>(); for(Json holiday : config.at("hasHolidays",Json.array()).asJsonList()) { if(holiday.has("hasDate")) { if(holiday.at("hasDate").isArray()) { for(Json holidayDate : holiday.at("hasDate").asJsonList()) { result.add(holidayDate.asString()); } }else { result.add(holiday.at("hasDate").asString()); } } } return result; } }