Schedule help, schedule input?

Hi, long time lurker. Finally got a question!

I’m looking for a way to import dates into a schedule. Preferably a calendarschedule. Can’t see any obvious way.

I have the dates currently as Niagara time based. (absTime)
I’ve made a little program that takes the next date compares to current and outputs the closest future date, which I could just use to enable/disable a normal BooleanSchedule via lessthan= current time but no harm asking if there is a better way!

If you’re looking for Holidays and such, in the special events tab, hit the add button, then select reference. Then in the reference dialog window, select the calendar schedule.

Yeah, that’s how it’s currently done

I’m looking for a way to import dates into that calendarSchedule. Rather than have to add them one by one. As I say I have all the dates in a Niagara time format, just none of the schedule objects have a way to feed data in.

Thanks for reply though.

Where are you importing them from? Like Google Calendar?

They come from an api, the OSSapi (I think all the various flavours of Niagara have one). I’m UK based so it’s OneSightSolutions. The driver pulls them in and decodes the .json. Simple enough.

it has its own time decoder which turns the date into Niagara based absTime. My solution is a program that just gets the closest date based on current time. Then I can compare that against current time and get a true/false value to enable/disable schedules.

Bringing them into the calendarSchedule if possible, it would it a bit cleaner. I just can’t see any slots or way of letting a calendar accept data. I’d presume it’s possible as the calendars can be exported/imported into other calendars across stations.

A program object would be the way to do that but by the time that code is done, you probably would’ve added them to the calendar faster.

/* Auto-generated ProgramImpl Code */

import java.util.*;              /* java Predefined*/
import javax.baja.nre.util.*;    /* nre Predefined*/
import javax.baja.sys.*;         /* baja Predefined*/
import javax.baja.status.*;      /* baja Predefined*/
import javax.baja.util.*;        /* baja Predefined*/
import com.tridium.program.*;    /* program-rt Predefined*/
import javax.baja.schedule.*;    /* schedule-rt User Defined*/
import javax.baja.naming.*;      /* baja User Defined*/
import javax.baja.collection.*;  /* baja User Defined*/

public class ProgramImpl
  extends com.tridium.program.ProgramBase
{

////////////////////////////////////////////////////////////////
// Getters
////////////////////////////////////////////////////////////////

  public BOrd getCalendarOrd() { return (BOrd)get("calendarOrd"); }
  public BOrd getEntriesOrd() { return (BOrd)get("entriesOrd"); }

////////////////////////////////////////////////////////////////
// Setters
////////////////////////////////////////////////////////////////

  public void setCalendarOrd(javax.baja.naming.BOrd v) { set("calendarOrd", v); }
  public void setEntriesOrd(javax.baja.naming.BOrd v) { set("entriesOrd", v); }

////////////////////////////////////////////////////////////////
// Program Source
////////////////////////////////////////////////////////////////

    public void onStart() throws Exception
    {
      // start up code here
      System.out.println("Calendar Schedule Program starting...");
    }
    
    public void onExecute() throws Exception
    {
      try {
        // Get the calendar schedule object first
        BCalendarSchedule calendar = null;
        try {
          BObject calendarObj = getCalendarOrd().resolve().get();
          if (calendarObj instanceof BCalendarSchedule) {
            calendar = (BCalendarSchedule)calendarObj;
            System.out.println("Calendar found: " + calendar.getDisplayName(null));
          } else {
            System.out.println("Calendar object is not a BCalendarSchedule: " + 
                             (calendarObj != null ? calendarObj.getType() : "null"));
            return;
          }
        } catch (Exception e) {
          System.out.println("Error getting calendar: " + e.getMessage());
          return;
        }
  
        // Query for objects that might contain schedule strings
        BObject stringObjects = null;
        BITable result = (BITable)getEntriesOrd().resolve().get();
        TableCursor<BObject> cursor = result.cursor();
        
        while (cursor.next()) {
          stringObjects = cursor.get();
          
          // Check if object is not null before accessing slots
          if (stringObjects != null) {
            try {
              String objectName = "UnknownObject";
              try {
                if (stringObjects instanceof BComponent) {
                  objectName = ((BComponent)stringObjects).getDisplayName(null);
                } else {
                  objectName = stringObjects.toString();
                }
              } catch (Exception e) {
                objectName = stringObjects.getType().getTypeName();
              }
              
              System.out.println("Processing object: " + objectName);
              
              // Look for "out" slot specifically
              if (stringObjects instanceof BComponent) {
                try {
                  BObject outSlot = ((BComponent)stringObjects).get("out");
                  if (outSlot != null) {
                    System.out.println(" - out slot found: " + outSlot.getType().getTypeName());
                    
                    // Check if out slot contains a string value
                    String stringValue = extractStringValue(outSlot);
                    
                    if (stringValue != null && !stringValue.trim().isEmpty()) {
                      System.out.println(" - String value found: '" + stringValue + "'");
                      processScheduleString(stringValue, calendar, objectName);
                    } else {
                      System.out.println(" - out slot does not contain a valid string value");
                    }
                  } else {
                    System.out.println(" - out slot value is NULL");
                  }
                } catch (Exception slotException) {
                  System.out.println(" - Error accessing out slot (slot may not exist): " + slotException.getMessage());
                }
              } else {
                System.out.println(" - object is not a BComponent");
              }
              
              // Also check for other common string properties
              checkForStringProperties(stringObjects, calendar, objectName);
              
            } catch (Exception slotException) {
              System.out.println("Error accessing slots for object: " + slotException.getMessage());
            }
          } else {
            System.out.println("Object is NULL");
          }
        }
        cursor.close();
        
      } catch (Exception e) {
        System.out.println("An error has occurred during execution: " + e.getMessage());
        e.printStackTrace();
      }
    }
    
    public void onStop() throws Exception
    {
      // shutdown code here
      System.out.println("Calendar Schedule Program stopping...");
    }
  
  ////////////////////////////////////////////////////////////////
  // Helper Methods
  ////////////////////////////////////////////////////////////////
  
    /**
     * Extract string value from various object types
     */
    private String extractStringValue(BObject obj)
    {
      if (obj == null) return null;
      
      try {
        if (obj instanceof BString) {
          // BString likely uses toString() or a different method
          return obj.toString();
        } else if (obj instanceof BStatusValue) {
          BStatusValue statusValue = (BStatusValue)obj;
          try {
            Object value = statusValue.getStatusValue();
            if (value != null) {
              return value.toString();
            }
          } catch (Exception e) {
            // If getValue() doesn't exist, try toString()
            return statusValue.toString();
          }
        } else {
          // Try to get string representation
          return obj.toString();
        }
      } catch (Exception e) {
        System.out.println("   Error extracting string value: " + e.getMessage());
      }
      
      return null;
    }
  
    /**
     * Check for common string properties in the object
     */
    private void checkForStringProperties(BObject obj, BCalendarSchedule calendar, String objectName)
    {
      if (!(obj instanceof BComponent)) return;
      
      BComponent comp = (BComponent)obj;
      
      // Check common property names that might contain schedule strings
      String[] propertyNames = {"value", "displayName", "description", "text", "schedule", "time"};
      
      for (String propName : propertyNames) {
        try {
          BObject propValue = comp.get(propName);
          String stringValue = extractStringValue(propValue);
          
          if (stringValue != null && !stringValue.trim().isEmpty()) {
            System.out.println(" - Found string in '" + propName + "' property: '" + stringValue + "'");
            processScheduleString(stringValue, calendar, objectName + "_" + propName);
          }
        } catch (Exception e) {
          // Continue if property doesn't exist or can't be accessed
          // This is normal - not all objects will have all properties
        }
      }
    }
  
    /**
     * Process a string value to determine if it represents a date or week/day schedule
     */
    private void processScheduleString(String stringValue, BCalendarSchedule calendar, String sourceName)
    {
      if (stringValue == null || stringValue.trim().isEmpty()) return;
      
      String value = stringValue.trim();
      System.out.println("   Analyzing string for schedule patterns: '" + value + "'");
      
      // Check for week/day pattern FIRST (before date pattern)
      // Look for "week" keyword to identify week/day schedules
      if (value.toLowerCase().contains("week")) {
        BWeekAndDaySchedule weekDaySchedule = parseAsWeekDaySchedule(value);
        if (weekDaySchedule != null) {
          addWeekDayScheduleToCalendar(weekDaySchedule, calendar, sourceName, value);
          return;
        }
      }
      
      // Try to parse as date schedule format (only if no "week" keyword found)
      BDateSchedule dateSchedule = parseAsDateSchedule(value);
      if (dateSchedule != null) {
        addDateScheduleToCalendar(dateSchedule, calendar, sourceName, value);
        return;
      }
      
      System.out.println("   No recognizable schedule pattern found in: '" + value + "'");
    }
  
    /**
     * Parse string as date schedule (e.g., "15 Mar 2024", "Monday 15 March", "25 Dec", "21,August,2025")
     */
    private BDateSchedule parseAsDateSchedule(String value) 
    {
      try {
        BDateSchedule dateSchedule = new BDateSchedule();
        boolean foundComponent = false;
        
        System.out.println("     Trying to parse as date schedule...");
        
        // Clean up the string - remove status info and extra characters
        String cleanValue = value.toLowerCase();
        // Remove everything after { or @ symbols (status information)
        if (cleanValue.contains("{")) {
          cleanValue = cleanValue.substring(0, cleanValue.indexOf("{")).trim();
        }
        if (cleanValue.contains("@")) {
          cleanValue = cleanValue.substring(0, cleanValue.indexOf("@")).trim();
        }
        
        System.out.println("     Cleaned value: '" + cleanValue + "'");
        
        // Split by both spaces and commas to handle different formats
        String[] parts = cleanValue.split("[\\s,]+");
        
        for (String part : parts) {
          part = part.trim();
          if (part.isEmpty()) continue;
          
          // Try to parse as day number (1-31)
          try {
            int day = Integer.parseInt(part);
            if (day >= 1 && day <= 31) {
              dateSchedule.setDay(day);
              foundComponent = true;
              System.out.println("     Found day: " + day);
              continue;
            }
          } catch (NumberFormatException e) {
            // Not a number, continue
          }
          
          // Try to parse as month
          BMonth month = parseMonth(part);
          if (month != null) {
            dateSchedule.setMonth(month);
            foundComponent = true;
            System.out.println("     Found month: " + month.getTag());
            continue;
          }
          
          // Try to parse as year (handle -1 as "any year")
          try {
            int year = Integer.parseInt(part);
            if (year == -1) {
              // -1 means "any year" - don't set a specific year
              System.out.println("     Found year: any (-1)");
            } else if (year >= 1900 && year <= 3000) {
              dateSchedule.setYear(year);
              foundComponent = true;
              System.out.println("     Found year: " + year);
            }
            continue;
          } catch (NumberFormatException e) {
            // Not a year, continue
          }
          
          // Try to parse as weekday
          BWeekday weekday = parseWeekday(part);
          if (weekday != null) {
            dateSchedule.setWeekday(weekday.getOrdinal());
            foundComponent = true;
            System.out.println("     Found weekday: " + weekday.getTag());
          }
        }
        
        if (foundComponent) {
          System.out.println("     Successfully parsed as date schedule");
          return dateSchedule;
        }
        
      } catch (Exception e) {
        System.out.println("     Failed to parse as date schedule: " + e.getMessage());
      }
      
      return null;
    }
  
    /**
     * Parse string as week and day schedule (e.g., "Monday Week 2", "Tue Week 3 Jan", "Monday, Week 3, September")
     */
    private BWeekAndDaySchedule parseAsWeekDaySchedule(String value)
    {
      try {
        BWeekAndDaySchedule schedule = new BWeekAndDaySchedule();
        boolean foundComponent = false;
        
        System.out.println("     Trying to parse as week/day schedule...");
        
        // Clean up the string - remove status info and extra characters
        String cleanValue = value.toLowerCase();
        // Remove everything after { or @ symbols (status information)
        if (cleanValue.contains("{")) {
          cleanValue = cleanValue.substring(0, cleanValue.indexOf("{")).trim();
        }
        if (cleanValue.contains("@")) {
          cleanValue = cleanValue.substring(0, cleanValue.indexOf("@")).trim();
        }
        
        System.out.println("     Cleaned value: '" + cleanValue + "'");
        
        // Split by both spaces and commas to handle different formats
        String[] parts = cleanValue.split("[\\s,]+");
        
        for (int i = 0; i < parts.length; i++) {
          String part = parts[i].trim();
          if (part.isEmpty()) continue;
          
          // Try to parse as weekday
          BWeekday weekday = parseWeekday(part);
          if (weekday != null) {
            schedule.setWeekday(weekday.getOrdinal());
            foundComponent = true;
            System.out.println("     Found weekday: " + weekday.getTag());
            continue;
          }
          
          // Try to parse as month
          BMonth month = parseMonth(part);
          if (month != null) {
            schedule.setMonth(month.getOrdinal());
            foundComponent = true;
            System.out.println("     Found month: " + month.getTag());
            continue;
          }
          
          // Look for week patterns like "week 2" or "week2"
          if (part.equals("week") && i + 1 < parts.length) {
            // Next part should be the week number
            String weekPart = parts[i + 1].trim();
            i++; // Skip next part as we consumed it
            
            try {
              int week = Integer.parseInt(weekPart);
              if (week >= 1 && week <= 6) {
                schedule.setWeek(week);
                foundComponent = true;
                System.out.println("     Found week: " + week);
              }
            } catch (NumberFormatException e) {
              // Not a valid week number
              System.out.println("     Invalid week number: " + weekPart);
            }
          } else if (part.startsWith("week")) {
            // Handle "week3" format
            String weekPart = part.substring(4);
            try {
              int week = Integer.parseInt(weekPart);
              if (week >= 1 && week <= 6) {
                schedule.setWeek(week);
                foundComponent = true;
                System.out.println("     Found week: " + week);
              }
            } catch (NumberFormatException e) {
              // Not a valid week number
            }
          }
        }
        
        if (foundComponent) {
          System.out.println("     Successfully parsed as week/day schedule");
          return schedule;
        }
        
      } catch (Exception e) {
        System.out.println("     Failed to parse as week/day schedule: " + e.getMessage());
      }
      
      return null;
    }
  
    /**
     * Parse month name from string
     */
    private BMonth parseMonth(String monthStr)
    {
      monthStr = monthStr.toLowerCase();
      
      if (monthStr.equals("january") || monthStr.equals("jan")) return BMonth.january;
      if (monthStr.equals("february") || monthStr.equals("feb")) return BMonth.february;
      if (monthStr.equals("march") || monthStr.equals("mar")) return BMonth.march;
      if (monthStr.equals("april") || monthStr.equals("apr")) return BMonth.april;
      if (monthStr.equals("may")) return BMonth.may;
      if (monthStr.equals("june") || monthStr.equals("jun")) return BMonth.june;
      if (monthStr.equals("july") || monthStr.equals("jul")) return BMonth.july;
      if (monthStr.equals("august") || monthStr.equals("aug")) return BMonth.august;
      if (monthStr.equals("september") || monthStr.equals("sep")) return BMonth.september;
      if (monthStr.equals("october") || monthStr.equals("oct")) return BMonth.october;
      if (monthStr.equals("november") || monthStr.equals("nov")) return BMonth.november;
      if (monthStr.equals("december") || monthStr.equals("dec")) return BMonth.december;
      
      return null;
    }
  
    /**
     * Parse weekday name from string
     */
    private BWeekday parseWeekday(String dayStr)
    {
      dayStr = dayStr.toLowerCase();
      
      if (dayStr.equals("sunday") || dayStr.equals("sun")) return BWeekday.sunday;
      if (dayStr.equals("monday") || dayStr.equals("mon")) return BWeekday.monday;
      if (dayStr.equals("tuesday") || dayStr.equals("tue") || dayStr.equals("tues")) return BWeekday.tuesday;
      if (dayStr.equals("wednesday") || dayStr.equals("wed")) return BWeekday.wednesday;
      if (dayStr.equals("thursday") || dayStr.equals("thu") || dayStr.equals("thurs")) return BWeekday.thursday;
      if (dayStr.equals("friday") || dayStr.equals("fri")) return BWeekday.friday;
      if (dayStr.equals("saturday") || dayStr.equals("sat")) return BWeekday.saturday;
      
      return null;
    }
  
    /**
     * Add a date schedule to the calendar
     */
    private void addDateScheduleToCalendar(BDateSchedule dateSchedule, BCalendarSchedule calendar, String sourceName, String originalValue)
    {
      try {
        // Create a unique name for this schedule entry
        String scheduleName = "DateSched_" + sourceName.replaceAll("[^a-zA-Z0-9]", "_");
        
        // Try to add the schedule to the calendar
        if (calendar instanceof BComponent) {
          try {
            ((BComponent)calendar).add(scheduleName, dateSchedule);
            System.out.println("   SUCCESS: Added date schedule '" + scheduleName + "' to calendar");
          } catch (Exception addException) {
            System.out.println("   ERROR: Failed to add date schedule to calendar: " + addException.getMessage());
            return;
          }
        } else {
          System.out.println("   ERROR: Calendar is not a BComponent, cannot add schedule");
          return;
        }
        
        System.out.println("     Original value: '" + originalValue + "'");
        System.out.println("     Parsed as - Day: " + (dateSchedule.getDay() > 0 ? dateSchedule.getDay() : "any") + 
                           ", Month: " + (dateSchedule.getMonth() >= 0 ? dateSchedule.getMonth() : "any") + 
                           ", Year: " + (dateSchedule.getYear() >= 0 ? dateSchedule.getYear() : "any") + 
                           ", Weekday: " + (dateSchedule.getWeekday() >= 0 ? dateSchedule.getWeekday() : "any"));
                 
      } catch (Exception e) {
        System.out.println("   ERROR: Failed to add date schedule to calendar: " + e.getMessage());
      }
    }
  
    /**
     * Add a week and day schedule to the calendar
     */
    private void addWeekDayScheduleToCalendar(BWeekAndDaySchedule weekDaySchedule, BCalendarSchedule calendar, String sourceName, String originalValue)
    {
      try {
        // Create a unique name for this schedule entry
        String scheduleName = "WeekDaySched_" + sourceName.replaceAll("[^a-zA-Z0-9]", "_");
        
        // Try to add the schedule to the calendar
        if (calendar instanceof BComponent) {
          try {
            ((BComponent)calendar).add(scheduleName, weekDaySchedule);
            System.out.println("   SUCCESS: Added week/day schedule '" + scheduleName + "' to calendar");
          } catch (Exception addException) {
            System.out.println("   ERROR: Failed to add week/day schedule to calendar: " + addException.getMessage());
            return;
          }
        } else {
          System.out.println("   ERROR: Calendar is not a BComponent, cannot add schedule");
          return;
        }
        
        System.out.println("     Original value: '" + originalValue + "'");
        System.out.println("     Parsed as - Weekday: " + (weekDaySchedule.getWeekday() >= 0 ? weekDaySchedule.getWeekday() : "any") + 
                           ", Week: " + (weekDaySchedule.getWeek() >= 0 ? weekDaySchedule.getWeek() : "any") + 
                           ", Month: " + (weekDaySchedule.getMonth() >= 0 ? weekDaySchedule.getMonth() : "any"));
                 
      } catch (Exception e) {
        System.out.println("   ERROR: Failed to add week/day schedule to calendar: " + e.getMessage());
      }
    }
}


Here is a program object that can query String Writables (assuming that your json info goes to strings. And add right now, date, and week and day. I haven't done a date range yet or custom. Which might be more difficult. 

This might be something you might be looking for.

1 Like

Haha it has become a bigger challenge than I expected but across multiple sites adding holidays can become time consuming. Also it may have use in being able to book workspaces.

Many Thanks for the program! I’ll have a look later when I’m at work. Feel like I’ve really learnt the power of Niagara from this experiment.

1 Like

Also didn’t notice your comment in the bottom of code box.

The dates I have are Niagara time format rather than string. The driver handles the .json already but the data before converting is probably a string.

Each date is individual so no ranges. I’ll have a play around! Thanks again.

1 Like
/* Auto-generated ProgramImpl Code */

import java.util.*;              /* java Predefined*/
import javax.baja.nre.util.*;    /* nre Predefined*/
import javax.baja.sys.*;         /* baja Predefined*/
import javax.baja.status.*;      /* baja Predefined*/
import javax.baja.util.*;        /* baja Predefined*/
import com.tridium.program.*;    /* program-rt Predefined*/
import javax.baja.schedule.*;    /* schedule-rt User Defined*/
import javax.baja.collection.*;  /* baja User Defined*/
import java.io.*;                /* java User Defined*/
import java.net.*;               /* java User Defined*/
import javax.baja.naming.*;      /* baja By Property*/

public class ProgramImpl
  extends com.tridium.program.ProgramBase
{

////////////////////////////////////////////////////////////////
// Getters
////////////////////////////////////////////////////////////////

  public BOrd getCalendarOrd() { return (BOrd)get("calendarOrd"); }
  public BStatusString getApiResponse() { return (BStatusString)get("apiResponse"); }
  public String getApiKey() { return getString("apiKey"); }

////////////////////////////////////////////////////////////////
// Setters
////////////////////////////////////////////////////////////////

  public void setCalendarOrd(javax.baja.naming.BOrd v) { set("calendarOrd", v); }
  public void setApiResponse(javax.baja.status.BStatusString v) { set("apiResponse", v); }
  public void setApiKey(String v) { setString("apiKey", v); }

////////////////////////////////////////////////////////////////
// Program Source
////////////////////////////////////////////////////////////////

  public void onStart() throws Exception
      {
        // start up code here
        System.out.println("Holiday Calendar Program starting...");
      }
      
      public void onExecute() throws Exception
      {
        try {
          // Get the calendar schedule object first
          BCalendarSchedule calendar = null;
          try {
            BObject calendarObj = getCalendarOrd().resolve().get();
            if (calendarObj instanceof BCalendarSchedule) {
              calendar = (BCalendarSchedule)calendarObj;
              System.out.println("Calendar found: " + calendar.getDisplayName(null));
            } else {
              System.out.println("Calendar object is not a BCalendarSchedule: " + 
                               (calendarObj != null ? calendarObj.getType() : "null"));
              return;
            }
          } catch (Exception e) {
            System.out.println("Error getting calendar: " + e.getMessage());
            return;
          }
    
          // Clean up any old test holidays
          if (calendar instanceof BComponent) {
            BComponent calendarComp = (BComponent)calendar;
            try {
              BObject existing = calendarComp.get("TestChristmas");
              if (existing != null) {
                System.out.println("Removing old TestChristmas...");
                calendarComp.remove("TestChristmas");
              }
            } catch (Exception e) {
              // TestChristmas doesn't exist, which is fine
            }
          }
          
          // Add US Holidays from API
          addUSHolidaysFromAPI(calendar);
          
          System.out.println("Holiday calendar population complete!");
          
        } catch (Exception e) {
          System.out.println("An error has occurred during execution: " + e.getMessage());
          e.printStackTrace();
        }
      }
      
      public void onStop() throws Exception
      {
        // shutdown code here
        System.out.println("Holiday Calendar Program stopping...");
        setApiResponse(new BStatusString("Program Stopped", BStatus.ok));
      }
    
    ////////////////////////////////////////////////////////////////
    // Helper Methods
    ////////////////////////////////////////////////////////////////
    
  
      /**
       * Fetch US Holidays from API Ninjas and add them to the calendar
       */
      private void addUSHolidaysFromAPI(BCalendarSchedule calendar)
      {
        try {
          String apiKey = getApiKey();
          if (apiKey == null || apiKey.trim().isEmpty()) {
            System.out.println("API Key not configured - skipping holiday fetch");
            setApiResponse(new BStatusString("ERROR: API Key not configured", BStatus.fault));
            return;
          }
          
          System.out.println("Fetching US Holidays from API Ninjas...");
          
          // Build API Ninjas URL for US Holidays (FREE TIER - no year parameter)
          String apiUrl = "https://api.api-ninjas.com/v1/publicholidays?country=US";
          
          System.out.println("API URL: " + apiUrl);
          System.out.println("Note: Using free tier - gets current year holidays automatically");
          
          // Make HTTP request to API Ninjas
          String jsonResponse = makeHttpRequest(apiUrl, apiKey);
          
          if (jsonResponse != null && !jsonResponse.isEmpty()) {
            parseApiNinjasResponse(jsonResponse, calendar);
          } else {
            System.out.println("Failed to get response from API Ninjas");
          }
          
        } catch (Exception e) {
          System.out.println("ERROR: Failed to fetch holidays from API: " + e.getMessage());
          setApiResponse(new BStatusString("ERROR: " + e.getMessage(), BStatus.fault));
          e.printStackTrace();
        }
      }
    
      /**
       * Make HTTP request to API Ninjas with API key in header
       */
      private String makeHttpRequest(String urlString, String apiKey)
      {
        try {
          System.out.println("Making request with API Key: " + (apiKey != null ? apiKey.substring(0, Math.min(10, apiKey.length())) + "..." : "null"));
          
          URL url = new URL(urlString);
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();
          connection.setRequestMethod("GET");
          connection.setConnectTimeout(10000); // 10 seconds
          connection.setReadTimeout(10000); // 10 seconds
          connection.setRequestProperty("User-Agent", "Niagara-HolidayCalendar/1.0");
          connection.setRequestProperty("X-Api-Key", apiKey); // API Ninjas uses X-Api-Key header
          
          System.out.println("Request headers set - User-Agent and X-Api-Key");
          
          int responseCode = connection.getResponseCode();
          System.out.println("HTTP Response Code: " + responseCode);
          
          if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(
              new InputStreamReader(connection.getInputStream())
            );
            
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
              response.append(line);
            }
            reader.close();
            
            // Store the raw response in the status property
            setApiResponse(new BStatusString(response.toString(), BStatus.ok));
            return response.toString();
            
          } else {
            // Try to read error response
            String errorResponse = "";
            try {
              BufferedReader errorReader = new BufferedReader(
                new InputStreamReader(connection.getErrorStream())
              );
              StringBuilder errorBuilder = new StringBuilder();
              String errorLine;
              while ((errorLine = errorReader.readLine()) != null) {
                errorBuilder.append(errorLine);
              }
              errorReader.close();
              errorResponse = errorBuilder.toString();
            } catch (Exception e) {
              errorResponse = "Could not read error response";
            }
            
            String errorMsg = "HTTP Error: " + responseCode + " - " + errorResponse;
            setApiResponse(new BStatusString(errorMsg, BStatus.fault));
            System.out.println(errorMsg);
            return null;
          }
          
        } catch (Exception e) {
          String errorMsg = "Connection Error: " + e.getMessage();
          setApiResponse(new BStatusString(errorMsg, BStatus.fault));
          System.out.println("ERROR making HTTP request: " + e.getMessage());
          e.printStackTrace();
          return null;
        }
      }
    
      /**
       * Parse API Ninjas JSON response - SIMPLE SEARCH APPROACH
       */
      private void parseApiNinjasResponse(String jsonResponse, BCalendarSchedule calendar)
      {
        try {
          System.out.println("Parsing API Ninjas response using simple search...");
          System.out.println("Response length: " + jsonResponse.length());
          
          int holidayCount = 0;
          int searchStart = 0;
          
          // Look for each "name": pattern in the JSON
          while (true) {
            // Find next "name": pattern
            int namePos = jsonResponse.indexOf("\"name\":", searchStart);
            if (namePos == -1) {
              break; // No more holidays found
            }
            
            // Extract the name value
            String name = extractQuotedValue(jsonResponse, namePos + 7);
            if (name == null) {
              searchStart = namePos + 7;
              continue;
            }
            
            // Skip Columbus Day since Indigenous Peoples' Day is on the same date
            if (name != null && name.toLowerCase().contains("columbus")) {
              System.out.println("Skipping Columbus Day (Indigenous Peoples' Day covers this date)");
              searchStart = namePos + 7;
              continue;
            }
            
            // Find the corresponding date for this holiday (should be after the name)
            int datePos = jsonResponse.indexOf("\"date\":", namePos);
            if (datePos == -1) {
              searchStart = namePos + 7;
              continue;
            }
            
            // Extract the date value
            String dateStr = extractQuotedValue(jsonResponse, datePos + 7);
            if (dateStr == null) {
              searchStart = namePos + 7;
              continue;
            }
            
            System.out.println("Found holiday: '" + name + "' on '" + dateStr + "'");
            
            // Parse and create the holiday
            if (createHolidayFromDate(calendar, name, dateStr)) {
              holidayCount++;
            }
            
            // Move search position past this holiday
            searchStart = datePos + 7;
          }
          
          System.out.println("Successfully processed " + holidayCount + " holidays from API");
          
        } catch (Exception e) {
          System.out.println("ERROR parsing API response: " + e.getMessage());
          e.printStackTrace();
        }
      }
      
      /**
       * Extract a quoted value starting from the given position
       */
      private String extractQuotedValue(String json, int startPos)
      {
        try {
          // Skip whitespace and find opening quote
          int pos = startPos;
          while (pos < json.length() && Character.isWhitespace(json.charAt(pos))) {
            pos++;
          }
          
          if (pos >= json.length()) return null;
          
          char quoteChar = json.charAt(pos);
          if (quoteChar != '"') {
            return null; // Not a quoted value
          }
          
          pos++; // Skip opening quote
          int startQuote = pos;
          
          // Find closing quote
          while (pos < json.length()) {
            char c = json.charAt(pos);
            if (c == '"') {
              // Found closing quote
              return json.substring(startQuote, pos);
            }
            pos++;
          }
          
          return null; // No closing quote found
          
        } catch (Exception e) {
          return null;
        }
      }
      
      /**
       * Create a holiday from name and date string
       */
      private boolean createHolidayFromDate(BCalendarSchedule calendar, String name, String dateStr)
      {
        try {
          // Parse date (format: "2025-01-01")
          String[] dateParts = dateStr.split("-");
          if (dateParts.length != 3) {
            System.out.println("  ERROR: Invalid date format: " + dateStr);
            return false;
          }
          
          int year = Integer.parseInt(dateParts[0]);
          int month = Integer.parseInt(dateParts[1]) - 1; // Convert to 0-based
          int day = Integer.parseInt(dateParts[2]);
          
          System.out.println("  Creating holiday: " + name + " on " + year + "-" + (month+1) + "-" + day);
          
          // Create and add holiday schedule
          createHolidaySchedule(calendar, name, day, month, year);
          
          // If this is Thanksgiving Day, also create Black Friday (the next day)
          if (name != null && name.toLowerCase().contains("thanksgiving")) {
            System.out.println("  Detected Thanksgiving - also creating Black Friday");
            
            // Calculate Black Friday date (day after Thanksgiving)
            int blackFridayDay = day + 1;
            int blackFridayMonth = month;
            int blackFridayYear = year;
            
            // Handle month rollover if Thanksgiving is on the last day of November
            if (blackFridayDay > 30) { // November has 30 days
              blackFridayDay = 1;
              blackFridayMonth = 11; // December (0-based)
            }
            
            System.out.println("  Creating Black Friday on " + blackFridayYear + "-" + (blackFridayMonth+1) + "-" + blackFridayDay);
            createHolidaySchedule(calendar, "Black Friday", blackFridayDay, blackFridayMonth, blackFridayYear);
          }
          
          return true;
          
        } catch (Exception e) {
          System.out.println("  ERROR: Failed to parse date '" + dateStr + "': " + e.getMessage());
          return false;
        }
      }
    
      /**
       * Create a holiday schedule and add it to the calendar
       */
      private void createHolidaySchedule(BCalendarSchedule calendar, String holidayName, int day, int month, int year)
      {
        try {
          System.out.println("   Creating BDateSchedule for: " + holidayName);
          
          BDateSchedule holiday = new BDateSchedule();
          holiday.setDay(day);
          holiday.setMonth(BMonth.make(month));
          holiday.setYear(year);
          
          String cleanName = cleanHolidayName(holidayName);
          
          if (calendar instanceof BComponent) {
            BComponent calendarComp = (BComponent)calendar;
            
            // Check if holiday already exists and remove it first
            try {
              BObject existing = calendarComp.get(cleanName);
              if (existing != null) {
                System.out.println("   Removing existing holiday: " + cleanName);
                calendarComp.remove(cleanName);
              }
            } catch (Exception e) {
              // Holiday doesn't exist, which is fine
            }
            
            // Add the holiday
            calendarComp.add(cleanName, holiday);
            System.out.println("   SUCCESS: Added holiday '" + cleanName + "' -> " + holidayName);
          } else {
            System.out.println("   ERROR: Calendar is not a BComponent");
          }
          
        } catch (Exception e) {
          System.out.println("   ERROR: Failed to create holiday '" + holidayName + "': " + e.getMessage());
        }
      }
    
      /**
       * Clean holiday name for use as Niagara object name
       */
      private String cleanHolidayName(String holidayName)
      {
        if (holidayName == null || holidayName.isEmpty()) {
          return "UnknownHoliday";
        }
        
        String cleaned = holidayName.trim();
        cleaned = cleaned.replaceAll("[''`]", "");
        cleaned = cleaned.replaceAll("[.,;:!?]", "");
        cleaned = cleaned.replaceAll("[()\\[\\]{}]", "");
        cleaned = cleaned.replaceAll("[-/\\\\]", " ");
        cleaned = cleaned.replaceAll("&", "And");
        cleaned = cleaned.replaceAll("\\+", "Plus");
        
        String[] words = cleaned.split("\\s+");
        StringBuilder result = new StringBuilder();
        
        for (String word : words) {
          if (word.length() > 0) {
            result.append(word.substring(0, 1).toUpperCase());
            if (word.length() > 1) {
              result.append(word.substring(1).toLowerCase());
            }
          }
        }
        
        String finalName = result.toString();
        
        if (finalName.length() > 0 && !Character.isLetter(finalName.charAt(0))) {
          finalName = "Holiday" + finalName;
        }
        
        if (finalName.isEmpty()) {
          finalName = "UnknownHoliday";
        }
        
        return finalName;
      }
    
      /**
       * Action to manually refresh holidays from API
       */
      public void doRefreshHolidays() throws Exception 
      {
        System.out.println("Manual refresh triggered...");
        
        BCalendarSchedule calendar = null;
        try {
          BObject calendarObj = getCalendarOrd().resolve().get();
          if (calendarObj instanceof BCalendarSchedule) {
            calendar = (BCalendarSchedule)calendarObj;
            addUSHolidaysFromAPI(calendar);
            System.out.println("Manual holiday refresh completed!");
          } else {
            System.out.println("ERROR: Calendar object is not a BCalendarSchedule");
            setApiResponse(new BStatusString("ERROR: Calendar object is not a BCalendarSchedule", BStatus.fault));
          }
        } catch (Exception e) {
          System.out.println("ERROR during manual refresh: " + e.getMessage());
          setApiResponse(new BStatusString("ERROR during manual refresh: " + e.getMessage(), BStatus.fault));
          throw e;
        }
      }
}

This might be better suited. I’m using api-ninjas free API for US holidays. I parse the JSON string for name and date and create instances that way

1 Like

Thanks again.

I haven’t a chance to throw the first one into a program block and compile yet but I’ve got a couple easy days at work next week to play around with it. I may even have a look over the weekend.

You’ll need to adjust some things for your API since I don’t know what time and such it makes, but this should give you a good enough base for it.

I would like to mention that you can make a single holiday calendar and reference the rest of the calendars to this holiday calendar. So you would only need to do this once per site.

Yeah thats currently how it’s being done but its a large portfolio! really it’s just a learning exercise and proving a point that it can be done automatically. It may also have some further benefits of linking other calendar based automation. Like tenants being able to apply their own holiday schedules, hot desking, meeting room booking etc.