Help plan your gardens using these homegrown python scripts
I’ve been busy planning gardens for next year.
If you think it’s too early, being mid-season and all, generally speaking scaling up, changing location and modifying methods presents risk, and risk in gardening usually results in… well, harsh language…
Tediously drafting plans can, at least, minimize some of these problems, hopefully.
A lot of people like using spreadsheets, such as Excel, but I prefer scripts, text files and the command line. I also use Linux. I know, I’m a horrible person. If I want to visualize something, I’ll throw it into a table, graph or latex template, but later. With the environment out of the way, hopefully the methods below won’t be too confusing.
The following is organized into sections:
Plan planting and harvest dates using days to maturity.
Identify calendar weeks for specific dates.
Report growing guide derived tasks for specified weeks.
Figuring out maturity and planting dates
The first neat and simple trick is a script to determine when a crop will finish, and determining when to plant if I want to harvest on a certain date.
This is where python modules come into play.
Let’s say I have a crop that has 90 days to maturity (i.e., DTM). Crops usually fall into the ranges of 60 DTM, 90 DTM or 120 DTM. The odd balls like radishes are shorter (i.e., 30 DTM) and some crops (e.g., certain leek varieties) take a llooonng time (i.e., 180 DTM) before they’re ready.
Playing with 90 DTM is a nice middle zone.
python gives me the ability to select a planting date (e.g., May 28th, 2024), and produce a new date 90 days into the future using timedelta.
from datetime import datetime, timedelta
# Days to maturity
DTM = 90
# Planting date
plant_date = datetime.strptime("2024-05-28", '%Y-%m-%d')
print(plant_date + timedelta(days=DTM))This provides me with an estimated harvest date of August 26th, 2024.
>>> 2024-08-26 00:00:00 I can modify the script to specify a harvest date (e.g., August 15th, 2024) and calculate a planting date backwards. This would be useful if I was trying to grow produce for market, and needed to generate a planting schedule for commercial quotas.
from datetime import datetime, timedelta
# Days to maturity
DTM = 90
# Harvest date
harvest_date = datetime.strptime("2024-08-15", '%Y-%m-%d')
print(harvest_date - timedelta(days=DTM))This tells me I need to plant on May 17th, 2024 to harvest August 15th, 2024.
>>> 2024-05-17 00:00:00 Pretty handy eh?
I can simplify the script to accept both variants, and provide a parameter to change DTM for the calculation. I also want to add variation for date formats, especially if I happen to be copying and pasting from another source, and I don’t like the time part (i.e., 00:00:00) printed out on the end for the date, so that’ll need a format change.
(See full script in Appendix A.1 — gsched.py.)
The script might look complicated, but now I have one script that can use different date formats for calculations. I can easily pass in start or end dates to help build my planting and harvesting schedule. I’m calling it gsched.py for the examples below.
Let’s test first.
(Protip™, try to test tools first to understand errors. This minimizes the time spent troubleshooting problems. Errors are usually differences between expected use and actual use.)
$ python gsched.py
Supply a plant or harvest date
$ python gsched.py -P
usage: gsched.py [-h] [-P PLANT] [-H HARVEST] [-D DTM]
gsched.py: error: argument -P/--plant: expected one argument
$ python gsched.py -P 2024-05-28
Days to maturity is required
$ python gsched.py -P 2024-05-28 -H 2024-08-15
Cannot supply both a plant and harvest dateOkay, let’s try some dates, three different formats and two different DTM.
$ python gsched.py -P 2024-05-28 -D 90
2024-08-26
$ python gsched.py -H 2024-08-15 -D 90
2024-05-17
$ python gsched.py -P 28/05/2024 -D 60
2024-07-27
$ python gsched.py -H 20240701 -D 60
2024-05-02The script is nice and short, passes the error tests, and pops out a date in a standard format for me to deal with later. If I wanted to, I could always wrap it in a python module to use elsewhere, but let’s keep things simple.
With this approach, I can make a flat text file with different dates (and days to maturity), and feed it into the script. This is good for quick and dirty estimates.
The sample input file containing successions is called plant_dates.csv.
# Crop Plant Date DTM
Lettuce, 2024-05-10, 60
Carrots, 2024-05-17, 60
Spr.On., 2024-05-24, 60
Lettuce, 2024-05-31, 60
Carrots, 2024-06-07, 60
Spr.On., 2024-06-14, 60
Lettuce, 2024-06-21, 60
Carrots, 2024-06-28, 6060 DTM is pretty normal for kitchen type crops like lettuce, carrots and spring onions. If I’m following this planting from May through June, when will the crops be ready? Well, I can just use this file in another script to give me the answer.
(See full script in Appendix A.4 — Bash script.)
My bash script might look a little ugly, especially if you’ve never used bash, but just I’m looping through each row, grabbing the columns, printing the planting date and calculating its harvest date.
(I made a python version too for readability, but bash is nicer for command outputs.)
The results of the script are below.
# Crop Plant Date Harvest Date
Lettuce, 2024-05-10, 2024-07-09
Carrots, 2024-05-17, 2024-07-16
Spr.On., 2024-05-24, 2024-07-23
Lettuce, 2024-05-31, 2024-07-30
Carrots, 2024-06-07, 2024-08-06
Spr.On., 2024-06-14, 2024-08-13
Lettuce, 2024-06-21, 2024-08-20
Carrots, 2024-06-28, 2024-08-27That’s pretty handy, because I can see I’m not at the end of the season with this schedule. To do that, I can use the -H switch on my last harvest date, which for fun let’s say is September 13th, 2024.
$ python gsched.py -H 2024-09-13 -D 60
2024-07-15That tells me I can continue my successions up until July 15th, 2024, which is two extra weeks of planting I can add to my original schedule. Not too shabby. This gives me the tools to plan the succession scheme I described in my previous article.
I can also do this for other things besides plants.
Let’s pretend I’m raising Cornish Cross meat chickens, which are 8 weeks to maturity, and I get them at 1 week old June 4th, 2024. When will the chickens be ready for the processor? Well this isn’t some high school math problem, and can easily be answered with my script on the command line. Cornish Cross take 8 - 9 weeks to mature before processing, there are 7 days in a week and they’re already 1 week old on June 4th.
$ python gsched.py -P 2024-06-04 -D $(python -c "print(7*(8-1))")
2024-07-238 weeks out (minus 1) from June 4th, 2024 is July 23rd, 2024.
That means I need to setup an appointment with the processor either the week of July 21st or the week of July 28th. They only take 2 months to mature, which looks about right minus 1 week. If for some reason I had a milestone that needed to be fulfilled at 6 weeks (check up, maybe?), I can tweak the calculation to generate a milestone date.
$ python gsched.py -P 2024-06-04 -D $(python -c "print(7*(6-1))")
2024-07-09So, the week of July 7th I can schedule my milestone activities for 6 weeks of maturity.
However, for each date I’m looking up the week in a calendar, which is annoying.
(God grace me with more patience, if there’s any still available.)
Is there an easier way?
Putting it in a calendar
The answer should be ‘of course,’ but I don’t want to waste too much time doing it.
Fortunately, python has a calendar module that allows me to return the entire week for a specified date. Useful, because I can take its outputs to draft a task planner and ultimately a day / week driven schedule.
I want a script that returns a week index and days from Sunday to Saturday in a list by just providing a date.
(This is a little uglier than the last script, but not too bad.)
(See full script in Appendix A.2 — gcal.py.)
I’m calling this gcal.py.
Let’s do some testing first…
$ python gcal.py
Supply a date
$ python gcal.py -D 2024-07-23
Supply switch for week number or week days
$ python gcal.py -D 2024-07-23 -N
35
$ python gcal.py -D 2024-07-23 -W
['2024-07-21', '2024-07-22', '2024-07-23', '2024-07-24', '2024-07-25', '2024-07-26', '2024-07-27']Okay, now I can double check the weeks for the pretend chicken milestone date of July 9th, 2024 and their processing date of July 23rd, 2024.
$ python gcal.py -D 2024-07-09 -N -W
33
['2024-07-07', '2024-07-08', '2024-07-09', '2024-07-10', '2024-07-11', '2024-07-12', '2024-07-13']
$ python gcal.py -D 2024-07-23 -N -W
35
['2024-07-21', '2024-07-22', '2024-07-23', '2024-07-24', '2024-07-25', '2024-07-26', '2024-07-27']I get the week of July 7th and week of July 21st.
Looks pretty good.
At this point, I can calculate start and finish dates forwards and backwards based on an arbitrary maturity in days, and lookup what week those days fall on to help me draft tasks. The beauty of this approach is the simplicity of the scripts. No bothersome modules or awkward infrastructure. Obviously, I’m using it in a Linux / *nix environment, so it’d be slightly different (and more awkward) on Windows, but it all works the same, because it’s just text. Easy enough to import into Excel too.
But wait, there’s still something else I want to know.
An even more realistic example
This is good enough for marking in a calendar or possibly building a gantt chart, but I would like something a little, extra. If I have 8 successions of one crop growing, I have to keep track of what stage each is at, and not to mention each type of crop develops differently and may need special attention at certain stages. I want to take advantage of the calendar function and when it was planted to help me out.
I’m going to use soybeans as an example.
For some crops, you can find detailed growing guides that breakdown either stages of life (i.e., sol) or specific tasks based on maturity. I’m going to take one for soybeans I found on the University of Minnesota Extension site.
This file is named soy_sol.txt
(Date ranges, short description and long description are colon (i.e., “:”) separated. I picked that format, because it was close to the original copy and paste, and is simple to read and write.)
Day 0-10: (VE) Emergence: Cotyledons above the soil surface.
Day 10-15: (VC) Cotyledon: Unifoliolate leaves sufficiently unroll, so the leaf edges do not touch.
Day 15-20: (V1) First node: Fully developed leaves at unifoliolate node.
Day 20-40: (V(n)) nth-node: The “n” represents the number of nodes on the main stem with fully developed leaves, beginning with the unifoliolate leaves.
Day 40-43: (R1) Beginning bloom: One open flower at any node on the main stem.
Day 43-46: (R2) Full bloom: Open flower at one of the two uppermost nodes on the main stem with a fully developed flower.
Day 46-56: (R3) Beginning pod: Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
Day 56-65: (R4) Full pod: Three-quarter-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
Day 65-74: (R5) Beginning seed: One-eighth-inch-long seed in a pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
Day 74-89: (R6) Full seed: Pod containing a green seed that fills the pod cavity at one of the four uppermost nodes on the main stem with a fully developed leaf.
Day 89-107: (R7) Beginning maturity: One normal pod on the main stem that has reached its mature pod color.
Day 107-116: (R8) Full maturity: 95 percent of the pods have reached their mature pod color. After R8, five to 10 days of drying weather are required to reduce soybean moisture levels to less than 15 percent.Note these are the average number of days. I could use the range column from the website (which is the standard deviation added and subtracted to the average), but then I’m in the realm of statistical estimates, and I’d have to factor that into my script. Just averages is good for now. Besides, I could always make another file that has a lower or upper timeline and modify the descriptions based on that if I felt it helped me.
I’m going to pretend I started these plants May 23rd, 2024. Using the stages of life above, I can calculate when they should be finished using the first script.
$ python gsched.py -P 2024-05-23 -D 116
2024-09-16According to the script, the beans should finish completely by September 16th, 2024. However, mid-season I may want to observe the plants, and cross reference it with the stages of life from the growing guide. Doing the lookup manually is going to be time consuming, so instead I can make use of another script I call gtask.py.
(Sorry, this one is longer and uglier.)
(See full script in Appendix A.3 — gtask.py.)
Let’s pick a day mid-season, say July 9th, 2024.
Then we can look at the week using my second script.
$ python gcal.py -D 2024-07-09 -N -W
33
['2024-07-07', '2024-07-08', '2024-07-09', '2024-07-10', '2024-07-11', '2024-07-12', '2024-07-13']This is week 33 in the calendar index.
Again, we’ll test the new script a little first.
(On a side note, this script isn’t that robust, because it doesn’t thoroughly validate the input.)
$ python gtask.py
Supply a date
$ python gtask.py -P 2024-05-23
Supply a week
$ python gtask.py -P 2024-05-23 -N 33
Supply a list
$ python gtask.py -P 2024-05-23 -N 33 -L soy_sol.txt
Supply short and/or long formatNow, I’m going to combine the planting date, the week number and the growing guide increments to tell me what stage the plants are at.
I want a short form first, for readability.
$ python gtask.py -P 2024-05-23 -N 33 -L soy_sol.txt -S
2024-07-07
(R2) Full bloom
2024-07-08
(R2) Full bloom
(R3) Beginning pod
2024-07-09
(R3) Beginning pod
2024-07-10
(R3) Beginning pod
2024-07-11
(R3) Beginning pod
2024-07-12
(R3) Beginning pod
2024-07-13
(R3) Beginning podI like this, because it alerts me that I should start to see pods that week.
Everyday you should be inspecting plants as much as possible to notice changes. I can’t count the number of times I wish I knew what transitions I’m looking for.
Now, for the short and long form.
$ python gtask.py -P 2024-05-23 -N 33 -L soy_sol.txt -S -F
2024-07-07
(R2) Full bloom
Open flower at one of the two uppermost nodes on the main stem with a fully developed flower.
2024-07-08
(R2) Full bloom
Open flower at one of the two uppermost nodes on the main stem with a fully developed flower.
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
2024-07-09
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
2024-07-10
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
2024-07-11
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
2024-07-12
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.
2024-07-13
(R3) Beginning pod
Three-sixteenths-inch-long pod at one of the four uppermost nodes on the main stem with a fully developed leaf.I like the long format, because if I need my memory jogged I can refresh it with detail.
You’ll notice how handy this is. You see, I can combine any list of tasks I want with the plant date and week. That means I can create a list of high level tasks that I call operations (i.e., ops), and follow along with the stages of life if I need more information.
The operations list is called soy_ops.txt.
Week 1-4: Cell tray
Week 4-5: Transplant
Week 5-15: Monitor
Week 15-17: HarvestNow, I can run the same script, but use the operations list to determine what I should be doing for a particular crop. I didn’t include a long form, but I could always add more information for each task group whenever I need it.
$ python gtask.py -P 2024-05-23 -N 33 -L soy_ops.txt -S -F
2024-07-07
Monitor
2024-07-08
Monitor
2024-07-09
Monitor
2024-07-10
Monitor
2024-07-11
Monitor
2024-07-12
Monitor
2024-07-13
MonitorI designed the gtask.py script to accept arbitrary time dependent input, and I organized that input so I can have levels of refinement depending on my need:
Operations.
High level notification in short form.
Instructions in long form.
Stages of life.
High level stage in short form.
Detailed changes in long form.
Okay, now let’s look closer at the harvest date of September 16th, 2024.
$ python gcal.py -D 2024-09-16 -N -W
44
['2024-09-15', '2024-09-16', '2024-09-17', '2024-09-18', '2024-09-19', '2024-09-20', '2024-09-21']
$ python gtask.py -P 2024-05-23 -N 44 -L soy_ops.txt -S -F
2024-09-15
Harvest
2024-09-16
Harvest
2024-09-17
Harvest
2024-09-18
Harvest
2024-09-19
Harvest
2024-09-20
2024-09-21
Not bad.
The tasks are shifted a little from September 16th, 2024, but that’s because I specified the operations in weeks. Besides, it’s close enough, and in reality the task would fall into a day range anyway.
Let’s take a look at the stages of growth at harvest.
$ python gtask.py -P 2024-05-23 -N 44 -L soy_sol.txt -S
2024-09-15
(R8) Full maturity
2024-09-16
(R8) Full maturity
2024-09-17
2024-09-18
2024-09-19
2024-09-20
2024-09-21Perfect. If I look at the week prior (i.e., week 43), the growing guide says it’s at full maturity as well. This lines up with my operations timeline at the week level.
$ python gtask.py -P 2024-05-23 -N 43 -L soy_sol.txt -S
2024-09-08
(R8) Full maturity
2024-09-09
(R8) Full maturity
2024-09-10
(R8) Full maturity
2024-09-11
(R8) Full maturity
2024-09-12
(R8) Full maturity
2024-09-13
(R8) Full maturity
2024-09-14
(R8) Full maturity
$ python gtask.py -P 2024-05-23 -N 43 -L soy_ops.txt -S -F
2024-09-08
Monitor
2024-09-09
Monitor
2024-09-10
Monitor
2024-09-11
Monitor
2024-09-12
Monitor
Harvest
2024-09-13
Harvest
2024-09-14
HarvestGood enough. I could always fiddle with the operations timeline if needed.
Also, not sure if you caught it, but the stages of life and operations are actually incomplete on the end. R8 in the long form said “five to 10 days of drying weather,” which means I should add another week or two to include field drying. That’s why it’s important to thoroughly read each guide, and use you own experience and intuition.
These are handy scripts that are quick and dirty for thought experiments and drafting.
No need for fancy software or diagrams. If I want to visualize something, I can always export the dates, import them into some other software and see what it looks like without wasting too much time or getting overly frustrated and lost.
Conclusion
These 3 scripts are quick and easy ways to generate a schedule and enumerate tasks.
gsched.py
Generate planting or harvest days based on days to maturity.-P: Planting date.-H: Harvest date.-D: Days to maturity.
gcal.py
Return the week number and calendar week from Sunday to Saturday for a date.-D: Date to lookup calendar week.-N: Generate week number.-W: Generate week days.
gtask.py
Return tasks based on planting date and list using week number and formatting.-P: Planting date.-N: Week number.-L: List of time dependent tasks.-S: Short format.-F: Long format.
What’s missing is a comprehensive growing guide for specific crops.
This is likely the most time consuming task, but I can probably just use high level operation activities at the week level. If there’s a particular crop of interest, perhaps it’s being grown commercially and quality is a factor, I can dig into the day level stages of life to compliment an operations list. What’s probably easier is to create cold / warm weather guides, root guides, leaf guides, fruit guides, etc. per days of maturity. That makes things simple, and if I need more specificity I can easily add it later.
The purpose of these scripts is to reduce work, not create more!
Also, don’t forget that some DTM values don’t include germination.
Make sure you carefully read what the guides and packages say.
For future work, I would consider condition-based tasks that can be generated based on average temperature, precipitation or possibly live weather forecasts. This would be useful to recommend protection / watering schemes for cold weather (e.g., fleece or plastic) or hot weather (e.g., shade cloth). I could also include “stretch factors” for colder weather. Some crops can grow in colder weather, but the days to maturity get stretched by 1.5x or 2x depending on the temperature. You’ll notice this scheme if you read through enough market gardener plans. Same goes with solar exposure after the fall equinox and before the spring equinox.
I already reverse engineered the Almanac Farmer last spring frost and first fall frost dates using meteostat weather data, so I should be able to suggest season extension schemes if I select a specific harvest date and days to maturity.
I could also examine historical extremes. I was trying to recalculate the USDA hardiness zones and noticed the zoning changes depending on the time range I select or if I use historical minimums instead of minimum average - standard deviation.
That means some perennials I plant based on the recommended zoning could die during one-off years. This is something to consider if I’m relying on perennials for food production. I could use this information to determine how valuable it is to develop disaster recovery plans if severe weather destroys a lot of crops or identify reliable protection schemes just-in-case.
These scripts are not intended to replace heuristics, best practices or experienced-based intuition. Instead, they provide an extra level of rigour to help simplify planning activities and produce a day / week level checklist that might be difficult to keep track of during busy times. The outputs should not be surprising, but reminding.
A tool should never replace a user, but rather improve efficiency and quality.
Let me know if you found these scripts useful, and if you can think of any other features that would be a time / quality saver for garden planning or execution.
Happy growing :)
Appendix A
A.1 — gsched.py
from datetime import datetime, timedelta
import argparse
# Allow for different date formats
def try_parsing_date(text):
for fmt in ('%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y','%Y%m%d'):
try:
return datetime.strptime(text, fmt)
except ValueError:
pass
raise ValueError('no valid date format found')
parser = argparse.ArgumentParser(description='Garden scheduler.')
# Arguments for commandline
parser.add_argument('-P','--plant',type=str,help='A plant date')
parser.add_argument('-H','--harvest',type=str,help='A harvest date')
parser.add_argument('-D','--dtm',type=int,help='Days to maturity')
args = parser.parse_args()
# Error check
if args.plant is None and args.harvest is None:
print("Supply a plant or harvest date")
exit(-1)
if args.plant is not None and args.harvest is not None:
print("Cannot supply both a plant and harvest date")
exit(-1)
if args.dtm is None:
print("Days to maturity is required")
exit(-1)
if args.plant:
plant_date = try_parsing_date(args.plant)
plant_date = plant_date + timedelta(days=args.dtm)
print(plant_date.strftime('%Y-%m-%d'))
if args.harvest:
harvest_date = try_parsing_date(args.harvest)
harvest_date = harvest_date - timedelta(days=args.dtm)
print(harvest_date.strftime('%Y-%m-%d'))A.2 — gcal.py
from datetime import datetime
import calendar
import argparse
cal = calendar.Calendar()
# Use Sunday as the first day of the week
cal.setfirstweekday(calendar.SUNDAY)
# Allow for different date formats
def try_parsing_date(text):
for fmt in ('%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y','%Y%m%d'):
try:
return datetime.strptime(text, fmt)
except ValueError:
pass
raise ValueError('no valid date format found')
parser = argparse.ArgumentParser(description='Week lookup.')
# Arguments for commandline
parser.add_argument('-D','--date',type=str,help='A date to lookup')
parser.add_argument('-N','--number', action='store_true', help='Week number')
parser.add_argument('-W','--week', action='store_true', help='Week days')
args = parser.parse_args()
# Error check
if args.date is None:
print("Supply a date")
exit(-1)
if not args.number and not args.week:
print("Supply switch for week number or week days")
exit(-1)
# Build two tables to lookup weeks by number or date
def build_lookup(year):
d={} # Date -> Week Number
k={} # Week Number -> Date
week_index=0
for row in [list(cal.monthdatescalendar(year, i)) for i in range(1,12+1)]:
for r in row:
for date_value in r:
d[str(date_value)] = week_index
if week_index not in k:
k[week_index] = [str(date_value)]
else:
k[week_index].append(str(date_value))
week_index = week_index + 1
return d,k
date_value = try_parsing_date(args.date)
lookup_by_date,lookup_by_week = build_lookup(date_value.year)
# Return week number on the first line
week = lookup_by_date[date_value.strftime('%Y-%m-%d')]
if args.number:
print(week)
# Return days by the week number
if args.week:
print(lookup_by_week[week])A.3 — gtask.py
from datetime import datetime, timedelta
import calendar
import argparse
import re
import os.path
cal = calendar.Calendar()
# Use Sunday as the first day of the week
cal.setfirstweekday(calendar.SUNDAY)
# Allow for different date formats
def try_parsing_date(text):
for fmt in ('%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y','%Y%m%d'):
try:
return datetime.strptime(text, fmt)
except ValueError:
pass
raise ValueError('no valid date format found')
parser = argparse.ArgumentParser(description='Task lookup by week and start date.')
# Arguments for commandline
parser.add_argument('-P','--plant',type=str,help='A plant date')
parser.add_argument('-N','--week',type=int,help='A week to lookup')
parser.add_argument('-L','--list',type=str,help='A list to reference')
parser.add_argument('-S','--short', action='store_true', help='Short description')
parser.add_argument('-F','--long', action='store_true', help='Long description')
args = parser.parse_args()
# Error check
if args.plant is None:
print("Supply a date")
exit(-1)
if args.week is None:
print("Supply a week")
exit(-1)
if args.list is None:
print("Supply a list")
exit(-1)
if not os.path.exists(args.list):
print("List file does not exist")
exit(-1)
if not args.short and not args.long:
print("Supply short and/or long format")
exit(-1)
# Build two tables to lookup weeks by number or date
def build_lookup(year):
d={} # Date -> Week Number
k={} # Week Number -> Date
week_index=0
for row in [list(cal.monthdatescalendar(year, i)) for i in range(1,12+1)]:
for r in row:
for date_value in r:
d[str(date_value)] = week_index
if week_index not in k:
k[week_index] = [str(date_value)]
else:
k[week_index].append(str(date_value))
week_index = week_index + 1
return d,k
# Parse the week / day ranges
def parse_time(value):
tunit = 1
if 'week' in value.lower():
tunit = 7
if 'day' in value.lower():
tunit = 1
if 'month' in value.lower():
tunit = 7*4
rex = "^\s*(\d+)\s+\S+[s]*\s*"
a = re.search(rex, value)
if a:
return a.group(1),None,tunit
rex = "^\s*(\d+)\s*[-]\s*(\d+)\s+\S+[s]*\s*"
a = re.search(rex, value)
if a:
return (a.group(1),a.group(2)),tunit
rex = "^\s*\S+[s]*\s+(\d+)\s*[-]\s*(\d+)*\s*"
a = re.search(rex, value)
if a:
return (a.group(1),a.group(2)),tunit
# Read the list and build out tasks
def read_list(list_text,start,target_days):
lines = [l.strip() for l in list_text.split("\n") if l]
tasks={}
for line in lines:
field = line.split(":")
t = parse_time(field[0])
day_range = t[0]
multiplier = t[1]
shortd = field[1].strip()
longd = ""
if len(field) == 3:
longd = field[2].strip()
for day in target_days:
days = (try_parsing_date(day) - start).days
if day not in tasks:
tasks[day] = []
if len(day_range) == 2:
if int(day_range[0])*multiplier <= days and days <= int(day_range[1])*multiplier:
tasks[day].append({"short":shortd,"long":longd})
return tasks
# Lookup weeks and days
date_value = try_parsing_date(args.plant)
lookup_by_date,lookup_by_week = build_lookup(date_value.year)
days = lookup_by_week[args.week]
# Get tasks and print them out
tasks = read_list(open(args.list,'r').read(),date_value,days)
for day in tasks:
print(day)
for item in tasks[day]:
if args.short:
if item['short']:
print('\t',item['short'])
if args.long:
if item['long']:
print('\t',item['long'])A.4 — Bash script
I used a bash script below.
Bash is the command line language and is typically used to “glue” scripts together.
I could easily do the same thing in python as well. This is the logic for the script: a.) I want to get rid of the comment ‘#’, so I do a grep -v. b.) The $(…) operator in bash captures outputs to a variable. c.) The iterator in bash by default is just spaces, which needs to be changed to newline. d.) sed also happens to be the easiest way to grab the fields.
IFS=$'\n' # make newlines the only separator
plant_dates=$(cat plant_dates.csv | grep -v '#') # ignore comment
format='s/(\S+),\s*(\S+),\s*(\S+)/' # 3 col csv format
echo "# Crop Plant Date Harvest Date"
for row in $plant_dates # iterate rows
do
crop=$(echo $row | sed -r $format'\1/g') # col 1
plant_dt=$(echo $row | sed -r $format'\2/g') # col 2
dtm=$(echo $row | sed -r $format'\3/g') # col 3
harvest_dt=$(python gsched.py -P $plant_dt -D $dtm) # calc harvest
echo "$crop, $plant_dt, $harvest_dt" # print in csv format
done I also made a python version if the bash version is too hard to read.
import re
import subprocess
sep=","
# Read file
content = open("plant_dates.csv",'r').read()
# Print header
print("# Crop Plant Date Harvest Date")
for line in content.split('\n'):
# Ignore header
line = re.sub('[#].*$','',line)
line = line.strip()
if line:
cols = [l.strip() for l in line.split(sep)]
if len(cols) == 3:
# Build command and run it
cmd = "python gsched.py -P {0} -D {1}".format(cols[1],cols[2])
harvestdate = subprocess.check_output(cmd, shell=True)
harvestdate = harvestdate.strip().decode('utf-8')
# Print results in csv format
print("{0}, {1}, {2}".format(cols[0],cols[1],harvestdate))
else:
print("err: Could not read '" + line + "'")







