198 lines
7.9 KiB
C#
198 lines
7.9 KiB
C#
using System.Globalization;
|
|
using System.Text.Json;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
/// <summary>날짜·시간 변환, 타임존, 기간 계산 유틸리티 도구.</summary>
|
|
public class DateTimeTool : IAgentTool
|
|
{
|
|
public string Name => "datetime_tool";
|
|
public string Description =>
|
|
"Date/time utility tool. Actions: " +
|
|
"'now' — get current date/time in various formats; " +
|
|
"'parse' — parse a date string into standard format; " +
|
|
"'diff' — calculate difference between two dates; " +
|
|
"'add' — add/subtract days/hours/minutes to a date; " +
|
|
"'epoch' — convert between Unix epoch and datetime; " +
|
|
"'format' — format a date into specified pattern.";
|
|
|
|
public ToolParameterSchema Parameters => new()
|
|
{
|
|
Properties = new()
|
|
{
|
|
["action"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Action to perform",
|
|
Enum = ["now", "parse", "diff", "add", "epoch", "format"],
|
|
},
|
|
["date"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Date string (for parse/diff/add/format/epoch). For epoch: Unix timestamp in seconds.",
|
|
},
|
|
["date2"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Second date string (for diff action)",
|
|
},
|
|
["amount"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Amount to add (for add action). E.g. '7' for 7 days",
|
|
},
|
|
["unit"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Unit for add action",
|
|
Enum = ["days", "hours", "minutes", "seconds", "months", "years"],
|
|
},
|
|
["pattern"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Format pattern (for format action). E.g. 'yyyy-MM-dd HH:mm:ss', 'ddd MMM d yyyy'",
|
|
},
|
|
},
|
|
Required = ["action"],
|
|
};
|
|
|
|
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
|
{
|
|
var action = args.GetProperty("action").GetString() ?? "";
|
|
|
|
try
|
|
{
|
|
return Task.FromResult(action switch
|
|
{
|
|
"now" => Now(),
|
|
"parse" => Parse(args),
|
|
"diff" => Diff(args),
|
|
"add" => Add(args),
|
|
"epoch" => Epoch(args),
|
|
"format" => FormatDate(args),
|
|
_ => ToolResult.Fail($"Unknown action: {action}"),
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromResult(ToolResult.Fail($"DateTime 오류: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private static ToolResult Now()
|
|
{
|
|
var now = DateTime.Now;
|
|
var utc = DateTime.UtcNow;
|
|
var epoch = new DateTimeOffset(utc).ToUnixTimeSeconds();
|
|
return ToolResult.Ok(
|
|
$"Local: {now:yyyy-MM-dd HH:mm:ss (ddd)} ({TimeZoneInfo.Local.DisplayName})\n" +
|
|
$"UTC: {utc:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"ISO: {now:O}\n" +
|
|
$"Epoch: {epoch}\n" +
|
|
$"Week: {CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(now, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday)}");
|
|
}
|
|
|
|
private static ToolResult Parse(JsonElement args)
|
|
{
|
|
var dateStr = args.TryGetProperty("date", out var d) ? d.GetString() ?? "" : "";
|
|
if (string.IsNullOrEmpty(dateStr)) return ToolResult.Fail("'date' parameter is required");
|
|
|
|
if (!DateTime.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) &&
|
|
!DateTime.TryParse(dateStr, CultureInfo.CurrentCulture, DateTimeStyles.None, out dt))
|
|
return ToolResult.Fail($"Cannot parse date: '{dateStr}'");
|
|
|
|
return ToolResult.Ok(
|
|
$"Parsed: {dt:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"Day: {dt:dddd}\n" +
|
|
$"ISO: {dt:O}\n" +
|
|
$"Epoch: {new DateTimeOffset(dt).ToUnixTimeSeconds()}");
|
|
}
|
|
|
|
private static ToolResult Diff(JsonElement args)
|
|
{
|
|
var d1 = args.TryGetProperty("date", out var v1) ? v1.GetString() ?? "" : "";
|
|
var d2 = args.TryGetProperty("date2", out var v2) ? v2.GetString() ?? "" : "";
|
|
if (string.IsNullOrEmpty(d1) || string.IsNullOrEmpty(d2))
|
|
return ToolResult.Fail("'date' and 'date2' parameters are required");
|
|
|
|
if (!DateTime.TryParse(d1, out var dt1)) return ToolResult.Fail($"Cannot parse date: '{d1}'");
|
|
if (!DateTime.TryParse(d2, out var dt2)) return ToolResult.Fail($"Cannot parse date: '{d2}'");
|
|
|
|
var diff = dt2 - dt1;
|
|
return ToolResult.Ok(
|
|
$"From: {dt1:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"To: {dt2:yyyy-MM-dd HH:mm:ss}\n\n" +
|
|
$"Difference:\n" +
|
|
$" {Math.Abs(diff.TotalDays):F1} days\n" +
|
|
$" {Math.Abs(diff.TotalHours):F1} hours\n" +
|
|
$" {Math.Abs(diff.TotalMinutes):F0} minutes\n" +
|
|
$" {Math.Abs(diff.TotalSeconds):F0} seconds\n" +
|
|
$" ({(diff.TotalDays >= 0 ? "forward" : "backward")})");
|
|
}
|
|
|
|
private static ToolResult Add(JsonElement args)
|
|
{
|
|
var dateStr = args.TryGetProperty("date", out var dv) ? dv.GetString() ?? "" : "";
|
|
var amountStr = args.TryGetProperty("amount", out var av) ? av.GetString() ?? "0" : "0";
|
|
var unit = args.TryGetProperty("unit", out var uv) ? uv.GetString() ?? "days" : "days";
|
|
|
|
if (string.IsNullOrEmpty(dateStr)) return ToolResult.Fail("'date' parameter is required");
|
|
if (!DateTime.TryParse(dateStr, out var dt)) return ToolResult.Fail($"Cannot parse date: '{dateStr}'");
|
|
if (!double.TryParse(amountStr, out var amount)) return ToolResult.Fail($"Invalid amount: '{amountStr}'");
|
|
|
|
var result = unit switch
|
|
{
|
|
"days" => dt.AddDays(amount),
|
|
"hours" => dt.AddHours(amount),
|
|
"minutes" => dt.AddMinutes(amount),
|
|
"seconds" => dt.AddSeconds(amount),
|
|
"months" => dt.AddMonths((int)amount),
|
|
"years" => dt.AddYears((int)amount),
|
|
_ => dt.AddDays(amount),
|
|
};
|
|
|
|
return ToolResult.Ok(
|
|
$"Original: {dt:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"Added: {amount} {unit}\n" +
|
|
$"Result: {result:yyyy-MM-dd HH:mm:ss} ({result:dddd})");
|
|
}
|
|
|
|
private static ToolResult Epoch(JsonElement args)
|
|
{
|
|
var input = args.TryGetProperty("date", out var dv) ? dv.GetString() ?? "" : "";
|
|
if (string.IsNullOrEmpty(input)) return ToolResult.Fail("'date' parameter is required");
|
|
|
|
// 숫자면 epoch → datetime
|
|
if (long.TryParse(input, out var epoch))
|
|
{
|
|
var dt = DateTimeOffset.FromUnixTimeSeconds(epoch);
|
|
return ToolResult.Ok(
|
|
$"Epoch: {epoch}\n" +
|
|
$"UTC: {dt.UtcDateTime:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"Local: {dt.LocalDateTime:yyyy-MM-dd HH:mm:ss}");
|
|
}
|
|
|
|
// 문자열이면 datetime → epoch
|
|
if (DateTime.TryParse(input, out var parsed))
|
|
{
|
|
var e = new DateTimeOffset(parsed).ToUnixTimeSeconds();
|
|
return ToolResult.Ok(
|
|
$"Date: {parsed:yyyy-MM-dd HH:mm:ss}\n" +
|
|
$"Epoch: {e}");
|
|
}
|
|
|
|
return ToolResult.Fail($"Cannot parse: '{input}'");
|
|
}
|
|
|
|
private static ToolResult FormatDate(JsonElement args)
|
|
{
|
|
var dateStr = args.TryGetProperty("date", out var dv) ? dv.GetString() ?? "" : "";
|
|
var pattern = args.TryGetProperty("pattern", out var pv) ? pv.GetString() ?? "yyyy-MM-dd" : "yyyy-MM-dd";
|
|
|
|
if (string.IsNullOrEmpty(dateStr)) return ToolResult.Fail("'date' parameter is required");
|
|
if (!DateTime.TryParse(dateStr, out var dt)) return ToolResult.Fail($"Cannot parse date: '{dateStr}'");
|
|
|
|
return ToolResult.Ok(dt.ToString(pattern, CultureInfo.InvariantCulture));
|
|
}
|
|
}
|