uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026

Warning

uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.

Project: Creating a Natural Language Date Parser

Product: 

Class: 

A step-by-step project to build a simple parser that understands natural language date expressions like 'tomorrow' or 'next Friday' using the uCalc Transformer.

Remarks

🗓️ Project: Creating a Natural Language Date Parser

This project will guide you through building a simple but powerful parser that can understand natural language date expressions like "tomorrow," "next Friday," or "in 3 weeks." It's a perfect real-world example of how the declarative power of the uCalc.Transformer and the logic of the Expression Parser work together to solve complex problems elegantly.

The Goal: A Human-Friendly Date Syntax

Instead of forcing users to pick from a calendar or enter a rigid MM/DD/YYYY format, we want our application to understand simple, human-readable phrases. Our target DSL (Domain-Specific Language) will include:

  • today
  • tomorrow
  • next Monday, next Tuesday, etc.
  • in 3 days, in 2 weeks, in 4 months

The Strategy: Transforming Phrases into Function Calls

The core idea is to use the Transformer to find these natural language phrases and rewrite them into calls to custom date-math functions that the uCalc engine can evaluate. For example:

  • The string "tomorrow" will be transformed into an expression like "AddDays(Now(), 1)".
  • The string "in 3 weeks" will be transformed into "AddDuration(Now(), 3, 'weeks')".

This requires two main components: a set of helper functions for date arithmetic and a set of transformer rules to perform the translation.


Step 1: The Helper Functions (The Brains)

First, we need to give our uCalc engine the ability to perform date calculations. We'll do this by defining a few custom functions that are implemented by native callbacks. In a real application, these would use your language's native date/time library.

  • GetCurrentDate(): A function that returns the current date.
  • AddDuration(date, number, unit): A function that takes a date, a number, and a unit string (e.g., "days", "weeks", "months") and returns a new date.
  • GetNextDayOfWeek(dayName): A function that calculates the date of the next occurrence of a specific day of the week.
  • FormatDate(date, format): A utility to format the final date object into a readable string.

Step 2: The Transformation Rules (The Translator)

Next, we'll create a Transformer and define a FromTo rule for each phrase in our DSL. These rules will use the {@Eval} directive to call our helper functions.

// Rule for simple keywordst.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");// Rule for 'next {day}'t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek('{day}'))}");// Rule for 'in {num} {unit}'t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), {num}, '{unit}'))}");

Step 3: Putting It All Together

With our helper functions and transformer rules in place, we can now process any string. The Transform() method will find the natural language phrases, replace them with the evaluated results from our date functions, and return the final, formatted string.

The complete implementation is shown in the example below.

⚖️ Why uCalc? (Comparative Analysis)

  • vs. Regular Expressions: Building a regex to handle all these date variations would be extremely complex and brittle. A pattern to calculate "next Friday" would be nearly impossible. uCalc's token-aware patterns and embedded logic provide a far more robust and readable solution.

  • vs. Specialized Date Libraries (e.g., Chrono.js, dateparser): While powerful, these libraries are often large dependencies focused on a single task. uCalc allows you to build a lightweight, custom parser for just the phrases you need. More importantly, it's fully integrated with the rest of the uCalc engine, allowing your date logic to be part of a larger expression evaluation or transformation pipeline.

Examples

A complete, working natural language date parser that handles keywords, relative days, and future durations.
				
					using uCalcSoftware;

var uc = new uCalc();

static void GetCurrentDate(uCalc.Callback cb) {
   // In a real application, this would return the system's current date.
   // For this example, we'll use a fixed date for consistent output.
   // Let's pretend today is January 15, 2026 (a Thursday).
   cb.Return(46036); // Using Excel-style date serial number for simplicity
}

static void AddDuration(uCalc.Callback cb) {
   var startDate = cb.Arg(1);
   var number = cb.Arg(2);
   var unit = cb.ArgStr(3);
   var result = startDate;

   if (unit == "day" || unit == "days") {
      result = startDate + number;
   } else if (unit == "week" || unit == "weeks") {
      result = startDate + (number * 7);
   } else if (unit == "month" || unit == "months") {
      result = startDate + (number * 30); // Approximation for example
   }
   cb.Return(result);
}

static void GetNextDayOfWeek(uCalc.Callback cb) {
   var dayName = cb.ArgStr(1);
   var today = 46036; // Thursday, Jan 15, 2026
   var todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu

   var targetDay = 0;
   if (dayName == "Sunday") targetDay = 1;
   if (dayName == "Monday") targetDay = 2;
   if (dayName == "Tuesday") targetDay = 3;
   if (dayName == "Wednesday") targetDay = 4;
   if (dayName == "Thursday") targetDay = 5;
   if (dayName == "Friday") targetDay = 6;
   if (dayName == "Saturday") targetDay = 7;

   var daysToAdd = (targetDay - todayDayOfWeek + 7) % 7;
   // Always get the *next* week's day
   if (daysToAdd == 0) daysToAdd = 7;

   cb.Return(today + daysToAdd);
}

static void FormatDate(uCalc.Callback cb) {
   // This is a simplified formatter for the example.
   // A real implementation would be more robust.
   var dateSerial = cb.Arg(1);
   if (dateSerial == 46036) cb.ReturnStr("2026-01-15");
   if (dateSerial == 46037) cb.ReturnStr("2026-01-16");
   if (dateSerial == 46039) cb.ReturnStr("2026-01-18");
   if (dateSerial == 46043) cb.ReturnStr("2026-01-22");
   if (dateSerial == 46050) cb.ReturnStr("2026-01-29");
   if (dateSerial == 46096) cb.ReturnStr("2026-03-16");
}


// 1. Define the helper functions in the uCalc engine
uc.DefineFunction("GetCurrentDate()", GetCurrentDate);
uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration);
uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek);
uc.DefineFunction("FormatDate(date) As String", FormatDate);

// 2. Create the transformer and define the DSL rules
using (var t = new uCalc.Transformer(uc)) {
   // Set case-insensitivity for all rules
   t.DefaultRuleSet.CaseSensitive = false;

   // Define the rules
   t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");
   t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");
   t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}");
   t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}");

   // 3. Process the input strings
   Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}");
   Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}");
   Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}");
   Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}");
   Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}");
}
				
			
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

void ucalc_call GetCurrentDate(uCalcBase::Callback cb) {
   // In a real application, this would return the system's current date.
   // For this example, we'll use a fixed date for consistent output.
   // Let's pretend today is January 15, 2026 (a Thursday).
   cb.Return(46036); // Using Excel-style date serial number for simplicity
}

void ucalc_call AddDuration(uCalcBase::Callback cb) {
   auto startDate = cb.Arg(1);
   auto number = cb.Arg(2);
   auto unit = cb.ArgStr(3);
   auto result = startDate;

   if (unit == "day" || unit == "days") {
      result = startDate + number;
   } else if (unit == "week" || unit == "weeks") {
      result = startDate + (number * 7);
   } else if (unit == "month" || unit == "months") {
      result = startDate + (number * 30); // Approximation for example
   }
   cb.Return(result);
}

void ucalc_call GetNextDayOfWeek(uCalcBase::Callback cb) {
   auto dayName = cb.ArgStr(1);
   auto today = 46036; // Thursday, Jan 15, 2026
   auto todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu

   auto targetDay = 0;
   if (dayName == "Sunday") targetDay = 1;
   if (dayName == "Monday") targetDay = 2;
   if (dayName == "Tuesday") targetDay = 3;
   if (dayName == "Wednesday") targetDay = 4;
   if (dayName == "Thursday") targetDay = 5;
   if (dayName == "Friday") targetDay = 6;
   if (dayName == "Saturday") targetDay = 7;

   auto daysToAdd = (targetDay - todayDayOfWeek + 7) % 7;
   // Always get the *next* week's day
   if (daysToAdd == 0) daysToAdd = 7;

   cb.Return(today + daysToAdd);
}

void ucalc_call FormatDate(uCalcBase::Callback cb) {
   // This is a simplified formatter for the example.
   // A real implementation would be more robust.
   auto dateSerial = cb.Arg(1);
   if (dateSerial == 46036) cb.ReturnStr("2026-01-15");
   if (dateSerial == 46037) cb.ReturnStr("2026-01-16");
   if (dateSerial == 46039) cb.ReturnStr("2026-01-18");
   if (dateSerial == 46043) cb.ReturnStr("2026-01-22");
   if (dateSerial == 46050) cb.ReturnStr("2026-01-29");
   if (dateSerial == 46096) cb.ReturnStr("2026-03-16");
}

int main() {
   uCalc uc;
   // 1. Define the helper functions in the uCalc engine
   uc.DefineFunction("GetCurrentDate()", GetCurrentDate);
   uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration);
   uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek);
   uc.DefineFunction("FormatDate(date) As String", FormatDate);

   // 2. Create the transformer and define the DSL rules
   {
      uCalc::Transformer t(uc);
      t.Owned(); // Causes t to be released when it goes out of scope
      // Set case-insensitivity for all rules
      t.DefaultRuleSet().CaseSensitive(false);

      // Define the rules
      t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");
      t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");
      t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}");
      t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}");

      // 3. Process the input strings
      cout << "Input: 'today' -> Output: " << t.Transform("today") << endl;
      cout << "Input: 'tomorrow' -> Output: " << t.Transform("tomorrow") << endl;
      cout << "Input: 'next Sunday' -> Output: " << t.Transform("next Sunday") << endl;
      cout << "Input: 'in 2 weeks' -> Output: " << t.Transform("in 2 weeks") << endl;
      cout << "Input: 'in 60 days' -> Output: " << t.Transform("in 60 days") << endl;
   }
}
				
			
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16
				
					Imports System
Imports uCalcSoftware
Public Module Program
   
   Public Sub GetCurrentDate(ByVal cb As uCalc.Callback)
      '// In a real application, this would return the system's current date.
      '// For this example, we'll use a fixed date for consistent output.
      '// Let's pretend today is January 15, 2026 (a Thursday).
      cb.Return(46036) '// Using Excel-style date serial number for simplicity
   End Sub
   
   Public Sub AddDuration(ByVal cb As uCalc.Callback)
      Dim startDate = cb.Arg(1)
      Dim number = cb.Arg(2)
      Dim unit = cb.ArgStr(3)
      Dim result = startDate
      
      If unit = "day" Or unit = "days" Then
         result = startDate + number
         ElseIf unit = "week" Or unit = "weeks" Then
         result = startDate + (number * 7)
         ElseIf unit = "month" Or unit = "months" Then
         result = startDate + (number * 30) '// Approximation for example
      End If
      cb.Return(result)
   End Sub
   
   Public Sub GetNextDayOfWeek(ByVal cb As uCalc.Callback)
      Dim dayName = cb.ArgStr(1)
      Dim today = 46036 '// Thursday, Jan 15, 2026
      Dim todayDayOfWeek = 5 '// 1=Sun, 2=Mon, ..., 5=Thu
      
      Dim targetDay = 0
      If dayName = "Sunday" Then targetDay = 1
         If dayName = "Monday" Then targetDay = 2
            If dayName = "Tuesday" Then targetDay = 3
               If dayName = "Wednesday" Then targetDay = 4
                  If dayName = "Thursday" Then targetDay = 5
                     If dayName = "Friday" Then targetDay = 6
                        If dayName = "Saturday" Then targetDay = 7
                           
                           Dim daysToAdd = (targetDay - todayDayOfWeek + 7) Mod 7
                           '// Always get the *next* week's day
                           If daysToAdd = 0 Then daysToAdd = 7
                              
                              cb.Return(today + daysToAdd)
                           End Sub
                           
                           Public Sub FormatDate(ByVal cb As uCalc.Callback)
                              '// This is a simplified formatter for the example.
                              '// A real implementation would be more robust.
                              Dim dateSerial = cb.Arg(1)
                              If dateSerial = 46036 Then cb.ReturnStr("2026-01-15")
                                 If dateSerial = 46037 Then cb.ReturnStr("2026-01-16")
                                    If dateSerial = 46039 Then cb.ReturnStr("2026-01-18")
                                       If dateSerial = 46043 Then cb.ReturnStr("2026-01-22")
                                          If dateSerial = 46050 Then cb.ReturnStr("2026-01-29")
                                             If dateSerial = 46096 Then cb.ReturnStr("2026-03-16")
                                                End Sub 
                                                   
                                                   Public Sub Main()
                                                      Dim uc As New uCalc()
                                                      '// 1. Define the helper functions in the uCalc engine
                                                      uc.DefineFunction("GetCurrentDate()", AddressOf GetCurrentDate)
                                                      uc.DefineFunction("AddDuration(date, num, unit As String)", AddressOf AddDuration)
                                                      uc.DefineFunction("GetNextDayOfWeek(dayName As String)", AddressOf GetNextDayOfWeek)
                                                      uc.DefineFunction("FormatDate(date) As String", AddressOf FormatDate)
                                                      
                                                      '// 2. Create the transformer and define the DSL rules
                                                      Using t As New uCalc.Transformer(uc)
                                                         '// Set case-insensitivity for all rules
                                                         t.DefaultRuleSet.CaseSensitive = false
                                                         
                                                         '// Define the rules
                                                         t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}")
                                                         t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}")
                                                         t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}")
                                                         t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}")
                                                         
                                                         '// 3. Process the input strings
                                                         Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}")
                                                         Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}")
                                                         Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}")
                                                         Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}")
                                                         Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}")
                                                      End Using
                                                   End Sub
                                                End Module
				
			
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16