# Runtime Schedule

**URL:** <https://forum.inductiveautomation.com/t/runtime-schedule/59745>\
**Category:** Ignition\
**Created:** [May 18, 2022, 7:26pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745 "2022-05-18T19:26:23Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![AJBerera](https://avatars.discourse-cdn.com/v4/letter/a/9fc29f/32.png) [@AJBerera](https://forum.inductiveautomation.com/u/AJBerera)\
**Post date:** [May 18, 2022, 7:26pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745/1 "2022-05-18T19:26:23Z")

</div>

Hi,

I am trying to build a schedule UI with three text fields/pop up shift selectors. Underneath the UI I would like to have a string of the format start1Time AM - end1Time PM , start2Time etc…  
Right now I have a good amount of code on the text fields and the popups to accomplish this without any overlapping for shifts. Has anyone built a complex scheduler UI in the past, and how did you overcome the scripting challenges, and maintain ease of user for the user when they are determined to use shift definitions instead of regular time ranges?

 ![CaptureScheduleQ](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/7/c7cbc72924cb06d4eab8039a73ed6717bf5fd246.png)

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [May 18, 2022, 7:29pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745/2 "2022-05-18T19:29:17Z")

</div>

Have you seen this discussion:

> [@Script for periodically changing shift time](https://forum.inductiveautomation.com/t/script-for-periodically-changing-shift-time/51058/3):
>
> Hi Berkay, I like to solve this kind of problem with a smart database, like PostgreSQL, where I can define a pattern and repeat it with table generating functions. I tweaked one of my implementations to serve as an example: create table shift\_schedules (shift\_id serial, shift\_name varchar(255), refdate date, repeat int, tzname varchar(255), primary key (shift\_id)); insert into shift\_schedules (shift\_name, refdate, repeat, tzname) values ('Berkay Cinar 3wk', '2021-07-26', 21, 'America/Ne…

Let them use shift definitions. No UI for it at the moment, but I think you can handle it. 😁

---

<div class="post-metadata">

**Author:** ![AJBerera](https://avatars.discourse-cdn.com/v4/letter/a/9fc29f/32.png) [@AJBerera](https://forum.inductiveautomation.com/u/AJBerera)\
**Post date:** [May 18, 2022, 7:55pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745/3 "2022-05-18T19:55:29Z")

</div>

Thanks for the link, I did look at the post but I’m not sure if I can take that approach here. I have restrictions where the time ranges have to look like the large strings I have circled.

Here is working code from one of the text fields where monday\_setup and tuesday\_setup are my long string properties that are copied from the monday and tuesday string values from the db:

```auto
if event.propertyName == 'text':
	
	def timeRangeBreakup(rangeStr, delimiter):
		componentList = []
		lastBreak = 0
		for i in range(len(rangeStr)):
			if rangeStr[i] == delimiter:
				componentList.append(rangeStr[lastBreak: i])
				lastBreak = i + 1
			if i == len(rangeStr)-1:
				componentList.append(rangeStr[lastBreak:len(rangeStr)].strip())
		return componentList
	
	event.source.parent.getComponent('select_shift 1').visible = 0
	rangesStrSetup = event.source.parent.monday_setup
	nextDayRangesStrSetup = event.source.parent.tuesday_setup
	
	
	
	try:
		rangeStr = event.source.text
		time = timeRangeBreakup(rangeStr, '-')
		start = time[0]
		startComponents = timeRangeBreakup(start,':')
		startRightSideTime = timeRangeBreakup(startComponents[1],' ')
		startMinute = int(startRightSideTime[0])
		startAMPM = startRightSideTime[1]
		if startAMPM.startswith('A'):
			startHour = int(startComponents[0])
		else:
			startHour = int(startComponents[0]) + 12
		
		end = time[1]
		endComponents = timeRangeBreakup(end,':')
		endRightSideTime = timeRangeBreakup(endComponents[1],' ')
		endMinute = int(endRightSideTime[0])
		endAMPM = endRightSideTime[1]
		if endAMPM.startswith('A'):
			endHour = int(endComponents[0])
		else:
			endHour = int(endComponents[0]) + 12

	except:
		startHour = 23
		startMinute = 58
		endHour = 23
		endMinute = 59

	nextDayActive = 0
	nextDayStartHour = 0
	nextDayStartMinute = 0
	nextDayEndHour = 0
	nextDayEndMinute = 0
	if startHour >= endHour:
		if startMinute > endMinute:
			#Range goes past midnight
			nextDayActive = 1
			nextDayEndHour = endHour
			nextDayEndMinute = endMinute
			endHour = 23
			endMinute = 59
	
	now = system.date.now()		
	startDate = system.date.setTime(now,startHour,startMinute,0)
	endDate = system.date.setTime(now,endHour,endMinute,0)
	
	tomorrow = system.date.addDays(now,1)
	nextDayStartDate = system.date.setTime(tomorrow,nextDayStartHour,nextDayStartMinute,0)
	nextDayEndDate = system.date.setTime(tomorrow,nextDayEndHour,nextDayEndMinute,0)
	
	overlap = False
	nextDayOverlap = False
	if len(rangesStrSetup) > 0:
		ranges = timeRangeBreakup(rangesStrSetup,',')

		for i in ranges:
			time = timeRangeBreakup(i,'-')
			start = time[0]
			startComponents = timeRangeBreakup(start,':')
			startHourSetup = int(startComponents[0])
			startMinuteSetup = int(startComponents[1])
			startDateSetup = system.date.setTime(system.date.now(),startHourSetup,startMinuteSetup,0)
		
			end = time[1]
			endComponents = timeRangeBreakup(end,':')
			endHourSetup = int(endComponents[0])
			endMinuteSetup = int(endComponents[1])
			endDateSetup = system.date.setTime(system.date.now(),endHourSetup,endMinuteSetup,0)

			if (system.date.isBetween(startDate,startDateSetup,endDateSetup) or
				 system.date.isBetween(endDate,startDateSetup,endDateSetup) or
				 system.date.isBetween(startDateSetup,startDate,endDate) or
				 system.date.isBetween(endDateSetup,startDate,endDate)):
				overlap = True					
				break
	
	if nextDayActive:
		if len(nextDayRangesStrSetup) > 0:
			nextDayRanges = timeRangeBreakup(nextDayRangesStrSetup,',')
			for i in nextDayRanges:
				time = timeRangeBreakup(i,'-')
				start = time[0]
				startComponents = timeRangeBreakup(start,':')
				nextDayStartHourSetup = int(startComponents[0])
				nextDayStartMinuteSetup = int(startComponents[1])
				nextDayStartDateSetup = system.date.setTime(tomorrow,nextDayStartHourSetup,nextDayStartMinuteSetup,0)
			
				end = time[1]
				endComponents = timeRangeBreakup(end,':')
				nextDayEndHourSetup = int(endComponents[0])
				nextDayEndMinuteSetup = int(endComponents[1])
				nextDayEndDateSetup = system.date.setTime(tomorrow,nextDayEndHourSetup,nextDayEndMinuteSetup,0)
				
				if (system.date.isBetween(nextDayStartDate,nextDayStartDateSetup,nextDayEndDateSetup) or
					 system.date.isBetween(nextDayEndDate,nextDayStartDateSetup,nextDayEndDateSetup) or
					 system.date.isBetween(nextDayStartDateSetup,nextDayStartDate,nextDayEndDate) or
					 system.date.isBetween(nextDayEndDateSetup,nextDayStartDate,nextDayEndDate)):
					nextDayOverlap = True					
					break

	formatStartTimeHH = system.date.format(startDate,"HH:mm")
	formatEndTimeHH = system.date.format(endDate,"HH:mm")
	formatStartTimehh = system.date.format(startDate,"hh:mm a")
	formatEndTimehh = system.date.format(endDate,"hh:mm a")

	timeRangeHH = formatStartTimeHH + ' - ' + formatEndTimeHH
	timeRangehh = formatStartTimehh + ' - ' + formatEndTimehh
	
	nextDayFormatStartTimeHH = system.date.format(nextDayStartDate,"HH:mm")
	nextDayFormatEndTimeHH = system.date.format(nextDayEndDate,"HH:mm")
	nextDayFormatStartTimehh = system.date.format(nextDayStartDate,"hh:mm a")
	nextDayFormatEndTimehh = system.date.format(nextDayEndDate,"hh:mm a")
	
	nextDayTimeRangeHH = nextDayFormatStartTimeHH + ' - ' + nextDayFormatEndTimeHH
	nextDayTimeRangehh = nextDayFormatStartTimehh + ' - ' + nextDayFormatEndTimehh
	
	if rangeStr is not None and len(rangeStr) > 0:
	
		if overlap == False and nextDayOverlap == False:
	
			if len(rangesStrSetup) > 0:
				rangesStr = rangesStrSetup + ' , ' + timeRangeHH
			else:
				rangesStr = timeRangeHH
			
			event.source.parent.monday_setup = rangesStr
			
			if nextDayActive:
				if len(nextDayRangesStrSetup) > 0:
					nextDayRangesStr = nextDayRangesStrSetup + ' , ' + nextDayTimeRangeHH
				else:
					nextDayRangesStr = nextDayTimeRangeHH
				
				event.source.parent.tuesday_setup = nextDayRangesStr
			event.source.parent.refresh = True
		else:
			if overlap:
				event.source.parent.getComponent('tf 1').nonEditableBackground = system.gui.color(255,0,0)
			if nextDayOverlap:
				1
				#system.gui.messageBox('This entry does not fit the schedule','Overlaps next day')
	else:
		daySetup = event.source.parent.monday_setup
		ranges = timeRangeBreakup(daySetup, ',')
		print ranges
		loopCount = 0
		newList = ''
		for i in ranges:
			if loopCount == 0:
				newList = i
			elif loopCount == 1:
				newList = newList + ' , ' + i
			elif loopCount == 2:
				newList = newList + ' , ' + i
			loopCount += 1
		
		event.source.parent.monday_setup = newList	
		event.source.parent.refresh = True
		#How to remove the corresponding item from the next day for shift 3?	

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [May 18, 2022, 8:06pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745/4 "2022-05-18T20:06:47Z")

</div>

> [@AJBerera](#):
>
> the time ranges have to look like the large strings I have circled.

I think this is a terrible mistake. A sequence of time ranges should always be specified by start time where the end time is implied by the following entry. When used in SQL for joins, it should look something like this:

```auto
SELECT ....
FROM some_production_data pd INNER JOIN production_shifts_for_range(....) s
  ON pd.t_stamp >= s.beginat AND pd.t_stamp < s.endat

```

Note the equals only for the start point of the shift. This is called a "half-open interval". Using half-open intervals is crucial to avoiding inefficient date conversions to/from strings to perform grouping and bounds checking.

My 2¢. Ignore as you wish.

Or present it your way in the UI and transform it into an efficient representation for actual use.

---

<div class="post-metadata">

**Author:** ![AJBerera](https://avatars.discourse-cdn.com/v4/letter/a/9fc29f/32.png) [@AJBerera](https://forum.inductiveautomation.com/u/AJBerera)\
**Post date:** [May 18, 2022, 9:50pm UTC](https://forum.inductiveautomation.com/t/runtime-schedule/59745/5 "2022-05-18T21:50:46Z")

</div>

Thanks for the feedback Phil.  
I am happy to have your diagnosis.

I am familiar with the sql properties to simplify the ui and time math equations in the way you describe. I will give your suggestion a try and reply with my progress. This isn’t the only use for my schedule system which has an mes component that needs to run transaction groups on remote tag servers.
