using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L7-3: 시간대 변환기 핸들러. "tz" 프리픽스로 사용합니다. /// /// 예: tz → 주요 도시 현재 시각 목록 /// tz seoul → 서울 현재 시각 /// tz new york → 뉴욕 현재 시각 /// tz 14:30 to la → 현재 시간대(KST) 14:30을 LA 시각으로 변환 /// tz meeting 09:00 → 서울 기준 9시 = 주요 도시별 동일 시각 표시 /// Enter → 결과를 클립보드에 복사. /// public class TimeZoneHandler : IActionHandler { public string? Prefix => "tz"; public PluginMetadata Metadata => new( "TimeZone", "시간대 변환기 — 주요 도시 현재 시각 · 시각 변환", "1.0", "AX"); // ── 주요 도시 시간대 목록 ────────────────────────────────────────────── private static readonly (string City, string TzId, string Flag, string[] Aliases)[] Cities = [ ("서울", "Korea Standard Time", "🇰🇷", ["seoul", "서울", "부산", "인천", "kst", "한국"]), ("도쿄", "Tokyo Standard Time", "🇯🇵", ["tokyo", "도쿄", "osaka", "오사카", "jst", "일본"]), ("베이징", "China Standard Time", "🇨🇳", ["beijing", "베이징", "상하이", "shanghai", "cst", "중국"]), ("방콕", "SE Asia Standard Time", "🇹🇭", ["bangkok", "방콕", "ict", "태국"]), ("두바이", "Arabian Standard Time", "🇦🇪", ["dubai", "두바이", "gst", "uae"]), ("모스크바", "Russia Time Zone 2", "🇷🇺", ["moscow", "모스크바", "msk", "러시아"]), ("파리", "Romance Standard Time", "🇫🇷", ["paris", "파리", "cet", "프랑스"]), ("런던", "GMT Standard Time", "🇬🇧", ["london", "런던", "gmt", "영국"]), ("뉴욕", "Eastern Standard Time", "🇺🇸", ["new york", "뉴욕", "nyc", "est", "동부"]), ("시카고", "Central Standard Time", "🇺🇸", ["chicago", "시카고", "cst", "중부"]), ("로스앤젤레스","Pacific Standard Time", "🇺🇸", ["los angeles", "la", "로스앤젤레스", "pst", "서부"]), ("시드니", "AUS Eastern Standard Time", "🇦🇺", ["sydney", "시드니", "aest", "호주"]), ("싱가포르", "Singapore Standard Time", "🇸🇬", ["singapore", "싱가포르", "sgt"]), ("뭄바이", "India Standard Time", "🇮🇳", ["mumbai", "뭄바이", "ist", "인도"]), ("도하", "Arab Standard Time", "🇶🇦", ["doha", "도하", "ast", "카타르"]), ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim().ToLowerInvariant(); var items = new List(); var now = DateTimeOffset.UtcNow; if (string.IsNullOrWhiteSpace(q)) { // 주요 도시 현재 시각 목록 items.Add(new LauncherItem( "주요 도시 현재 시각", $"기준: UTC {now:HH:mm}", null, null, Symbol: "\uE917")); foreach (var (city, tzId, flag, _) in Cities) { var (timeStr, offsetStr) = GetCityTime(tzId, now); items.Add(new LauncherItem( $"{flag} {city}", $"{timeStr} ({offsetStr})", null, ("copy_time", $"{city}: {timeStr} ({offsetStr})"), Symbol: "\uE917")); } return Task.FromResult>(items); } // "meeting HH:mm" 모드 — 서울 기준 특정 시각을 전 도시 변환 if (q.StartsWith("meeting ") || q.StartsWith("미팅 ")) { var timePart = q.Contains(' ') ? q[(q.IndexOf(' ') + 1)..].Trim() : ""; return Task.FromResult>( BuildMeetingItems(timePart, now)); } // "HH:mm to " 또는 "HH:mm " 변환 모드 var convResult = TryParseConversion(q, now); if (convResult != null) return Task.FromResult>(convResult); // 도시 검색 var matched = Cities .Where(c => c.Aliases.Any(a => a.Contains(q))) .ToList(); if (matched.Count > 0) { foreach (var (city, tzId, flag, _) in matched) { var (timeStr, offsetStr) = GetCityTime(tzId, now); items.Add(new LauncherItem( $"{flag} {city} {timeStr}", $"UTC {offsetStr}", null, ("copy_time", $"{city}: {timeStr} ({offsetStr})"), Symbol: "\uE917")); // 이 도시와 서울의 시차 var seoulOffset = GetOffset("Korea Standard Time"); var cityOffset = GetOffset(tzId); var diff = (cityOffset - seoulOffset).TotalHours; var diffStr = diff == 0 ? "서울과 동일" : diff > 0 ? $"서울보다 +{diff:+0;-0}시간" : $"서울보다 {diff:+0;-0}시간"; items.Add(new LauncherItem( diffStr, "서울(KST) 기준 시차", null, null, Symbol: "\uE8F4")); } } else { // 미인식 → 모든 도시 표시 foreach (var (city, tzId, flag, _) in Cities) { var (timeStr, offsetStr) = GetCityTime(tzId, now); items.Add(new LauncherItem( $"{flag} {city} {timeStr}", offsetStr, null, ("copy_time", $"{city}: {timeStr} ({offsetStr})"), Symbol: "\uE917")); } } return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy_time", string text)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("시간대", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } } return Task.CompletedTask; } // ── 헬퍼 ──────────────────────────────────────────────────────────────── private static (string Time, string Offset) GetCityTime(string tzId, DateTimeOffset utcNow) { try { var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId); var local = TimeZoneInfo.ConvertTime(utcNow, tz); var off = tz.GetUtcOffset(utcNow); var offStr = $"UTC{(off >= TimeSpan.Zero ? "+" : "")}{off.Hours:D2}:{off.Minutes:D2}"; return (local.ToString("HH:mm (ddd)"), offStr); } catch { return ("--:--", ""); } } private static TimeSpan GetOffset(string tzId) { try { var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId); return tz.GetUtcOffset(DateTimeOffset.UtcNow); } catch { return TimeSpan.Zero; } } private IEnumerable BuildMeetingItems(string timePart, DateTimeOffset utcNow) { var items = new List(); if (!TimeOnly.TryParse(timePart, out var meetingTime)) { items.Add(new LauncherItem("시각 형식 오류", "HH:mm 형식으로 입력하세요 (예: tz meeting 10:00)", null, null, Symbol: "\uE783")); return items; } // 서울 기준으로 날짜+시각 설정 var seoulTz = TimeZoneInfo.FindSystemTimeZoneById("Korea Standard Time"); var seoulNow = TimeZoneInfo.ConvertTime(utcNow, seoulTz); var seoulDt = new DateTimeOffset(seoulNow.Date.AddHours(meetingTime.Hour).AddMinutes(meetingTime.Minute), seoulTz.GetUtcOffset(utcNow)); var seoulUtc = seoulDt.ToUniversalTime(); items.Add(new LauncherItem( $"🇰🇷 서울 미팅 시각: {meetingTime:HH:mm}", "주요 도시 동일 시각", null, null, Symbol: "\uE917")); foreach (var (city, tzId, flag, _) in Cities) { if (tzId == "Korea Standard Time") continue; var (timeStr, offsetStr) = GetCityTime(tzId, seoulUtc); items.Add(new LauncherItem( $"{flag} {city} {timeStr}", offsetStr, null, ("copy_time", $"{city}: {timeStr}"), Symbol: "\uE917")); } return items; } private IEnumerable? TryParseConversion(string q, DateTimeOffset utcNow) { // "HH:mm to " 또는 "HH:mm " 패턴 var sep = q.Contains(" to ") ? " to " : q.Contains(' ') ? " " : null; if (sep == null) return null; var parts = q.Split(sep, 2); if (parts.Length < 2) return null; if (!TimeOnly.TryParse(parts[0].Trim(), out var inputTime)) return null; var cityQuery = parts[1].Trim().ToLowerInvariant(); var targetCities = Cities .Where(c => c.Aliases.Any(a => a.Contains(cityQuery))) .ToList(); if (targetCities.Count == 0) return null; var items = new List(); // 서울 기준으로 입력 시각 해석 var seoulTz = TimeZoneInfo.FindSystemTimeZoneById("Korea Standard Time"); var seoulNow = TimeZoneInfo.ConvertTime(utcNow, seoulTz); var seoulDt = new DateTimeOffset( seoulNow.Date.AddHours(inputTime.Hour).AddMinutes(inputTime.Minute), seoulTz.GetUtcOffset(utcNow)); var seoulUtc = seoulDt.ToUniversalTime(); items.Add(new LauncherItem( $"🇰🇷 서울 {inputTime:HH:mm} 기준 변환", "Enter → 클립보드 복사", null, null, Symbol: "\uE8F4")); foreach (var (city, tzId, flag, _) in targetCities) { var (timeStr, offsetStr) = GetCityTime(tzId, seoulUtc); items.Add(new LauncherItem( $"{flag} {city} {timeStr}", offsetStr, null, ("copy_time", $"{city}: {timeStr}"), Symbol: "\uE917")); } return items; } }