The weekend came, and passed, but here is finally an attempt at solving this using slightly experimental extensions to the DQL language. Toss the following stuff into a note of its own, and play around with it and see if it matches your request:
## Defining `isRipe()` and `ripeRange()`
```dataviewjs
const dve = dv.evaluationContext.functions
/* isRipe(isoDateTxt, ripeField) return whether the
* isoDateTxt indicates this is "unripe", "ripe" or "overripe"
* according to the ripe period from the ripeField
*
* A few examples
* isRipe("2024-08-08", "september") => "unripe"
* isRipe("2024-08-08", "early aug") => "ripe"
* isRipe("2024-08-08", "jun/jul") => "overripe"
*/
dve.isRipe = (ctx, isoDateTxt, ripeField) => {
const [start, end] = dve.ripeRange(ctx, ripeField)
const beforeEnd = isoDateTxt <= end
const afterStart = isoDateTxt >= start
// console.log(isoDateTxt, " vs ", start, " - ", end, beforeEnd, afterStart)
if (beforeEnd && afterStart) return "ripe"
else if (beforeEnd) return "unripe"
else if (afterStart) return "overripe"
}
/* ripeRange(ripeField) gives start and end period
* for that ripeField value
*
* Internally it only uses the first three characters of
* any period/date indication, and it should understand:
* "jan" - "dec" : All the months
* "early" : July 16th through August 31st
* "mid" : The month of september
* "late" : October 1st through November 15th
* "early <mon>" : From 1st through 14th
* "mid <mon>" : From 8th through 24th
* "late <mon>" : From 16th through end of month
* Combinations are also allowed using "/" between the parts,
* and then the minimum and maximum dates are returned, so
* "mid jan/mar" would return "Jan 8" through "Mar 31"
*
* Note the function returns the isoDate strings, not actual dates
*/
dve.ripeRange = (ctx, ripeField) => {
const periods = {
"jan": [ "-01-01", "-01-31"],
"feb": [ "-02-01", "-02-28"],
"mar": [ "-03-01", "-03-31"],
"apr": [ "-04-01", "-04-30"],
"may": [ "-05-01", "-05-31"],
"jun": [ "-06-01", "-06-30"],
"jul": [ "-07-01", "-07-31"],
"aug": [ "-08-01", "-08-31"],
"sep": [ "-09-01", "-09-30"],
"oct": [ "-10-01", "-10-31"],
"nov": [ "-11-01", "-11-30"],
"dec": [ "-12-01", "-12-31"],
"ear": [ "-07-16", "-08-31"],
"mid": [ "-09-01", "-09-31"],
"lat": [ "-10-01", "-11-15"],
}
const ripePeriods = ripeField.trim().split("/")
let dates = []
for (const period of ripePeriods ) {
const parts = period
.trim()
.toLowerCase()
.split(" ")
.map(a => a.trim().slice(0, 3))
const year = new Date().getFullYear().toString().padStart(4, "0")
// console.log(parts, parts.length, typeof parts)
if (parts.length == 1) {
const dateParts = periods[parts]
dates.push( year + dateParts[0] )
dates.push( year + dateParts[1] )
} else {
let dateParts = periods[parts[1]]
// A bit lazy here, but
// ear is 01 - through 14
// mid is 08 - 24
// lat is 16 - 28/30/31
// Correct start and end of month according to
// ear/mid/lat
if (parts[0] == "ear" )
dateParts[1] = dateParts[1].slice(0, 4) + "14"
else if (parts[0] == "mid" ) {
dateParts[0] = dateParts[0].slice(0, 4) + "08"
dateParts[1] = dateParts[1].slice(0, 4) + "24"
} else if (parts[0] == "lat" )
dateParts[0] = dateParts[0].slice(0, 4) + "16"
dates.push(year + dateParts[0])
dates.push(year + dateParts[1])
}
}
return [dates.reduce((min, val) => min < val ? min : val),
dates.reduce((max, val) => max > val ? max : val) ]
}
```
## Testing various ripe values
```dataview
TABLE WITHOUT ID ripeField,
join(period, " — ") as Range,
isRipe("2024-08-06", ripeField) as "isRipe(aug 6th)",
durationformat(date(period[0]) - date("2024-08-06"), "d") as "daysSince Aug 6th"
WHERE file = this.file
FLATTEN list(
"early", "mid", "late",
"janAuaRY", "FEBru", "march", "apricot",
"maybe", "june", "july",
"early august",
"mid september",
"late octopus",
"late november / early december") as ripeField
FLATTEN list(ripeRange(ripeField)) as period
SORT period[0], period[1]
```
## Sorting according to "ripeness"
Here I'm assuming that each apple has its own note, and that there is a field called `ripe` indicating when that variety is supposedly ripe.
```dataview
TABLE ripe, join(ripePeriod, " – ") as Period
WHERE ripe
FLATTEN list(ripeRange(ripe)) as ripePeriod
SORT ripePeriod[0], ripePeriod[2]
```
The example note consists of three parts: The function definitions, a test query displaying the various options for defining ripeness, and a final query sorting according to ripeness.
The function definitions
Let me start of by stating that these functions do work, but we’re tapping into slightly unchartered territories of Dataview usage, so they might break at any point in time. But they shouldn’t be destructive in any ways, so as long as they work, they work…
I defined two functions in this section, and this code block section needs to be included in every place you want to use those functions (or at least the code block needs to have been executed/run before used in an ordinary DQL query like the two other code blocks shown).
The isRipe()
functions takes an ISO8601 date string, i.e. “2024-04-07”, and a ripe field text. It then returns whether that date text related to the ripeness period is “unripe”, “ripe” or “overripe”. It uses the ripeRange()
function to find the period.
The ripeRange()
function takes a text paramter, the ripe field text, and returns two ISO8601 date strings (not dates). If you want them to be dates for duration calculation or similar, you need to do date(...)
on either start or end of that period. See example in the middle query where I calculate days since start of the ripe period, which can be used to show when to expect it to be ripe or similar.
Test examples
For some examples on both the ripeRange()
and isRipe()
function see the result of the middle query, which should show something like:
Note how I’m just using the three first letters of any date indications, and that they’re lowercased as well. So you could opt to use “january”, “jan”, “JAN”, “jansdfsdlkfjsdf” which would all match for the January entry. I’ve not included any error handling for date indications not already defined, so if you write “unknown” or some other random text, the result is indeed unknown.
Sorting according to ripeness
In the test section I’ve exploded a manually defined list of ripe periods, for ease of reference and coding and just for the purpose of the example. In the latter code block I’ve included a sample query which would look more normal to your every day use case. This query is untested but hopefully you’ll understand how to adapt to your own case.
```dataview
TABLE ripe, join(ripePeriod, " – ") as Period
WHERE ripe
FLATTEN list(ripeRange(ripe)) as ripePeriod
SORT ripePeriod[0], ripePeriod[2]
```
The most “critical” part of this is the FLATTEN list(ripeRange(ripe)) as ripePeriod
, this will extend your data set to include two values: ripePeriod[0]
which is the date text for the start of the period, and ripePeriod[1]
which is the date text for the end of the period.
The two first lines is just to display some values and to limit the query to only list notes (aka apples) which have the field ripe
present. This you’d replace with any query of yours which limits it to show only the apple notes, and the field name you’ve used to indicate the ripe period.
The last line does the actual sorting, and it’s using two sorting parameters. First it sorts by the start of the period, and if that’s equal it then uses the end of the period to give a natural sorting according to the ripeness period.
If you’d want you could also do stuff like:
WHERE isRipe(dateformat(date(today), "YYYY-MM-DD"), ripe) = "ripe"
To check which apple variants are supposedly ripe today.
Well, hopefully this will help you in organising and/or working with your apple varieties, and that it is somewhat understandable how to use this.