Skip to content

Releases: bertalhia/swift-chrono-pt

0.13.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 21:54

Added

  • Match.isApproximate: "umas 8", "por volta de 15h", "daqui a pouco",
    "meados de outubro".
  • ChronoPT.strippingDates(_:from:) takes matches an app already has.
  • Recurrence.rrule(allDayIn:) writes UNTIL as a date for events with no
    time, and Recurrence.recurrenceRule(in:) gives Foundation's
    Calendar.RecurrenceRule from iOS 18 and macOS 15.
  • A weekday that ends a phrase is a day: "dentista terça", "ligar sexta pro
    João". After "a", "o" or "em" it stays an ordinal.
  • A weekday after its date: "15/10, quinta", "28/9 (seg)", "ter., 29 de set.".
  • Opening phrases: "a partir de segunda", "antes das 10", "depois das 18h",
    also taken out by strippingDates with the brackets around a date.
  • Chat spellings: "semana q vem", "próx. segunda", "sexta próxima", "dia 1°",
    "15hrs30", "td dia"; with past dates, "sábado retrasado".
  • "meio dia" without a hyphen next to a day or with minutes: "amanhã meio
    dia", "meio dia e meia".
  • Short hours and prescriptions: "a cada 4h", "8/8h", "12/12h por 10 dias";
    "daqui uns 10 dias", "1 mês e meio", "5 minutinhos".
  • Repeating forms: "todo dia útil", "todo fim de semana", "todo fim de mês",
    "todo dia 15 e 30", "toda segunda a sexta", "segundas e quartas às 19h",
    "toda semana na quarta", "quinzenal às quintas", "toda noite às 22h".

Fixed

  • "todo dia útil" was daily, weekends included.
  • "1 hora de academia" and "2 horas de estudo" were clock times.

Performance

  • Compiled regexes are shared through a pool: after the first parse in a
    process, a parse on a new thread takes 0.5 ms instead of 10 ms, and memory
    follows how many parses run at once, not how many threads ran one.
  • Business days keep each year's holidays for the length of a count: "em 999
    dias úteis" went from 8.4 ms to 1.4 ms with interpret.

0.12.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 21:26

The API as it is meant to stay for 1.0. See Stability in the README.

Breaking

  • Recurrence.weekdays is a set of Recurrence.Weekday, each with its own
    place in the month (.every(.monday), .nth(-1, .friday));
    weekdayOrdinal is gone.
  • Recurrence.timesPerPeriod becomes rate, a count with its own unit.
  • Recurrence and Options have chosen Codable forms: End writes
    {"until": …} or {"count": 5}, sets are written in order, invalid values
    are refused, and Options writes defaultHour. Options saved by 0.x still
    decode.
  • Options.moments maps phrases to ChronoPT.TimeOfDay, so a moment can
    have minutes; ["no treino": 7] still compiles.
  • Match.interval becomes dateInterval: whole days for a date with no time,
    start to end for a time range.
  • Parser defaults to Calendar.autoupdatingCurrent.

Added

  • Recurrence.timesOfDay: "remédio às 8h e às 20h todo dia" is one daily rule
    at both times.
  • PartialDate.timeZone keeps the zone the text named.
  • A day inside a period: "semana que vem, na quarta", "dia 25 do mês que vem",
    "em outubro, dia 5", "dia 20 de outubro do ano que vem".
  • Named days in a month: "a primeira segunda-feira de outubro", "na última
    sexta do mês", "fim de outubro", "meados de outubro", "na primeira semana de
    outubro", "5º dia útil", "último dia útil de outubro".
  • Ranges: "do dia 10 ao 15", "de 10 a 15/10", "10-15 de outubro", "de outubro
    a dezembro", and a time at each end, "de segunda às 14h até sexta às 18h".
  • From now: "daqui a 1h", "em 30min", "daqui 1h30", "daqui a 2 horas e meia".
  • "às 7:30 pm", "à uma", "às 3 h", "9:30hrs"; "no natal de 2027"; "toda terça,
    3 vezes ao dia".
  • A Stability section in the README, and doc comments on every public symbol.

Fixed

  • "dia 10 às 14h" was a range from 10:00 to 14:00; it is the 10th at 14:00.
  • A second clock time was dropped ("às 8h e às 20h"), and interpret took a
    time that belonged to another day.
  • "todo dia 30 minutos" was monthly; "em setembro" said in September meant
    next year; weekends counted wrong on a Sunday.
  • Ordinals, fractions, scores and street names were read as dates: "na
    segunda fase", "1/2 xícara", "Rua 25 de Março".
  • "as 2 crianças" and "reunião de 2h" were times; "hoje às 9" said at 10:00
    gave the past 9:00 instead of 21:00.
  • Normalization shifted positions after some Unicode marks, and
    strippingDates was quadratic in a run of spaces.

Performance

  • Joining steps look only at neighbouring candidates, and each rule tries its
    regex only near its trigger words: 7 KB of notes with a date on every line
    went from 100 ms to 28 ms, and repeated dates that took seconds take tens
    of milliseconds.

0.11.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 20:18

Added

  • Approximate hours: "umas 8", "lá pras 3", "por volta de 15h", "em torno
    das 10", "perto do meio-dia". They read like exact ones, and a bare number
    needs the end of a phrase, so "umas 8 laranjas" and "umas 2 horas" are not
    times.
  • Approximate amounts from now: "em uns 15 minutos", "daqui umas 2 horas".
  • With past dates on: "semana retrasada", "mês retrasado", "ano retrasado",
    and a redundant "atrás" in "há duas semanas atrás".
  • The bug report form has an optional context field (thanks, @anjugoyal954).

0.10.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 20:04

Breaking

  • Recurrence is a struct with the fields of an iCalendar rule (RFC 5545),
    the same ones EKRecurrenceRule takes: frequency, interval,
    weekdays, weekdayOrdinal, daysOfMonth, months, timesPerPeriod and
    end. .daily(), .weekly(on:), .monthly(day:), .yearly(),
    .hourly(every:) and .minutely(every:) build the common ones. One reading
    has one value: "diariamente" and "a cada 1 dia" are both .daily().
  • Recurrence.description is its rrule: "FREQ=WEEKLY;BYDAY=MO,WE".

Added

  • Where a repeating date stops: "toda terça até dezembro", "todo dia por 10
    dias", "toda segunda, 5 vezes", in Recurrence.end.
  • More repeating dates: "3x ao dia", "duas vezes por semana", "dia sim, dia
    não", "toda última sexta do mês", "todo ano em julho", "todo 25 de
    dezembro".
  • Match.isAllDay for "amanhã o dia todo"; whole parts of the day, "a manhã
    toda" (6:00 to 12:00), "a tarde inteira", "a noite toda".
  • Lengths of time: "por 3 dias", "nos próximos 5 dias", "amanhã por uma
    semana", "durante a semana".
  • Semesters, trimesters and halves of the month: "no primeiro semestre", "no
    próximo trimestre", "primeira quinzena de outubro".
  • A weekday with a bare day number: "sexta, 25" is the 25th when it is a
    Friday.
  • Time zones after a clock time: "15h BRT", "10h UTC", "16h GMT-3", "às 9
    horário de Brasília". knownComponents has .timeZone.
  • English clock times and ISO date-times: "9am", "7:30 pm",
    "2026-10-15T14:30:00-03:00".
  • Options.moments, an app's own phrases with the hour each one means:
    ["no treino": 7].

Fixed

  • "todo dia 25 de dezembro" is yearly, not monthly.
  • "na 2ª quinzena" and "na segunda quinzena" are no longer Monday.

Performance

  • Mapping results back to the original text resumes where the last one
    stopped instead of walking from the start each time.

0.9.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 19:33

Added

  • ChronoPT.Parser keeps a calendar and options for an app that reads many
    texts the same way.
  • PartialDate.alternative: "às 7" reads as 19:00, and the alternative holds
    7:00, so a UI can offer it.
  • Dates counted from another: "dois dias antes do natal", "véspera do ano
    novo", "uma semana depois do dia 10".
  • Vague times: "daqui a pouco" (30 minutes) and "mais tarde" (2 hours).
    "agora" alone stays unread.
  • Date formats: "10/out", "25-12-2027", "25.12.2027", "dez/2027", "12/2027".
  • Recurrence has a stable description, weekdays in week order.
  • DocC articles: getting started, the grammar, and recipes.

Changed

  • A time counted from now joins a day when it lands on that day: "hoje mais
    tarde" is two hours from now.

Performance

  • Rules skip their regex when the text holds none of the words it needs. In a
    release build, 10 KB of text with no date goes from 105 ms to 2.3 ms, 7.7 KB
    of notes from 118 ms to 28 ms, and a one-line note from 1.1 ms to 0.28 ms.
  • Normalization no longer allocates per character, and the part-of-day table
    is indexed by first word instead of searched 102 times.

0.8.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 19:02

0.8.0

Breaking

  • The public types moved into the ChronoPT namespace: ChronoPT.Match,
    ChronoPT.PartialDate, ChronoPT.Options and ChronoPT.Recurrence. An app
    with its own Recurrence keeps compiling.

Added

  • ChronoPT.strippingDates(from:) returns the text without the dates, along
    with the word that introduced each one: "comprar pão amanhã no almoço" gives
    "comprar pão".
  • Match.ranges, every span that produced the date, so a time said apart from
    its day can be highlighted or removed too.
  • Match.interval, PartialDate.hasDay, PartialDate.dateComponents(in:),
    public initializers, Hashable on every type, Codable on Options and
    Recurrence, and a debug description that prints the text and the date.
  • Business days: "em 5 dias úteis", "no próximo dia útil", "primeiro dia útil
    do mês", counting the national holidays and the three the banks close for.
  • Deadlines: "prazo de 5 dias", "em até 48 horas".
  • Years in amounts: "daqui a 1 ano", "em 2 anos", "há 3 anos".
  • Periods: "fim do ano", "no início do mês", "no meio da semana", and a month
    on its own ("férias em dezembro", "março de 2027").
  • Intervals of hours and minutes: "de 8 em 8 horas", "a cada 30 minutos".
  • Ordinal indicators and numbered weekdays: "dia 1º", "6ª feira", "toda 2ª
    feira"; "sábado agora" and "quinta dessa semana"; more parts of the day
    ("no comecinho da tarde", "na boquinha da noite", "de madruga").

Fixed

  • Parsing was cubic: 2.4 KB of repeated weekdays took 13 seconds and a pasted
    note could freeze a UI thread. It now takes 105 ms. The regexes were never
    the problem; the candidate pair loops asked questions that scanned the rest
    of the text.
  • A time counted from now no longer takes over a day said in the same text:
    "consulta dia 30, sair daqui a 20 minutos" is the 30th.
  • "sáb - 3/10" is that date, not a range from Saturday to it. Two rules
    produced the same span and the winner depended on the sort being stable.
  • Any calendar is honoured: a Buddhist calendar used to give 1483 for
    "25/09/2026", and every movable holiday moved.
  • A time inside a daylight saving gap stays on its day.
  • Normalization keeps one character for each character, so a combining mark
    after a line break no longer shifts every range after it.
  • "da meia-noite à meia-noite" is a whole day.
  • Digits that are not ASCII are no longer read as zero.
  • "fim de semana que vem" and "fim do mês que vem" matched the shorter phrase.
  • "de manhã bem cedo" gave 9:00, "reunião 10 às 12" gave a single 12:00,
    "de 8 em 8 horas" lost its recurrence, and "em 5 dias úteis" gave a Saturday.

0.7.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 18:09

New

  • Recurrence.every(DateComponents) for intervals between repeats: "a cada 15 dias", "de 2 em 2 semanas", "toda semana", "todo mês", "semanalmente", "mensalmente" (#18). The components go straight into Calendar.date(byAdding:to:). Counts that disagree, as in "de 2 em 3 semanas", are not an interval.
  • A weekday that needs a hint now takes a time before it when "de" joins them: "às 10 de quinta" is Thursday (#17). "às 10 segunda via do boleto" stays a plain time.

Adding a case to Recurrence breaks exhaustive switches over it.

0.6.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 18:05

Breaking change

ParsedResult now has start: ParsedDate and end: ParsedDate?, in place of date, end: Date? and hasTime.

let note = ChronoPT.interpret("25/09 às 14h")
note?.start.date              // 25 September at 14:00
note?.start.hasTime           // true
note?.start.knownComponents   // [.day, .month, .hour, .minute]

New

  • ParsedDate.knownComponents says which calendar components the text fixed, by naming them ("25/09" gives the day and the month) or by counting from the reference date ("amanhã" gives the day, the month and the year). The rest comes from the reference or from defaults, so an app can show "25/09" without inventing a year (#10).
  • hasTime is derived from the components, and each end of a period or a range has its own date and components.
  • Recurrence.weekly takes a Set<Locale.Weekday> and reads as .weekly(on: [.tuesday]), so "às quartas e segundas" equals "às segundas e quartas".

Nothing depended on the old shape yet, so there is no migration path beyond renaming result.date to result.start.date.

0.5.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 17:56

New API

  • ParseOptions, a new parameter of parse and interpret (#4, #11). Its defaults keep the current behavior.
    • allowsPast reads "ontem", "anteontem", "sexta passada", "na última sexta", "semana passada", "mês passado", "há 2 dias", "3 dias atrás" and "há 2 horas". Without it, "sexta passada às 10" gives no date instead of next Friday.
    • defaultHour sets the time of a day with no time (noon by default).
  • ParsedResult.recurrence and the Recurrence enum (#5): .daily ("todo dia", "diariamente"), .weekly([Locale.Weekday]) ("toda terça", "às segundas e quartas") and .monthly(day:) ("todo dia 5", "todo mês no dia 10"). date is the next time it happens, counting today.
ChronoPT.interpret("tirar o lixo toda terça às 20h")?.recurrence  // .weekly([.tuesday])
ChronoPT.interpret("paguei ontem", options: ParseOptions(allowsPast: true))

Source compatible with 0.4.0: the new parameter has a default, and the new property is additive.

0.4.0

Choose a tag to compare

@bertalhia bertalhia released this 22 Sep 17:45

New

  • Abbreviations: "na seg", "qua 14h", "sex que vem", "no sáb", "de seg a sex", "amn", "dps de amanhã", "dps do almoço", "prox sexta", "prox semana" (#12). "ter" is always the verb "to have", never Tuesday.
  • More written times: "15:30h", "15h30m", "15h30min" (#13).
  • Forms common in Portugal: "ao pequeno-almoço", "ao jantar", "à hora de almoço", "ao fim da tarde" (#13).
  • A weekday followed by its date is that date: "segunda-feira, dia 5" (#13).
  • Agenda-style ranges: "14h às 16h", "9h até 12h", "10h-11h", "segunda a sexta", "seg-sex" (#15).

Performance

  • Compiled regexes are kept per thread: parse went from 11 ms to 0.58 ms per call in a release build (#14).

Tests

  • A corpus of 60 notes written the way people write them, with the answer the person meant (#15).

No API changes.