using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L13-4: 나이·D-day 계산기 핸들러. "age" 프리픽스로 사용합니다. /// /// 예: age 1990-05-15 → 나이 계산 (만/한국식) /// age 1990.05.15 → 점 구분자도 지원 /// age 19900515 → 숫자만 입력 (YYYYMMDD) /// age 2025-12-25 → D-day 계산 (미래 날짜) /// age next monday → 다음 월요일까지 D-day /// age christmas → 크리스마스까지 D-day /// Enter → 결과를 클립보드에 복사. /// public class AgeHandler : IActionHandler { public string? Prefix => "age"; public PluginMetadata Metadata => new( "Age", "나이·D-day 계산기 — 만 나이 · 한국 나이 · D-day", "1.0", "AX"); // 특수 날짜 키워드 private static readonly Dictionary> Keywords = new(StringComparer.OrdinalIgnoreCase) { ["christmas"] = t => new DateTime(t.Month > 12 || (t.Month == 12 && t.Day >= 26) ? t.Year + 1 : t.Year, 12, 25), ["xmas"] = t => new DateTime(t.Month > 12 || (t.Month == 12 && t.Day >= 26) ? t.Year + 1 : t.Year, 12, 25), ["newyear"] = t => new DateTime(t.Year + 1, 1, 1), ["new year"] = t => new DateTime(t.Year + 1, 1, 1), ["설날"] = t => GetNextLunarNewYear(t), ["chuseok"] = t => GetNextChuseok(t), ["추석"] = t => GetNextChuseok(t), }; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); var today = DateTime.Today; if (string.IsNullOrWhiteSpace(q)) { items.Add(new LauncherItem("나이·D-day 계산기", "예: age 1990-05-15 / age 2025-12-25 / age christmas", null, null, Symbol: "\uE787")); items.Add(new LauncherItem("age 1990-01-01", "생년월일 → 나이", null, null, Symbol: "\uE787")); items.Add(new LauncherItem("age 2025-12-25", "미래 날짜 D-day", null, null, Symbol: "\uE787")); items.Add(new LauncherItem("age christmas", "크리스마스 D-day", null, null, Symbol: "\uE787")); items.Add(new LauncherItem("age newyear", "신년 D-day", null, null, Symbol: "\uE787")); return Task.FromResult>(items); } // 특수 키워드 확인 foreach (var (kw, fn) in Keywords) { if (q.Equals(kw, StringComparison.OrdinalIgnoreCase)) { var targetDate = fn(today); items.AddRange(BuildDdayItems(targetDate, kw, today)); return Task.FromResult>(items); } } // 요일 키워드: "next monday" if (TryParseNextWeekday(q, out var weekdayDate)) { items.AddRange(BuildDdayItems(weekdayDate, q, today)); return Task.FromResult>(items); } // 날짜 파싱 시도 if (!TryParseDate(q, out var date)) { items.Add(new LauncherItem("날짜 형식 오류", "예: 1990-05-15 / 1990.05.15 / 19900515 / 2025-12-25", null, null, Symbol: "\uE783")); return Task.FromResult>(items); } if (date <= today) { // 과거 날짜 → 나이/경과 계산 items.AddRange(BuildAgeItems(date, today)); } else { // 미래 날짜 → D-day items.AddRange(BuildDdayItems(date, date.ToString("yyyy-MM-dd"), today)); } return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy", string text)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("Age", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } } return Task.CompletedTask; } // ── 나이 계산 ───────────────────────────────────────────────────────────── private static IEnumerable BuildAgeItems(DateTime birth, DateTime today) { var ageInt = CalcAge(birth, today); var ageKor = today.Year - birth.Year + 1; // 한국식 나이 var days = (today - birth).Days; var months = (today.Year - birth.Year) * 12 + (today.Month - birth.Month); var nextBirthday = NextBirthday(birth, today); var daysToNext = (nextBirthday - today).Days; var summary = $""" 생년월일: {birth:yyyy-MM-dd} 만 나이: {ageInt}세 한국 나이: {ageKor}세 경과 일수: {days:N0}일 경과 개월: {months:N0}개월 다음 생일: {nextBirthday:yyyy-MM-dd} (D-{daysToNext}) """; yield return new LauncherItem( $"만 {ageInt}세 (한국식 {ageKor}세)", $"{birth:yyyy-MM-dd} · {days:N0}일 경과 · Enter 복사", null, ("copy", summary), Symbol: "\uE787"); yield return new LauncherItem("만 나이", $"{ageInt}세", null, ("copy", ageInt.ToString()), Symbol: "\uE787"); yield return new LauncherItem("한국 나이", $"{ageKor}세", null, ("copy", ageKor.ToString()), Symbol: "\uE787"); yield return new LauncherItem("경과 일수", $"{days:N0}일", null, ("copy", days.ToString()), Symbol: "\uE787"); yield return new LauncherItem("경과 개월", $"{months:N0}개월", null, ("copy", months.ToString()), Symbol: "\uE787"); yield return new LauncherItem( "다음 생일", $"{nextBirthday:yyyy-MM-dd} · D-{daysToNext}", null, ("copy", nextBirthday.ToString("yyyy-MM-dd")), Symbol: "\uE787"); // 요일 yield return new LauncherItem("태어난 요일", DayKor(birth.DayOfWeek), null, null, Symbol: "\uE787"); } private static IEnumerable BuildDdayItems(DateTime target, string label, DateTime today) { var diff = (target - today).Days; var absDiff = Math.Abs(diff); var dLabel = diff > 0 ? $"D-{diff}" : diff == 0 ? "D-Day!" : $"D+{absDiff}"; yield return new LauncherItem( dLabel, $"{target:yyyy-MM-dd} ({label}) · {DayKor(target.DayOfWeek)}", null, ("copy", dLabel), Symbol: "\uE787"); yield return new LauncherItem("날짜", target.ToString("yyyy-MM-dd"), null, ("copy", target.ToString("yyyy-MM-dd")), Symbol: "\uE787"); yield return new LauncherItem("요일", DayKor(target.DayOfWeek), null, null, Symbol: "\uE787"); yield return new LauncherItem("남은 일수", diff > 0 ? $"{diff:N0}일 후" : diff == 0 ? "오늘!" : $"{absDiff:N0}일 전", null, ("copy", absDiff.ToString()), Symbol: "\uE787"); yield return new LauncherItem("남은 주", $"{diff / 7}주 {diff % 7}일", null, null, Symbol: "\uE787"); } // ── 파싱 헬퍼 ───────────────────────────────────────────────────────────── private static bool TryParseDate(string s, out DateTime result) { result = default; s = s.Trim(); // YYYYMMDD if (s.Length == 8 && s.All(char.IsDigit)) { return DateTime.TryParseExact(s, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result); } // 다양한 구분자 (-, ., /) var normalized = s.Replace('.', '-').Replace('/', '-'); var formats = new[] { "yyyy-M-d", "yyyy-MM-dd", "yy-M-d", "M-d" }; foreach (var fmt in formats) { if (DateTime.TryParseExact(normalized, fmt, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result)) return true; } // "M월 d일" 한국어 형식 if (normalized.Contains('월')) { var parts = normalized.Split('월', '일'); if (parts.Length >= 2 && int.TryParse(parts[0].Trim(), out var m) && int.TryParse(parts[1].Trim(), out var d)) { result = new DateTime(DateTime.Today.Year, m, d); return true; } } return false; } private static bool TryParseNextWeekday(string q, out DateTime result) { result = default; var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 2 || !parts[0].Equals("next", StringComparison.OrdinalIgnoreCase)) return false; DayOfWeek? dow = parts[1].ToLowerInvariant() switch { "monday" or "mon" or "월" or "월요일" => DayOfWeek.Monday, "tuesday" or "tue" or "화" or "화요일" => DayOfWeek.Tuesday, "wednesday" or "wed" or "수" or "수요일" => DayOfWeek.Wednesday, "thursday" or "thu" or "목" or "목요일" => DayOfWeek.Thursday, "friday" or "fri" or "금" or "금요일" => DayOfWeek.Friday, "saturday" or "sat" or "토" or "토요일" => DayOfWeek.Saturday, "sunday" or "sun" or "일" or "일요일" => DayOfWeek.Sunday, _ => null, }; if (dow == null) return false; var today = DateTime.Today; var daysAhead = ((int)dow.Value - (int)today.DayOfWeek + 7) % 7; if (daysAhead == 0) daysAhead = 7; // "next"이므로 다음 주 result = today.AddDays(daysAhead); return true; } // ── 날짜 계산 헬퍼 ──────────────────────────────────────────────────────── private static int CalcAge(DateTime birth, DateTime today) { var age = today.Year - birth.Year; if (today.Month < birth.Month || (today.Month == birth.Month && today.Day < birth.Day)) age--; return age; } private static DateTime NextBirthday(DateTime birth, DateTime today) { var thisYear = new DateTime(today.Year, birth.Month, birth.Day); return thisYear >= today ? thisYear : thisYear.AddYears(1); } // 간략화된 양력 설날 근사값 (실제 음력 계산은 복잡하므로 고정 근사) private static DateTime GetNextLunarNewYear(DateTime today) { // 설날은 대략 1월 말 ~ 2월 초이므로 2월 5일을 기준점으로 사용 var approx = new DateTime(today.Year, 2, 5); return approx >= today ? approx : new DateTime(today.Year + 1, 2, 5); } private static DateTime GetNextChuseok(DateTime today) { // 추석은 대략 9월 말 ~ 10월 초이므로 9월 28일을 기준점으로 사용 var approx = new DateTime(today.Year, 9, 28); return approx >= today ? approx : new DateTime(today.Year + 1, 9, 28); } private static string DayKor(DayOfWeek dow) => dow switch { DayOfWeek.Monday => "월요일", DayOfWeek.Tuesday => "화요일", DayOfWeek.Wednesday => "수요일", DayOfWeek.Thursday => "목요일", DayOfWeek.Friday => "금요일", DayOfWeek.Saturday => "토요일", DayOfWeek.Sunday => "일요일", _ => "", }; }