Current section

Files

Jump to
nous lib nous tools date_time_tools.ex
Raw

lib/nous/tools/date_time_tools.ex

defmodule Nous.Tools.DateTimeTools do
@moduledoc """
Built-in tools for date and time operations.
These tools provide common date/time functionality that AI agents often need:
- Get current date/time in various formats
- Parse and format dates
- Calculate date differences
- Check business days and weekends
- Timezone conversions
## Usage
agent = Nous.new("lmstudio:qwen3-vl-4b-thinking-mlx",
tools: [
&DateTimeTools.current_date/2,
&DateTimeTools.current_time/2,
&DateTimeTools.current_datetime/2
]
)
{:ok, result} = Nous.run(agent, "What day is today?")
"""
@doc """
Get the current date in the specified format.
## Arguments
- format: "iso8601" (default), "us" (MM/DD/YYYY), "eu" (DD/MM/YYYY), "full" (Monday, January 1, 2025)
- timezone: Optional timezone (e.g., "America/New_York", "Europe/London", "UTC")
"""
def current_date(_ctx, args) do
format = Map.get(args, "format", "iso8601")
timezone = Map.get(args, "timezone", "Etc/UTC")
date = get_date_in_timezone(timezone)
formatted_date =
case format do
"iso8601" -> Date.to_iso8601(date)
"us" -> Calendar.strftime(date, "%m/%d/%Y")
"eu" -> Calendar.strftime(date, "%d/%m/%Y")
"full" -> Calendar.strftime(date, "%A, %B %-d, %Y")
"short" -> Calendar.strftime(date, "%b %-d, %Y")
_ -> Date.to_iso8601(date)
end
%{
date: formatted_date,
format: format,
timezone: timezone,
day_of_week: Calendar.strftime(date, "%A"),
is_weekend: Date.day_of_week(date) in [6, 7]
}
end
@doc """
Get the current time in the specified format.
## Arguments
- format: "24h" (default, HH:MM:SS), "12h" (hh:MM:SS AM/PM), "short" (HH:MM)
- timezone: Optional timezone
"""
def current_time(_ctx, args) do
format = Map.get(args, "format", "24h")
timezone = Map.get(args, "timezone", "Etc/UTC")
time = get_time_in_timezone(timezone)
formatted_time =
case format do
"24h" -> Time.to_iso8601(time)
"12h" -> Calendar.strftime(time, "%I:%M:%S %p")
"short" -> Calendar.strftime(time, "%H:%M")
_ -> Time.to_iso8601(time)
end
%{
time: formatted_time,
format: format,
timezone: timezone,
hour: time.hour,
minute: time.minute,
second: time.second
}
end
@doc """
Get the current date and time together.
## Arguments
- format: "iso8601" (default), "rfc3339", "unix", "human"
- timezone: Optional timezone
"""
def current_datetime(_ctx, args) do
format = Map.get(args, "format", "iso8601")
timezone = Map.get(args, "timezone", "Etc/UTC")
datetime = get_datetime_in_timezone(timezone)
formatted =
case format do
"iso8601" -> DateTime.to_iso8601(datetime)
"rfc3339" -> DateTime.to_string(datetime)
"unix" -> DateTime.to_unix(datetime) |> to_string()
"human" -> Calendar.strftime(datetime, "%A, %B %-d, %Y at %I:%M %p %Z")
_ -> DateTime.to_iso8601(datetime)
end
%{
datetime: formatted,
format: format,
timezone: timezone,
unix_timestamp: DateTime.to_unix(datetime),
date: Date.to_iso8601(DateTime.to_date(datetime)),
time: Time.to_iso8601(DateTime.to_time(datetime))
}
end
@doc """
Calculate the difference between two dates.
## Arguments
- date1: First date in ISO8601 format (YYYY-MM-DD)
- date2: Second date in ISO8601 format (YYYY-MM-DD)
- unit: "days" (default), "weeks", "months", "years"
"""
def date_difference(_ctx, args) do
# Support both date1/date2 and start_date/end_date parameter names
date1_str = Map.get(args, "date1") || Map.get(args, "start_date")
date2_str = Map.get(args, "date2") || Map.get(args, "end_date")
unit = Map.get(args, "unit", "days")
with {:ok, date1} <- Date.from_iso8601(date1_str),
{:ok, date2} <- Date.from_iso8601(date2_str) do
diff_days = Date.diff(date2, date1)
result =
case unit do
"days" -> diff_days
"weeks" -> Float.round(diff_days / 7, 2)
# Average month length
"months" -> Float.round(diff_days / 30.44, 2)
# Account for leap years
"years" -> Float.round(diff_days / 365.25, 2)
_ -> diff_days
end
%{
date1: date1_str,
date2: date2_str,
difference: result,
unit: unit,
diff_days: diff_days,
direction: if(diff_days > 0, do: "future", else: "past")
}
else
{:error, reason} ->
%{error: "Invalid date format: #{inspect(reason)}"}
end
end
@doc """
Add or subtract days from a date.
## Arguments
- date: Date in ISO8601 format (YYYY-MM-DD), defaults to today
- days: Number of days to add (positive) or subtract (negative)
"""
def add_days(_ctx, args) do
date_str = Map.get(args, "date")
days = Map.get(args, "days", 0)
base_date =
if date_str do
case Date.from_iso8601(date_str) do
{:ok, date} -> date
_ -> Date.utc_today()
end
else
Date.utc_today()
end
result_date = Date.add(base_date, days)
%{
original_date: Date.to_iso8601(base_date),
days_added: days,
result_date: Date.to_iso8601(result_date),
result_day_of_week: Calendar.strftime(result_date, "%A"),
is_weekend: Date.day_of_week(result_date) in [6, 7]
}
end
@doc """
Check if a date is a weekend.
## Arguments
- date: Date in ISO8601 format (YYYY-MM-DD), defaults to today
"""
def is_weekend(_ctx, args) do
date_str = Map.get(args, "date")
date =
if date_str do
case Date.from_iso8601(date_str) do
{:ok, date} -> date
_ -> Date.utc_today()
end
else
Date.utc_today()
end
day_of_week = Date.day_of_week(date)
is_weekend = day_of_week in [6, 7]
%{
date: Date.to_iso8601(date),
day_of_week: Calendar.strftime(date, "%A"),
day_number: day_of_week,
is_weekend: is_weekend,
is_weekday: !is_weekend
}
end
@doc """
Get the day of the week for a given date.
## Arguments
- date: Date in ISO8601 format (YYYY-MM-DD), defaults to today
"""
def day_of_week(_ctx, args) do
date_str = Map.get(args, "date")
date =
if date_str do
case Date.from_iso8601(date_str) do
{:ok, date} -> date
_ -> Date.utc_today()
end
else
Date.utc_today()
end
%{
date: Date.to_iso8601(date),
day_of_week: Calendar.strftime(date, "%A"),
day_abbreviation: Calendar.strftime(date, "%a"),
day_number: Date.day_of_week(date),
is_weekend: Date.day_of_week(date) in [6, 7]
}
end
@doc """
Parse a human-readable date string.
## Arguments
- date_string: Date in various formats (ISO8601, MM/DD/YYYY, DD/MM/YYYY, etc.)
- format: Expected format hint ("iso8601", "us", "eu")
"""
def parse_date(_ctx, args) do
# Support both "date_string" and "date" parameter names
date_string = Map.get(args, "date_string") || Map.get(args, "date")
format_hint = Map.get(args, "format", "iso8601")
result =
case format_hint do
"iso8601" ->
Date.from_iso8601(date_string)
"us" ->
# MM/DD/YYYY
parse_us_date(date_string)
"eu" ->
# DD/MM/YYYY
parse_eu_date(date_string)
_ ->
Date.from_iso8601(date_string)
end
case result do
{:ok, date} ->
%{
original: date_string,
parsed_date: Date.to_iso8601(date),
day_of_week: Calendar.strftime(date, "%A"),
success: true
}
{:error, reason} ->
%{
original: date_string,
error: inspect(reason),
success: false
}
end
end
@doc """
Get the start and end of the current week.
## Arguments
- timezone: Optional timezone
- week_start: Day to consider start of week (1=Monday default, 7=Sunday)
"""
def current_week(_ctx, args) do
timezone = Map.get(args, "timezone", "Etc/UTC")
# Monday
week_start = Map.get(args, "week_start", 1)
today = get_date_in_timezone(timezone)
day_of_week = Date.day_of_week(today)
# Calculate days to subtract to get to week start
days_to_start = rem(day_of_week - week_start + 7, 7)
week_start_date = Date.add(today, -days_to_start)
week_end_date = Date.add(week_start_date, 6)
%{
week_start: Date.to_iso8601(week_start_date),
week_end: Date.to_iso8601(week_end_date),
current_date: Date.to_iso8601(today),
days_in_week: generate_week_days(week_start_date)
}
end
@doc """
Get information about the current month.
## Arguments
- timezone: Optional timezone
"""
def current_month(_ctx, args) do
timezone = Map.get(args, "timezone", "Etc/UTC")
today = get_date_in_timezone(timezone)
first_day = Date.beginning_of_month(today)
last_day = Date.end_of_month(today)
days_in_month = Date.diff(last_day, first_day) + 1
%{
month: Calendar.strftime(today, "%B"),
month_number: today.month,
year: today.year,
first_day: Date.to_iso8601(first_day),
last_day: Date.to_iso8601(last_day),
days_in_month: days_in_month,
current_date: Date.to_iso8601(today)
}
end
# Private helper functions
defp get_date_in_timezone(timezone) do
DateTime.now!(timezone)
|> DateTime.to_date()
end
defp get_time_in_timezone(timezone) do
DateTime.now!(timezone)
|> DateTime.to_time()
end
defp get_datetime_in_timezone(timezone) do
DateTime.now!(timezone)
end
defp parse_us_date(date_string) do
# MM/DD/YYYY
case String.split(date_string, "/") do
[month, day, year] ->
Date.from_iso8601(
"#{year}-#{String.pad_leading(month, 2, "0")}-#{String.pad_leading(day, 2, "0")}"
)
_ ->
{:error, :invalid_format}
end
end
defp parse_eu_date(date_string) do
# DD/MM/YYYY
case String.split(date_string, "/") do
[day, month, year] ->
Date.from_iso8601(
"#{year}-#{String.pad_leading(month, 2, "0")}-#{String.pad_leading(day, 2, "0")}"
)
_ ->
{:error, :invalid_format}
end
end
defp generate_week_days(start_date) do
0..6
|> Enum.map(fn offset ->
date = Date.add(start_date, offset)
%{
date: Date.to_iso8601(date),
day_of_week: Calendar.strftime(date, "%A"),
is_weekend: Date.day_of_week(date) in [6, 7]
}
end)
end
end