Grp_AirTicketReservations.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. using System.Text.RegularExpressions;
  2. namespace OASystem.Domain.Entities.Groups;
  3. /// <summary>
  4. /// 机票费用录入
  5. /// </summary>
  6. [SugarTable("Grp_AirTicketReservations")]
  7. public class Grp_AirTicketReservations : EntityBase
  8. {
  9. /************************* 2026版数据结构 *************************/
  10. /// <summary>
  11. /// 团组外键编号
  12. /// </summary>
  13. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  14. public int DIId { get; set; }
  15. /// <summary>
  16. /// 记录类型:0-正常机票 1-退票记录
  17. /// </summary>
  18. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  19. public int RecordType { get; set; } = 0;
  20. /// <summary>
  21. /// 关联的原始机票记录ID(退票记录指向原机票记录)
  22. /// </summary>
  23. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  24. public int OriginalReservationId { get; set; }
  25. /// <summary>
  26. /// 航段代码描述
  27. /// </summary>
  28. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
  29. public string FlightsDescription { get; set; }
  30. /// <summary>
  31. /// 航班基础信息(去程、联程、返程)
  32. /// </summary>
  33. [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(500)")]
  34. public List<AirTicketBasicInfo> AirTicketBasicInfos { get; set; } = new List<AirTicketBasicInfo>();
  35. /// <summary>
  36. /// 客人名称
  37. /// RecordType = 0 时,存储正常机票的客人名称;RecordType = 1 时,存储退票记录的客人名称(可能与原机票记录不同)
  38. /// </summary>
  39. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
  40. public string ClientName { get; set; }
  41. /// <summary>
  42. /// 客户人数
  43. /// </summary>
  44. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  45. public int ClientNum { get; set; }
  46. /// <summary>
  47. /// 舱类型(数据类型外键)
  48. /// </summary>
  49. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  50. public int CType { get; set; }
  51. /// <summary>
  52. /// 机票全价
  53. /// </summary>
  54. [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
  55. public decimal Price { get; set; }
  56. /// <summary>
  57. /// 币种
  58. /// </summary>
  59. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  60. public int Currency { get; set; }
  61. /// <summary>
  62. /// 费用信息(含退票信息)
  63. /// </summary>
  64. [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(max)")]
  65. public List<CustTicketInfo> CustTicketInfos { get; set; } = new List<CustTicketInfo>();
  66. /// <summary>
  67. /// 报价说明
  68. /// </summary>
  69. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
  70. public string PriceDescription { get; set; }
  71. /************************* 2026版数据结构 *************************/
  72. #region 旧版兼容以前的数据结构,2026版不使用
  73. /// <summary>
  74. /// 航班号
  75. /// </summary>
  76. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
  77. public string FlightsCode { get; set; }
  78. /// <summary>
  79. /// 城市A-B
  80. /// </summary>
  81. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
  82. public string FlightsCity { get; set; }
  83. /// <summary>
  84. /// 航班日期
  85. /// </summary>
  86. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
  87. public string FlightsDate { get; set; }
  88. /// <summary>
  89. /// 航班时间
  90. /// </summary>
  91. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
  92. public string FlightsTime { get; set; }
  93. /// <summary>
  94. /// 抵达时间
  95. /// </summary>
  96. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
  97. public string ArrivedTime { get; set; }
  98. /// <summary>
  99. /// 是否值机
  100. /// 0 否 1 是
  101. /// </summary>
  102. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  103. public int IsCheckIn { get; set; }
  104. /// <summary>
  105. /// 是否选座
  106. /// 0 否 1 是
  107. /// </summary>
  108. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  109. public int IsSetSeat { get; set; }
  110. /// <summary>
  111. /// 是否购买行李服务
  112. /// 0 否 1 是
  113. /// </summary>
  114. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  115. public int IsPackage { get; set; }
  116. /// <summary>
  117. /// 是否行李直挂
  118. /// 0 否 1 是
  119. /// </summary>
  120. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  121. public int IsBagHandle { get; set; }
  122. /// <summary>
  123. /// 是否火车票出票选座
  124. /// 0 否 1 是
  125. /// </summary>
  126. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  127. public int IsTrain { get; set; }
  128. /// <summary>
  129. /// 去程航班描述代码
  130. /// </summary>
  131. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
  132. public string LeaveDescription { get; set; }
  133. /// <summary>
  134. /// 返程航班描述代码
  135. /// </summary>
  136. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
  137. public string ReturnDescription { get; set; }
  138. /// <summary>
  139. /// 出票前报价
  140. /// </summary>
  141. [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
  142. public decimal PrePrice { get; set; }
  143. /// <summary>
  144. /// 出票前报价币种
  145. /// </summary>
  146. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  147. public int PreCurrency { get; set; }
  148. /// <summary>
  149. /// 机票编号
  150. /// </summary>
  151. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
  152. public string TicketNumber { get; set; }
  153. /// <summary>
  154. /// 机票票号
  155. /// </summary>
  156. [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
  157. public string TicketCode { get; set; }
  158. /// <summary>
  159. /// 客人类型(数据类型外键)
  160. /// </summary>
  161. [SugarColumn(IsNullable = true, ColumnDataType = "int")]
  162. public int PassengerType { get; set; }
  163. #endregion
  164. }
  165. /// <summary>
  166. /// 机票基础信息
  167. /// </summary>
  168. public class AirTicketBasicInfo
  169. {
  170. /// <summary>
  171. /// 序号
  172. /// </summary>
  173. public int No { get; set; }
  174. /// <summary>
  175. /// 航班号
  176. /// </summary>
  177. public string FlightsCode { get; set; }
  178. /// <summary>
  179. /// 城市A-B
  180. /// </summary>
  181. public string FlightsCity { get; set; }
  182. /// <summary>
  183. /// 航班日期
  184. /// </summary>
  185. public string FlightsDate { get; set; }
  186. /// <summary>
  187. /// 航班时间
  188. /// </summary>
  189. public string FlightsTime { get; set; }
  190. /// <summary>
  191. /// 抵达时间
  192. /// </summary>
  193. public string ArrivedTime { get; set; }
  194. }
  195. /// <summary>
  196. /// 客户机票信息
  197. /// </summary>
  198. public class CustTicketInfo
  199. {
  200. /// <summary>
  201. /// 客户ID
  202. /// </summary>
  203. public int ClientId { get; set; }
  204. /// <summary>
  205. /// 舱类型(数据类型外键)
  206. /// </summary>
  207. public int CType { get; set; }
  208. /// <summary>
  209. /// 实际价格
  210. /// </summary>
  211. public decimal ActualPrice { get; set; }
  212. /// <summary>
  213. /// 机票票号
  214. /// </summary>
  215. public string TicketCode { get; set; }
  216. /// <summary>
  217. /// 机票编号
  218. /// </summary>
  219. public string TicketNumber { get; set; }
  220. /// <summary>
  221. /// 选中服务ID集合
  222. /// </summary>
  223. public List<int> SelectedServiceIds { get; set; } = new List<int>();
  224. /// <summary>
  225. /// 附加服务 json数组
  226. /// </summary>
  227. public List<AdditionalService> AdditionalServices { get; set; } = new List<AdditionalService>();
  228. /// <summary>
  229. /// 机票总费用
  230. /// 实际价格 + 附加服务费
  231. /// </summary>
  232. public decimal TotalTicketPrice { get; set; }
  233. /// <summary>
  234. /// 是否已退票
  235. /// </summary>
  236. public bool IsRefund { get; set; } = false;
  237. /// <summary>
  238. /// 退票记录
  239. /// </summary>
  240. public RefundRecord RefundRecord { get; set; } = new RefundRecord();
  241. }
  242. /// <summary>
  243. /// 退票记录
  244. /// </summary>
  245. public class RefundRecord
  246. {
  247. /// <summary>
  248. /// 退票金额
  249. /// </summary>
  250. public decimal RefundAmount { get; set; }
  251. /// <summary>
  252. /// 不可退税费
  253. /// </summary>
  254. public decimal NonRefundableTax { get; set; }
  255. /// <summary>
  256. /// 退票时间
  257. /// </summary>
  258. public string RefundTime { get; set; }
  259. /// <summary>
  260. /// 退款账户
  261. /// Setdata Id
  262. /// </summary>
  263. public int RefundAccount { get; set; }
  264. /// <summary>
  265. /// 退票原因
  266. /// </summary>
  267. public string RefundReason { get; set; }
  268. }
  269. /// <summary>
  270. /// 附加服务
  271. /// </summary>
  272. public class AdditionalService
  273. {
  274. /// <summary>
  275. /// 服务类型Id(Sys_SetData)
  276. /// </summary>
  277. public int ServiceTypeId { get; set; }
  278. /// <summary>
  279. /// 金额
  280. /// </summary>
  281. public decimal Amount { get; set; }
  282. }
  283. /// <summary>
  284. /// 航班信息
  285. /// </summary>
  286. public class FlightInfo
  287. {
  288. public int SequenceNo { get; set; } // 序号
  289. public string FlightNumber { get; set; } // 航班号
  290. public string CabinClass { get; set; } // 舱位等级
  291. public string WeekDay { get; set; } // 星期(MO,TU,WE,TH,FR,SA,SU)
  292. public string DepartureDateRaw { get; set; } // 原始日期字符串
  293. public DateTime DepartureDate { get; set; } // 起飞日期
  294. public string DepartureTimeRaw { get; set; } // 原始起飞时间
  295. public string DepartureTime { get; set; } // 格式化起飞时间
  296. public string ArrivalTimeRaw { get; set; } // 原始到达时间
  297. public string ArrivalTime { get; set; } // 格式化到达时间
  298. public string DepartureAirport { get; set; } // 起飞机场
  299. public string ArrivalAirport { get; set; } // 到达机场
  300. public bool IsNextDayArrival { get; set; } // 是否次日到达
  301. public int NextDayCount { get; set; } // 跨天天数(+1表示第二天)
  302. public string Terminal { get; set; } // 航站楼
  303. public string AircraftType { get; set; } // 机型
  304. public string Duration { get; set; } // 飞行时长
  305. public string Status { get; set; } // 状态(HK1,KK1,TK1等)
  306. public string RawText { get; set; } // 原始文本
  307. }
  308. /// <summary>
  309. /// 航班解析器
  310. /// </summary>
  311. public static class FlightParser
  312. {
  313. #region 常量定义
  314. /// <summary>
  315. /// 月份映射
  316. /// </summary>
  317. private static readonly Dictionary<string, int> MonthMap = new()
  318. {
  319. { "JAN", 1 }, { "FEB", 2 }, { "MAR", 3 }, { "APR", 4 },
  320. { "MAY", 5 }, { "JUN", 6 }, { "JUL", 7 }, { "AUG", 8 },
  321. { "SEP", 9 }, { "OCT", 10 }, { "NOV", 11 }, { "DEC", 12 }
  322. };
  323. /// <summary>
  324. /// 星期映射
  325. /// </summary>
  326. private static readonly Dictionary<string, string> WeekDayMap = new()
  327. {
  328. { "MO", "周一" }, { "TU", "周二" }, { "WE", "周三" }, { "TH", "周四" },
  329. { "FR", "周五" }, { "SA", "周六" }, { "SU", "周日" }
  330. };
  331. #endregion
  332. #region 主解析方法
  333. /// <summary>
  334. /// 解析航班文本(自动识别格式)
  335. /// </summary>
  336. public static List<FlightInfo> ParseFlights(string rawText, int year = 2024)
  337. {
  338. if (string.IsNullOrWhiteSpace(rawText))
  339. return new List<FlightInfo>();
  340. var lines = rawText.Trim().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
  341. var result = new List<FlightInfo>();
  342. foreach (var line in lines)
  343. {
  344. var flight = ParseLine(line, year);
  345. if (flight != null)
  346. {
  347. result.Add(flight);
  348. }
  349. }
  350. return result.OrderBy(x => x.SequenceNo).ToList();
  351. }
  352. /// <summary>
  353. /// 解析单行文本(自动识别格式)
  354. /// </summary>
  355. private static FlightInfo ParseLine(string line, int year)
  356. {
  357. line = line.Trim();
  358. if (string.IsNullOrEmpty(line))
  359. return null;
  360. // 格式1:HU7086 C MO18MAY TFUHAK HK1 1605 1820 E T2T2
  361. if (line.Contains("HK1") || line.Contains("KK1") || line.Contains("TK1"))
  362. {
  363. return ParseFormat1(line, year);
  364. }
  365. // 格式2:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
  366. // 格式2变体:2. LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M
  367. if (Regex.IsMatch(line, @"^\d+\.[A-Z0-9]+\s+[A-Z]{2}\d{2}[A-Z]{3}"))
  368. {
  369. return ParseFormat2(line, year);
  370. }
  371. return null;
  372. }
  373. #endregion
  374. #region 格式1解析
  375. /// <summary>
  376. /// 解析格式1
  377. /// </summary>
  378. private static FlightInfo ParseFormat1(string line, int year)
  379. {
  380. // 提取序号
  381. var seqMatch = Regex.Match(line, @"^(\d+)\.\s*");
  382. if (!seqMatch.Success) return null;
  383. var sequenceNo = int.Parse(seqMatch.Groups[1].Value);
  384. var content = line.Substring(seqMatch.Length).Trim();
  385. // 正则匹配
  386. var pattern = @"^([A-Z0-9]+)\s+([A-Z])\s+([A-Z]{2})(\d{2}[A-Z]{3})\s+([A-Z]{3})([A-Z]{3})\s+([A-Z]{2,3}\d?)\s+(\d{3,4})\s+(\d{3,4})(?:\+(\d+))?\s+([A-Z])\s+([A-Z0-9]+)";
  387. var match = Regex.Match(content, pattern);
  388. if (!match.Success) return null;
  389. var weekDay = match.Groups[3].Value;
  390. var dateRaw = match.Groups[4].Value;
  391. var departureAirport = match.Groups[5].Value;
  392. var arrivalAirport = match.Groups[6].Value;
  393. var departureTime = match.Groups[8].Value;
  394. var arrivalTime = match.Groups[9].Value;
  395. var isNextDay = match.Groups[10].Success && match.Groups[10].Value == "1";
  396. var terminalFlag = match.Groups[12].Value;
  397. return new FlightInfo
  398. {
  399. SequenceNo = sequenceNo,
  400. FlightNumber = match.Groups[1].Value,
  401. CabinClass = match.Groups[2].Value,
  402. WeekDay = weekDay,
  403. DepartureDateRaw = $"{weekDay}{dateRaw}",
  404. DepartureAirport = departureAirport,
  405. ArrivalAirport = arrivalAirport,
  406. DepartureTimeRaw = departureTime,
  407. ArrivalTimeRaw = arrivalTime,
  408. IsNextDayArrival = isNextDay,
  409. DepartureTime = FormatTime(departureTime),
  410. ArrivalTime = FormatTime(arrivalTime),
  411. DepartureDate = ParseDate(dateRaw, year),
  412. Status = match.Groups[7].Value,
  413. Terminal = terminalFlag,
  414. RawText = line
  415. };
  416. }
  417. #endregion
  418. #region 格式2解析(修复版)
  419. /// <summary>
  420. /// 解析格式2
  421. /// 示例:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
  422. /// </summary>
  423. private static FlightInfo ParseFormat2(string line, int year)
  424. {
  425. // 1. 提取序号(支持 "1." 和 "1. ")
  426. var seqMatch = Regex.Match(line, @"^(\d+)\.\s*");
  427. if (!seqMatch.Success) return null;
  428. var sequenceNo = int.Parse(seqMatch.Groups[1].Value);
  429. var content = line.Substring(seqMatch.Length).Trim();
  430. // 2. 分割空格
  431. var parts = content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  432. if (parts.Length < 8) return null;
  433. try
  434. {
  435. var flight = new FlightInfo
  436. {
  437. SequenceNo = sequenceNo,
  438. RawText = line
  439. };
  440. int idx = 0;
  441. // 航班号
  442. flight.FlightNumber = parts[idx++];
  443. // 日期(如 TU27MAY)
  444. var dateRaw = parts[idx++];
  445. if (dateRaw.Length >= 7)
  446. {
  447. flight.WeekDay = dateRaw.Substring(0, 2);
  448. flight.DepartureDateRaw = dateRaw;
  449. }
  450. // 机场(如 TFUFRA 或分开的格式)
  451. var airportStr = parts[idx++];
  452. if (airportStr.Length >= 6)
  453. {
  454. // 连写格式:TFUFRA -> TFU + FRA
  455. flight.DepartureAirport = airportStr.Substring(0, 3);
  456. flight.ArrivalAirport = airportStr.Substring(3, 3);
  457. }
  458. else
  459. {
  460. // 分开格式
  461. flight.DepartureAirport = airportStr;
  462. flight.ArrivalAirport = parts[idx++];
  463. }
  464. // 起飞时间
  465. flight.DepartureTimeRaw = parts[idx++];
  466. flight.DepartureTime = FormatTime(flight.DepartureTimeRaw);
  467. // 到达时间(可能带 +1)
  468. var arrivalRaw = parts[idx++];
  469. if (arrivalRaw.Contains('+'))
  470. {
  471. var arrParts = arrivalRaw.Split('+');
  472. flight.ArrivalTimeRaw = arrParts[0];
  473. flight.IsNextDayArrival = true;
  474. if (arrParts.Length > 1 && int.TryParse(arrParts[1], out var days))
  475. {
  476. flight.NextDayCount = days;
  477. }
  478. }
  479. else
  480. {
  481. flight.ArrivalTimeRaw = arrivalRaw;
  482. }
  483. flight.ArrivalTime = FormatTime(flight.ArrivalTimeRaw);
  484. // 航站楼(可选,以T开头)
  485. if (idx < parts.Length && parts[idx].StartsWith("T"))
  486. {
  487. flight.Terminal = parts[idx++];
  488. }
  489. // 序号(可选)
  490. if (idx < parts.Length && int.TryParse(parts[idx], out _))
  491. {
  492. // 这个序号可能是经停次数或行程序号
  493. idx++;
  494. }
  495. // 机型(可选,数字或数字+字母)
  496. if (idx < parts.Length && Regex.IsMatch(parts[idx], @"^\d{2,3}[A-Z]?$"))
  497. {
  498. flight.AircraftType = parts[idx++];
  499. }
  500. // 飞行时长(可选,包含H)
  501. if (idx < parts.Length && parts[idx].Contains('H'))
  502. {
  503. flight.Duration = parts[idx++];
  504. }
  505. // 解析日期
  506. flight.DepartureDate = ParseDate(dateRaw, year);
  507. return flight;
  508. }
  509. catch
  510. {
  511. return null;
  512. }
  513. }
  514. #endregion
  515. #region 辅助方法
  516. /// <summary>
  517. /// 格式化时间(1905 -> 19:05)
  518. /// </summary>
  519. private static string FormatTime(string time)
  520. {
  521. if (string.IsNullOrWhiteSpace(time))
  522. return string.Empty;
  523. // 移除 +1 等后缀
  524. var cleanTime = time.Split('+')[0];
  525. // 4位数字
  526. if (cleanTime.Length == 4 && int.TryParse(cleanTime, out _))
  527. {
  528. return $"{cleanTime.Substring(0, 2)}:{cleanTime.Substring(2, 2)}";
  529. }
  530. // 3位数字(905 -> 09:05)
  531. if (cleanTime.Length == 3 && int.TryParse(cleanTime, out _))
  532. {
  533. return $"0{cleanTime.Substring(0, 1)}:{cleanTime.Substring(1, 2)}";
  534. }
  535. // 已经是 HH:mm 格式
  536. if (Regex.IsMatch(cleanTime, @"^\d{1,2}:\d{2}$"))
  537. {
  538. var parts = cleanTime.Split(':');
  539. return $"{parts[0].PadLeft(2, '0')}:{parts[1]}";
  540. }
  541. return cleanTime;
  542. }
  543. /// <summary>
  544. /// 解析日期(27MAY -> 2024-05-27)
  545. /// </summary>
  546. private static DateTime ParseDate(string dateStr, int year)
  547. {
  548. if (string.IsNullOrWhiteSpace(dateStr))
  549. return DateTime.MinValue;
  550. // 移除星期前缀
  551. var cleanDate = Regex.Replace(dateStr, @"^[A-Z]{2}", "");
  552. var match = Regex.Match(cleanDate, @"(\d{2})([A-Z]{3})");
  553. if (match.Success)
  554. {
  555. var day = int.Parse(match.Groups[1].Value);
  556. var monthStr = match.Groups[2].Value;
  557. if (MonthMap.TryGetValue(monthStr.ToUpper(), out var month))
  558. {
  559. try
  560. {
  561. return new DateTime(year, month, day);
  562. }
  563. catch
  564. {
  565. return DateTime.MinValue;
  566. }
  567. }
  568. }
  569. return DateTime.MinValue;
  570. }
  571. /// <summary>
  572. /// 获取完整起飞时间(含时分)
  573. /// </summary>
  574. public static DateTime GetFullDepartureTime(FlightInfo flight, int year)
  575. {
  576. if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.DepartureTimeRaw))
  577. return DateTime.MinValue;
  578. var time = ParseTimeToTimeSpan(flight.DepartureTimeRaw);
  579. return flight.DepartureDate.Add(time);
  580. }
  581. /// <summary>
  582. /// 获取完整到达时间(含时分和跨天)
  583. /// </summary>
  584. public static DateTime GetFullArrivalTime(FlightInfo flight, int year)
  585. {
  586. if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.ArrivalTimeRaw))
  587. return DateTime.MinValue;
  588. var time = ParseTimeToTimeSpan(flight.ArrivalTimeRaw);
  589. var arrivalDate = flight.DepartureDate.Add(time);
  590. if (flight.IsNextDayArrival)
  591. {
  592. arrivalDate = arrivalDate.AddDays(flight.NextDayCount > 0 ? flight.NextDayCount : 1);
  593. }
  594. return arrivalDate;
  595. }
  596. /// <summary>
  597. /// 解析时间字符串为 TimeSpan
  598. /// </summary>
  599. private static TimeSpan ParseTimeToTimeSpan(string time)
  600. {
  601. var cleanTime = time.Split('+')[0];
  602. if (cleanTime.Length == 4)
  603. {
  604. return new TimeSpan(
  605. int.Parse(cleanTime.Substring(0, 2)),
  606. int.Parse(cleanTime.Substring(2, 2)),
  607. 0);
  608. }
  609. if (cleanTime.Length == 3)
  610. {
  611. return new TimeSpan(
  612. int.Parse(cleanTime.Substring(0, 1)),
  613. int.Parse(cleanTime.Substring(1, 2)),
  614. 0);
  615. }
  616. return TimeSpan.Zero;
  617. }
  618. /// <summary>
  619. /// 获取星期中文名称
  620. /// </summary>
  621. public static string GetWeekDayName(string weekDay)
  622. {
  623. return WeekDayMap.TryGetValue(weekDay.ToUpper(), out var name) ? name : weekDay;
  624. }
  625. #endregion
  626. }