Morel Cookbook

Problem

My rows carry dates as strings like "2025-09-02", and I want to work with them as dates — day of week, grouping by month, readable labels.

Setup

Morel 0.9 added the Date and Time structures. Pin the timezone first, or a date built without an explicit offset picks up whatever your machine is set to and your output stops being reproducible:

Sys.set ("timeZone", "UTC");

val orders = [
  { id = 1,  quantity = 12, date = "2025-09-02" },
  { id = 2,  quantity = 30, date = "2025-09-03" },
  { id = 6,  quantity = 20, date = "2025-10-12" },
  { id = 8,  quantity = 25, date = "2025-10-18" },
  { id = 11, quantity = 16, date = "2025-11-27" },
  { id = 12, quantity = 22, date = "2025-11-30" }
];

Example

Date.fromString won't read an ISO string, so split the parts and build the date yourself:

fun monthOf 1 = Jan | monthOf 2 = Feb | monthOf 3  = Mar | monthOf 4  = Apr
  | monthOf 5 = May | monthOf 6 = Jun | monthOf 7  = Jul | monthOf 8  = Aug
  | monthOf 9 = Sep | monthOf 10 = Oct | monthOf 11 = Nov | monthOf _  = Dec;

fun parseDate s =
  case List.map Int.fromString (String.tokens (fn c => c = #"-") s) of
      [SOME y, SOME m, SOME d] =>
        Date.date { year = y, month = monthOf m, day = d, hour = 0,
                    minute = 0, second = 0, offset = SOME Time.zeroTime }
    | _ => raise Fail ("not an ISO date: " ^ s);

from ord in orders
  yield { ord.id, weekday = Date.weekDay (parseDate ord.date) };
val it =
  [{id=1,weekday=Tue},{id=2,weekday=Wed},{id=6,weekday=Sun},
   {id=8,weekday=Sat},{id=11,weekday=Thu},{id=12,weekday=Sun}]
  : {id:int, weekday:weekday} list

What's happening

parseDate : string -> date is the piece you have to write. The rough edge is that Date.fromString follows Standard ML and expects "Tue Sep 02 00:00:00 2025", not ISO 8601 — feed it "2025-09-02" and you get NONE with no explanation. Splitting on #"-" and calling Date.date is the reliable route, and raise Fail on the fallthrough means a malformed row fails loudly instead of silently becoming a wrong date. Note that raise is all you get — Morel 0.9 has no handle, so the failure stops the query rather than being caught and skipped.

What you get back is worth the ceremony. month and weekday are real datatypes, not integers or strings, so Date.month d = Oct is a typed comparison the compiler checks — misspell Otc and it's a compile error, not an empty result set. That's the difference from a string-typed date column, where WHERE month = 'Otc' just quietly returns nothing.

Variations

Group by month. The key is a month value, so the output column has type month:

from ord in orders
  group { month = Date.month (parseDate ord.date) }
  compute { orders = count over (), totalQty = sum over ord.quantity };
val it =
  [{month=Sep,orders=2,totalQty=42},{month=Oct,orders=2,totalQty=45},
   {month=Nov,orders=2,totalQty=38}]
  : {month:month, orders:int, totalQty:int} list

Filter on a month and format for display with Date.fmt, which takes the same % codes as C's strftime:

from ord in orders
  where Date.month (parseDate ord.date) = Oct
  yield { ord.id, label = Date.fmt "%d %b %Y" (parseDate ord.date) };
val it = [{id=6,label="12 Oct 2025"},{id=8,label="18 Oct 2025"}]
  : {id:int, label:string} list

See also